Event

SwiftGarden 3.自動給水を行う – SwiftでラズパイのGPIOを操作する

3.自動給水を行う – SwiftでラズパイのGPIOを操作する

この記事はiOSDC2023で発表したセッション、Swiftでりんごを育ててみたの発表の第3部の内容をまとめた記事になります。

「2.ラズパイでFirebaseにデータを送る – SwiftのCLIツールを動かす」の記事の続編です。もしまだ読んでいない方は読んでいただけると!

SwiftGarden 2.ラズパイでFirebaseにデータを送る - SwiftのCLIツールを動かすこの記事は、「Swiftでりんごを育ててみた」の2部「ラズパイでFirebaseにデータを送る - SwiftのCLIツールを動かす」の内容をまとめた記事になります!...

今回は④の給水の実装、そして①~④の処理をスケジューリングする方法をまとめます

家庭菜園の植物に自動で給水できるようにする

ラズパイとポンプを使い、給水を制御できる仕組みを作ることを目指します
今回はこちらの記事を参考に実装を進めました
使用するポンプはこちら12v電源で動きます、リレーはこちらのものです!
最初にポンプを動かした時に12vではない電源を使っていて、動作しなかったので、しっかり12vのものを用意しましょう!

今回はラズパイのGPIOを利用して、このポンプを制御します
そもそものラズパイ(RapberryPi)やGPIOについては、昨年度の発表記事に説明があるので、詳細はそちらを確認してください

0.導入 - 発表やSwiftHomeの概要紹介この記事は、「Swiftで我が家をより便利に、安全に」の発表の導入部分の内容をまとめた記事になります!...
3. ラズパイでセンサーの値を読み取る - 重量センサーの値を取得するこの記事は、「Swiftで我が家をより便利に、安全に」の発表の3部「3. ラズパイでセンサーの値を読み取る - 重量センサーの値を取得する 」の内容をまとめた記事になります!...

今回、配線は以下のようになっています

ちなみにリレーモジュールとは、以下のようなものです

用語解説

リレーモジュールとは?

電気回路や電子デバイスで高電流や高電圧の回路を低電流・低電圧の信号で制御するためのスイッチの役割を果たす電子部品

今回の実装では、GPIO21ピンから、リレーのスイッチを操作し、12vの電気の流れをON/OFFできるようにします

SwiftyGPIOで実装する

上記のリレーの切り替えの実装を昨年も利用したSwiftyGPIOを利用して行います
なおSwiftyGPIOのコードはLinux環境でしか動作しないので、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")
    }
}

今回は21ピンを操作するので、21ピン用のインスタンスを用意します(①)
また今回は電流を流すので、direction.OUTに指定します

次にdrainWater関数の実装です
給水の挙動としては、GPIOピンのインスタンスのvalueプロパティを1にすると給水、0にすると給水停止状態になります
今回は5秒だけ給水するようにしたいので、給水開始→5秒sleep→給水停止という処理を実装しました

今回トラップだったのが、このラズパイのOSが32ビットだったので、Task.sleep(nanoseconds:)に明示的なUInt64を渡すとエラーをthrowするので、後続の給水停止処理が実行されない点です
最初はこのsleep時間を以下のように実装していましたが、そうするとエラーがthrowされてしまったので、上記のような実装になっています

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")
}

これを実行すると、こんな感じでポンプから水が出ます!
思ったよりもポンプの水圧が高かったので、給水の仕方は要改善です!

スクリプトを定期実行できるようにする

次にこれまで実装した処理をスケジューリングできるようにします
今回は①〜③の処理を1時間に1回、④の処理を1日1回実行できるようにします

そのための準備として、以下の作業を行います

  1. 上記の処理をそれぞれコマンドのオプションをつけて呼び出せるようにする
  2. systemdを使って、スケジューリングする

コマンドのオプションを用意する

今回は以下のようなコマンドを実行して、動くようにします

  1. SwiftGardenPi –captureData
  2. SwiftGardenPi –drainWater

terminalで実行した時のオプションはCommandLine.argumentsにStringの配列の形式で取得できます
そこでそのオプションを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!")
        }
    }
}

あとはこのenumをswitchして、それぞれ実行する関数を呼び出し分ければOKです!

systemdでスケジューリングする

そもそもsystemdとは何かとわからない人向けに(作者も知らなかったので)、ChatGPTに聞いてみました

用語解説

systemdとは?

systemdはLinuxシステムの初期化プロセスとして動作し、システムの起動やサービスの管理を担当するシステムおよびセッションマネージャーです。近年の多くの主要なLinuxディストリビューションで、従来のSysVinitやUpstartといった初期化システムの代わりに採用されています。

systemdの主な特徴や機能には以下のようなものがあります:

  1. 並行処理: systemdはサービスを並行して起動することで、システムの起動時間を短縮します。
  2. サービスの依存関係: サービス間の依存関係を定義することで、必要なサービスが利用可能になったときに自動的に起動します。
  3. システムの状態管理: systemctl コマンドを使用して、サービスの起動、停止、再起動、状態の確認などを行うことができます。
  4. ログ管理: journald というコンポーネントを使用してシステムのログを集中的に管理します。
  5. cgroupの統合: リソースの使用量(CPU、メモリ、ディスクIOなど)を制御・監視することができます。
  6. タイマー: Cronのように、計画的なジョブの実行をスケジュールすることができます。
  7. ソケットベースのアクティベーション: サービスは要求が来たときにのみ起動することができるため、リソースを節約できます。

これらの特徴により、systemdはLinuxシステムの起動と運用の効率性を向上させるとともに、管理の容易さも提供しています。ただし、その設計思想や独自の方法について賛否両論があります。

これを使用すると、指定時刻に指定のスクリプトを実行するといったことができるようになります
今回はこちらの記事を参考にさせてもらいました
例えば給水処理の場合、以下のような設定をしました
実行するスクリプトは、ログ用のファイル出力もしたいので、別のファイルを用意します

#!/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

次にサービスファイル(1)をタイマーファイル(2)というものを用意します

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

サービスファイルは以下のようにしています

[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}

ExecStartに実行するスクリプトファイルのパスを記載します
Descriptionはわかりやすいコメントを書いています

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

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

[Install]
WantedBy=timers.target

タイマーファイルでは、OnCalendarの箇所にどのタイミングで実行するかを指定します
記載方法はこちらに詳細があります

これで以下のコマンドを実行して、ラズパイを再起動すればOKです!

sudo systemctl enable drain_water.timer

これで指定時刻になると、指定の処理を実行し、かつ実行後にそのログをログのファイルに残します
ログに残るのは、swiftでprintしたものなどです

これでラズパイ側の実装は完了です!

まとめ

この記事では、SwiftGardenの発表の3部の自動給水の内容をまとめました
次の記事は「4. iOSアプリでデータを確認する – Swift Chartsの実装」です!

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

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA