ESP32とサーボを使ってノーコードプログラミング

前回の投稿「[ESP32＋QuickJS＋Blocklyの例](https://qiita.com/poruruba/items/a3e53ed75cdeae6dfec8)」に引き続き、以下のJavascript実行環境のファームウェアを使ったBlocklyをやっていきます。

電子書籍：M5StackとJavascriptではじめるIoTデバイス制御

https://poruruba.booth.pm/items/3735175

今回は、M5StickCを使います。また、M5StickC用に最適なサーボハットがあります。

https://www.switch-science.com/catalog/6076/

サーボは、実はノーコードプログラミングでの操作のリアクションとしてインパクトがあるので、ちょうどよい素材になるのではないでしょうか。
M5StickCとサーボ以外に用意するのは、ストローと折り紙です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/1b731e66-f6e7-3b77-7c16-f928a40314c8.png)

歯車をサーボに取り付けて、ストローを歯車の先に差し込みます。
そして、好きな色の折り紙を切って、ストローの先に張り付ければ完成です。
これだけでも、結構見た目がインパクトになります。

今回は、このサーボ縛りで、以下の4つのノーコードプログラミングをします。

* 外出中サイン
* おばけ
* 番犬
* 赤あげて、青あげて

# 外出中サイン

席を外すときに、「食事中」と書いた札をたてる、あれです。

＜必要な周辺デバイス＞
M5Stack用SK6812搭載 フェーダーユニット

https://www.switch-science.com/catalog/7479/

＜使い方＞
フェーダーユニットを上いっぱいにすると、札がたつのと同時に、フェーダーが光ります。

＜仕組み＞
フェーダーの値に応じて、サーボの角度を変え、フェーダーの値が最大値のときに、フェーダーに付属のRGBカラーLED(14個)を点灯させます。フェーダは、analogReadで位置を取得できますが、0から4096の範囲ですので、同じ割合の位置にサーボを動かすようにします。

＜スケッチ＞

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/3734f787-410d-9f12-24d0-19f411762c7e.png)

＜Javascript＞
以下にJavascriptを示します。多少読みやすくしています。

```js
import * as gpio from "Gpio";
import * as ledc from "Ledc";
import * as pixels from "Pixels";

pixels.begin(32, 14);
pixels.clear();
ledc.setup(0, 50, 16);
ledc.attachPin(26, 0);

const appear = 4600;
const hide = 8100;

async function loop(){
	var pos = gpio.analogRead(33);
	ledc.write(0, pos / 4096 * (hide - appear) + appear);
	if( pos > 4000 ){
		for( var i = 0 ; i < 14 ; i++ )
			pixels.setPixelColor(i, 0x00ff00);
	}else{
		pixels.clear();
	}
	await wait_async(50);
}
```

# おばけ

おばけが出るさまをサーボで表現します。だけど、日光に当たるとすぐに隠れます。

＜必要な周辺デバイス＞
M5Stack用光センサユニット

https://www.switch-science.com/catalog/4051/

＜使い方＞
暗くしておくとサーボが動いておばけが現れますが、光を当てるとさっと隠れます。

＜仕組み＞
光センサで光の強度を図り、一定値以上の場合にはサーボの位置を隠れる位置にし、そうでない場合はゆっくり正面に動かすようにします。

＜スケッチ＞

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/69506d5a-e07c-c716-0c85-bc2e6666f357.png)

＜Javascript＞

Javascriptはこんな感じです。

```js
import * as gpio from "Gpio";
import * as ledc from "Ledc";

ledc.setup(0, 50, 16);
ledc.attachPin(26, 0);

gpio.pinMode(32, gpio.INPUT);

const appear = 4600;
const hide = 8100;
var counter = hide;

ledc.write(0, counter);

async function loop(){
	if( gpio.analogRead(33) < 500 ){
		counter = hide;
		ledc.write(0, counter);
	}else{
		if( counter > appear ){
			counter -= 50;
			ledc.write(0, counter);
		}
	}
	await wait_async(50);
}
```

# 番犬

人を検知すると、突然番犬が飛び出します。

＜必要な周辺デバイス＞
M5Stack用PIRセンサユニット

https://www.switch-science.com/catalog/5697/

＜使い方＞
普段は隠れていますが、人の動きを検知すると、サーボを動かして犬を登場させ、ぶるぶる震わせます。

＜仕組み＞
人の動きの検知は、PIRを使います。検知している間は1を返すので、正面位置に動かしますが、継続して1である間は、乱数を使って、正面位置に対して少しだけ位置を＋－しています。

＜スケッチ＞

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/9758e4e6-1700-3edf-adf2-ede79b127e1c.png)

＜Javascript＞

Javascriptはこんな感じです。

```js
import * as gpio from "Gpio";
import * as ledc from "Ledc";

const appear = 4600;
const hide = 8100;
var counter = hide;

ledc.setup(0, 50, 16);
ledc.attachPin(26, 0);
ledc.write(0, counter);

gpio.pinMode(33, gpio.INPUT);

async function loop(){
	if( gpio.digitalRead(33) ){
		counter = appear + random(-100, 100);
		ledc.write(0, counter);
	}else{
		if( counter < hide ){
			counter += 100;
			ledc.write(0, counter);
		}
	}
	await wait_async(50);
}
```

# 赤あげて、青あげて

これまでの内容の総集編です。
かなり長いコードになりました。

左右に赤い旗と青い旗を付けて、上げた方の旗と同じボタンを押すゲームです。

＜必要な周辺デバイス＞
M5Stack用デュアルボタンユニット

https://www.switch-science.com/catalog/4048/

＜使い方＞
LCDに「Ready?」と表示されるので、M5StickCのボタンを押すとゲームが始まります。
右側の赤い旗または左側の青い旗のどちらかを上げます。
90度旗を揚げ切ったところで、旗の色に合わせたボタンを押します。ボタンは、赤いボタンと青いボタンの2つあります。
これを5回続けて、最後にLCDに結果が表示されます。
ここで、もし5回すべて成功していた場合は、次にゲームを開始したときには、スピードが速くなっていきます。

＜仕組み＞
ボタンは、digitalReadで押下状態を確認するだけです。それ以外は、他のスケッチと同様です。

＜スケッチ＞
まずは、初期化部分とM5StickCのボタン押下時の処理です。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/3789bd71-f54d-df0a-8c7e-f74c1067e17e.png)

以下が、M5StickCのボタンが押された後の繰り返し処理です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/261826/f69b6673-a019-da78-4885-aa4c968c4a4f.png)

＜Javascript＞

Javascriptはこんな感じです。ここまでくると、スケッチでは追いにくいですね。

```js
import * as ledc from 'Ledc';
import * as input from 'Input';
import * as lcd from 'Lcd';
import * as gpio from "Gpio";

var max = 8100, min = 1700;
var center = 4600;
var freq = [(max - center) / 3 + center, center, (min - center) / 3 + center];
var is_running = false;
var counter;
var num_of_ok;
var num_of_attack;
var num_of_win = 0;

lcd.setRotation(0);
lcd.fillScreen(0x000000);
lcd.setTextSize(2);
lcd.setCursor(0, 0);
lcd.print("Ready?");

gpio.pinMode(32, gpio.INPUT);
gpio.pinMode(33, gpio.INPUT);

ledc.setup(0, 50, 16);
ledc.attachPin(26, 0);

input.onButtonWasPressed(input.BUTTON_A, () => {
	if( is_running )
		return;
		
	num_of_ok = 0;
	num_of_attack = 5;
	counter = num_of_attack ;

	lcd.fillScreen(0x000000);
	lcd.setCursor(0, 0);
	lcd.print("GO!");

	is_running = true;
});

async function loop(){
	if( !is_running )
		return;

	ledc.write(0, center);
	await wait_async(1000);

	ledc.write(0, freq[random(freq.length)]);
	await wait_async(random(200, 800) / (num_of_win + 1));

	ledc.write(0, freq[random(freq.length)]);
	await wait_async(random(200, 800) / (num_of_win + 1));

	var answer = random(2);
	ledc.write(0, answer ? max : min);

	lcd.fillScreen(0x000000);
	lcd.setCursor(0, 0);
	lcd.println("赤?青?");
	var ready_answer = true;
	var start = millis();
	while( (millis() - start) < 2000 / (num_of_win + 1) ){
		if( !ready_answer )
			continue;

		var btn1 = gpio.digitalRead(32);
		var btn2 = gpio.digitalRead(33);

		if( btn1 != 0 && btn2 == 0 ){
			if( answer == 1 ){
				lcd.print("OK");
				num_of_ok++;
			}else{
				lcd.print("NG");
			}
			ready_answer = false;
		}else if( btn1 == 0 && btn2 != 0 ){
			if( answer == 0 ){
				lcd.print("OK");
				num_of_ok++;
			}else{
				lcd.print("NG");
			}
			ready_answer = false;
		}
	}
	if( ready_answer  ){
		lcd.print("NG");
		ready_answer = false;
	}

	counter--;
	if( counter <= 0 ){
		if( num_of_ok >= num_of_attack )
			num_of_win++;

		lcd.fillScreen(0x000000);
		lcd.setCursor(0, 0);

		lcd.println("試合結果");
		lcd.println(num_of_ok + ' / ' + num_of_attack);
		lcd.println("完勝数");
		lcd.println(num_of_win);
		
		is_running  = false;
	}
}
```

# 終わりに

結構楽しいです。

〇電子書籍：M5StackとJavascriptではじめるIoTデバイス制御

https://poruruba.booth.pm/items/3735175

〇ESP32のファームウェアのサポートサイト

https://github.com/poruruba/QuickJS_ESP32_IoT_Device

以上