ROIを使うとかなり細かいコントロールができる一方、シリアル接続なのでルンバにArduinoを載せる必要があります。
頑張ってルンバから電源を取りつつArduinoをルンバに内蔵することも可能ではありますが。。。
今回やりたいことは単なる電源ON/OFFだけなので、ルンバとは有線接続せずに赤外線で済ませたいところです。
lircでやろうとしたところ、リモコンコードを読み取れずエラーになってしまいました。
テレビと同様に38Hzのようですが、信号が長すぎるのかもしれません。
そこで、Arduinoで赤外線のリモコンコードを読み取って赤外線LEDでルンバに送ります。
やっていることはRaspberry Pi+licdと同じですが、Arduinoなのでもっと泥臭い感じになります。
- 材料を揃える。
- 母艦のRaspberry PiにNode-REDをセットアップしてAmazon Echo Dotに認識させる
- Raspberry Pi Zero WをヘッドレスでWiFi対応セットアップする
- Raspberry Piで赤外線リモコンのコードをコピーする
- Node-REDから赤外線機能を呼び出してテレビを操作する
- FlashAirのGPIO機能でArduinoをHTTP API経由で制御する
- (おまけ)ArduinoからROI経由でルンバを動かす
- Arduinoから赤外線経由でルンバを動かす【今回】
- Node-REDからFlashAir/Arduino経由でルンバを動かす
回路を組む
Arduinoに赤外線受信モジュールと赤外線送信モジュールを繋ぎます。受信モジュールは赤外線リモコンを覚えさせる際に使用するだけなので、覚えた後は外しても構いません。
リモコンコードをArduinoのEEPROMに書き込む
赤外線受信モジュールからの入力信号がHIGHとLOWの変化する時間間隔を計測します。計測結果を配列に詰めてEEPROMに書き込めばOK。
このコードでは2種類のリモコンコードを覚えられるようになっています。
#include <EEPROM.h> #define PIN_IR_SENSOR 11 #define SCAN_INITIAL_TIMEOUT 10000000 #define SCAN_TIMEOUT 10000000 #define IR_BUF_LEN 64 volatile int IR_BUF[IR_BUF_LEN]; volatile bool SETUP_FLAG = false; void setup() { Serial.begin(9600); SETUP_FLAG = true; pinMode(PIN_IR_SENSOR, INPUT); updateIR(0); updateIR(1); } int updateIR(int pos) { Serial.println("SCAN START"); memset(IR_BUF,0,IR_BUF_LEN); for( int i=0; i<3; i++ ){ Serial.print("PUSH BUTTON "); Serial.println(pos); int result = scanIR(i); for(int j=0; j<result; j++){ Serial.print(IR_BUF[j]); Serial.print(" "); } Serial.print("\n"); Serial.print(result); Serial.println(" bytes were read"); delay(3000); } saveBuffer(pos * IR_BUF_LEN*2); Serial.println("SCAN END"); return 1; } int scanIR(int repeat) { int idx=0; int ir_status = HIGH; unsigned long lastStatusChanged = 0; unsigned long timeout = SCAN_INITIAL_TIMEOUT; while(1){ if ( ir_status == LOW ){ while( digitalRead(PIN_IR_SENSOR) == LOW){ // wait ; } } else { if( wait_high_signal( micros() + timeout ) < 0 ){ break; } } unsigned long now = micros(); if( lastStatusChanged > 0 ){ int val = (int)((now - lastStatusChanged) / 10); if( repeat > 0 ){ if( IR_BUF[idx] > 1 ){ break; } if( abs((int)(IR_BUF[idx] - (int)val)) > IR_BUF[idx]*0.3 ){ idx = 0; break; } IR_BUF[idx] = (int)( IR_BUF[idx] * repeat + val ) / (repeat + 1); } else { IR_BUF[idx] = val; } idx++; if( idx == IR_BUF_LEN -1 ){ break; } } lastStatusChanged = now; if (ir_status == HIGH) { ir_status = LOW; } else { ir_status = HIGH; } timeout = SCAN_TIMEOUT; } // while 1 if( idx > 0 ){ IR_BUF[idx] = -1; } return idx; } int wait_high_signal(unsigned long timeout) { while( digitalRead(PIN_IR_SENSOR) == HIGH ){ if( micros() > timeout ) { return -1; } } return 1; } void saveBuffer(int pos) { Serial.println("SAVE START"); byte buf; for( int i=0; i < IR_BUF_LEN; i++ ){ buf = lowByte(IR_BUF[i]); EEPROM.write( pos+i*2, buf ); delay(5); buf = highByte(IR_BUF[i]); EEPROM.write( pos+i*2+1, buf ); delay(5); if( buf < 0 ){ break; } } Serial.println("SAVE END"); }
EEPROMから読み込んだコードで赤外線送信する
今度はEEPROMから配列を読み込んで、覚えていた時間間隔でLEDを点滅させます。我が家のRoombaが具合悪いだけかもしれませんが、
1回送信しただけでは正常に反応しないことが多いので、3回連続で送信しています。
また、loop関数で繰り返し送信されてしまうので送信し終わったらスリープさせます。
#include <EEPROM.h> #define PIN_IR 10 #define IR_BUF_LEN 64 volatile int IR_BUF[IR_BUF_LEN]; void setup() { Serial.begin(9600); pinMode(PIN_IR, OUTPUT); Serial.println("Start!"); } void loop() { Serial.println("Run"); loadBuffer(0); executeIR(); executeIR(); executeIR(); check_sleep(true); } void wakeup(){ Serial.println("Wakeup"); } void check_sleep(bool flag) { if ( ! flag ) { return; } Serial.println("Sleep"); delay(1000); attachInterrupt(0, wakeup, CHANGE); set_sleep_mode(SLEEP_MODE_STANDBY); sleep_enable(); sleep_mode(); sleep_disable(); } void loadBuffer(int pos) { Serial.println("LOAD START"); int h, l; for( int i=0; i<IR_BUF_LEN; i++ ){ l = EEPROM.read(pos+i*2); h = EEPROM.read(pos+i*2+1); IR_BUF[i] = (h << 8) + l; Serial.print(IR_BUF[i]); Serial.print(" "); if( IR_BUF[i] < 0 ){ break; } } Serial.println("\nLOAD END"); } void executeIR() { for(int i=0; IR_BUF[i] > 0; i++){ unsigned long len = (unsigned long)IR_BUF[i] * 10; unsigned long now = micros(); do { digitalWrite(PIN_IR, 1-i&1); delayMicroseconds(8); digitalWrite(PIN_IR, 0); delayMicroseconds(7); } while( now + len > micros() ); } }
ここまででArduinoからRoombaに掃除をスタートするコマンドを送れるようになりました。
ところが、このままではArduinoの電源を入れた直後にしか信号が飛びません。
次回、Node-REDからのリクエストをFlashAirで受けて、Arduinoをスリープから復帰させるように改造します。
0 件のコメント:
コメントを投稿