前回、ラズパイ3機のI2Cバスを5V化した先に接続したIO Expander MCP23017の動作確認をPython使って行いました。今回はIO Expanderの持つA、B2つの8ビットポートのうちAポートについて、ブラウザに表示されているNode-REDダッシュボードから点灯/消灯操作を出来るようにしてみました。
※「やっつけな日常」投稿順 indexはこちら
Node-REDのダッシュボードには、ATOMLiteとかmicro:bitとか各マイコン毎にタブを作ってあり、実験できるようになっておるのです。しかし、Node-REDサーバであるRaspberry Pi 3 model B+機のタブはありません。今回タブを初追加となります。
とりあえずの「見た目」はこんな感じです。ビット毎のスイッチが並べてあり、そのスイッチを操作しておいて上部のSENDボタンを押すとスイッチに対応するIO ExpanderのPort Aのビットに取り付けられているLEDが点灯する、と。
Node-REDのフロー
今回追加のNode-REDのフローの構成要素は以下です。
-
- Sendを指示するボタン。Functionノードに処理を指示するとともに、DashboardのSwitchノード(ダッシュボードでないSwitchノードもあるので要注意)を初期化。
- ビット毎8個のダッシュボードSwitchノード
- Switchノードに後続するChangeノード
- SwitchノードのON/OFF結果を集計し、Sendボタンが押されたら 実行にトリガをかける Functionノード
- 実際にIO Expanderを制御するPythonスクリプトを起動する Execノード
- デバッグ用のDebugノード
フローは以下に。
Dashboardのswitchノードの設定が以下に。初期化にPass throughモードを使ってます。また、スイッチがONのときにfalse設定になっているのは、後に出てくる回路図見ていただくと対応とれますが、LED点灯時がLOWアクティブなためです。
changeノードの設定が以下に、OFFのときがマイナスになっているのは、スイッチをON/OFF何度か操作した後に、SENDと押しても大丈夫なように、ONでもOFFでもFunctionノードが保持している値を操作するためです。
Functionノードでは、スイッチのON/OFFに応じてcontextに保持した値を操作していき、Sendが押されると保持した値を送り出すようになっています。
Execノードでは、実際にIO Expanderを操作するためのPythonスクリプトにFunctionノードが生成したON/OFF状態をエンコードした値を引数として与えて起動しています。
IO Expanderを制御するPythonスクリプト
やっていることは前回の動作確認用スクリプトとあまり変わらない(前回機能を包含)のですが、コマンドライン引数の処理など入ったので、大分長くなってしまいました。こんな感じ。
#! /usr/bin/python3 # coding: utf-8 import argparse import smbus import sys import time versionSTR = "ioexpander.py v0.0" i2c = smbus.SMBus(1) IOEX0 = 0x20 IOEX1 = 0x21 IOCONA = 0x0A IOCONB = 0x0B IODIRA = 0x00 IODIRB = 0x01 GPIOA = 0x12 GPIOB = 0x13 OLATA = 0x14 OLATB = 0x15 device = IOEX0 def errPrint(mes, opt=True): sys.stderr.write(mes) if opt: sys.stderr.write('\n') def initMCP23017(): """initialize MCP23017 IO expander IOCON config BANK=0, MIRROR=0, SEQOP=1(NO ADDR POINTER INC), DISSLW=0, ... """ i2c.write_byte_data(device, IOCONA, 0x20) i2c.write_byte_data(device, IODIRA, 0x00) #OUTPUT i2c.write_byte_data(device, IODIRB, 0x00) #OUTPUT i2c.write_byte_data(device, OLATA, 0xFF) #LATCH=0xFF i2c.write_byte_data(device, OLATB, 0xFF) #LATCH=0xFF def testMode(): """test Mode Output test pattern to PortA/B. """ while (1): err = i2c.write_byte_data(device, OLATB, 0xAA) if err: print("ERROR") i2c.write_byte_data(device, OLATA, 0xAA) time.sleep(0.1) i2c.write_byte_data(device, OLATB, 0x55) i2c.write_byte_data(device, OLATA, 0x55) time.sleep(0.1) def outputPORT(prt, dat): """output Port prt=0(A)/1(B), dat=8bit int """ OLAT = OLATA if prt: OLAT = OLATB ov = int(dat) & 0xFF i2c.write_byte_data(device, OLAT, ov) def main(): parser = argparse.ArgumentParser(description=versionSTR) parser.add_argument('--OUTA', nargs=1, help='Message.') parser.add_argument('--OUTB', nargs=1, help='Message.') parser.add_argument('-T', dest='TEST', help='Start Test Mode.', action='store_true', default=False) parser.add_argument('-V', dest='VERSION', help='Show Version, then exit', action='store_true', default=False) args = parser.parse_args() if args.VERSION: errPrint(versionSTR, opt=False) sys.exit(1) initMCP23017() if args.TEST: testMode() sys.exit(2) #Never! if args.OUTA is not None: outputPORT(0, args.OUTA[0]) if args.OUTB is not None: outputPORT(1, args.OUTB[0]) # Normal End sys.exit(0) if __name__ == "__main__": main()
IO Expander周りの回路と実ボードの動作
回路図的には前回と変わっているのは、外部5V電源をPCA9306のREFA電源に与えたことです(前回はラズパイ側の5Vだった。)これで、外部電源がOFFのときにPCA9306のEN端子がディセーブルになり、5V側のI2Cバスとラズパイ側のI2C端子の間はHiZ化されます。
実際にPort Aを全点灯させているところが以下に。ダッシュボードのスイッチをOFFしてSENDすると、該当ビットが消灯します。
これで、パソコン上に開いたブラウザ画面から、ラズパイ3機に接続したIO Expanderが制御できるようになったです。まあやっつけ仕事だな。いつものことだい。