TL;DR
目的
筆者はアクアリウムが好きです。
ライトやエアレーションといった、アクアリウムを支える機器のオンオフをスケジューリングやスマートフォンから制御したい。(本記事)
ゆくゆくはセンサーを駆使して最適な水槽環境を維持したい為、RaspberryPiを用いてソフトに制御したい。(未実証)
要件
- AC100V機器の電源をスケジューリングで制御できること
- AC100V機器の電源をスマートフォンから制御できること
方法
RaspberryPiのGPIOピンとスイッチングリレーを用いてAC100Vのスイッチングを行う。
その為に、GPIOピンから制御できる電源タップを作る(DIY要素)。
GPIOから制御できれば(=ソフトウェア的に制御できる)、あとはcrontabによるスケジュール実行とスマートフォンから制御できるようにwebサーバーをホスティングすればおk
アーキテクチャ
ラズパイがハードとソフトの仲介役になる形です(IoTらしさ)
リレーというのは簡単に言えば、普段人間がぽちぽち電気のON/OFFを切り替えるところを電気信号を用いて切り替える装置です。今回はその信号がラズパイのGPIOピンの5V信号となります。
あと今回制御したい機器を紹介します。(青枠内)
LEDランプ: 水草の光合成に必須。日中のみ点灯させます。
CO2添加装置: 水草の光合成に必須。LEDランプが点灯している間は共に作動させます。CO2はボンベで供給する為、有限資源(?)です。
エアレーション: 水草やお魚の呼吸に必要です。ぶくぶく。CO2添加装置が作動していない時に作動させます。
実装
ここから具体的な実装や作業を記していきます。
GPIO電源タップの作成(DIY)
AC100V機器の改造やDIYには危険が伴い、施工に資格が必要な場合があります。
参考にする場合は自己責任でお願いします。
※ 筆者はなぜか第二種電気工事士を持っています。
回路図(手書き)
怖いんでAC100V側にはヒューズを付けました。
調達〜作成
主に秋葉原駅周辺にお世話になりました。はんだごても持っていなかった為、購入。
リレーについてはネット通販で購入。
成果物
ちょっとごちゃついてますが、収納ボックスに入れて短絡が起きないよう固定して使っています。
※ 最低限の絶縁しか施していないので、お子さんやペットがいる家庭は要注意
スケジュール実行(crontab)
OSの機能であるcrontabでGPIOを制御します。単純。
こんなかんじ
# 2:LED
00 18 * * * gpio -g write 2 1
0 23 * * * gpio -g write 2 0
# 3:CO2
00 18 * * * gpio -g write 3 1
50 22 * * * gpio -g write 3 0
# 4:AIR
0 23 * * * gpio -g write 4 1
00 18 * * * gpio -g write 4 0
スマホアプリ
スマホからぽちぽちON/OFFできるようにします。
iOS,Android双方で使いたかった為、webアプリ(PWA)にしました。
フロントエンドはVue3を試用
バックエンドはNode
サーバー機能はKoa.js
GraphQL周りはApolloを使用
DB(SQLite)も色々あって使ってますが説明等は割愛
GraphQLのSubscription(≒プッシュ型API)を用いて他の端末からスイッチをいれた時でも画面に反映されるようにしています。
バックエンド
スキーマ定義はこんな感じです。シンプル。
type Gpio {
id: Int!
name: String
enabled: Boolean!
}
type Query {
version: String
gpios(id: Int): [Gpio]
}
type Mutation {
switchGpio(id: Int!, enable: Boolean!): Gpio
}
type Subscription {
switchedGpio: Gpio
}
GPIOの操作は以下のようにexec
を使います。使いやすいようにPromise化してます。
import { exec } from 'child_process';
export const runCommandAsync = async (command: string): Promise<{ stdout: string; stderr: string }> => {
return new Promise((resolve, reject) => {
exec(command, (err, stdout, stderr) => {
if (!!err) {
reject(err);
}
resolve({
stdout: stdout,
stderr: stderr,
});
});
});
};
runCommandAsync("gpio -g write 2 1"); // GPIOの操作
あとはリゾルバ実装して終わり。Subscription周りはApolloのinstallSubscriptionHandlers
を使えばおk
apolloServer.installSubscriptionHandlers(httpServer);
### フロントエンド
画面的には1つなので画面のソースはっつけます。
<template>
<div class="container">
<div class="switch-box" v-for="gpio in gpios" :key="gpio.id">
<Button
class="main-switch p-button-rounded"
:class="{ 'button-enabled':!gpio.enabled, 'p-button-outlined':!gpio.enabled, 'p-button-secondary':!gpio.enabled }"
style="border-radius:10rem"
v-model="gpio.enabled"
:label="gpio.name"
@click="switchGpio(gpio)"
:disabled="gpio.progress"
/>
</div>
</div>
</template>
<script lang="ts">
import { ref, defineComponent, Ref } from 'vue'
import Button from 'primevue/button'
import { GraphqlServiceSingleton } from '../services/grapqhqlService'
import gql from 'graphql-tag'
import { Gpio } from '../types/graphql'
type AppGpio = {
id: number,
name?: string | null,
enabled: boolean;
progress: boolean
}
export default defineComponent({
name: 'Main',
components: {
Button
},
setup: () => {
const gpios:Ref<AppGpio[]> = ref([])
const toggle = ref(false)
const graphql = GraphqlServiceSingleton.getInstance()
graphql.query({
query: gql`
query{
gpios{
id
name
enabled
}
}
`
}).then((result)=>{
gpios.value = (result.data.gpios as Gpio[]).map(gpio=>({
id: gpio.id,
name: gpio.name,
enabled: gpio.enabled,
progress: false
}))
})
const switchGpio = (gpio:AppGpio) => {
gpio.progress = true
graphql.mutation({
mutation: gql`
mutation switch($id:Int!,$enable:Boolean!){
switchGpio(id: $id enable: $enable){
id
name
enabled
}
}
`,
variables:{
id: gpio.id,
enable: !gpio.enabled
}
}).finally(()=>{
gpio.progress = false
})
}
let disposable = graphql.subscribe({
query:gql`
subscription{
switchedGpio{
id
name
enabled
}
}
`
}).subscribe({
next:(data:any)=>{
const updatedGpio:Gpio = data.data.switchedGpio
const index = gpios.value.findIndex(gpio=>gpio.id == updatedGpio.id)
if(index >= 0){
gpios.value[index] = {
id: updatedGpio.id,
name: updatedGpio.name,
enabled: updatedGpio.enabled,
progress: false
}
}
},
error:(error)=>{
console.error(error)
}
})
const onBeforeUnmount = () => {
console.log("onBeforeUnmount")
disposable?.unsubscribe()
}
return { toggle, gpios, switchGpio, onBeforeUnmount }
},
})
</script>
<style>
.container {
display: flex;
flex-direction: column;
justify-content:center;
align-items: center;
}
.switch-box {
margin-bottom: 5vh;
}
.main-switch {
height:10vh;
width:70vw;
font-size: 7vw;
}
.main-switch * {
display:inline-block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
展望など
- センサーと組み合わせて水槽環境を良くしてみたい。CO2センサーでCO2添加量を最適化とか。
- 自作した電源タップをメーカーに発注(きっとお高い...)するかGPIOピン専用のリレーモジュールに替えて安全性を高めたい。