ラズパイ3に7インチディスプレイ追加 キオスクモードでWebIOPiボタン操作&Grafanaグラフ表示

Webiopiをディスプレイに表示させてボタン操作をするのと、そのWebiopiの表示の中にGrafanaで作った水位や気温・湿度などを表示させる設定をしていきます。

Webiopiは母艦のラズパイに入れて16連のリレーをつなげて水槽のポンプや充電器などのスイッチをオン・オフするボタンに使います。今回ディスプレイを設置したサブ機のラズパイはIPアドレスで母艦のラズパイに接続してWebIOPiの画面をキオスクモードで表示する。

この記事は複数のページに別れています
  1. (今このページを見ています)

WebIOPi インストール・設定

WebIOPiを使うとブラウザからラズパイを操作できるというのでやってみました。

どうやら開発は終了しているようで・・・残念です。

サイト

本家のサイト

The Raspberry Pi Internet of Things Toolkit - Now in two flavors

インストールで参考にしたページ

WebIOPiを使ってブラウザからRaspberry PiのGPIOを操作してみる | DevelopersIO
どうも!大阪オフィスの西村祐二です。 最近、Raspberry Piをよくさわります。 PCやスマホのブラウザからRaspberry Pi を操作してみたいということはよくあると思います。 今回は、WebIOPiというも …

インストール・設定

$ wget https://sourceforge.net/projects/webiopi/files/WebIOPi-0.7.1.tar.gz
$ tar xvzf WebIOPi-0.7.1.tar.gz
$ cd WebIOPi-0.7.1
#パッチも当てておく
$ wget https://raw.githubusercontent.com/doublebind/raspi/master/webiopi-pi2bplus.patch
$ patch -p1 -i webiopi-pi2bplus.patch

準備できたらセットアップ実行

$ sudo ./setup.sh

自動起動設定

systemctlで自動起動できるように設定するためwebiopi.serviceをダウンロードする。

$ cd /etc/systemd/system/
$ sudo wget https://raw.githubusercontent.com/doublebind/raspi/master/webiopi.service

webiopi.serviceファイル確認

$ sudo cat /etc/systemd/system/webiopi.service
#webiopi.serviceは以下のようになっていた
[Unit]
Description = WebIOPi
After = syslog.target network.target

[Service]
Type = simple
WorkingDirectory = /usr/share/webiopi/htdocs
ExecStart = /usr/bin/python3 -m webiopi -l /var/log/webiopi -c /etc/webiopi/config

[Install]
WantedBy = multi-user.target

起動・終了・再起動

$ sudo systemctl start webiopi   #スタート
$ sudo systemctl stop webiopi    #ストップ
$ sudo systemctl restart webiopi #リスタート
$ sudo systemctl enable webiopi #ラズパイ起動時に起動
$ sudo systemctl status webiopi  #ステータス

起動確認

$ sudo systemctl status webiopi
● webiopi.service - WebIOPi
   Loaded: loaded (/etc/systemd/system/webiopi.service; disabled; vendor preset:
   Active: active (running) since Fri 2019-03-29 15:58:52 JST; 4s ago
 Main PID: 15116 (python3)
   CGroup: /system.slice/webiopi.service
           └─15116 /usr/bin/python3 -m webiopi -l /var/log/webiopi -c /etc/webio

 3月 29 15:58:52 raspberrypi systemd[1]: Started WebIOPi.
 3月 29 15:58:54 raspberrypi python3[15116]: 2019-03-29 15:58:54 - WebIOPi - INFO
 3月 29 15:58:54 raspberrypi python3[15116]: 2019-03-29 15:58:54 - WebIOPi - INFO
 3月 29 15:58:54 raspberrypi python3[15116]: 2019-03-29 15:58:54 - WebIOPi - INFO
 3月 29 15:58:54 raspberrypi python3[15116]: 2019-03-29 15:58:54 - WebIOPi - INFO
 3月 29 15:58:54 raspberrypi python3[15116]: 2019-03-29 15:58:54 - WebIOPi - INFO
 3月 29 15:58:54 raspberrypi python3[15116]: 2019-03-29 15:58:54 - WebIOPi - INFO
 3月 29 15:58:54 raspberrypi python3[15116]: 2019-03-29 15:58:54 - WebIOPi - INFO

デバッグモードで起動

これで起動すると一応WebページにはアクセスできるがGPIOの状態がうまく表示されずリレーなどのオン・オフ操作もできなかった。これだとカスタマイズのページは404エラーになって表示されない。

$ sudo webiopi -d
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.digitalCount to REST GET /GPIO/count
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.digitalRead to REST GET /GPIO/%(channel)d/value
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.digitalWrite to REST POST /GPIO/%(channel)d/value/%(value)d
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.getFunctionString to REST GET /GPIO/%(channel)d/function
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.getPulse to REST GET /GPIO/%(channel)d/pulse
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.outputSequence to REST POST /GPIO/%(channel)d/sequence/%(args)s
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.portRead to REST GET /GPIO/*/integer
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.portWrite to REST POST /GPIO/*/integer/%(value)d
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.pulse to REST POST /GPIO/%(channel)d/pulse/
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.pulseAngle to REST POST /GPIO/%(channel)d/pulseAngle/%(value)f
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.pulseRatio to REST POST /GPIO/%(channel)d/pulseRatio/%(value)f
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.setFunctionString to REST POST /GPIO/%(channel)d/function/%(value)s
2019-04-14 17:24:13 - WebIOPi - DEBUG - Mapping GPIO.wildcard to REST GET /GPIO/*
2019-04-14 17:24:13 - WebIOPi - INFO - GPIO - Native mapped to REST API /GPIO
2019-04-14 17:24:13 - WebIOPi - WARNING - Access unprotected
2019-04-14 17:24:13 - WebIOPi - INFO - HTTP Server binded on http://192.168.31.53:8000/
2019-04-14 17:24:13 - WebIOPi - INFO - CoAP Server binded on coap://192.168.31.53:5683/
2019-04-14 17:24:13 - WebIOPi - INFO - CoAP Server binded on coap://224.0.1.123:5683/ (MULTICAST)
2019-04-14 17:24:13 - HTTP - DEBUG - "GET /* HTTP/1.1" - 200 OK (Client: ::ffff:192.168.31.118 <Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0>)
2019-04-14 17:24:13 - HTTP - DEBUG - "GET /* HTTP/1.1" - 200 OK (Client: ::ffff:192.168.31.118 <Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0>)
2019-04-14 17:24:13 - HTTP - DEBUG - "GET /* HTTP/1.1" - 200 OK (Client: ::ffff:192.168.31.118 <Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0>)
2019-04-14 17:24:13 - HTTP - DEBUG - "GET /* HTTP/1.1" - 200 OK (Client: ::ffff:192.168.31.118 <Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:68.0) Gecko/20100101 Firefox/68.0>)
2019-04-14 17:24:40 - WebIOPi - DEBUG - Closing device GPIO - GPIO
2019-04-14 17:24:40 - WebIOPi - INFO - CoAP Server stopped

[Ctrl]+[c]で終了してもコマンドプロンプトが表示されなかった。(Puttyで操作した時)

configをカスタマイズしてページを作った時は以下のようにしてデバッグモードでテストすることができた。

$ sudo webiopi -c /etc/webiopi/config -d

Webiopiページにブラウザでアクセス

WindowsのWebブラウザからアクセスしてみる

WindowsのFirefoxでラズパイのWebiopiのページにアクセスしてみます。

以下のようなBasic認証画面が表示されるのでwebiopi・raspberryを入力

webiopi

  • ID:webiopi
  • Pass:raspberry

ラズパイのGPIOの状況が表示されるページなどGPIOの状況を分かりやすい。

webiopi

このままだと、どのPINを何に使っているのか分からないので、専用ページを作っていきます。

Webiopi リレースイッチ用の専用ページを作る

HTML・Python・CSSでボタン操作できるページを作っていきます。

元のファイルをコピー

ディレクトリwebiopiとbackを作って元のファイルをコピーしておきます。

$ mkdir -p /home/pi/script/webiopi/back
$ cp -r /usr/share/webiopi/htdocs/*  /home/pi/script/webiopi/back

設定

$ sudo vim /etc/webiopi/config
・・・省略
[SCRIPTS]
#myscript = /home/pi/webiopi/examples/scripts/macros/script.py
myscript = /home/pi/script/webiopi/script.py#追加
[HTTP]
#doc-root = /home/pi/webiopi/examples/scripts/macrosこの下に追加
doc-root = /home/pi/script/webiopi#追加

設定を適用させるために再起動、しかし再起動に時間がかかります。

$ sudo systemctl restart webiopi

Grafanaの温度・湿度・水位など表示させる

iframeでWebiopiのページの中にGrafanaのグラフを表示させます。

こんな感じ↓に作ってsrcにGrafanaで取得したURLを入れます。

<div class="grafana" style="height:300px;">
<iframe src="GrafanaのURL" width="auto" height="100" frameborder="0" style="border:0" allowfullscreen></iframe>
</div>
<div class="grafana" style="height:1260px;">
<iframe src="GrafanaのURL" width="auto" height="400" frameborder="0" style="border:0" allowfullscreen></iframe>
</div>

Grafanaでダッシュボードを作って表示させたいグラフを入れていきます。

右上のほうにあるShareアイコンを押してLinkでURLを取得。Webiopiのページは背景が白なのでThemeをLightを選びました。

Grafana設定

以下のようにCurrent Time RangeやTemplate Variablesもオフにしてみました。

設定後 Grafana

Grafana ログインしないで表示させる

キオスクモードで表示させたいので、Grafanaも再起動のたびにキーボードを使ってログインするのも面倒なのでログインしないでもGrafanaのグラフが表示されるようにする。

$ sudo vim /etc/grafana/grafana.ini
[auth]
oauth_auto_login = true
[auth.anonymous] #この項目を探して以下のようにTrueにする。
enabled = true
$ sudo systemctl stop grafana-server #設定反映させるためにストップしてからスタート
$ sudo systemctl start grafana-server

これでログインしないでも表示されるようになりました。

Grafana V6.2から表示されなくなった

V6.2からX-Frame-Options:denyになったので<iframe>の中にGrafanaのリンクを入れても表示されなくなった。

Configure Grafana | Grafana documentation
Configuration documentation

対応方法は以下のようにallow_embedding = trueを追加する。

$ sudo vim /etc/grafana/grafana.ini
#[security]を探して下に以下をコメントアウト・編集
cookie_samesite = none
allow_embedding = true
$ sudo systemctl stop grafana-server #設定反映
$ sudo systemctl start grafana-server

これでアクセスを拒否されずに表示するようになった。

Webiopi ページの中にGrafana入れる

WebiopiのHTMLの中にこんな感じでiframeで入れてみた。

<div class="grafana">
<iframe src="http://192.168.31.53:3000/d/g_Ju8BZRk/panel?orgId=1&theme=light" width="auto" height="1200" frameborder="0" style="border:0" allowfullscreen>></iframe>
</div>

以下は、メインのラズパイ3B+のWebiopiで、ディスプレイを表示させるサブのラズパイ3B+にchromiumでWebiopiのページをキオスクモードで表示させています。(ちょっとややこしくなってきた)

WebiopiのHTML/py/cssの全文も載せておきます。

ディスプレイ表示用のWebiopi HTML

HTMLはこんな感じ↓でWebiopiでボタンを作って16連リレーをラズパイにつなげてオン・オフしてます。ついでにGrafanaのグラフを表示させるページもiframeで挿入します。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>Hydroponic culture  Control</title>
	<meta name="viewport" content="width=device-width, initial=1.0" />
	<script type="text/javascript" src="/webiopi.js"></script>
	<link href="https://use.fontawesome.com/releases/v5.6.1/css/all.css" rel="stylesheet">
	<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
	<script type="text/javascript">
	webiopi().ready(function() {

		var button12 = webiopi().createGPIOButton(12, "左1ポンプ");
		var button25 = webiopi().createGPIOButton(25, "左2ポンプ");
		var button5 = webiopi().createGPIOButton(5, "右1ポンプ");
		var button24 = webiopi().createGPIOButton(24, "右2ポンプ");

		var button13 = webiopi().createGPIOButton(13, "弱USBポンプ");
		var button21 = webiopi().createGPIOButton(21, "水位計測");

		var button19 = webiopi().createGPIOButton(19, "エア");
		var button26 = webiopi().createGPIOButton(26, "オゾン");
		var button16 = webiopi().createGPIOButton(16, "LEDライト");
		var button18 = webiopi().createGPIOButton(18, "ファン");

		var button20 = webiopi().createGPIOButton(20, "ペルチェポンプ");
		var button17 = webiopi().createGPIOButton(17, "ペルチェ");

		$("#water1").append(button25,button5);
		$("#water2").append(button12,button24);

		$("#pump1").append(button21);
		$("#pump2").append(button13);

		$("#air1").append(button19);
		$("#air2").append(button26);
		$("#light1").append(button16);
		$("#light2").append(button18);
		$("#peltier1").append(button20);
		$("#peltier2").append(button17);

		var button6 = webiopi().createGPIOButton(6, "未使用USB 6");
		var button22 = webiopi().createGPIOButton(22, "充電器");
		var button23 = webiopi().createGPIOButton(23, "未使用 23");
		var button27 = webiopi().createGPIOButton(27, "掃除機");
		//$("#other2").append(button23);
		$("#other1").append(button27,button22);
		$("#other2").append(button23,button6);

		webiopi().refreshGPIO(true);
		w().refreshGPIO(true);
	});
	</script>
	<link rel="stylesheet" type="text/css" href="style.css">

</head>
<body>
<div align="center">

<h2 class="icon-air">空気</h2>
<ul class="air">
<li id="air1"></li>
<li id="air2"></li>
</ul>
<h2 class="icon-water">水 ポンプ</h2>
<ul class="water">
<li id="water1"></li>
<li id="water2"></li>
<li id="water3"></li>
<li id="water4"></li>
</ul>
<h2 class="icon-pump">ポンプ サブ</h2>
<ul class="pump">
<li id="pump1"></li>
<li id="pump2"></li>
</ul>

<h2 class="icon-light">光・風</h2>
<ul class="light">
<li id="light1"></li>
<li id="light2"></li>
</ul>
<h2 class="icon-peltier">冷却</h2>
<ul class="peltier">
<li id="peltier1"></li>
<li id="peltier2"></li>
</ul>

<h2 class="icon-pump">Other</h2>
<ul class="other">
<li id="other1"></li>
<li id="other2"></li>
</ul>

<div class="grafana" style="height:300px;">
<iframe src="http://192.168.31.53:3000/d/lp-WN-Rgz/waterlevel?orgId=1&refresh=2s&theme=light&kiosk" width="auto" height="100" frameborder="0" style="border:0" allowfullscreen></iframe>
</div>
<div class="grafana" style="height:1260px;">
<iframe src="http://192.168.31.53:3000/d/g_Ju8BZRk/panel?orgId=1&refresh=1m&theme=light&kiosk" width="auto" height="400" frameborder="0" style="border:0" allowfullscreen></iframe>
</div>

</div>
</body>
</html>

script.py

script.pyは最低限の設定

GPIO = webiopi.GPIO

BUTTON20 = 20
BUTTON16 = 16

BUTTON19  = 19
BUTTON26 = 26

BUTTON12  = 12
BUTTON6   = 6
BUTTON13 = 13
BUTTON21  = 21

BUTTON17 = 17
BUTTON18 = 18
BUTTON22 = 22
BUTTON23 = 23
BUTTON27 = 27
BUTTON5 = 5
BUTTON24 = 24
BUTTON25 = 25

def setup():
	# ラズパイ再起動 OUT LED以外は全部オンになる。
	GPIO.setFunction(BUTTON20, GPIO.OUT)
	GPIO.setFunction(BUTTON19, GPIO.OUT)
	GPIO.setFunction(BUTTON26, GPIO.OUT)
	GPIO.setFunction(BUTTON12, GPIO.OUT)
	GPIO.setFunction(BUTTON6, GPIO.OUT)
	GPIO.setFunction(BUTTON16, GPIO.OUT)
	GPIO.setFunction(BUTTON13, GPIO.OUT)
	GPIO.setFunction(BUTTON21, GPIO.OUT)
	GPIO.setFunction(BUTTON17, GPIO.OUT)
	GPIO.setFunction(BUTTON18, GPIO.OUT)
	GPIO.setFunction(BUTTON22, GPIO.OUT)
	GPIO.setFunction(BUTTON23, GPIO.OUT)
	GPIO.setFunction(BUTTON27, GPIO.OUT)
	GPIO.setFunction(BUTTON5, GPIO.OUT)
	GPIO.setFunction(BUTTON24, GPIO.OUT)
	GPIO.setFunction(BUTTON25, GPIO.OUT)

	GPIO.digitalWrite(BUTTON19, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON20, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON26, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON12, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON6, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON16, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON13, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON21, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON17, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON18, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON22, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON23, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON27, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON5, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON24, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON25, GPIO.HIGH)

def destroy():
	GPIO.digitalWrite(BUTTON19, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON20, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON26, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON12, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON6, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON13, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON21, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON17, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON18, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON22, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON23, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON27, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON5, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON24, GPIO.HIGH)
	GPIO.digitalWrite(BUTTON25, GPIO.HIGH)

style.css

CSSも載せておきます。

/* Grafana */
.grafana{
	position:relative;
	width:100%;
	marigin:0;
}
.grafana iframe{
	position:absolute;
	top:0;
	left:0;
	width:100%;
	height:100%;
}

body{
	margin:0;
}

.water, .air, .light, .wind, .other, .peltier, .pump{
	display: table;
	background: #FFF;
	margin: 5px 5px 5px -40px;
}
[id^="water"],[id^="air"],[id^="light"],[id^="wind"],[id^="peltier"],[id^="other"],[id^="pump"] {
	display: table-cell;
	text-align: center;
	vertical-align: middle;
}
h2 {
	position:relative;
	margin: 5px;
	line-height: 250%;
	text-decoration: none;
	width: 350px;
	font-size: 16px;
	/*vertical-align: bottom;*/
	padding: 2px 4px;
	background: -moz-linear-gradient( top, #787878 0%, #a8a8a8);
	background: -webkit-gradient( linear, left top, left bottom, from(#787878), to(#a8a8a8));
	-moz-border-radius: 3px;
	-webkit-border-radius: 3px;
	border-radius: 3px;
	border: 1px solid #c2c2c2;
	-moz-box-shadow: 0px 2px 0px rgb(213, 213, 213), inset 0px 2px 0px rgba(255,255,255,1);
	-webkit-box-shadow: 0px 2px 0px rgb(213, 213, 213), inset 0px 2px 0px rgba(255,255,255,1);
	box-shadow: 0px 2px 0px rgb(213, 213, 213), inset 0px 2px 0px rgba(255,255,255,1);
	text-shadow: 0px 2px 2px rgba(199,199,199,1), 0px -2px 5px rgba(255,255,255,1);
	background: #f5f5f5 ;
}
button {
	display: block !important;
	margin: 10px;
	width: 150px;
	font-size: 14pt;
	height:40px;
	line-height:20px;
	padding: 5px 10px;
	color: #FFF;
	text-decoration: none;
	/*text-align: center;*/
	/*background-color: Silver !important;*/
	/*border-bottom: solid 6px #ec6c00;*/
	border-bottom: initial;
	box-shadow: 0 3px 0 #dadada;
	text-shadow: 0 1px 1px rgba(0, 0, 0, .4);
	border-radius: 3px;
	border:initial;
}
/*button:hover{
	background-color: #f9c500;
	border-bottom: solid 6px #f39800;
}*/
button:active {
	background-color: #f9c500 !important;
	box-shadow: 0 3px 0 #f3d703 inset;
	margin-top: 6px;
	-webkit-transform: translateY(6px);
	transform: translateY(6px);
	border:initial;
}
.HIGH{
	background-color: Silver;
}
.LOW{
	background-color:#501b0a;
	text-shadow: 0 1px 1px #1e1a1a;
	box-shadow: 0 3px 0 #1e1a1a inset;
}
ul.air button.LOW{
	background-color: #3576ad;
	box-shadow: 0 3px 0 #1f4980 inset;
}
ul.water button.LOW{
	background-color: #46c5f8;
	box-shadow: 0 3px 0 #80bcd4 inset;
}
ul.light button.LOW{
	background-color: #f6d416;
	box-shadow: 0 3px 0 #edc157 inset;
}
ul.peltier button.LOW{
	background-color: #16cbdb;
	box-shadow: 0 3px 0 #aee5ea inset;
}
ul.pump button.LOW{
	background-color: #e82727;
	box-shadow: 0 3px 0 #9f1111 inset;
}

button:before,[class^="icon-"]:before{
	position: absolute;
	font-family: "Font Awesome 5 Free";
	border-radius: 4px;
	top: 0;
	left: 0;
	bottom: 0;
	/*text-align:center;*/
	width:30px;
	/*vertical-align: middle;*/
	/*background: #0c0601e0;*/
	/*padding: 10px 0 0 5px;*/
}
.icon-light:before{
	content: '\f0eb';
}
.icon-air:before{
	content: '\f72e';
}
.icon-water:before{
	content: '\f773';
}
.icon-pump:before{
	content: '\f0c3';
}
.icon-peltier:before{
	content: '\f769';
}
@media screen and (max-width:639px){
	body{
		padding: 5px;
	}
}

メインで使っているラズパイと16連リレーを接続した様子など記事にしてないので後ほど詳しく説明していきますね。

コメント

タイトルとURLをコピーしました