1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

金魚の自動餌やり機を作ってみた

Last updated at Posted at 2022-11-26

Motivation

長女が秋祭りの金魚すくいで金魚を持って帰ってきて、エアポンプ付きの小さな水槽買って環境整えて、長女も毎日餌やったり水槽の掃除したり世話してるともう2ヶ月以上元気に泳いでる。旅行等で家を空ける時に餌やりどうしようかなと思って自動餌やり器とか物色してみたが、このくらいなら自作できるんじゃね、ということでググったら、みんなLEDチカ並み?に自作してたので自作してみた。

Prerequisite

新しく買ったものはありません。

Mini Servoをタイマ制御する

はじめての電子工作 Arduinoで金魚の自動餌やり機を自作してみたを参考にMini Servoをタイマ制御しようとしたが、手持ちの純正Arduino Nano 33 BLE Senseではなく、あとで配線を戻しやすい上記ESP-WROOM-32Dを使おうとしたところ、まあ動かない。
ググったところ、CPUアーキテクチャ?が違うらしく、下記を参照してコード修正した。

下記コードは12時間毎に、初期位置から180度回転したのち、-180度戻るだけです。
ESP-WROOM-32DにはWifiついてるので、センサデータをArduinoで取得して、ゲートウェイ経由でsyslogサーバに集めてみた同様、餌やりしたときにsyslogサーバにsyslog飛ばすようにしてます。ログだけは旅行先からでも見れる。。。後述するAlexaをしゃべらせるためのCGIアクセスのhttp client実装はhttps://101010.fun/iot/http-client-get-post.htmlを参考にしました。

#include <ESP32Servo.h>
//#include <MsTimer2.h>
#include <Ticker.h>
#include <WiFi.h>
#include <WiFiUdp.h>
#include <Syslog.h>
#include <HTTPClient.h>

#define SYSLOG_PORT 514

Servo myservo;  // create servo object to control a servo
int pos = 0; // initial servo position (degree)

//wifi configuration
char* ssid     = "Wifi Network";
char* password = "guest";
IPAddress wifiIp;
WiFiUDP udpClient;

char* server = "192.168.0.11"; // syslog server
Syslog syslog(udpClient, server, SYSLOG_PORT, "esp32", LOG_KERN);

// timer variables
Ticker periodicTicker;
volatile boolean tsw = true;
int hour_cnt = 0;
int min_cnt = 0;
int sec_cnt = 0;

void setup() {
  // put your setup code here, to run once:
  myservo.attach(2);  // attaches the servo on pin 2 to the servo object
  Serial.begin(115200);
  myservo.write(pos); // tell servo to go to position in variable 'pos'

  periodicTicker.attach_ms(1000, timecnt);  //timecnt() is called every 1000ms

  // We start by connecting to a WiFi network
  delay(10);
  if(Serial) {
    Serial.println(ssid);
  }
  WiFi.begin(ssid, password);
  while(WiFi.status() != WL_CONNECTED) {
      delay(500);
      if(Serial)
        Serial.print(".");
  }
  wifiIp = WiFi.localIP();
  if(Serial) {
    Serial.println("");
    Serial.println(WiFi.localIP());
  }
  syslog.log(LOG_INFO, "setup done!");
}

void loop() {
  // put your main code here, to run repeatedly:
  // timer action
  if( tsw ){
    motor_action();
    syslog.log(LOG_INFO, "motor_action()!");
    tsw=false;

    //call alexa api to speak when motor_action()
    WiFiClient client;
    HTTPClient http;

    if( !http.begin(client, "http://192.168.0.11/speak.sh") ) {
      if(Serial)
        Serial.println("Failed HTTPClient begin!");
    }else{
      if(Serial) {
        Serial.println("HTTPClient begin!");
      }
      http.addHeader("Content-Type", "text/html");
      int responseCode = http.GET();
      if(Serial) {
        Serial.println(responseCode);
        Serial.println(http.getString());
      }
      http.end();
      syslog.logf(LOG_INFO, "responseCode : %d", responseCode);
    }
  }
}

void motor_action() {
  for (pos = 0; pos <=180; pos += 1) { // goes from 0 degrees to 180 degrees
    // in steps of 1 degree
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(3);                        // waits 3ms for the servo to reach the position
    if(Serial) {
      if(pos%10 == 0) {
        Serial.print(pos);
        Serial.print(", ");
      }
    }
  }
  if(Serial)
    Serial.println("");
  for (pos = 180; pos >= 0; pos -= 1) { // goes from 0 degrees to 180 degrees
    myservo.write(pos);              // tell servo to go to position in variable 'pos'
    delay(3);                        // waits 3ms for the servo to reach the position
    if(Serial) {
      if(pos%10 == 0) {
        Serial.print(pos);
        Serial.print(", ");
      }
    }
  }
  if(Serial)
    Serial.println("");
}

void timecnt() {
  sec_cnt++;
  if( sec_cnt > 59 )
  {
//    tsw=true;
    min_cnt++;
    sec_cnt=0;
    if( min_cnt > 59 )
    {
//      tsw=true;
      hour_cnt++;
      min_cnt=0;
      if( hour_cnt > 11 )
      {
        tsw=true;
          hour_cnt=0;
       }
    }
    if( min_cnt%10 == 0 || (hour_cnt > 10 && min_cnt > 40))
      syslog.logf(LOG_INFO, "%d h %d min %d sec", hour_cnt, min_cnt, sec_cnt);    
  }
  if(Serial) {
    Serial.print(hour_cnt);
    Serial.print("h");
    Serial.print(min_cnt);
    Serial.print("min");
    Serial.print(sec_cnt);
    Serial.println("sec");
  }
}

サーボを固定する治具とか餌を貯めておく容器を作る

Autodesk Tinkercadで下記の通り設計して、前述の通りMicro 3D使ってプリントアウト

試験

ezgif.com-video-to-gif-2.gif
一応動いた。わかりにくいのですが、奥側の開けた穴が垂直下向きになった時に餌がちゃんと落ちます。

Alexaに通知させる

60秒で回転するように仮設定して試験はしたのですが、所望の時間(12時間毎)で長期安定試験する時つい確認し忘れるので、下記を参考にsyslogサーバとして利用しているraspberry piを経由して、回転したらAlexaにしゃべらせるようにして、Alexaがしゃべったら急いで水槽を見に行き金魚が食べるより先に餌が適量出ていることを確認しました。。。

前者サイト「CGI(シェル)の作成」のspeak.shは引数を取らせず、下記の通りしゃべるセリフは埋め殺しです。

echo `/var/www/html/alexa.sh -e "speak:goldfish was fed"`

前者サイトにはいくつかtypo?があるようです。

  • パッケージでいろいろインストール」のapt install fcgiwarpapt install fcgiwrapです。
  • fcgiwrapの設定」の下記fastcgi_paramは最後に/が要りました。
    location ~ \.sh$ {
        try_files $uri =404;
        root   /var/www/html/;
        fastcgi_split_path_info ^(.+\.sh)(/.+)$;
        fastcgi_pass      unix:/var/run/fcgiwrap.socket;
        include           /etc/nginx/fastcgi_params;
        fastcgi_param     DOCUMENT_ROOT /var/www/html/; #最後に/が要る!
        fastcgi_param     SCRIPT_FILENAME   $document_root$fastcgi_script_name;
        fastcgi_intercept_errors    on;
    }

後者サイトのInitial login page has changed #166にあるように、Alexa API?のURLが変わっており、2023/5時点では、シェルスクリプト内のhttps://alexa.${AMAZON}https://alexa.${AMAZON}/spa/index.htmlに修正する必要がありました。
また、前者サイト「alexa-remote-control.shの導入」のSET_MFA_SECRET: Amazonの認証コードは「認証コード」の手順に加え念のため、後者サイト「Old option MFA」の通り、「新しいアプリの追加」の「バーコードをスキャンできませんか?」で表示されるMFA shared secretを引数として、下記で出力されるワンタイムパスワードをAmazonの登録webフォームに入力してアプリ追加しておきました。

oathtool -b --totp <MFA shared secret>

私のraspberry piにはoathtoolがインストールされてなかったので、インストールして実行しました。

(2024/5/4追記)
Login procedure does not work anymore (even with new uri) #171の通り、Amazon側の仕様変更で上記のユーザ/パスワード/MFAによる認証は昨年末くらいには使えなくなり、トークン認証のみとなりました。
alexa-remote-control READMELogin via REFRESH_TOKENの記載の通り、MacOS上にalexa-cookie-cliをcloneし、そのディレクトリにコピーしたalexa-cookie-cli-macos-x64をTerminal上で./alexa-cookie-cli-macos-x64 -p amazon.co.jpと実行し、同MacOSのSafariからhttp://127.0.0.1:8080/ にアクセス、自分のamazon.co.jpアカウントでログインするとTerminal上にAtnr|で始まるrefreshTokenが返ってきます。
このトークンを、raspberry pi上ののalexa_remote_control.shSET_REFRESH_TOKEN=''に設定するとユーザ/パスワード/MFA認証と同様に動いてます。

最後に

家空ける時にセッティングしますが、ちゃんと動くはず。。。

1
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?