ATOM Echo で おしゃべりティラノくんを作製
前回は ATOM Echo に定時発話させるためのシステムをNode-REDを活用して構築しました。
ここでは、ATOM Echoの購入理由であった おしゃべりティラノくん を実現いたしましたので報告いたします。
目次
おしゃべりコンシェルジュ ティラノくん
おしゃべりティラノくんとは以前ラズパイで作った我が家のコンシェルジュです。
定時で発話したり気圧センサの測定値を報告してくれます。
スマートスピーカAlexaとも連動しています。
もう2年近く元気に毎日動いてくれております。
ちょっとラズパイZEROはもっと違うことに使いたくなってきましたし
ここで低額のATOM Echoに変更することにしました。
構成
ティラノくんの口を動かすためのサーボモータと気圧センサをATOM Echoに接続しました。
スピーカも壊れてしまったため、高出力のモノに交換しております。
部品
- ATOM Echo
- サーボモータ
制御線はATOMのG25ピンに接続
- 気圧センサ
ATOMのGroveピンに接続してI2Cで通信
- スピーカ 8Ω-8W
純正スピーカが故障してしまったので交換
システム概要
以下がティラノくんの動作システムの概要図です。
前回同様に定期的に発話させるmp3のURLをラズパイサーバからATOMにUDPで送信します。
URLを受けてストリーミング再生し、その後に気圧センサの測定値をサーバにUDPで返します。
気圧測定値を受けてサーバで気圧報告用の発話mp3 URLをATOMに送ってストリーミング再生します。
ATOMでのストリーミング再生中にサーボをランダムで動かしてティラノくんの口を動かします。
動作
サーボと気圧センサを接続して動かしてみました。
サーボと気圧センサを追加#M5Stack #M5AtomEcho #ATOMEcho
GAOHOU の 気圧センサーモジュール BMP280 を Amazon でチェック! https://t.co/aehzNV5RE8 @amazonより pic.twitter.com/H1UFhFDL37
— HomeMadeGarbage (@H0meMadeGarbage) June 21, 2020
発話中にサーボをランダムに動かうようにしました。
定時発話後に気圧センサの値をサーバに返して、更に気圧用の発話mp3 URLを受け取り発話します。
Node-RED
以下が今回のシステムの Node-RED フローです。
①~③は前回と同じで気圧測定値の受信と気圧報告用の発話生成部を追加しました。
④ UDP受信ノード
ATOMで測定した気圧を文字列としてUDPで受け取ります。
ポートを指定します。
⑤ functionノード
前段で受信した気圧測定値を受けて、気圧報告発話文を生成し
②のgoogle-ttsノードに渡してATOMにmp3 URLをUDP送信します。
ATOM Echo用 Arduinoコード
気圧センサBMP280のライブラリは以下を使用しました。
https://github.com/Seeed-Studio/Grove_BMP280
以下の修正を施して使用しています。
- センサのI2Cアドレス修正
Seeed_BMP280.h
#define BMP280_ADDRESS 0x77 → 0x76に変更
- I2C通信ピン指定
Seeed_BMP280.cpp
Wire.begin(); → Wire.begin(26, 32); //SDA, SCLに変更
ESP32用のサーボモータライブラリESP32Servoも使用しました。
https://github.com/madhephaestus/ESP32Servo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
#include <M5Atom.h> #include <WiFi.h> #include <WiFiClient.h> #include "AsyncUDP.h" #include <ESP32Servo.h> #include "AudioFileSourceICYStream.h" #include "AudioFileSourceBuffer.h" #include "AudioGeneratorMP3.h" #include "AudioOutputI2S.h" #include "Seeed_BMP280.h" #include <Wire.h> BMP280 bmp280; int pressure; String http; const char * ssid = "WiFi SSID"; const char * password = "WiFi PASS"; AsyncUDP udp; int prayState = 0; IPAddress ip(192, 168, AAA, BBB); // for fixed IP Address IPAddress gateway(192,168, AAA, CCC); // IPAddress subnet(255, 255, DDD, EEE); // IPAddress DNS(192, 168, AAA, CCC); Servo myservo; AudioGeneratorMP3 *mp3; AudioFileSourceICYStream *file; AudioFileSourceBuffer *buff; AudioOutputI2S *out; #define CONFIG_I2S_BCK_PIN 19 #define CONFIG_I2S_LRCK_PIN 33 #define CONFIG_I2S_DATA_PIN 22 // Called when a metadata event occurs (i.e. an ID3 tag, an ICY block, etc. void MDCallback(void *cbData, const char *type, bool isUnicode, const char *string) { const char *ptr = reinterpret_cast<const char *>(cbData); (void) isUnicode; // Punt this ball for now // Note that the type and string may be in PROGMEM, so copy them to RAM for printf char s1[32], s2[64]; strncpy_P(s1, type, sizeof(s1)); s1[sizeof(s1)-1]=0; strncpy_P(s2, string, sizeof(s2)); s2[sizeof(s2)-1]=0; Serial.printf("METADATA(%s) '%s' = '%s'\n", ptr, s1, s2); Serial.flush(); } // Called when there's a warning or error (like a buffer underflow or decode hiccup) void StatusCallback(void *cbData, int code, const char *string) { const char *ptr = reinterpret_cast<const char *>(cbData); // Note that the string may be in PROGMEM, so copy it to RAM for printf char s1[64]; strncpy_P(s1, string, sizeof(s1)); s1[sizeof(s1)-1]=0; Serial.printf("STATUS(%s) '%d' = '%s'\n", ptr, code, s1); Serial.flush(); } //デュアルコア Servo制御 void servo(void *pvParameters) { for (;;){ disableCore0WDT(); if(prayState > 0){ myservo.write(random(55, 125)); delay(250); } } } void setup(){ M5.begin(true, false, true); Serial.begin(115200); WiFi.config(ip, gateway, subnet,DNS); // Set fixed IP address delay(1000); M5.update(); if(!bmp280.init()){ Serial.println("Device error!"); } WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); if (WiFi.waitForConnectResult() != WL_CONNECTED) { Serial.println("WiFi Failed"); while(1) { delay(1000); } } //Servo タスク xTaskCreatePinnedToCore( servo , "servo" // A name just for humans , 4096 // This stack size can be checked & adjusted by reading the Stack Highwater , NULL , 1 // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest. , NULL , 0); if(udp.listen(XXXX)) { //XXXX:UDPポート番号 Serial.print("UDP Listening on IP: "); Serial.println(WiFi.localIP()); udp.onPacket([](AsyncUDPPacket packet) { int length = packet.length(); char https[length]; memcpy(https, packet.data(), length); http = "http" + String(https).substring(5, length); Serial.println(http); if(prayState == 0) { prayState = 1; }else if(prayState == 1) { prayState = 2; } }); } } void loop(){ if(prayState > 0){ audioLogger = &Serial; file = new AudioFileSourceICYStream( (const char *)http.c_str() ); file->RegisterMetadataCB(MDCallback, (void*)"ICY"); buff = new AudioFileSourceBuffer(file, 10240); buff->RegisterStatusCB(StatusCallback, (void*)"buffer"); out = new AudioOutputI2S(); out->SetPinout(CONFIG_I2S_BCK_PIN, CONFIG_I2S_LRCK_PIN, CONFIG_I2S_DATA_PIN); out->SetChannels(1); out->SetGain(0.4); mp3 = new AudioGeneratorMP3(); mp3->RegisterStatusCB(StatusCallback, (void*)"mp3"); mp3->begin(buff, out); myservo.attach(25); Serial.print(pressure = int(bmp280.getPressure()/100.0)); Serial.println(" hPa"); while(mp3->isRunning()) { Serial.println(prayState); if (!mp3->loop()) { mp3->stop(); myservo.detach(); if(prayState == 1){ prayState = 2; udp.broadcastTo((const char *)String(pressure).c_str(), XXXX); //気圧UDP送信、XXXX:UDPポート番号 delay(1000); }else if(prayState == 2){ prayState = 0; } } } } M5.update(); } |
Node-REDからのmp3 URLをUDPで受信してストリーミング再生しています。
再生後に気圧センサの測定値をサーバにUDP送信します。
mp3ストリーミング再生中はデュアルコアにしてもう一方のコアでサーボをランダムに動かします。
ティラノくん
システムが出来上がりましたのでティラノくんを組み立てます。
サーボとティラノくんはヒモでつなげて口を動かします。
動作
定時発話の後に気圧を報告してくれます。
おわりに
目的としていたATOM Echoを用いたおしゃべりティラノくんの制作が実現できました。
次回はスマートスピーカAlexaとの連動を考えてみたいと思います。