Event

SwiftGarden 3. Perform automatic water supply – Operate Raspberry Pi GPIO with Swift

3. Perform automatic water supply – Operate Raspberry Pi GPIO with Swift

This article summarizes the contents of Part 3 of the session presented at iOSDC2023, the presentation of growing apples with Swift.

This is a sequel to the article “2. Send data to Firebase with Raspberry Pi – Run Swift’s CLI tool”. If you haven’t read it yet, please read it!

SwiftGarden 2.Send data to Firebase with Raspberry Pi - Run Swift's CLI toolThis article is an article that summarizes the contents of the second part of "I tried growing apples with Swift", "Send data to Firebase with Raspberry Pi - Run Swift's CLI tool"!...

This time, we will summarize how to implement the water supply of ④ and how to schedule the processing of ① to ④.

Enable automatic watering of plants in the home garden

We aim to create a mechanism that can control water supply using Raspberry Pi and a pump.
This time, I proceeded with the implementation with reference to this article
The pump I’m using runs on a 12v power supply here, and the relay is here!
The first time I ran the pump, I was using a non-12v power supply and it didn’t work, so make sure you have a 12v power supply!

This time, we will use Raspberry Pi’s GPIO to control this pump.
Regarding the original RaspberryPi and GPIO, there is an explanation in last year’s announcement article, so please check that for details.

0.Introduction - Announcement and overview of SwiftHomeThis article is an article that summarizes the contents of the introductory part of the announcement of "Make your home more convenient and safe with Swift"!...
3. Read sensor value with Raspberry Pi - Get weight sensor valueThis article is an article that summarizes the contents of Part 3 "3. Read the sensor value with Raspberry Pi - Get the weight sensor value" of the announcement "Make your home more convenient and safe with Swift"!...

This time the wiring is as follows

By the way, the relay module is as follows

Glossary

What is a relay module?

Electronic components that act as switches in electrical circuits and electronic devices to control high-current and high-voltage circuits with low-current and low-voltage signals

In this implementation, from the GPIO21 pin, we will be able to operate the switch of the relay and turn on/off the flow of 12v electricity.

Implement with SwiftyGPIO

The relay switching implementation above will be done using SwiftyGPIO, which was also used last year
SwiftyGPIO The code works only in Linux environment, so I assume it works only in Linux

import SwiftyGPIO

final class GPIOManager {
    static let shared = GPIOManager()
    private let waterDrainGPIO: GPIO

    init() {
        let gpios = SwiftyGPIO.GPIOs(for: .RaspberryPi4)
        waterDrainGPIO = gpios[.P21]! // ①
        waterDrainGPIO.direction = .OUT // ②
    }

    func drainWater() async {
        waterDrainGPIO.value = 1
        print("Started draining water")
        // Can't use UInt64 because raspberry-pi is 32bit
        try! await Task.sleep(nanoseconds: 5 * 1_000_000_000)
        waterDrainGPIO.value = 0
        print("Stopped draining water")
    }
}

Since we will be operating the 21 pin this time, we will prepare an instance for the 21 pin (①)
Also, since the current will flow this time, direction is set to .OUT

Next is the implementation of the drainWater function
As for the water supply behavior, set the value property of the GPIO pin instance to 1. If you set it to 0, the water supply will stop.
This time, we want to supply water for only 5 seconds, so I implemented process like Start water supply → sleep for 5 seconds → water supply.

The trap this time was that the Raspberry Pi OS was 32-bit, so if you pass an explicit UInt64 to Task.sleep(nanoseconds:) Since it throws an error, the subsequent water supply stop processing is not executed.
At first, I implemented this sleep time as follows, but then an error was thrown, so Implemented

func drainWater(second: Int) async {
    waterDrainGPIO.value = 1
    print("Started draining water")
    // Can't use UInt64 because raspberry-pi is 32bit
    try! await Task.sleep(nanoseconds: UInt64(second * 1_000_000_000))
    waterDrainGPIO.value = 0
    print("Stopped draining water")
}

When you do this, water will come out of the pump like this!
The water pressure of the pump was higher than I expected, so the water supply method needs to be improved!

Enable scheduled script execution

Next, we will make it possible to schedule the processes we have implemented so far
This time, we will be able to execute processes ① to ③ once an hour and process ④ once a day

To prepare for that, do the following

  1. Each of the above processes can be called with command options
  2. Scheduling with systemd

Prepare command options

This time, run the following command to make it work

  1. SwiftGardenPi –captureData
  2. SwiftGardenPi –drainWater

You can get the options when running in terminal in CommandLine.arguments in the form of an array of String
so you can convert the options to an enum

enum CommandKind {
    case captureData
    case drainWater
    case switchLight(isON: Bool)

    static func converted(from arguments: [String]) -> CommandKind {
        print(arguments)
        if arguments.isEmpty {
            fatalError("Parameter is required!")
        } else if arguments.count == 3 {
            switch arguments {
            case "--switchLight":
                switch arguments {
                case "isOn":
                    return .switchLight(isON: true)
                case "isOff":
                    return .switchLight(isON: false)
                default:
                    fatalError("Invalid parameter")
                }
            default:
                fatalError("Invalid parameter")
            }
        } else if arguments.count == 2 {
            switch arguments {
            case "--captureData":
                return .captureData
            case "--drainWater":
                return .drainWater
            default:
                fatalError("Invalid parameter")
            }
        } else {
            fatalError("Parameter is required!")
        }
    }
}

After that, switch this enum and call the functions to be executed respectively.

Scheduling with systemd

For those who don’t know what systemd is in the first place (because the author didn’t know either), I asked ChatGPT

Glossary

What is systemd?

systemd is the system and session manager that runs as the Linux system initialization process and is responsible for booting the system and managing services. Many major Linux distributions in recent years have adopted it instead of the traditional initialization systems such as SysVinit and Upstart.

Some of the main features and functions of systemd include:

  1. Concurrency: systemd speeds up system boot time by starting services in parallel.
  2. Service Dependencies: By defining dependencies between services, the required services will start automatically when they are available.
  3. System State Management: You can use the systemctl command to start, stop, restart, check the status of services, and more.
  4. Log Management: Centrally manage system logs using a component called journald.
  5. Cgroup integration: You can control and monitor resource usage (CPU, memory, disk IO, etc.).
  6. Timer: Like Cron, you can schedule scheduled job executions.
  7. Socket-Based Activation: Services can only be activated on demand, saving resources.

With these features, systemd improves the efficiency of booting and operating Linux systems, while also providing ease of administration. However, there are pros and cons about its design concept and unique method.

By using this, you can do things like run a specified script at a specified time
This timeI referred to the article here
For example, in the case of water supply processing, I made the following settings
I also want to output a file for the log in the script to be executed. , prepare another file

#!/bin/bash

current_date=$(date +"%Y%m%d%H%M%S")
output_file="/home/${HOME_DIRECTORY_NAME}/result/Result/DrainWater/output_drain_water_${current_date}.txt"
SwiftGardenPi --drainWater >> $output_file

Next, prepare a service file (1) and a timer file (2)

# 1
sudo nano /etc/systemd/system/drain_water.service
# 2
sudo nano /etc/systemd/system/drain_water.timer

The service file looks like this

[Unit]
Description=Drain water automatically(This is comment)
After=network.target

[Service]
ExecStart=/path/to/your/drain_water.sh
WorkingDirectory=/home/${HOME_DIRECTORY_NAME}/${APP_DIRECTORY}

Describe the path of the script file to be executed in ExecStart
Description is an easy-to-understand comment

[Unit]
Description=Drain water every 9 A.M.
After=network.target

[Timer]
OnCalendar=*-*-* 09:00:00

[Install]
WantedBy=timers.target

In the timer file, specify when to run in OnCalendar
How to write details here

Now run the following command and reboot your Raspberry Pi!

sudo systemctl enable drain_water.timer

At the specified time, the specified process will be executed, and the log will be saved in the log file after execution
What remains in the log is what was print in swift. and so on

This completes the implementation on the Raspberry Pi side!

Summary

In this article, we have summarized the contents of the three parts of the SwiftGarden announcement of automatic water supply
The next article is “4. Checking data in the iOS app – Implementing Swift Charts”!

SwiftGarden 4.Check data in iOS app - Swift Charts implementationこの記事は、「Swiftでりんごを育ててみた」の4部「iOSアプリでデータを確認する - Swift Chartsの実装」の内容をまとめた記事になります!...
0

COMMENT

Your email address will not be published. Required fields are marked *

CAPTCHA