siroitori0413のアイコン画像
siroitori0413 2021年11月08日作成 (2021年12月20日更新)
製作品 製作品 閲覧数 3010
siroitori0413 2021年11月08日作成 (2021年12月20日更新) 製作品 製作品 閲覧数 3010

GR-ROSEを使ったIoT鳩時計の制作

GR-ROSEを使ったIoT鳩時計の制作

IoT鳩時計をつくろう

GR-ROSEでAzureを使ったシステム開発のコンテストに応募することになりました。
コンテストありきで何を作ろうと考えたわけなのですが、GR-ROSEはロボットに強いということだったのでとにかくサーボモーターを使おうと思ってからの鳩時計の発想になりました。

材料

工作する

まずは段ボールで作成


簡単に作ってみようと思いましたが、なかなか難しかったです。
普段設計なんて何もやらない人間が作ると、基本的なところで間違ったりします。
たとえばドアの軸の位置をずらして扉が閉まらなくなったり…

段ボールで作ることによってだいたいどういうのを作ったら良いか見えてきたので、ここは3Dプリンターで作ってみることにしました。

3Dモデルを作成して3Dプリンターで出力


扉にワンポイント欲しかったので文字を入れてみることにしました。なかなかのダサい感じに仕上がりました。

次に枠を作ろうと思ったのですが、3Dプリンターのサイズオーバーだったので諦めて、サーボモーターを固定する部分だけ出力するようにしました。

良い感じに収まりました。
左上が扉パーツで、それ以外がサーボモーターを固定するためのものです。これは扉を裏側から見ている状態になります。

本当は壁全体を3Dプリントしたかったですが大きさ的に無理でした。分割して作成する方法もあったのですが、今回は壁は段ボールにすることにしました。

裏側から見たところ

構成

構成図
上記のように配線しました。

サーボモーターは扉用と鳥用の2つがあります。

Grove MP3モジュールはSerial4(6番と7番のピン)を使用することにしました。

電源

外部電源を使えるようにしました。
解説本に「ケーブル側のハウジングはJST社のVHR-2Nを使用」と書いてあったので、VHR-2Nのコネクタを買って自分の持っている電源アダプタに合う変換プラグを作ってみました。

ただサーボモーター他電子部品のGR-ROSEへの配線は、USB給電でも動く配線(ピン)にしています。

プログラム(抜粋)

Arduinoによるプログラムを書きました。

GR-ROSEではプログラム開発環境が「Webコンパイラ」「IDE for GR」「e2studio」の3種類準備されており、私はe2studioを使用してプログラムを書きました。

GR-ROSE IoTシステム開発コンテストのサイト で公開されているAzure IoT Centralと連携するサンプルのrose_sketch_azure_20210911.zipをベースとして、これに手を入れて作成しました。

サーボモーター:鳥と扉

#include <Servo.h>
Servo svBird;
Servo svDoor;

// 鳥によるお知らせ
void birdNotify(){
	// LED2点灯
	digitalWrite(PIN_LED2, true);

	// 扉をあける
	svDoor.write(90);
	delay(400);//回転のため0.4秒待つ
	svDoor.write(15);

	// 鳥のさえずり
    playBirdSound(hour);

	delay(1000);
	// 鳥をだす
	svBird.write(90);
	delay(400);//回転のため0.4秒待つ
	svBird.write(170);
	delay(1000);

	svBird.write(90);
	delay(400);//回転のため0.4秒待つ
	// 扉をもどす
	svDoor.write(90);
	delay(400);

	// LED2消灯
	digitalWrite(PIN_LED2, false);
}

Servo.hを使用した、マイクロサーボモーターの(多分)一般的なロジックです。

Grove MP3 モジュール

//For Grove MP3
#define WT2003S_SD_PLAY_FILE_IN_ROOT	(0xA3)
#define WT2003S_START_CODE				(0x7E)
#define WT2003S_END_CODE				(0xEF)

uint8_t sendCommand(uint8_t commandLength, uint8_t* data, uint8_t len) {
    long time;
    bool is_again = true;
    int  again_count = 0;
again:
    //Clear anything in the buffer
    while (Serial4.available() > 0) {
        Serial4.read();
    }
    Serial4.write(WT2003S_START_CODE);
    Serial4.write(commandLength + 2); //Add one byte for 'length', one for CRC

    //Begin sending command bytes while calc'ing CRC
    byte crc = commandLength + 2;
    for (byte x = 0; x < commandLength; x++) { //Length + command code + parameter
    	Serial4.write(commandBytes[x]); //Send this byte
        crc += commandBytes[x];          //Add this byte to the CRC
    }

    Serial4.write(crc); //Send CRC
    Serial4.write(WT2003S_END_CODE);

    time = millis();
    if (data == NULL) {
        while (! Serial4.available() && ((millis() - time) < 1000)) {}
        return Serial4.read();
    } else {
        time = millis();
        while ((millis() - time) < 1000) {
            if (Serial4.available()) {
                if (commandBytes[0] == Serial4.read()) {
                    is_again = false;
                    break;
                }
            }
        }
        if (is_again) {
            if (++again_count == 5) {
                return -1;
            }
            Serial.println(String(commandBytes[0], HEX) + "again");
            goto again;
        }
        if (len > 9) {
            len = 9;
        }
        for (int i = 1; i < len; i++) {
            time = millis();
            while (! Serial4.available() && ((millis() - time) < 1000)) {}
            data[i - 1] = Serial4.read();
        }
    }
    return 0;
}

uint8_t playSDSong(const char* fileName) {
    commandBytes[0] = WT2003S_SD_PLAY_FILE_IN_ROOT;
    commandBytes[1] = fileName[0];
    commandBytes[2] = fileName[1];
    commandBytes[3] = fileName[2];
    commandBytes[4] = fileName[3];
    return sendCommand(5, NULL, 0);
}

void playBirdSound(int hour){
// 本当は配列にしたい
	switch (hour){
		case 9:
		    playSDSong("cook.mp3");
			break;
		case 10:
		    playSDSong("umineko.mp3");
			break;
		case 11:
		    playSDSong("akahara.mp3");
			break;
		case 12:
		    playSDSong("ooruri.mp3");
			break;
		case 13:
		    playSDSong("pichiku.mp3");
			break;
		case 14:
		    playSDSong("suzume.mp3");
			break;
		case 15:
		    playSDSong("uguisu.mp3");
			break;
		case 16:
		    playSDSong("kamo.mp3");
			break;
		case 17:
		    playSDSong("piyo.mp3");
			break;
		case 18:
		    playSDSong("washi.mp3");
			break;
		default:
		    playSDSong("cook.mp3");
			break;
	}

}

時間ごとに違った鳥の鳴き声が聴けるようにしました。

Seeed StudioのGrove MP3 モジュールのライブラリがこちらに公開されています。
このライブラリをincludeしたかったのですがコンパイルエラーとなりこのままでは使うことができず悩んでみましたがどうすればよいかわかりませんでした。

このライブラリでは今回使ったV3のものは「WT2003S」を使わないといけないということだったので(「KT403A」はV2みたいです)直接ライブラリの中のWT2003S_Player.cppを覗いてみて、playSDSong関数とsendCommand関数をそのまま持ってきました。また、Serial通信はSerial4を使用しているので、Serial4に置き換えています。
そうすることで無事動作させることができました。

ちなみにsetup時には

Serial4.begin(9600);

が必要です。

Grove 4-Digit Display:時計

//4 digit display
#include <TM1637.h>
#define CLK 13 //pins definitions for TM1637 and can be changed to other ports
#define DIO 12
TM1637 tm1637(CLK,DIO);
int8_t TimeDisp[] = {0x00,0x00,0x00,0x00};
boolean isPoint = false;
//For Clock
#include <RTC.h>
RTC rtc

// 時計表示
void dispClock(){
	// 時刻表示更新
	int year, mon, day, hour, min, sec, week;
	rtc.getDateTime(year, mon, day, hour, min, sec, week);

	Serial.print(year, DEC);Serial.print("/");
	Serial.print(mon, DEC); Serial.print("/");
	Serial.print(day, DEC); Serial.print(" ");
	Serial.print(hour, DEC); Serial.print(":");
	Serial.print(min, DEC); Serial.print(":");
	Serial.println(sec, DEC);
	TimeDisp[0] = hour / 10;
	TimeDisp[1] = hour % 10;
	TimeDisp[2] = min / 10;
	TimeDisp[3] = min % 10;

	// 時刻表示
	tm1637.display(TimeDisp);
	// 「:」も表示する(ループごとに切り替えて点滅表示させる)
	if (isPoint){
		tm1637.point(POINT_ON);
	}else{
		tm1637.point(POINT_OFF);
	}
	isPoint = !isPoint;
}

int setClock(char setTime[]){
	Serial.print("setClock : ");

	int bufint1;
	int bufint2;
	// 時
	bufint1 = setTime[1] - '0';
	Serial.print("bufint1 = ");
	Serial.println(bufint1);
	bufint2 = setTime[2] - '0';
	Serial.print("bufint2 = ");
	Serial.println(bufint2);
	int setHour = bufint1 * 10 + bufint2;

	// 分
	bufint1 = setTime[4] - '0';
	Serial.print("bufint1 = ");
	Serial.println(bufint1);
	bufint2 = setTime[5] - '0';
	Serial.print("bufint2 = ");
	Serial.println(bufint2);
	int setMin = bufint1 * 10 + bufint2;

	// 秒
	bufint1 = setTime[7] - '0';
	Serial.print("bufint1 = ");
	Serial.println(bufint1);
	bufint2 = setTime[8] - '0';
	Serial.print("bufint2 = ");
	Serial.println(bufint2);
	int setSec = bufint1 * 10 + bufint2;

	Serial.print("setClock setHour : ");
	Serial.println(setHour);
	Serial.print("setClock setMin : ");
	Serial.println(setMin);
	Serial.print("setClock setSec : ");
	Serial.println(setSec);

	int year, mon, day, hour, min, sec, week;
	rtc.getDateTime(year, mon, day, hour, min, sec, week);
	rtc.setDateTime(year, mon, day, setHour, setMin, setSec, week);

	TimeDisp[0] = hour / 10;
	TimeDisp[1] = hour % 10;
	TimeDisp[2] = min / 10;
	TimeDisp[3] = min % 10;

	// 時刻表示
	tm1637.display(TimeDisp);

	return setHour;
}

loop関数がコールされるたびに本関数を実行し現在時刻をディスプレイにセットします。
現在時刻はRTCで保持しています。
時分の間の「:」は都度表示/非表示を切り替えることで点滅させるようにしました。

一番最初はこの部分はString型で処理をさせようとロジックを書いたのですが、なぜかどうやってもStringに文字を格納することができず(未解決…)int型で処理するように書きなおしました。

Seeed Studioが提供しているGrove 4-Digit Displayのライブラリはこちらを使用しています。こちらはGR-ROSEのArduinoコードにincludeさせることができました。

また、起動時のsetup関数では以下のように初期処理を行っています。

//4digit display
tm1637.set();
tm1637.init();
//Clock
rtc.begin();
rtc.setDateTime(2021, 1, 1, 0, 0, 0, RTC_WEEK_SATURDAY);

2021/1/1 00:00:00が初期セットされますが、Azure IoT Centralから時刻を実行したり、時刻のジョブが実行されたタイミングで時刻合わせを行います。次の「Azure IoT Central連携」に記載します。

Azure IoT Central連携

Azure IoT Central の デバイス - コマンドで「JustTime」というコマンドを追加しました。
時刻をパラメータとして送信できるようにすることで、ここから「実行」することで時計の時刻合わせを可能にしました。

Azure IoT Central デバイス - コマンド

また鳩時計の機能として、毎時に起動する「ジョブ」を作成しました。

Azure IoT Central ジョブ

午前9~18時のちょうどの時刻に鳩時計がお知らせしてくれるように設定しました。
各ジョブの内容は以下のようになります。

  • ジョブの種類:コマンド
  • コマンド:JustTime
  • JustTimeパラメータ:各時刻(画像では午前9時ちょうど)

Azure IoT Central ジョブプロパティ

プログラム側ではAzureからのコマンドを受け取ったあと、

  • 扉と鳩を動作
  • 音を再生
  • パラメータの時刻に時計を合わせる(時計のずれを修正)

という処理を実行しています。

/////////////////////////////////////////////////////
// For direct method
/////////////////////////////////////////////////////
void sample_direct_method_thread_entry(ULONG parameter)
{
 :
(省略)
 :

        if(strcmp("Angle", method)==0){

 :
(省略)
 :

        } else {
            if(strcmp("LED1", method)==0){
 :
(省略)
 :

            }
            if(strcmp("LED2", method)==0){
 :
(省略)
 :

            }
        	// ----------- ▼ 時報 ▼ -----------------
            if(strcmp("JustTime", method)==0){
            	Serial.println("JustTime Comming");
                char packet[11] = {0};
                strncpy(packet, (CHAR *)packet_ptr -> nx_packet_prepend_ptr, (INT)(packet_ptr -> nx_packet_append_ptr - packet_ptr -> nx_packet_prepend_ptr));
                Serial.print("Receive = ");
                Serial.println(packet);

            	// 時計合わせ
            	int hour = setClock(packet);
            	// 鳥によるお知らせ
            	birdNotify(hour);
            }
        	// ----------- ▲ 時報 ▲ -----------------
 :
(省略)
 :

大変だったところ

サーボモーターの接続方法

シリアルサーボモーターが接続できるピン0~7の隣のVmは、ここに今回のSG90のようなマイクロサーボを繋いだとき、USB給電ではない外部電源接続時でないと電源が来ない(使えない)ということがわからず、どうして動かないのだろうと悩んでしまってました。

マイクロサーボを使う時でも複数制御したりするならば外部電源とったほうが良いという有難いアドバイスをいただき外部電源を使えるようにしました。

そうこうしながら何度も動かしているうちにサーボモーターの動きが不安定になり、次第にまったく動かなくなりましたが(多分電圧がまずかったと思います…)、サーボモーターを新調することでまた動くようになりました。

音を鳴らす

音を鳴らすのに最初はGrove MP3モジュールを使わずやろうとしていましたがとても難しかったので断念し、GR-ROSEコミュニティでGrove MP3モジュールの情報をいただいたのでそれを使って実装しました。有難うございました!

ほかに使いたかった電子部品

実は赤外線送信なども行いたかったのですが、GR-ROSEでの実装が難しくて組み込みを断念しました。

マイコンボードの難しさ

今までにM5StackをはじめとしてESP32しか触ってきたことが無く、今回は初めてのESP32以外のマイコンボードによる開発でした。
一筋縄ではいかない!もっと基本的な理解が必要!と思いました。

さいごに

このGR-ROSEはROS2の使えるマイコンボードなのですがその特徴を活かしきれていません。それ以前に基本的な理解が欠如してますので、ゆくゆくはいろいろできるようになったらいいなと思っています。

Azureについては毎時のジョブしか利用できておらず・・もっと使ってみたいです。

本当は、温湿度データもセンシングしてAzureで管理し、規定値を超えた場合に鳩通知するなども行いたいなと思っていましたが今回は間に合いませんでした。

簡単に見えて結構うまくいかないこともあって時間がかかり、今回はとても勉強になりました。
これからも精進したいです。

1
siroitori0413のアイコン画像
https://siroitori.hatenablog.com/
ログインしてコメントを投稿する