Motivation
長女が秋祭りの金魚すくいで金魚を持って帰ってきて、エアポンプ付きの小さな水槽買って環境整えて、長女も毎日餌やったり水槽の掃除したり世話してるともう2ヶ月以上元気に泳いでる。旅行等で家を空ける時に餌やりどうしようかなと思って自動餌やり器とか物色してみたが、このくらいなら自作できるんじゃね、ということでググったら、みんなLEDチカ並み?に自作してたので自作してみた。
Prerequisite
新しく買ったものはありません。
- ESP-WROOM-32D(Arduino互換マイコン): センサデータをArduinoで取得して、ゲートウェイ経由でsyslogサーバに集めてみたで買ったものが遊んでたので、再利用
- Mini Servo: 昔買ったArduino Sidekick Basic Kitに入ってた。使ったことない。。。
- サーボを固定する治具とか餌を貯めておく容器: これも昔買った3DプリンタMicro 3Dで作った(後述)。
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使ってプリントアウト
-
水槽に固定する治具
-
餌を貯めておく容器(蓋)
-
餌を貯めておく容器(本体)
餌を貯めておく容器の横に、回転させた時に餌が出てくるよう穴を5つ開けた。3Dプリントしたものとサーボを組み合わせるとこんな感じに
試験
一応動いた。わかりにくいのですが、奥側の開けた穴が垂直下向きになった時に餌がちゃんと落ちます。
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 fcgiwarp
はapt 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 READMEのLogin 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.shのSET_REFRESH_TOKEN=''
に設定するとユーザ/パスワード/MFA認証と同様に動いてます。
最後に
家空ける時にセッティングしますが、ちゃんと動くはず。。。