1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

アクアリウムIoT スマート電源タップを自作してみた

Posted at

TL;DR

目的

筆者はアクアリウムが好きです。
ライトやエアレーションといった、アクアリウムを支える機器のオンオフをスケジューリングやスマートフォンから制御したい。(本記事)

ゆくゆくはセンサーを駆使して最適な水槽環境を維持したい為、RaspberryPiを用いてソフトに制御したい。(未実証)

要件

  • AC100V機器の電源をスケジューリングで制御できること
  • AC100V機器の電源をスマートフォンから制御できること

方法

RaspberryPiのGPIOピンとスイッチングリレーを用いてAC100Vのスイッチングを行う。
その為に、GPIOピンから制御できる電源タップを作る(DIY要素)。
GPIOから制御できれば(=ソフトウェア的に制御できる)、あとはcrontabによるスケジュール実行とスマートフォンから制御できるようにwebサーバーをホスティングすればおk

アーキテクチャ

アーキテクチャ.png

ラズパイがハードとソフトの仲介役になる形です(IoTらしさ)
リレーというのは簡単に言えば、普段人間がぽちぽち電気のON/OFFを切り替えるところを電気信号を用いて切り替える装置です。今回はその信号がラズパイのGPIOピンの5V信号となります。

あと今回制御したい機器を紹介します。(青枠内)
LEDランプ: 水草の光合成に必須。日中のみ点灯させます。
CO2添加装置: 水草の光合成に必須。LEDランプが点灯している間は共に作動させます。CO2はボンベで供給する為、有限資源(?)です。
エアレーション: 水草やお魚の呼吸に必要です。ぶくぶく。CO2添加装置が作動していない時に作動させます。

実装

ここから具体的な実装や作業を記していきます。

GPIO電源タップの作成(DIY)

AC100V機器の改造やDIYには危険が伴い、施工に資格が必要な場合があります。
参考にする場合は自己責任でお願いします。

※ 筆者はなぜか第二種電気工事士を持っています。

回路図(手書き)

iOS の画像.jpg

怖いんでAC100V側にはヒューズを付けました。

調達〜作成

主に秋葉原駅周辺にお世話になりました。はんだごても持っていなかった為、購入。
リレーについてはネット通販で購入。

成果物

S__4014093.jpg

ちょっとごちゃついてますが、収納ボックスに入れて短絡が起きないよう固定して使っています。
最低限の絶縁しか施していないので、お子さんやペットがいる家庭は要注意

スケジュール実行(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ピン専用のリレーモジュールに替えて安全性を高めたい。
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?