enebularで、ATOM Echoにツイートを喋らせる！

ATOM Echoにツイートを喋らせてみました！
ぬいぐるみにうめこんだら、いい感じになりそう。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">特定のユーザーのツイートが投稿されるとAtom Echoで内容をしゃべってくれる(ちょっとラグがある🙆‍♀️ <a href="https://t.co/Pg3pNbozoS">pic.twitter.com/Pg3pNbozoS</a></p>&mdash; はりねずみ麺 (@hedgehog_noodl) <a href="https://twitter.com/hedgehog_noodl/status/1340595189165903872?ref_src=twsrc%5Etfw">December 20, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

enebularでツイートを取得し、テキストをMQTTで送信、最後にATOM Echoで再生します。
MQTTブローカーはshiftr.ioを使用しました。


## Atom echo側のコード

はじめはUIFlowでやろうと試みたのですが、M5burnerでATOM Echo TTSのtoken取得ができず断念...

こちらの記事を参考にさせていただきました!
[ATOM Echo でTTSを堪能](https://homemadegarbage.com/atomecho02)
[M5StackとNode-REDをMQTTで連携するメモ](https://www.1ft-seabass.jp/memo/2018/05/10/m5stack-meets-nodered-with-mqtt/)

```c
//*-----------------------------------*
// ボード: M5Stick-C
// Upload Speed: 115200
//*-----------------------------------*

#include "M5Atom.h"
#include <WiFi.h>
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <google-tts.h>

#include "AudioFileSourceICYStream.h"
#include "AudioFileSourceBuffer.h"
#include "AudioGeneratorMP3.h"
#include "AudioOutputI2S.h"

#define CONFIG_I2S_BCK_PIN      19
#define CONFIG_I2S_LRCK_PIN     33
#define CONFIG_I2S_DATA_PIN     22

// Wi-FiのSSID
char *ssid = "";
// Wi-Fiのパスワード
char *password = "";
// MQTTの接続先のIP
const char *endpoint = "";
// MQTTのポート
const int port = 1883;
// デバイスID
char *deviceID = "M5Stack";  // デバイスIDは機器ごとにユニークにします
// メッセージを知らせるトピック
char *userName = "";              // MQTTブローカ認証ユーザ名
char *userPass = "";      // MQTTブローカ認証パスワード
char *pubTopic = "/pub/M5Stack";
// メッセージを待つトピック
char *subTopic = "/sub/M5Stack";

TTS tts;

AudioGeneratorMP3 *mp3;
AudioFileSourceICYStream *file;
AudioFileSourceBuffer *buff;
AudioOutputI2S *out;
 
////////////////////////////////////////////////////////////////////////////////
   
WiFiClient httpsClient;
PubSubClient mqttClient(httpsClient);


void setup() {
  Serial.begin(115200);
  M5.begin();

  // Start WiFi
  Serial.println("Connecting to ");
  Serial.print(ssid);
  WiFi.begin(ssid, password);
 
  while (WiFi.status() != WL_CONNECTED) {
      delay(500);
      Serial.print(".");
  }

  // WiFi Connected
  Serial.println("\nWiFi Connected.");
   
  mqttClient.setServer(endpoint, port);
  mqttClient.setCallback(mqttCallback);

  Serial.println(tts.getSpeechUrl("こんにちは、世界！", "ja"));
  Serial.println(tts.getSpeechUrl("Hello, World!"));
 
  connectMQTT();
  
}

void loop() {
  mqttLoop();
  M5.update();
if (M5.Btn.isPressed()){
  speakText("こんにちは、私です");
}
  

  delay(100);
}

void speakText(String text){

  Serial.println(text);
 
  String http = "http" + tts.getSpeechUrl(text, "ja").substring(5);
  Serial.println(http);
  
  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);
 
  while(mp3->isRunning()) {
    Serial.println("playing");
     
    if (!mp3->loop()) {
      mp3->stop();
      break;
    }
  }
}

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();
}


void mqttCallback (char* topic, byte* payload, unsigned int length) {
 
    String str = "";
    Serial.print("Received. topic=");
    Serial.println(topic);
    for (int i = 0; i < length; i++) {
        Serial.print((char)payload[i]);
        str += (char)payload[i];
    }
    String text = str;
    
    speakText(text);
     
}

void connectMQTT() {
    while (!mqttClient.connected()) {
        if (mqttClient.connect(deviceID,userName,userPass)) {
            Serial.println("Connected.");
            int qos = 0;
            mqttClient.subscribe(subTopic, qos);
            Serial.println("Subscribed.");
        } else {
            Serial.print("Failed. Error state=");
            Serial.println(mqttClient.state());
            errorReport();
            // Wait 5 seconds before retrying
            delay(5000);
        }
    }
}
void mqttLoop() {
    if (!mqttClient.connected()) {
        connectMQTT();
    }
    mqttClient.loop();
}

void errorReport(){
  Serial.print("Failed. Error state = ");

  switch (mqttClient.state()) {
    case MQTT_CONNECT_UNAUTHORIZED:
      Serial.println("MQTT_CONNECT_UNAUTHORIZED");
      break;
    case MQTT_CONNECT_BAD_CREDENTIALS:
      Serial.println("MQTT_CONNECT_BAD_CREDENTIALS");
      break;
    case MQTT_CONNECT_UNAVAILABLE:
      Serial.println("MQTT_CONNECT_UNAVAILABLE");
      break;
    case MQTT_CONNECT_BAD_CLIENT_ID:
      Serial.println("MQTT_CONNECT_BAD_CLIENT_ID");
      break;
    case MQTT_CONNECT_BAD_PROTOCOL:
      Serial.println("MQTT_CONNECT_BAD_PROTOCOL");
      break;
    case MQTT_CONNECTED:
      Serial.println("MQTT_CONNECTED");
      break;
    case MQTT_DISCONNECTED:
      Serial.println("MQTT_DISCONNECTED");
      break;
    case MQTT_CONNECT_FAILED:
      Serial.println("MQTT_CONNECT_FAILED");
      break;
    case MQTT_CONNECTION_LOST:
      Serial.println("MQTT_CONNECTION_LOST");
      break;
    case MQTT_CONNECTION_TIMEOUT:
      Serial.println("MQTT_CONNECTION_TIMEOUT");
      break;
  }

  delay(5000); // Wait 5 seconds before retrying
}
```

## enebular
### ツイッターノードをインストール
パレットから、`node-red-node-twitter`を追加し、下記のようにつなぎます。
![スクリーンショット 2020-12-20 18.29.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/24c6afd5-6840-1c4d-1a57-6e4fd576e39c.png)


### Twitter 
TwitterのDeveloperコンソールから新しくアプリを作成。
developer.twitter.com/en/apps
![スクリーンショット 2020-12-20 18.16.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/98fa098a-a548-907e-792e-f8f2e5c8381d.png)

- API key
- API secret key
- Access token
- Access token secret
を生成し、ノードに設定。
![スクリーンショット 2020-12-20 18.08.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/480279/a772532c-dab0-bdf8-165a-b5750cc24e93.png)

ニュースのツイッターアカウントなど、早く情報を知りたいアカウントを設定すると便利そうです。