Node-RED MCU用のノードを作成してみる その2(Servoノード)
はじめに
以前Node-RED MCU用のServoノードの作成について、Qiitaに記事を投稿しました。その時点ではPWM outノードと一緒に使う前提で、msg.payloadの加工程度のことしかしていませんでした。
▼こちらの記事です。
https://qiita.com/background/items/9b820251aa9dda5a3167
今回はソースコードをさらに読み込んで、Servoノードのみで使えるようにしてみました。PWM outノードを参考にしています。
まだJavaScriptについて完全に理解しているわけではないので、間違い等がありましたらご指摘いただけると幸いです。
▼プログラムはGitHubにも公開しています。
https://github.com/404background/node-red-mcu-servo/tree/develop
▼Raspberry Pi 400にインストールしたNode-REDで実行しています。
▼以前の記事はこちら
ソースコードを読む
ファイル構成
▼Node-RED MCUに特有のノードはこちら
https://github.com/phoddie/node-red-mcu/tree/main/nodes/mcu
例えばpwmノードのフォルダには、以下の4つのファイルが入っています。
- manifest.json : ビルド時にノードを含めるために必要。node_types.jsonにパスを記述する。
- mcu_pwm.html : Node-REDでの表示の設定
- mcu_pwm.js : Node-REDに表示するノードの作成
- pwm.js : ノードの具体的な処理について記述
今回は特にpwm.jsファイルを読みます。
▼リポジトリではこちら
https://github.com/phoddie/node-red-mcu/blob/main/nodes/mcu/pwm/pwm.js
pwm.jsの中身
"nodered"からNodeをインポートしています。
import {Node} from "nodered";
▼Nodeクラスはnode-red-mcu/nodered.jsでexportされています。REDクラスもこちらにありますね。
pwm.jsではNodeクラスを継承しています。PWMOutNodeクラスで具体的な処理が記述されています。
class PWMOutNode extends Node { ...
configが使われていますね。htmlファイルのinputから入力を受け付けたdefaultsの値は、jsファイルではconfig.変数名で受け取ることができます。
▼プロパティについてはこちら
https://nodered.jp/docs/creating-nodes/properties
なお、credentialsに設定した値はフローを書き出す際に含まれないためご注意ください。Node-RED MCUでビルドするときも含まれません。
onStartではピンや周波数の設定が行われています。statusはデバッガに表示されるものです。
onStart(config) { ...
try {
const options = {
pin: config.pin,
};
if (config.hz)
options.hz = config.hz;
this.#io = io = new device.io.PWM(options);
cache.set(config.pin, io);
}
catch {
this.status({fill: "red", shape: "dot", text: "node-red:common.status.error"});
}
onMessageでmsg.payloadの値をもとに処理が行われています。
onMessage(msg, done) {
if (this.#io) {
this.#io.write(msg.payload * ((1 << this.#io.resolution) - 1));
this.status({fill:"green", shape:"dot", text: msg.payload.toString()});
}
done();
}
計算にresolutionプロパティが使われていますが、これはModdable側で設定されているようです。サンプルにもありました。
▼詳しくはこちらをご覧ください。PWMクラスもこちらにあります。
https://github.com/Moddable-OpenSource/moddable/blob/public/documentation/io/io.md#pwm
その他:JavaScriptの書き方
「??=」はNull合体代入演算子というものだそうです。nullまたはundefinedの場合に代入されます。
▼こちらに書かれています。
ioに#記号がついていますが、これはES2022の書き方だと思います。プライベートなプロパティになるようです。
▼こちらに書かれていました。
https://jsprimer.net/basic/class/#private-class-fields
ノードを作成する
ここからはServoノードを作成していきます。
まずhtmlファイルについて、PWM outノードでも使われていたmcuHelperを取り入れています。共通の処理をまとめたものです。
▼mcu_servo.htmlはこちら。PWM outノードに繋げないので、outputsは0にしました。
<script type="text/javascript">
RED.nodes.registerType('mcu_servo',{
category: mcuHelper.category,
color: mcuHelper.color,
defaults: {
name: { value:"SG90" },
pin: { validate: RED.validators.number() },
hz: { value:"50" },
pulseMin: {value:"0.5"},
pulseMax: {value:"2.4"},
angleMin: {value:"0"},
angleMax: {value:"180"},
moddable_manifest: {value: {include: "./manifest.json"}},
},
inputs:1,
outputs:0,
icon: "serial.svg",
paletteLabel: 'Servo',
label: function() {
return this.name || "Servo " + this.pin;
},
oneditprepare: function() {
const div = $("#node-mcu-rows");
mcuHelper.appendProperties.PWM(div, "io", {});
mcuHelper.restoreProperties.PWM(this, "io");
},
oneditsave: function() {
mcuHelper.saveProperties.PWM(this, "io");
},
});
</script>
<script type="text/html" data-template-name="mcu_servo">
<div class="form-row">
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
<input type="text" id="node-input-name" placeholder="Servo">
</div>
<div id="node-mcu-rows">
</div>
<div class="form-row">
<label for="node-input-pulse"><i class="fa fa-arrows-h"></i> pulse width(us)</label>
<span for="node-input-pulseMin">min</span>
<input type="number" id="node-input-pulseMin" style="width:60px" step="0.1">
<span for="node-input-pulseMax" style="margin-left:22px;">max</span>
<input type="number" id="node-input-pulseMax" style="width:60px" step="0.1">
</div>
<div class="form-row">
<label for="node-input-angle"><i class="fa fa-arrows-h"></i> angle</label>
<span for="node-input-angleMin">min</span>
<input type="number" id="node-input-angleMin" style="width:60px" step="0.1">
<span for="node-input-angleMax" style="margin-left:22px;">max</span>
<input type="number" id="node-input-angleMax" style="width:60px" step="0.1">
</div>
</script>
<script type="text/markdown" data-help-name="mcu_servo">
</script>
▼ノードの入力欄はこんな感じ。
設定値をもとに、計算はservo.jsで行います。数値計算でエラーが出ることがあったのですが、Number関数に渡すと解決しました。数値に変換しておくのが確実なのかもしれません。
▼Number関数についてはこちら
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number
▼プログラムはこちら
import {Node} from "nodered";
let cache;
class ServoNode extends Node {
#io; #cycle; #pulseWidth; #angleWidth; #pulseMin;
onStart(config) {
super.onStart(config);
if (!globalThis.device?.io?.PWM)
return void this.status({fill: "red", shape: "dot", text: "node-red:common.status.error"});
cache ??= new Map;
let io = cache.get(config.pin);
this.#cycle = 1 / Number(config.hz) * 10 ** 3;
this.#pulseWidth = Number(config.pulseMax) - Number(config.pulseMin);
this.#angleWidth = Number(config.angleMax) - Number(config.angleMin);
this.#pulseMin = Number(config.pulseMin);
if (io) {
this.#io = io;
}
else {
try {
const options = {
pin: config.pin,
};
if (config.hz)
options.hz = config.hz;
this.#io = io = new device.io.PWM(options);
cache.set(config.pin, io);
}
catch {
this.status({fill: "red", shape: "dot", text: "node-red:common.status.error"});
}
}
}
onMessage(msg, done) {
let ratio = (Number(msg.payload) / this.#angleWidth * this.#pulseWidth + this.#pulseMin) / this.#cycle;
if (this.#io) {
this.#io.write(ratio * ((1 << this.#io.resolution) - 1));
this.status({fill:"green", shape:"dot", text: ratio.toString()});
}
done();
}
static type = "mcu_servo";
static {
RED.nodes.registerType(this.type, this);
}
}
PWM outノードでは0~1の数値で出力を調整します。周期とパルス幅から、サーボモーターを制御するための値を計算しています。
▼SG90の場合は0.5ms~2.4msです。0.5msの場合は0.025になります。
他のファイルはPWM outノードとほとんど同じです。
▼manifest.jsonはこちら
{
"modules": {
"servo": "./servo"
},
"preload": [
"servo"
]
}
▼mcu_servo.jsはこちら
module.exports = function(RED) {
function ServoNode(config) {
RED.nodes.createNode(this, config);
console.log(config)
}
RED.nodes.registerType("mcu_servo", ServoNode);
}
ノードをインストールする
以下のコマンドでフォルダを指定してパッケージをインストールすることができます。Node-RED MCUの環境を構築したときのディレクトリで実行してください。node_modulesフォルダに追加されます。
npm install フォルダのパス
node_modules/@ralphwetzel/node-red-mcu-plugin/node-red-mcuにある、node_types.jsonにmanifest.jsonのパスを追加します。今回は相対パスで指定します。
▼同じnode_modulesフォルダに追加されるので、そこまで遡っています。
"servo": "../../../mcu_servo/manifest.json"
▼パスが適切に設定されていない、またはmanifest.jsonでの名前が適切でない場合、以下のようなエラーが出ます。
Disabling unsupported node type "mcu_servo"!
Node-REDを起動して、ノードが追加されているかどうかを確認してください。
▼インストール後、uiノードのsliderに繋いでみました。
なお、ディスプレイに使われているピンとサーボモータ用のピンが被っていると、画面が更新されないことがあります。
▼実際の動作はこちら
最後に
node-red-mcuとmoddableリポジトリのどちらに書かれているのか、そもそもJavaScript特有の書き方なのかなど、知らないことがまだまだ多いので読むのに時間がかかりました。
Moddable側のプログラムが使えるなら、WiFi関連のノードが欲しいなと思っているところです。アクセスポイントにしたり、SSIDを設定したりできるようにしたいですね。