Node-RED から obniz ノードをつかって Grove Water Level Sensor をつなげてみる

この記事は [obniz Advent Calendar 2020](https://qiita.com/advent-calendar/2020/obniz) 4日目の記事です。

[obniz に Grove Water Level Sensor をつなげるメモ](https://www.1ft-seabass.jp/memo/2020/09/04/grove-connect-grove-water-level-sensor/) をベースにNode-RED から obniz ノードをつかって Grove Water Level Sensor をつなげてみます。

裏テーマとしては、 I2C に関する複雑なコードでも obniz ノードで行けるのかどうかを試してみたいのです！

## 元のソース

もともと obniz クラウドで動いていたソースはこちらです。

```js
var obniz = new Obniz("Obniz_ID");
      obniz.onconnect = async function() {
         
        obniz.display.clear();
        obniz.display.print("Hello World");
         
        // 黒：0番
        obniz.io0.output(false)
        // 赤：1番
        obniz.io1.output(true)
 
        obniz.i2c0.onerror = function(err) {
          console.error(err)
        }
 
        // 接続が整うまでに待ち時間が必要
        await obniz.wait(100);
 
        // I2C接続
        obniz.i2c0.start({mode:"master", sda:2, scl:3, clock:400000 });
 
        // https://wiki.seeedstudio.com/Grove-Water-Level-Sensor/#software を参考
        let low_data = [];
        let high_data = [];
 
        let THRESHOLD = 100;
        let ATTINY1_HIGH_ADDR = 0x78;
        let ATTINY2_LOW_ADDR = 0x77;
 
        while(true){
 
          let touch_val = 0;
          let trig_section = 0;
          low_count = 0;
          high_count = 0;
 
          high_data = await obniz.i2c0.readWait(ATTINY1_HIGH_ADDR, 12);
          low_data = await obniz.i2c0.readWait(ATTINY2_LOW_ADDR, 8);
 
          let i;
 
          for (i = 0 ; i < 8; i++) {
            if (low_data[i] > THRESHOLD) {
              touch_val |= 1 << i;
            }
          }
          for (i = 0 ; i < 12; i++) {
            if (high_data[i] > THRESHOLD) {
              touch_val |= 1 << (8 + i);
            }
          }
           
          while (touch_val & 0x01)
          {
            trig_section++;
            touch_val >>= 1;
          }
 
          let water_level_mm = trig_section * 5;
          // console.log("c = " + water_level_mm + "%");
           
          // HTML に反映
          $("#waterlevel").html("[water level] " + water_level_mm + " %");
 
          // ディスプレイに反映
          obniz.display.clear();
          obniz.display.font(null,16)
          obniz.display.print("[water level]");
          obniz.display.font(null,30)
          obniz.display.print(water_level_mm + " mm");
 
          await obniz.wait(1000);
        }
 
      };
```

## Node-RED のフローに書き換えてみる

自分は Node-RED を手元のPCのローカルで立ち上げています。

<img width="1658" alt="2020-12-03_20h46_35.jpg (280.4 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/0802b9e3-dc01-49f0-9ab3-08be7e7fe251.jpg">

書き換えてみて、ちゃんと動きました。

<img width="686" alt="image.png (17.5 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/2e02c3a6-d287-4ed7-b6d8-70fe04215115.png">

このようなフローになりました。

```json
[{"id":"782b6ae2.9d4a24","type":"inject","z":"87a7ba2e.1c34b8","name":"起動時","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"","payloadType":"date","x":400,"y":560,"wires":[["c704cf1f.e04c7"]]},{"id":"c704cf1f.e04c7","type":"obniz-function","z":"87a7ba2e.1c34b8","obniz":"e1f5c3f5.3c7f5","name":"obniz 準備","code":"obniz.display.clear();\r\nobniz.display.print(\"Hello World\");\r\n\r\n// 黒：0番\r\nobniz.io0.output(false)\r\n// 赤：1番\r\nobniz.io1.output(true)\r\n\r\nobniz.i2c0.onerror = function(err) {\r\nconsole.error(err)\r\n}\r\n\r\n// 接続が整うまでに待ち時間が必要\r\nawait obniz.wait(100);\r\n\r\n// I2C接続\r\nobniz.i2c0.start({mode:\"master\", sda:2, scl:3, clock:400000 });","x":610,"y":560,"wires":[[]]},{"id":"af578fce.de206","type":"obniz-repeat","z":"87a7ba2e.1c34b8","obniz":"e1f5c3f5.3c7f5","name":"","interval":"1000","code":"// https://wiki.seeedstudio.com/Grove-Water-Level-Sensor/#software を参考\r\n\r\nlet low_data = [];\r\nlet high_data = [];\r\n\r\nlet THRESHOLD = 100;\r\nlet ATTINY1_HIGH_ADDR = 0x78;\r\nlet ATTINY2_LOW_ADDR = 0x77;\r\n\r\n// while(true){\r\n\r\n  let touch_val = 0;\r\n  let trig_section = 0;\r\n  low_count = 0;\r\n  high_count = 0;\r\n\r\n  high_data = await obniz.i2c0.readWait(ATTINY1_HIGH_ADDR, 12);\r\n  low_data = await obniz.i2c0.readWait(ATTINY2_LOW_ADDR, 8);\r\n\r\n  let i;\r\n\r\n  for (i = 0 ; i < 8; i++) {\r\n    if (low_data[i] > THRESHOLD) {\r\n      touch_val |= 1 << i;\r\n    }\r\n  }\r\n  for (i = 0 ; i < 12; i++) {\r\n    if (high_data[i] > THRESHOLD) {\r\n      touch_val |= 1 << (8 + i);\r\n    }\r\n  }\r\n   \r\n  while (touch_val & 0x01)\r\n  {\r\n    trig_section++;\r\n    touch_val >>= 1;\r\n  }\r\n\r\n  let water_level_mm = trig_section * 5;\r\n  \r\n  // ディスプレイに反映\r\n  obniz.display.clear();\r\n  // obniz.display.font(null,16)\r\n  obniz.display.print(\"[water level]\\n\");\r\n  // obniz.display.font(null,30)\r\n  obniz.display.print(water_level_mm + \" mm\");\r\n\r\n  // await obniz.wait(1000); // この 1000 ミリ秒を interval(ms) に反映\r\n//}\r\n\r\n// 後に続くノードに水のレベル water_level_mm を伝える\r\nmsg.payload = water_level_mm;\r\n\r\nreturn msg;\r\n","x":610,"y":660,"wires":[["95074474.3c46b8"]]},{"id":"95074474.3c46b8","type":"debug","z":"87a7ba2e.1c34b8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":810,"y":660,"wires":[]},{"id":"e1f5c3f5.3c7f5","type":"obniz","z":"","obnizId":"0000-0000","deviceType":"obnizboard","name":"","accessToken":"","code":""}]
```

インポートして使えるフローJSONデータなこちらです。

<img width="665" alt="image.png (14.0 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/0d0a9ba0-2dc1-4659-864b-4e607f773a95.png">

obniz のIDは `0000-0000` にしています。

<img width="673" alt="image.png (21.5 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/72138ead-9dd2-443b-89dd-005985a5e9f2.png">

自分の obniz のIDと device type を指定して使いましょう。

### 起動時の動作

<img width="508" alt="image.png (7.8 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/02dffe33-82c1-4211-92fb-19e489be0f85.png">

起動時は inject ノードで1秒後に obniz ノードが実行されます。

<img width="673" alt="image.png (35.9 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/7605094d-c509-42b4-8a17-5b8af41ad61b.png">

obniz ノードの中身です。

```js
obniz.display.clear();
obniz.display.print("Hello World");

// 黒：0番
obniz.io0.output(false)
// 赤：1番
obniz.io1.output(true)

obniz.i2c0.onerror = function(err) {
console.error(err)
}

// 接続が整うまでに待ち時間が必要
await obniz.wait(100);

// I2C接続
obniz.i2c0.start({mode:"master", sda:2, scl:3, clock:400000 });
```

元のソースの while ループを回す前の処理を移植しています。

### ループ処理 obniz repeat

<img width="402" alt="image.png (5.7 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/b649e9fa-8685-4411-892d-673941705728.png">

while ループの中身は obniz repeat のほうに任せます。

<img width="678" alt="image.png (39.9 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/520da2a0-8380-4c7b-b8ed-2260f5c427f6.png">

obniz ノードの中身です。

```js
// https://wiki.seeedstudio.com/Grove-Water-Level-Sensor/#software を参考

let low_data = [];
let high_data = [];

let THRESHOLD = 100;
let ATTINY1_HIGH_ADDR = 0x78;
let ATTINY2_LOW_ADDR = 0x77;

// while(true){

  let touch_val = 0;
  let trig_section = 0;
  low_count = 0;
  high_count = 0;

  high_data = await obniz.i2c0.readWait(ATTINY1_HIGH_ADDR, 12);
  low_data = await obniz.i2c0.readWait(ATTINY2_LOW_ADDR, 8);

  let i;

  for (i = 0 ; i < 8; i++) {
    if (low_data[i] > THRESHOLD) {
      touch_val |= 1 << i;
    }
  }
  for (i = 0 ; i < 12; i++) {
    if (high_data[i] > THRESHOLD) {
      touch_val |= 1 << (8 + i);
    }
  }
   
  while (touch_val & 0x01)
  {
    trig_section++;
    touch_val >>= 1;
  }

  let water_level_mm = trig_section * 5;
  
  // ディスプレイに反映
  obniz.display.clear();
  // obniz.display.font(null,16)
  obniz.display.print("[water level]\n");
  // obniz.display.font(null,30)
  obniz.display.print(water_level_mm + " mm");

  // await obniz.wait(1000); // この 1000 ミリ秒を interval(ms) に反映
//}

// 後に続くノードに水のレベル water_level_mm を伝える
msg.payload = water_level_mm;

return msg;
```

元のソースを見比べてみると、 while ループと待ち処理をしている `await obniz.wait(1000); ` をコメントアウトして、この 1000 ミリ秒を interval(ms) に反映しています。

<img width="333" alt="image.png (12.2 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/a0406018-8692-451c-b247-6521267d42b0.png">

`return msg;` で後に続くノードに水のレベル water_level_mm を伝えるので、debugノードに伝わります。

## スピーカーで水位に合わせて音を変える

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Node-RED から obniz ノード使って Grove Water Level Sensor 動かせた。スピーカーも連動させて指で触れて動かすと音が変わります！<a href="https://twitter.com/hashtag/obniz?src=hash&amp;ref_src=twsrc%5Etfw">#obniz</a> <a href="https://twitter.com/hashtag/nodered?src=hash&amp;ref_src=twsrc%5Etfw">#nodered</a> <a href="https://twitter.com/hashtag/noderedjp?src=hash&amp;ref_src=twsrc%5Etfw">#noderedjp</a> <a href="https://twitter.com/hashtag/seeed?src=hash&amp;ref_src=twsrc%5Etfw">#seeed</a> <a href="https://t.co/dzlaZ6uqiB">pic.twitter.com/dzlaZ6uqiB</a></p>&mdash; Tanaka Seigo (@1ft_seabass) <a href="https://twitter.com/1ft_seabass/status/1334460782986231812?ref_src=twsrc%5Etfw">December 3, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

このようにスピーカーのドレミファソラシドの音と、水位を連動させてみました。

<img width="1026" alt="image.png (27.2 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/65cd31bf-d1a4-4635-aa4e-8df4cabc6583.png">

このようなフローになります。

```
[{"id":"782b6ae2.9d4a24","type":"inject","z":"87a7ba2e.1c34b8","name":"起動時","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"","payloadType":"date","x":400,"y":560,"wires":[["c704cf1f.e04c7"]]},{"id":"c704cf1f.e04c7","type":"obniz-function","z":"87a7ba2e.1c34b8","obniz":"e1f5c3f5.3c7f5","name":"obniz 準備","code":"obniz.display.clear();\r\nobniz.display.print(\"Hello Water!\");\r\n\r\n// 黒：0番\r\nobniz.io0.output(false)\r\n// 赤：1番\r\nobniz.io1.output(true)\r\n\r\nobniz.i2c0.onerror = function(err) {\r\n    console.error(err)\r\n}\r\n\r\n// I2C接続\r\nobniz.i2c0.start({mode:\"master\", sda:2, scl:3, clock:400000 });\r\n\r\n// スピーカー\r\nobnizParts.speaker = obniz.wired(\"Speaker\", {signal:9, gnd:11});","x":610,"y":560,"wires":[[]]},{"id":"af578fce.de206","type":"obniz-repeat","z":"87a7ba2e.1c34b8","obniz":"e1f5c3f5.3c7f5","name":"","interval":"1000","code":"// https://wiki.seeedstudio.com/Grove-Water-Level-Sensor/#software を参考\r\n\r\nlet low_data = [];\r\nlet high_data = [];\r\n\r\nlet THRESHOLD = 100;\r\nlet ATTINY1_HIGH_ADDR = 0x78;\r\nlet ATTINY2_LOW_ADDR = 0x77;\r\n\r\n// while(true){\r\n\r\n  let touch_val = 0;\r\n  let trig_section = 0;\r\n  low_count = 0;\r\n  high_count = 0;\r\n\r\n  high_data = await obniz.i2c0.readWait(ATTINY1_HIGH_ADDR, 12);\r\n  low_data = await obniz.i2c0.readWait(ATTINY2_LOW_ADDR, 8);\r\n\r\n  let i;\r\n\r\n  for (i = 0 ; i < 8; i++) {\r\n    if (low_data[i] > THRESHOLD) {\r\n      touch_val |= 1 << i;\r\n    }\r\n  }\r\n  for (i = 0 ; i < 12; i++) {\r\n    if (high_data[i] > THRESHOLD) {\r\n      touch_val |= 1 << (8 + i);\r\n    }\r\n  }\r\n   \r\n  while (touch_val & 0x01)\r\n  {\r\n    trig_section++;\r\n    touch_val >>= 1;\r\n  }\r\n\r\n  let water_level_mm = trig_section * 5;\r\n  \r\n  // ディスプレイに反映\r\n  obniz.display.clear();\r\n  // obniz.display.font(null,16)\r\n  obniz.display.print(\"[water level]\\n\");\r\n  // obniz.display.font(null,30)\r\n  obniz.display.print(water_level_mm + \" mm\");\r\n\r\n  // await obniz.wait(1000); // この 1000 ミリ秒を interval(ms) に反映\r\n//}\r\n\r\n// 後に続くノードに水のレベル water_level_mm を伝える\r\nmsg.payload = water_level_mm;\r\n\r\nreturn msg;\r\n","x":610,"y":660,"wires":[["b5d68dcd.60277"]]},{"id":"95074474.3c46b8","type":"debug","z":"87a7ba2e.1c34b8","name":"","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1150,"y":660,"wires":[]},{"id":"b5d68dcd.60277","type":"obniz-function","z":"87a7ba2e.1c34b8","obniz":"e1f5c3f5.3c7f5","name":"水のレベルによって音を変える","code":"/*\r\nド 523.23\r\nレ 587.34\r\nミ 659.25\r\nファ 698.45\r\nソ 783.98\r\nラ 879.99\r\nシ 987.75\r\nド 1046.5\r\n*/\r\n\r\n// 水のレベルによって音を変える\r\nif(msg.payload >= 40){\r\n    // ド\r\n    obnizParts.speaker.play(1046);\r\n} else if(msg.payload >= 35){\r\n    // シ\r\n    obnizParts.speaker.play(987);\r\n} else if(msg.payload >= 30){\r\n    // ラ\r\n    obnizParts.speaker.play(879);\r\n} else if(msg.payload >= 25){\r\n    // ソ\r\n    obnizParts.speaker.play(783);\r\n} else if(msg.payload >= 20){\r\n    // ファ\r\n    obnizParts.speaker.play(698);\r\n} else if(msg.payload >= 15){\r\n    // ミ\r\n    obnizParts.speaker.play(659);\r\n} else if(msg.payload >= 10){\r\n    // レ\r\n    obnizParts.speaker.play(587);\r\n} else if(msg.payload >= 5){\r\n    // ド\r\n    obnizParts.speaker.play(523);\r\n} else {\r\n    obnizParts.speaker.stop();\r\n}","x":890,"y":660,"wires":[["95074474.3c46b8"]]},{"id":"e1f5c3f5.3c7f5","type":"obniz","z":"","obnizId":"0000-0000","deviceType":"obnizboard","name":"","accessToken":"","code":""}]
```

インポートして使えるフローJSONデータなこちらです。先ほどのフローと同様に使うときは、自分の obniz のIDと device type を指定して使いましょう。

### 起動時の動作

<img width="508" alt="image.png (7.8 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/02dffe33-82c1-4211-92fb-19e489be0f85.png">

こちらの obniz ノードではスピーカーの呼び出しだけ加えています。

```js
obniz.display.clear();
obniz.display.print("Hello Water!");

// 黒：0番
obniz.io0.output(false)
// 赤：1番
obniz.io1.output(true)

obniz.i2c0.onerror = function(err) {
    console.error(err)
}

// I2C接続
obniz.i2c0.start({mode:"master", sda:2, scl:3, clock:400000 });

// スピーカー
obnizParts.speaker = obniz.wired("Speaker", {signal:9, gnd:11});
```

### ループ処理 obniz repeat

<img width="755" alt="image.png (9.8 kB)" src="https://img.esa.io/uploads/production/attachments/3062/2020/12/03/8131/ea8ec693-ddcd-4c3d-96f5-d98ec8aa7e80.png">

obniz repeat ノードのほうは内容に変更はなく、「水のレベルによって音を変える」となっている obniz ノードで、スピーカーのドレミファソラシドの音を水位と合わせるプログラムを書いています。

```js
/*
ド 523.23
レ 587.34
ミ 659.25
ファ 698.45
ソ 783.98
ラ 879.99
シ 987.75
ド 1046.5
*/

// 水のレベルによって音を変える
if(msg.payload >= 40){
    // ド
    obnizParts.speaker.play(1046);
} else if(msg.payload >= 35){
    // シ
    obnizParts.speaker.play(987);
} else if(msg.payload >= 30){
    // ラ
    obnizParts.speaker.play(879);
} else if(msg.payload >= 25){
    // ソ
    obnizParts.speaker.play(783);
} else if(msg.payload >= 20){
    // ファ
    obnizParts.speaker.play(698);
} else if(msg.payload >= 15){
    // ミ
    obnizParts.speaker.play(659);
} else if(msg.payload >= 10){
    // レ
    obnizParts.speaker.play(587);
} else if(msg.payload >= 5){
    // ド
    obnizParts.speaker.play(523);
} else {
    obnizParts.speaker.stop();
}
```

もっと良く書ける気もしますが、まずは動くの重視でとってもストレートに書いています。

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">Node-RED から obniz ノードをつかって Grove Water Level Sensor とスピーカーの連動！実際に水入れて試してみた。ドレミファソラシド～♪って奏でてくれる！<a href="https://twitter.com/hashtag/obniz?src=hash&amp;ref_src=twsrc%5Etfw">#obniz</a> <a href="https://twitter.com/hashtag/nodered?src=hash&amp;ref_src=twsrc%5Etfw">#nodered</a> <a href="https://twitter.com/hashtag/noderedjp?src=hash&amp;ref_src=twsrc%5Etfw">#noderedjp</a> <a href="https://twitter.com/hashtag/seeed?src=hash&amp;ref_src=twsrc%5Etfw">#seeed</a> <a href="https://t.co/ET7BG5sAP8">pic.twitter.com/ET7BG5sAP8</a></p>&mdash; Tanaka Seigo (@1ft_seabass) <a href="https://twitter.com/1ft_seabass/status/1334618572547485696?ref_src=twsrc%5Etfw">December 3, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

## 明日は

明日の obniz Advent Calendar 2020 の担当は @tmisuoka0423 さんの「冬 x 睡眠 x obniz」です！

楽しみです！