- 外部のセンサを接続 (ADC)
+ アナログセンサを接続 (ADC)
ADCは、Analog to Digital Converter の意味です。
analogRead(PIN) は、PIN番ピンの電圧(0~3.3V)を、0〜4095 の値で返します。一般に、抵抗値が変化するタイプのセンサは、この方法をつかって、読み取ることができます。
警告
- ADC にかいてあるように、G26, G32, G33, G36のみ使えます。G26は、無線利用時には使えません。
+ ADC にかいてあるように、G26, G32, G33, G36のみ使えます。G26は、無線利用時には使えません。
リスト 10 はCdSセル(照度センサ) の値を読み取るサンプルです。
@@ -907,7 +916,8 @@
15
16
17
-18 | | int PIN = 26;
// +--- 15kΩ抵抗 --+-- CdSセル --+
// | | |
@@ -917,6 +927,7 @@
// 注意点: https://lang-ship.com/reference/unofficial/M5StickC/Peripherals/ADC/
void setup() {
+ Serial.begin(115200);
pinMode(PIN, ANALOG); // PINのモード設定
// https://lang-ship.com/blog/work/m5stickc-io/
}
@@ -930,8 +941,8 @@
圧力センサや、曲げセンサも、同様の方法で利用することができます。
-
- 加速度センサ
+
+ 加速度センサ
リスト 11 は、内蔵されている加速度センサ(MPU6886) の値を表示するサンプルです。ちなみに、IMUとは、Inertial Measurement Unit: 慣性計測装置 の略です。
「ツール」→「シリアルプロッタ」をひらくと、値をグラフでみることができます。
ジャイロセンサの値も取得可能です。
@@ -996,23 +1007,28 @@
赤外(InfraRed)リモコン
-
- 信号の読み取り
- 赤外線リモコン受信モジュールが必要です。ここでは、GP1UXC41QS を前提に、話をすすめます。
+
+ 信号の読み取り
+ 赤外線リモコン受信モジュールが必要です。ここでは、GP1UXC41QS を前提に、話をすすめます。
また、準備として、ライブラリマネージャにて、IRremoteESP8266 をインストールします。ちなみに、テストしたバージョンは2.7.15でした。
+
+ 注釈
+ ライブラリマネージャは、「スケッチ」→「ライブラリをインクルード」→「ライブラリを管理…」で、ひらきます。
+
ファイルメニュー → スケッチ例 → IRremoteESP8266 → IRrecvDumpV2 を選択します。
- ブレッドボードに、以下の図のように配線します。Pとかいてある面が、受光器が出っ張っている面です。47Ωの抵抗を、5Vとの間に入れます。
+ ブレッドボードに、以下の図のように配線します。Pとかいてある面が、受光器が出っ張っている面だとおもってください。47Ωの抵抗を、5Vとの間に入れます。
赤外線リモコン受信モジュールに接続したピンを const uint16_t kRecvPin = 36; として設定します。
-受光器にリモコンを向けて、ボタンを押すと、シリアルモニタに情報が表示されます。ここでは、Protocol : NEC , Code 0x2FD48B7 (32 Bits) と表示されたとします。
+受光器にリモコンを向けて、ボタンを押すと、シリアルモニタに情報が表示されます。ここでは、Protocol : NEC , Code 0x2FD48B7 (32 Bits) と表示されたとします。この数値(uint32_t)を覚えておきます。
-
- 信号の送信
+
+ 信号の送信
内蔵の赤外LEDを用いて、信号を送信する例を リスト 12 に示します。
内蔵の赤外LED光はあまり強くないため、50cm程度まで近づかないと反応しない場合があります。
単体の赤外LEDを接続して用いると、距離を伸ばすことができます。
+ なお、NECフォーマットではない赤外線リモコンの通信フォーマットについては、 赤外線リモコンの通信フォーマット や、スケッチ例を参考にしてください。
リスト 12 src/irsend01.ino
1
@@ -1141,31 +1157,1294 @@
Wifi 接続
+ リスト 14 は、Wifi接続のサンプルです。ssid と password には、環境にあわせたものを入力してください。接続すると、m5デバイスのIPアドレスを画面に表示します。本当にWifi接続できたかどうかを、PCのターミナルからpingを打つなどして、確認してみましょう。(Windowsの場合、コマンドプロンプトをひらき、ping のあとに、半角スペースと、確認したいIPアドレスを入れます)
+
+ リスト 14 src/wifi01.ino
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32 | #include <M5StickCPlus.h>
+#include <WiFi.h>
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+void setup() {
+ M5.begin();
+ M5.Lcd.setRotation(3);
+ M5.Lcd.fillScreen(ORANGE);
+ M5.Lcd.setCursor(10, 50, 4);
+
+ WiFi.begin(ssid, password); // 接続開始
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ M5.Beep.tone(2000); delay(200);
+ M5.Beep.mute(); delay(300);
+ M5.Lcd.print(".");
+ }
+ // 接続完了!!
+ M5.Beep.tone(4000);
+ M5.Lcd.fillScreen(GREEN);
+ M5.Lcd.setCursor(0, 40, 4);
+ M5.Lcd.setTextColor(BLACK, GREEN);
+ M5.Lcd.print(" Wifi Connected!\n ");
+ String gotip = WiFi.localIP().toString(); // m5デバイスのIPアドレス
+ M5.Lcd.println(gotip);
+ delay(1500);
+ M5.Beep.mute();
+}
+
+void loop() {
+}
+
+ |
+
+ Wifi接続するだけでは、あまり意味がないので、Telnetサーバを起動する例を リスト 15 に示します。
+シリアルモニタを開いて、IPアドレスを確認したら、ターミナル(コマンドプロンプト)で、telnet IPaddr と入力して、接続します。
+telnet から文字を入力すると、シリアルモニタに表示されます。
+逆に、シリアルモニタから文字を入力すると、Telnet接続しているターミナルに、文字が表示されます。
+WiFiServer server(23) で、23番ポートで待ち受けるサーバを、作成しています。
+ちなみに、WiFiMulti は、複数のアクセスポイントに対して、Wifi接続を試みることができる機能(クラス)です。ただし、最終的に繋がるのは1つのアクセスポイントになります。
+
+ 注釈
+ Telnet接続を切断するときは、まずControlキーをおしながら ] をおしてください。プロンプトが telnet> と表示されますので、quit と打ち込むと終了します。 Escape character is '^]'. の ^ は、Controlキーのことです。
+
+
+ リスト 15 src/telnet01.ino
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+ 10
+ 11
+ 12
+ 13
+ 14
+ 15
+ 16
+ 17
+ 18
+ 19
+ 20
+ 21
+ 22
+ 23
+ 24
+ 25
+ 26
+ 27
+ 28
+ 29
+ 30
+ 31
+ 32
+ 33
+ 34
+ 35
+ 36
+ 37
+ 38
+ 39
+ 40
+ 41
+ 42
+ 43
+ 44
+ 45
+ 46
+ 47
+ 48
+ 49
+ 50
+ 51
+ 52
+ 53
+ 54
+ 55
+ 56
+ 57
+ 58
+ 59
+ 60
+ 61
+ 62
+ 63
+ 64
+ 65
+ 66
+ 67
+ 68
+ 69
+ 70
+ 71
+ 72
+ 73
+ 74
+ 75
+ 76
+ 77
+ 78
+ 79
+ 80
+ 81
+ 82
+ 83
+ 84
+ 85
+ 86
+ 87
+ 88
+ 89
+ 90
+ 91
+ 92
+ 93
+ 94
+ 95
+ 96
+ 97
+ 98
+ 99
+100
+101
+102
+103
+104
+105
+106
+107
+108
+109
+110
+111
+112
+113
+114
+115
+116
+117
+118
+119
+120
+121
+122
+123
+124
+125
+126
+127
+128
+129
+130 | // オリジナルのWifiTelnetToSerial を、改変しました。
+/*
+ WiFiTelnetToSerial - Example Transparent UART to Telnet Server for ESP32
+
+ Copyright (c) 2017 Hristo Gochkov. All rights reserved.
+ This file is part of the ESP32 WiFi library for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include <WiFi.h>
+#include <WiFiMulti.h>
+
+WiFiMulti wifiMulti;
+
+//how many clients should be able to telnet to this ESP32
+#define MAX_SRV_CLIENTS 3
+const char* ssid = "**********";
+const char* password = "**********";
+
+WiFiServer server(23);
+WiFiClient serverClients[MAX_SRV_CLIENTS];
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("\nConnecting");
+
+ wifiMulti.addAP(ssid, password);
+ // wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2");
+ // wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3");
+
+ Serial.println("Connecting Wifi ");
+ for (int loops = 10; loops > 0; loops--) {
+ if (wifiMulti.run() == WL_CONNECTED) {
+ Serial.println("");
+ Serial.print("WiFi connected ");
+ Serial.print("IP address: ");
+ Serial.println(WiFi.localIP());
+ break;
+ }
+ else {
+ Serial.println(loops);
+ delay(1000);
+ }
+ }
+ if (wifiMulti.run() != WL_CONNECTED) {
+ Serial.println("WiFi connect failed");
+ delay(1000);
+ ESP.restart();
+ }
+
+ //start UART and the server
+ // Serial2.begin(9600);
+ server.begin();
+ server.setNoDelay(true);
+
+ Serial.print("Ready! Use 'telnet ");
+ Serial.print(WiFi.localIP());
+ Serial.println(" 23' to connect");
+}
+
+void loop() {
+ uint8_t i;
+ if (wifiMulti.run() == WL_CONNECTED) {
+ //check if there are any new clients
+ if (server.hasClient()) {
+ for (i = 0; i < MAX_SRV_CLIENTS; i++) {
+ //find free/disconnected spot
+ if (!serverClients[i] || !serverClients[i].connected()) {
+ if (serverClients[i]) serverClients[i].stop();
+ serverClients[i] = server.available();
+ if (!serverClients[i]) Serial.println("available broken");
+ Serial.print("New client: ");
+ Serial.print(i); Serial.print(' ');
+ Serial.println(serverClients[i].remoteIP());
+ break;
+ }
+ }
+ if (i >= MAX_SRV_CLIENTS) {
+ //no free/disconnected spot so reject
+ server.available().stop();
+ }
+ }
+ //check clients for data
+ for (i = 0; i < MAX_SRV_CLIENTS; i++) {
+ if (serverClients[i] && serverClients[i].connected()) {
+ if (serverClients[i].available()) {
+ //get data from the telnet client and push it to the UART
+ while (serverClients[i].available()) Serial.write(serverClients[i].read());
+ }
+ }
+ else {
+ if (serverClients[i]) {
+ serverClients[i].stop();
+ }
+ }
+ }
+ //check ==UART== => Serial for data
+ if (Serial.available()) {
+ size_t len = Serial.available();
+ uint8_t sbuf[len];
+ Serial.readBytes(sbuf, len);
+ //push UART data to all connected telnet clients
+ for (i = 0; i < MAX_SRV_CLIENTS; i++) {
+ if (serverClients[i] && serverClients[i].connected()) {
+ serverClients[i].write(sbuf, len);
+ delay(1);
+ }
+ }
+ }
+ }
+ else {
+ Serial.println("WiFi not connected!");
+ for (i = 0; i < MAX_SRV_CLIENTS; i++) {
+ if (serverClients[i]) serverClients[i].stop();
+ }
+ delay(1000);
+ }
+}
+
+ |
+
NTPサーバ (Network Time Protocol) と時刻の取得
+ M5StickCPlusには、システム時間(localTime)と、RTC(リアルタイムクロック:時計の機能を備えたICのこと)の2種類の時計があります。システム時間は、システムリセット(再起動)のたびに、時刻もリセットされますが、後者のRTCはリセットされません。
+ リスト 16 に、NTPサーバを使ってシステム時間の修正をしたのち、システム時間を1秒ごとに取得して、シリアルモニタに表示する例を示します。こちらは、M5のライブラリは不要です。configTime()でNTPサーバを設定しておくと、1時間に1回、NTPサーバに接続して、時刻修正します。
+
+ リスト 16 src/ntp01.ino
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30 | #include <WiFi.h>
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+void setup() {
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ Serial.print(".");
+ }
+ char* ntpserver = "10.64.7.184"; // 学外なら、たとえば "ntp.nict.jp"
+ configTime(9 * 3600, 0, ntpserver);//GMTとの時差(秒), サマータイムで進める時間(秒)
+}
+
+void loop() {
+ struct tm localTime;
+ char buf[30];
+ getLocalTime(&localTime);
+ sprintf(buf, ">> %04d/%02d/%02d %02d:%02d:%02d",
+ localTime.tm_year + 1900,
+ localTime.tm_mon + 1,
+ localTime.tm_mday,
+ localTime.tm_hour,
+ localTime.tm_min,
+ localTime.tm_sec
+ );
+ Serial.println(buf);
+ delay(1000);
+}
+
+ |
+
+
+ 警告
+ configTime() を行わない状況で、システム時間(localTime)を取得しようとすると、取得に数秒ほど時間がかかります。
+
+ リスト 17 は、RTCの時刻を表示するサンプルです。なお、5行目で USE_NTP に 1 が設定してあれば、RTCに時刻を設定します。常時Wifiネットワークに接続できない場合は、RTCを利用することが望ましいです。
+
+ リスト 17 src/rtc01.ino
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64 | #include <M5StickCPlus.h>
+#include <WiFi.h>
+// #include "time.h"
+
+#define USE_NTP 0 // NTPからRTCに時刻設定するなら1
+
+void setRTCfromLT(struct tm lt) {
+ RTC_DateTypeDef DateStruct;
+ DateStruct.Year = lt.tm_year + 1900;
+ DateStruct.Month = lt.tm_mon + 1;
+ DateStruct.Date = lt.tm_mday;
+ DateStruct.WeekDay = lt.tm_wday;
+ M5.Rtc.SetData(&DateStruct);
+
+ RTC_TimeTypeDef TimeStruct;
+ TimeStruct.Hours = lt.tm_hour;
+ TimeStruct.Minutes = lt.tm_min;
+ TimeStruct.Seconds = lt.tm_sec+1;
+ M5.Rtc.SetTime(&TimeStruct);
+}
+
+void getRTC(char* buf) {
+ RTC_DateTypeDef DateStruct;
+ RTC_TimeTypeDef TimeStruct;
+ M5.Rtc.GetData(&DateStruct);
+ M5.Rtc.GetTime(&TimeStruct);
+ sprintf(buf, "%04d/%02d/%02d %02d:%02d:%02d",
+ DateStruct.Year, DateStruct.Month, DateStruct.Date,
+ TimeStruct.Hours, TimeStruct.Minutes, TimeStruct.Seconds
+ );
+}
+
+void setup() {
+ M5.begin();
+ M5.Lcd.setRotation(3);
+ Serial.begin(115200);
+
+ if (USE_NTP) {
+ const char* ssid = "**********";
+ const char* password = "**********";
+ const char* ntpserver = "10.64.7.184"; // "ntp.nict.jp"
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ Serial.print(".");
+ }
+ configTime(9 * 3600, 0, ntpserver);
+
+ struct tm localTime;
+ while (localTime.tm_year < 80) {
+ getLocalTime(&localTime); delay(50);
+ }
+ setRTCfromLT(localTime);
+ }
+}
+
+void loop() {
+ char buf[30];
+ getRTC(buf); // bufに、日時文字列を書き込む
+ Serial.println(buf);
+ M5.Lcd.fillScreen(BLUE);
+ M5.Lcd.setCursor(0, 50, 4);
+ M5.Lcd.println(buf);
+ delay(1000);
+}
+
+ |
+
+
+ 注釈
+ RTCに一旦時刻を設定しておくと、初期状態で書き込まれているプログラムFactoryTest の「BMP8563 RTC Time」でも、その時刻が表示されるようになります。
+
WebClient
+ リスト 18 は、HTTP通信で天気予報Web APIに接続するサンプルです。HTTPClientクラスを用いると、ブラウザでURLを指定してWebページを開くように、WebサーバにGETメソッドやPOSTメソッドでリクエストを送信して、ステータスコードやレスポンスを取得することができます。このサンプルでは、シリアルコンソールに、天気予報をJSON形式で表示します。JSON(ジェイソン)とは、Javascriptのオブジェクトの形式でデータを表現する記法です。
+
+ リスト 18 src/httpclient01.ino
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33 | #include <WiFi.h>
+#include <HTTPClient.h>
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+// 天気予報API https://weather.tsukumijima.net/ から、千葉の天気を取得
+const char* weatherapi_url = "http://weather.tsukumijima.net/api/forecast/city/120010";
+
+void setup() {
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ delay(50);
+ Serial.print(".");
+ }
+ delay(1000);
+
+ HTTPClient http;
+ http.begin(weatherapi_url); //HTTPでサーバに接続
+ int httpCode = http.GET();
+ if (httpCode > 0) {
+ Serial.println(httpCode);
+ if (httpCode == HTTP_CODE_OK) {
+ String payload = http.getString();
+ Serial.println(payload);
+ }
+ }
+ http.end();
+}
+
+void loop() {
+}
+
+ |
+
+
+ 警告
+ https (SSL) 通信をする場合は、スケッチ例→HTTPClient→BasicHttpsClient を参照して、WiFiClientSecure クラスを使用してください。
+
+
+
+ Google Spreadsheet にデータを送信する
+ HTTP通信で、サーバにデータを送信して格納したいとおもっても、適当なサーバを準備するのは手間がかかることがあります。
+ Google Apps Scriptを用いると、HTTP通信で Google Spreadsheet にデータを書き込んだり、読み取ったりするWebサービスを作成し、WebAPIとして公開することができます。
+ (Google Spreadsheetのメニューで) 拡張機能→Apps Script または、ツール→スクリプトエディタ で、リスト 19 を「コード.gs」に書き込みます。15行目で「シート1」の一番下の行に、配列array の要素を追加します。19行目のgetRange(1,3).getValue()は、スプレッドシートのC1(3列1行)の値を取得しています。ここに「=average(C2:C200)」のようにしておくと、データの平均値を取得することもできます。
+ 作成した「コード.gs」について、「公開」→「ウェブアプリケーションとして導入...」で、Webアプリとして公開できます。(なお、「新しいエディタ」では表示が異なるため、できない可能性があります。)
+ 作成できたかどうかをパソコンから確認するには、コマンドラインから、以下のように入力します(ただし、curlが必要です。val1=10 と val2=20 のあいだは&記号ですので、シェルでバックグラウンド処理されないように、URLをシングルクォートで囲っています)
+ curl -L 'https://script.google.com/macros/s/XxXxXxXxXx/exec?val1=10&val2=20'
+
+
+
+ リスト 19 src/doget.js
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21 | function doGet(e) {
+ var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
+ var sheet = spreadsheet.getSheetByName('シート1');
+ var now = new Date();
+ var array = [];
+ array.push(now);
+
+ for(var k in e['parameter']) {
+ array.push(k);
+ array.push(e['parameter'][k]);
+ }
+ var params = JSON.stringify(e);
+ array.push(params);
+ sheet.appendRow( array );
+
+ var lastRow = sheet.getLastRow();
+
+ var output = ContentService.createTextOutput(sheet.getRange(1,3).getValue()+" "+lastRow);
+ output.setMimeType(ContentService.MimeType.TEXT);
+ return output;
+}
+
+ |
+
WebServer
+ すこし長いですが、リスト 20 は、80番ポートでHTTPでの通信を待ち受け(listenし)て、クライアントからの接続情報(ヘッダ情報)を返すWebサーバのシンプルな例です。クライアント(ブラウザ)からのリクエスト行のうち、 GET または POST ではじまる行があれば、変数 meth に格納します。つまり、変数 meth には、ブラウザで発行したリクエストのURLが含まれることになります。POSTメソッドで送信されていれば、リクエストボディに記述されたデータを変数 post に格納します。 クライアント(ブラウザ)に返す「レスポンス」は、 client.println() で送信します。最後に、client.stop() で、サーバ側から接続を切断します。
+
+ リスト 20 src/httpserver01.ino
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83
+84 | #include <WiFi.h>
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+WiFiServer server(80);
+
+void setup() {
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ delay(50);
+ Serial.print(".");
+ }
+ String ip = WiFi.localIP().toString(); // m5デバイスのIPアドレス
+ Serial.printf("\nopen http://%s\n\n", ip.c_str() );
+
+ server.begin(); // Webサーバを開始
+}
+
+void loop() {
+ WiFiClient client = server.available();
+ if (client) {
+ String req = "" ;
+ String tmp = "" , meth = "" ;
+ while (client.connected()) { // loop while the client's connected
+ if (client.available()) { // if there's bytes to read from the client,
+ char c = client.read(); // read a byte, then
+ req += c;
+ if (c == '\n') { // if the byte is a newline character
+ if (tmp.length() == 0) { // end of request, break while loop
+ break;
+ } else { //まだ継続
+ if (tmp.startsWith("GET ") || tmp.startsWith("POST ") ) {
+ meth = tmp;
+ }
+ tmp = "";
+ }
+ } else if (c != '\r') { // if you got anything else but a carriage return character,
+ tmp += c; // add it to the end of the currentLine
+ }
+ }
+ } // end of while
+
+ Serial.println(meth);
+ if ( meth.startsWith("GET /") ) {
+ client.println("HTTP/1.1 200 OK"); // header (with response code)
+ client.println("Content-Type:text/plain");
+ client.println(""); // HTTPでは、header と body の区切りは改行
+ client.println(meth);
+ client.println("-- request --");
+ client.println(req);
+ }
+
+ if ( meth.startsWith("POST ") ) {
+ String post = "";
+ char buf[257];
+ int n;
+ while ((n = client.available()) > 0) {
+ if (n < 256) {
+ client.readBytes(buf, n) ;
+ buf[n] = 0 ;
+ } else {
+ client.readBytes(buf, 256) ;
+ buf[256] = 0 ;
+ }
+ }
+ post += buf ;
+
+ client.println("HTTP/1.1 200 OK");
+ client.println("Content-Type:text/plain");
+ client.println(""); // HTTPでは、header と body の区切りは改行
+ client.println(meth);
+ client.println("-- request --");
+ client.println(req);
+ client.println("-- post data --");
+ client.println(post);
+ }
+ // close the connection:
+ client.stop();
+ Serial.println(" --- Client Disconnected.");
+ }
+ delay(100);
+}
+
+ |
+
+ センサデータを返すだけなら問題ありませんが、クライアントからのデータを GET / POST で受信して処理する場合は、 key1=val1&key2=val2 のような文字列を要素に分解する必要がでてきます。あまり深入りしませんが、正規表現で文字列を照合・抽出する Regexp や、抽出した結果をハッシュ/辞書として保存する Dictionary ライブラリを導入すると、複雑なリクエストやデータを扱いやすくなるでしょう。
+リスト 21 に、RGBの色指定文字列などのリクエスト文字列をパースして辞書に追加するプログラムの断片を示します。注意:このプログラムは、単体では動作しません
+
+ リスト 21 src/regexp01.ino
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25 | #include <Regexp.h>
+#include <Dictionary.h>
+
+Dictionary *dict = new Dictionary(); // 辞書/ハッシュ。キー文字列→値 を保持するデータ構造
+
+void match_callback (const char * match, // matching string (not null-terminated)
+ const unsigned int length, // length of matching string
+ const MatchState & ms) // MatchState in use (to get captures)
+{
+ char k [10]; // size must be large enough to hold captures
+ char v [10]; // size must be large enough to hold captures
+ ms.GetCapture(k, 0);
+ ms.GetCapture(v, 1);
+ dict->insert(k, v); // 辞書に追加 (たとえば、 red→120 を追加)
+} // end of match_callback
+
+// たとえば、*cbuf = "red=120&green=255&blue=9" のような文字列を想定する
+void param2dict(char *cbuf){
+ MatchState ms (cbuf); //正規表現マッチャーの作成
+ ms.GlobalMatch ("([a-z]+)=([0-9]+)", match_callback); // (key)=(value) で複数回マッチングする。match_callback は別関数。
+ int r = dict->search("red").toInt(); // 辞書 dict
+ int g = dict->search("green").toInt();
+ int b = dict->search("blue").toInt();
+ int32_t bgcolor = (int(r * 31 / 255) << 11) | (int(g * 63 / 255) << 5) | (int(b * 31 / 255));
+}
+
+ |
+
+
+
+ MQTT
+ MQTT(Message Queue Telemetry Transport) は、センサデータをデバイス間で共有・流通させるときなどに使われる、軽量のメッセージ送受信プロトコルです。
+ここで、「軽量」とは、HTTPに比べて、ヘッダ部分のデータが少ない、という意味です。
+MQTTでは、サーバのことを「ブローカ」と呼びます。ここでは、データを送信するPublisherと、データを受信するSubscriberの2つのデバイスと、ブローカの3つの構成要素で説明します。
+ Publisherは、ブローカに接続しデータを送信します。このとき、 「トピック」 と呼ばれる、データの登録先を文字列で指定します。また、retainデータとして送信するかどうかも指定します。retainとは「保持・維持」の意味で、最後に送ったデータをブローカに残しておきたい場合、rateinデータとします。
+Subscriberは、ブローカに接続し、トピックをサブスクライブ(購読)します。このとき、トピックに書き込まれているretainデータがあれば、最初にそのデータを受信します。retainデータがなければ、Publisherが新しいデータをトピックに送信したタイミングで、データを受信します。
+このような通信手段(プロトコル)を、パブリッシュ/サブスクライブモデル と呼びます。参考サイト:
+IoT初心者向け!「MQTT」について簡単にまとめてみる
+
+ MQTT Publish
+ リスト 22 は、MQTT Publisherのサンプルです。実験用ブローカ(mqtt.istlab.info)に接続して、 office/temp というトピックにデータ(シリアルコンソールで送信した文字列)を書き込みます。 パスワードはここには書けませんので、講義ポータル資料を参照してください。
+
+ リスト 22 src/mqtt01pub.ino
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70 | // MQTT Publisher example
+//#include <M5StickCPlus.h>
+#include <WiFi.h>
+// ライブラリで PubSubClient をいれておく
+#include <PubSubClient.h>
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+const char* server = "mqtt.istlab.info";
+const int port = 1883;
+// 注:学内ネットワークからは上記ポート番号に接続できない。
+const char* pubTopic = "office/temp"; //"ex1/groupXX/sensor";
+const char* mquser = "ex1";
+const char* mqpass = "***PASSWORD***";
+WiFiClient wifiClient;
+char* clientid = "m5stickc01_00000001"; //デバイス個別に設定すればなんでもよい
+PubSubClient mqttClient(wifiClient); // MQTT Client
+
+void setup() {
+ // M5.begin();
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ Serial.print(".");
+ delay(200);
+ }
+ Serial.println( WiFi.localIP().toString() ); //取得したIPアドレス
+ // 参考:WiFiデバイスのMACアドレスを取得し、clientid として用いる
+ // (18行目のclientidの定義を変更し、char clientid[20]とするのがのぞましい)
+ // uint8_t mac[6];
+ // esp_read_mac(mac, ESP_MAC_WIFI_STA);
+ // sprintf(clientid, "%02X:%02X:%02X:%02X:%02X:%02X",
+ // mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] );
+ mqttClient.setServer(server, port);
+
+ if (!mqttClient.connected()) {
+ Serial.println("Try (re)connecting...");
+ reconnect();
+ }
+}
+
+void loop() {
+ // シリアルコンソールから書き込みがあれば, publishする
+ byte mbuf[100]; int pos = 0;
+ while (Serial.available()) { // ノンブロッキング
+ mbuf[pos] = Serial.read();
+ pos++;
+ }
+ if (pos > 0) {
+ mbuf[pos-1] = 0; //改行をNULLに置き換える
+ Serial.println((char*)mbuf);
+ // ブローカにデータを送信する。最後のfalse を true にすると、retained になる。
+ bool ret = mqttClient.publish(pubTopic, mbuf, pos-1, false);
+ if (!ret){
+ Serial.println("publish failed.");
+ }
+ }
+ delay(10);
+}
+
+void reconnect() {
+ while (!mqttClient.connected()) {
+ if (mqttClient.connect(clientid, mquser, mqpass)) {
+ Serial.println("Connected to MQTT Broker.");
+ } else {
+ Serial.printf("Connect Failed. state=%d", mqttClient.state());
+ }
+ }
+}
+
+ |
+
+
+
+ MQTT Subscribe
+ リスト 23 は、MQTT Subscriberのサンプルです。実験用ブローカ(mqtt.istlab.info)に接続して、 office/temp というトピックを購読します。 パスワードはここには書けませんので、講義ポータル資料を参照してください。 JSON形式のデータを処理するときは、ArduinoJsonをつかってパージング/deserialize すると便利です(78行目以降にサンプルがあります)。
+
+ リスト 23 src/mqtt01sub.ino
+ 1
+ 2
+ 3
+ 4
+ 5
+ 6
+ 7
+ 8
+ 9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+41
+42
+43
+44
+45
+46
+47
+48
+49
+50
+51
+52
+53
+54
+55
+56
+57
+58
+59
+60
+61
+62
+63
+64
+65
+66
+67
+68
+69
+70
+71
+72
+73
+74
+75
+76
+77
+78
+79
+80
+81
+82
+83 | // MQTT Subscriber example
+// #include <M5StickCPlus.h>
+#include <WiFi.h>
+// ライブラリで PubSubClient をいれておく
+#include <PubSubClient.h>
+// ライブラリで ArduinoJson v6.xをいれておく。
+// (注意:Arduino_Json v0.1.0 は別物)
+#include <ArduinoJson.h>
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+const char* server = "mqtt.istlab.info";
+const int port = 1883;
+// 注:学内ネットワークからは上記ポート番号に接続できない。
+const char* pubTopic = "office/temp"; // 例: ex1/groupXX/sensor
+const char* mquser = "ex1";
+const char* mqpass = "***PASSWORD***";
+WiFiClient wifiClient;
+char* clientid = "m5stickc01_00000001"; //デバイス個別に設定すればなんでもよい
+PubSubClient mqttClient(wifiClient); // MQTT Client
+
+void setup() {
+// M5.begin();
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ Serial.print(".");
+ delay(200);
+ }
+ Serial.println( WiFi.localIP().toString() ); //取得したIPアドレス
+ // 参考:WiFiデバイスのMACアドレスを取得し、clientid として用いる
+ // (18行目のclientidの定義を変更し、char clientid[20]とするのがのぞましい)
+ // uint8_t mac[6];
+ // esp_read_mac(mac, ESP_MAC_WIFI_STA);
+ // sprintf(clientid, "%02X:%02X:%02X:%02X:%02X:%02X",
+ // mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] );
+ mqttClient.setServer(server, port);
+ mqttClient.setCallback(callback_on_subscribe);
+
+ if (!mqttClient.connected()) {
+ Serial.println("Try (re)connecting...");
+ reconnect();
+ }
+ mqttClient.subscribe(pubTopic);
+
+}
+
+void loop() {
+
+ mqttClient.loop(); // データがpublishされたら、callback_on_subscribe が呼ばれる
+
+ delay(100);
+}
+
+void reconnect() {
+ while (!mqttClient.connected()) {
+ if (mqttClient.connect(clientid, mquser, mqpass)) {
+ Serial.println("Connected to MQTT Broker.");
+ } else {
+ Serial.printf("Connect Failed. state=%d", mqttClient.state());
+ }
+ }
+}
+
+// データがPublishされたら、ここが実行される。(39行目でコールバック関数を設定しているため)
+void callback_on_subscribe(char* topic, byte* payload, unsigned int len) {
+ char buf[100];
+ Serial.print("Message arrived [");
+ Serial.print(topic);
+ Serial.print("] ");
+ for (int i = 0; i < len ; i++) {
+ buf[i] = (char)payload[i];
+ }
+ buf[len] = 0;
+ Serial.println(buf);
+ return;
+ // 参考:JSON Parsing example
+ // StaticJsonDocument<200> sjdoc;
+ // deserializeJson(sjdoc, buf);
+ // int intval = sjdoc["intval"];
+ // const char* str = sjdoc["string"];
+}
+
+ |
+
+
+
+ トピック名とワイルドカード
+ サブスクライブするときのトピック名には、ワイルドカードが指定できます。例えば、 office/+
+と指定すると、 office/temp にも office/humid にもマッチします。参考:MQTT の仕様
+
+
+ mosquitto コマンド例
+ mosquitto は、オープンソースのMQTT Broker/Client 実装の1つです。参考までに、mosquitto クライアントを使用するコマンド例を示します。:
+ #(パブリッシュ。retainなし: -m "メッセージ" )
+ mosquitto_pub -h mqtt.istlab.info -u ex1 -P PASS -t office/temp -m "data or message"
+#(パブリッシュ。retainあり: -r )
+ mosquitto_pub -h mqtt.istlab.info -u ex1 -P PASS -t office/temp -m "data or message" -r
+#(retainデータを削除。-n : send a null (zero length) message.)
+ mosquitto_pub -h mqtt.istlab.info -u ex1 -P PASS -t office/temp -n -r
+#(サブスクライブ)
+ mosquitto_sub -h mqtt.istlab.info -u ex1 -P PASS -t office/temp
+
+
+ 3つ目の例に示すように、長さ0の非retainデータを送信すると、retainデータを削除できます。
+
+
+
+ Bluetooth Serial Protocol Profile (SPP)
Bluetooth Low Energy
アドバータイズ
-
- Bluetooth Serial Protocol Profile (SPP)
-
Preference
-
- 電力制御
+
+ 電力制御
-
- その他
+
+ その他
- CO2 (CCS811)
- RCS-620S (Felica)
@@ -1174,12 +2453,6 @@
- OTA
-
diff --git a/_build/html/week2.html b/_build/html/week2.html
index 396d742..8720a6b 100644
--- a/_build/html/week2.html
+++ b/_build/html/week2.html
@@ -92,9 +92,13 @@
@@ -165,6 +169,17 @@
2週目
1週目で学んだことを活かして、組み合わせてみましょう。
+
+ 複数人で書いたプログラムを統合する
+
+- Arduino IDEでは、プログラムを複数のファイル(たとえば、main.ino / sub.ino / hoge.cpp / hoge.h ) に分割して記述することができます。複数ファイルに分割することで、関数定義を機能別にまとめることができ、管理しやすくなります。
+- Arduino IDEでは、プログラムを構成する、複数のファイル(スケッチブック)を、1つのフォルダに入れて管理します。(スケッチブックのことを、他のIDEでは「プロジェクト」と呼ぶ場合もある)
+- スケッチブックに別のファイル(hogehoge.ino) を追加するには、右上にあるシリアルモニタをひらくアイコンの、下の「▼」ボタンからメニューをひらき、「新規タブ」を選択し、ファイル名(拡張子.ino をのぞいた、hogehogeの部分のみ)を入力します。
+- スケッチブックをコピーしたいときは、内包するフォルダごとコピーしてください。その際、フォルダ名と、メインのソースコードファイル名(拡張子以外の部分)は、一致している必要があります。
+- 複数のファイルを置いたときの挙動について:
***.ino ファイルの内容は、単純にメインのタブ(フォルダ名と同じinoファイル)にマージされます。***.cpp や ***.c という拡張子でファイルを作成した場合は、***.h を作成する必要があります。参考:Properly using separate tabs with Arduino IDE
+- Git を利用すると、複数人で作業したファイルを統合しやすいです。
+
+
diff --git a/src/cds01.ino b/src/cds01.ino
index 25b5a3b..539570c 100644
--- a/src/cds01.ino
+++ b/src/cds01.ino
@@ -8,6 +8,7 @@
// 注意点: https://lang-ship.com/reference/unofficial/M5StickC/Peripherals/ADC/
void setup() {
+ Serial.begin(115200);
pinMode(PIN, ANALOG); // PINのモード設定
// https://lang-ship.com/blog/work/m5stickc-io/
}
diff --git a/src/doget.js b/src/doget.js
new file mode 100644
index 0000000..83d7899
--- /dev/null
+++ b/src/doget.js
@@ -0,0 +1,21 @@
+function doGet(e) {
+ var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
+ var sheet = spreadsheet.getSheetByName('シート1');
+ var now = new Date();
+ var array = [];
+ array.push(now);
+
+ for(var k in e['parameter']) {
+ array.push(k);
+ array.push(e['parameter'][k]);
+ }
+ var params = JSON.stringify(e);
+ array.push(params);
+ sheet.appendRow( array );
+
+ var lastRow = sheet.getLastRow();
+
+ var output = ContentService.createTextOutput(sheet.getRange(1,3).getValue()+" "+lastRow);
+ output.setMimeType(ContentService.MimeType.TEXT);
+ return output;
+}
\ No newline at end of file
diff --git a/src/httpclient01.ino b/src/httpclient01.ino
new file mode 100644
index 0000000..e2f9a83
--- /dev/null
+++ b/src/httpclient01.ino
@@ -0,0 +1,33 @@
+#include
+#include
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+// 天気予報API https://weather.tsukumijima.net/ から、千葉の天気を取得
+const char* weatherapi_url = "http://weather.tsukumijima.net/api/forecast/city/120010";
+
+void setup() {
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ delay(50);
+ Serial.print(".");
+ }
+ delay(1000);
+
+ HTTPClient http;
+ http.begin(weatherapi_url); //HTTPでサーバに接続
+ int httpCode = http.GET();
+ if (httpCode > 0) {
+ Serial.println(httpCode);
+ if (httpCode == HTTP_CODE_OK) {
+ String payload = http.getString();
+ Serial.println(payload);
+ }
+ }
+ http.end();
+}
+
+void loop() {
+}
\ No newline at end of file
diff --git a/src/httpserver01.ino b/src/httpserver01.ino
new file mode 100644
index 0000000..1d6e808
--- /dev/null
+++ b/src/httpserver01.ino
@@ -0,0 +1,84 @@
+#include
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+WiFiServer server(80);
+
+void setup() {
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ delay(50);
+ Serial.print(".");
+ }
+ String ip = WiFi.localIP().toString(); // m5デバイスのIPアドレス
+ Serial.printf("\nopen http://%s\n\n", ip.c_str() );
+
+ server.begin(); // Webサーバを開始
+}
+
+void loop() {
+ WiFiClient client = server.available();
+ if (client) {
+ String req = "" ;
+ String tmp = "" , meth = "" ;
+ while (client.connected()) { // loop while the client's connected
+ if (client.available()) { // if there's bytes to read from the client,
+ char c = client.read(); // read a byte, then
+ req += c;
+ if (c == '\n') { // if the byte is a newline character
+ if (tmp.length() == 0) { // end of request, break while loop
+ break;
+ } else { //まだ継続
+ if (tmp.startsWith("GET ") || tmp.startsWith("POST ") ) {
+ meth = tmp;
+ }
+ tmp = "";
+ }
+ } else if (c != '\r') { // if you got anything else but a carriage return character,
+ tmp += c; // add it to the end of the currentLine
+ }
+ }
+ } // end of while
+
+ Serial.println(meth);
+ if ( meth.startsWith("GET /") ) {
+ client.println("HTTP/1.1 200 OK"); // header (with response code)
+ client.println("Content-Type:text/plain");
+ client.println(""); // HTTPでは、header と body の区切りは改行
+ client.println(meth);
+ client.println("-- request --");
+ client.println(req);
+ }
+
+ if ( meth.startsWith("POST ") ) {
+ String post = "";
+ char buf[257];
+ int n;
+ while ((n = client.available()) > 0) {
+ if (n < 256) {
+ client.readBytes(buf, n) ;
+ buf[n] = 0 ;
+ } else {
+ client.readBytes(buf, 256) ;
+ buf[256] = 0 ;
+ }
+ }
+ post += buf ;
+
+ client.println("HTTP/1.1 200 OK");
+ client.println("Content-Type:text/plain");
+ client.println(""); // HTTPでは、header と body の区切りは改行
+ client.println(meth);
+ client.println("-- request --");
+ client.println(req);
+ client.println("-- post data --");
+ client.println(post);
+ }
+ // close the connection:
+ client.stop();
+ Serial.println(" --- Client Disconnected.");
+ }
+ delay(100);
+}
\ No newline at end of file
diff --git a/src/mqtt01pub.ino b/src/mqtt01pub.ino
new file mode 100644
index 0000000..9a23cbb
--- /dev/null
+++ b/src/mqtt01pub.ino
@@ -0,0 +1,70 @@
+// MQTT Publisher example
+//#include
+#include
+// ライブラリで PubSubClient をいれておく
+#include
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+const char* server = "mqtt.istlab.info";
+const int port = 1883;
+// 注:学内ネットワークからは上記ポート番号に接続できない。
+const char* pubTopic = "office/temp"; //"ex1/groupXX/sensor";
+const char* mquser = "ex1";
+const char* mqpass = "***PASSWORD***";
+WiFiClient wifiClient;
+char* clientid = "m5stickc01_00000001"; //デバイス個別に設定すればなんでもよい
+PubSubClient mqttClient(wifiClient); // MQTT Client
+
+void setup() {
+ // M5.begin();
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ Serial.print(".");
+ delay(200);
+ }
+ Serial.println( WiFi.localIP().toString() ); //取得したIPアドレス
+ // 参考:WiFiデバイスのMACアドレスを取得し、clientid として用いる
+ // (18行目のclientidの定義を変更し、char clientid[20]とするのがのぞましい)
+ // uint8_t mac[6];
+ // esp_read_mac(mac, ESP_MAC_WIFI_STA);
+ // sprintf(clientid, "%02X:%02X:%02X:%02X:%02X:%02X",
+ // mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] );
+ mqttClient.setServer(server, port);
+
+ if (!mqttClient.connected()) {
+ Serial.println("Try (re)connecting...");
+ reconnect();
+ }
+}
+
+void loop() {
+ // シリアルコンソールから書き込みがあれば, publishする
+ byte mbuf[100]; int pos = 0;
+ while (Serial.available()) { // ノンブロッキング
+ mbuf[pos] = Serial.read();
+ pos++;
+ }
+ if (pos > 0) {
+ mbuf[pos-1] = 0; //改行をNULLに置き換える
+ Serial.println((char*)mbuf);
+ // ブローカにデータを送信する。最後のfalse を true にすると、retained になる。
+ bool ret = mqttClient.publish(pubTopic, mbuf, pos-1, false);
+ if (!ret){
+ Serial.println("publish failed.");
+ }
+ }
+ delay(10);
+}
+
+void reconnect() {
+ while (!mqttClient.connected()) {
+ if (mqttClient.connect(clientid, mquser, mqpass)) {
+ Serial.println("Connected to MQTT Broker.");
+ } else {
+ Serial.printf("Connect Failed. state=%d", mqttClient.state());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/mqtt01sub.ino b/src/mqtt01sub.ino
new file mode 100644
index 0000000..cce725d
--- /dev/null
+++ b/src/mqtt01sub.ino
@@ -0,0 +1,83 @@
+// MQTT Subscriber example
+// #include
+#include
+// ライブラリで PubSubClient をいれておく
+#include
+// ライブラリで ArduinoJson v6.xをいれておく。
+// (注意:Arduino_Json v0.1.0 は別物)
+#include
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+const char* server = "mqtt.istlab.info";
+const int port = 1883;
+// 注:学内ネットワークからは上記ポート番号に接続できない。
+const char* pubTopic = "office/temp"; // 例: ex1/groupXX/sensor
+const char* mquser = "ex1";
+const char* mqpass = "***PASSWORD***";
+WiFiClient wifiClient;
+char* clientid = "m5stickc01_00000001"; //デバイス個別に設定すればなんでもよい
+PubSubClient mqttClient(wifiClient); // MQTT Client
+
+void setup() {
+// M5.begin();
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ Serial.print(".");
+ delay(200);
+ }
+ Serial.println( WiFi.localIP().toString() ); //取得したIPアドレス
+ // 参考:WiFiデバイスのMACアドレスを取得し、clientid として用いる
+ // (18行目のclientidの定義を変更し、char clientid[20]とするのがのぞましい)
+ // uint8_t mac[6];
+ // esp_read_mac(mac, ESP_MAC_WIFI_STA);
+ // sprintf(clientid, "%02X:%02X:%02X:%02X:%02X:%02X",
+ // mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] );
+ mqttClient.setServer(server, port);
+ mqttClient.setCallback(callback_on_subscribe);
+
+ if (!mqttClient.connected()) {
+ Serial.println("Try (re)connecting...");
+ reconnect();
+ }
+ mqttClient.subscribe(pubTopic);
+
+}
+
+void loop() {
+
+ mqttClient.loop(); // データがpublishされたら、callback_on_subscribe が呼ばれる
+
+ delay(100);
+}
+
+void reconnect() {
+ while (!mqttClient.connected()) {
+ if (mqttClient.connect(clientid, mquser, mqpass)) {
+ Serial.println("Connected to MQTT Broker.");
+ } else {
+ Serial.printf("Connect Failed. state=%d", mqttClient.state());
+ }
+ }
+}
+
+// データがPublishされたら、ここが実行される。(39行目でコールバック関数を設定しているため)
+void callback_on_subscribe(char* topic, byte* payload, unsigned int len) {
+ char buf[100];
+ Serial.print("Message arrived [");
+ Serial.print(topic);
+ Serial.print("] ");
+ for (int i = 0; i < len ; i++) {
+ buf[i] = (char)payload[i];
+ }
+ buf[len] = 0;
+ Serial.println(buf);
+ return;
+ // 参考:JSON Parsing example
+ // StaticJsonDocument<200> sjdoc;
+ // deserializeJson(sjdoc, buf);
+ // int intval = sjdoc["intval"];
+ // const char* str = sjdoc["string"];
+}
\ No newline at end of file
diff --git a/src/ntp01.ino b/src/ntp01.ino
new file mode 100644
index 0000000..1c32b99
--- /dev/null
+++ b/src/ntp01.ino
@@ -0,0 +1,30 @@
+#include
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+void setup() {
+ Serial.begin(115200);
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ Serial.print(".");
+ }
+ char* ntpserver = "10.64.7.184"; // 学外なら、たとえば "ntp.nict.jp"
+ configTime(9 * 3600, 0, ntpserver);//GMTとの時差(秒), サマータイムで進める時間(秒)
+}
+
+void loop() {
+ struct tm localTime;
+ char buf[30];
+ getLocalTime(&localTime);
+ sprintf(buf, ">> %04d/%02d/%02d %02d:%02d:%02d",
+ localTime.tm_year + 1900,
+ localTime.tm_mon + 1,
+ localTime.tm_mday,
+ localTime.tm_hour,
+ localTime.tm_min,
+ localTime.tm_sec
+ );
+ Serial.println(buf);
+ delay(1000);
+}
\ No newline at end of file
diff --git a/src/regexp01.ino b/src/regexp01.ino
new file mode 100644
index 0000000..5ac99bf
--- /dev/null
+++ b/src/regexp01.ino
@@ -0,0 +1,25 @@
+#include
+#include
+
+Dictionary *dict = new Dictionary(); // 辞書/ハッシュ。キー文字列→値 を保持するデータ構造
+
+void match_callback (const char * match, // matching string (not null-terminated)
+ const unsigned int length, // length of matching string
+ const MatchState & ms) // MatchState in use (to get captures)
+{
+ char k [10]; // size must be large enough to hold captures
+ char v [10]; // size must be large enough to hold captures
+ ms.GetCapture(k, 0);
+ ms.GetCapture(v, 1);
+ dict->insert(k, v); // 辞書に追加 (たとえば、 red→120 を追加)
+} // end of match_callback
+
+// たとえば、*cbuf = "red=120&green=255&blue=9" のような文字列を想定する
+void param2dict(char *cbuf){
+ MatchState ms (cbuf); //正規表現マッチャーの作成
+ ms.GlobalMatch ("([a-z]+)=([0-9]+)", match_callback); // (key)=(value) で複数回マッチングする。match_callback は別関数。
+ int r = dict->search("red").toInt(); // 辞書 dict
+ int g = dict->search("green").toInt();
+ int b = dict->search("blue").toInt();
+ int32_t bgcolor = (int(r * 31 / 255) << 11) | (int(g * 63 / 255) << 5) | (int(b * 31 / 255));
+}
diff --git a/src/rtc01.ino b/src/rtc01.ino
new file mode 100644
index 0000000..0d40192
--- /dev/null
+++ b/src/rtc01.ino
@@ -0,0 +1,64 @@
+#include
+#include
+// #include "time.h"
+
+#define USE_NTP 0 // NTPからRTCに時刻設定するなら1
+
+void setRTCfromLT(struct tm lt) {
+ RTC_DateTypeDef DateStruct;
+ DateStruct.Year = lt.tm_year + 1900;
+ DateStruct.Month = lt.tm_mon + 1;
+ DateStruct.Date = lt.tm_mday;
+ DateStruct.WeekDay = lt.tm_wday;
+ M5.Rtc.SetData(&DateStruct);
+
+ RTC_TimeTypeDef TimeStruct;
+ TimeStruct.Hours = lt.tm_hour;
+ TimeStruct.Minutes = lt.tm_min;
+ TimeStruct.Seconds = lt.tm_sec+1;
+ M5.Rtc.SetTime(&TimeStruct);
+}
+
+void getRTC(char* buf) {
+ RTC_DateTypeDef DateStruct;
+ RTC_TimeTypeDef TimeStruct;
+ M5.Rtc.GetData(&DateStruct);
+ M5.Rtc.GetTime(&TimeStruct);
+ sprintf(buf, "%04d/%02d/%02d %02d:%02d:%02d",
+ DateStruct.Year, DateStruct.Month, DateStruct.Date,
+ TimeStruct.Hours, TimeStruct.Minutes, TimeStruct.Seconds
+ );
+}
+
+void setup() {
+ M5.begin();
+ M5.Lcd.setRotation(3);
+ Serial.begin(115200);
+
+ if (USE_NTP) {
+ const char* ssid = "**********";
+ const char* password = "**********";
+ const char* ntpserver = "10.64.7.184"; // "ntp.nict.jp"
+ WiFi.begin(ssid, password);
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ Serial.print(".");
+ }
+ configTime(9 * 3600, 0, ntpserver);
+
+ struct tm localTime;
+ while (localTime.tm_year < 80) {
+ getLocalTime(&localTime); delay(50);
+ }
+ setRTCfromLT(localTime);
+ }
+}
+
+void loop() {
+ char buf[30];
+ getRTC(buf); // bufに、日時文字列を書き込む
+ Serial.println(buf);
+ M5.Lcd.fillScreen(BLUE);
+ M5.Lcd.setCursor(0, 50, 4);
+ M5.Lcd.println(buf);
+ delay(1000);
+}
\ No newline at end of file
diff --git a/src/telnet01.ino b/src/telnet01.ino
new file mode 100644
index 0000000..2576bfb
--- /dev/null
+++ b/src/telnet01.ino
@@ -0,0 +1,130 @@
+// オリジナルのWifiTelnetToSerial を、改変しました。
+/*
+ WiFiTelnetToSerial - Example Transparent UART to Telnet Server for ESP32
+
+ Copyright (c) 2017 Hristo Gochkov. All rights reserved.
+ This file is part of the ESP32 WiFi library for Arduino environment.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+*/
+#include
+#include
+
+WiFiMulti wifiMulti;
+
+//how many clients should be able to telnet to this ESP32
+#define MAX_SRV_CLIENTS 3
+const char* ssid = "**********";
+const char* password = "**********";
+
+WiFiServer server(23);
+WiFiClient serverClients[MAX_SRV_CLIENTS];
+
+void setup() {
+ Serial.begin(115200);
+ Serial.println("\nConnecting");
+
+ wifiMulti.addAP(ssid, password);
+ // wifiMulti.addAP("ssid_from_AP_2", "your_password_for_AP_2");
+ // wifiMulti.addAP("ssid_from_AP_3", "your_password_for_AP_3");
+
+ Serial.println("Connecting Wifi ");
+ for (int loops = 10; loops > 0; loops--) {
+ if (wifiMulti.run() == WL_CONNECTED) {
+ Serial.println("");
+ Serial.print("WiFi connected ");
+ Serial.print("IP address: ");
+ Serial.println(WiFi.localIP());
+ break;
+ }
+ else {
+ Serial.println(loops);
+ delay(1000);
+ }
+ }
+ if (wifiMulti.run() != WL_CONNECTED) {
+ Serial.println("WiFi connect failed");
+ delay(1000);
+ ESP.restart();
+ }
+
+ //start UART and the server
+ // Serial2.begin(9600);
+ server.begin();
+ server.setNoDelay(true);
+
+ Serial.print("Ready! Use 'telnet ");
+ Serial.print(WiFi.localIP());
+ Serial.println(" 23' to connect");
+}
+
+void loop() {
+ uint8_t i;
+ if (wifiMulti.run() == WL_CONNECTED) {
+ //check if there are any new clients
+ if (server.hasClient()) {
+ for (i = 0; i < MAX_SRV_CLIENTS; i++) {
+ //find free/disconnected spot
+ if (!serverClients[i] || !serverClients[i].connected()) {
+ if (serverClients[i]) serverClients[i].stop();
+ serverClients[i] = server.available();
+ if (!serverClients[i]) Serial.println("available broken");
+ Serial.print("New client: ");
+ Serial.print(i); Serial.print(' ');
+ Serial.println(serverClients[i].remoteIP());
+ break;
+ }
+ }
+ if (i >= MAX_SRV_CLIENTS) {
+ //no free/disconnected spot so reject
+ server.available().stop();
+ }
+ }
+ //check clients for data
+ for (i = 0; i < MAX_SRV_CLIENTS; i++) {
+ if (serverClients[i] && serverClients[i].connected()) {
+ if (serverClients[i].available()) {
+ //get data from the telnet client and push it to the UART
+ while (serverClients[i].available()) Serial.write(serverClients[i].read());
+ }
+ }
+ else {
+ if (serverClients[i]) {
+ serverClients[i].stop();
+ }
+ }
+ }
+ //check ==UART== => Serial for data
+ if (Serial.available()) {
+ size_t len = Serial.available();
+ uint8_t sbuf[len];
+ Serial.readBytes(sbuf, len);
+ //push UART data to all connected telnet clients
+ for (i = 0; i < MAX_SRV_CLIENTS; i++) {
+ if (serverClients[i] && serverClients[i].connected()) {
+ serverClients[i].write(sbuf, len);
+ delay(1);
+ }
+ }
+ }
+ }
+ else {
+ Serial.println("WiFi not connected!");
+ for (i = 0; i < MAX_SRV_CLIENTS; i++) {
+ if (serverClients[i]) serverClients[i].stop();
+ }
+ delay(1000);
+ }
+}
\ No newline at end of file
diff --git a/src/wifi01.ino b/src/wifi01.ino
new file mode 100644
index 0000000..dde2180
--- /dev/null
+++ b/src/wifi01.ino
@@ -0,0 +1,32 @@
+#include
+#include
+
+const char* ssid = "**********";
+const char* password = "**********";
+
+void setup() {
+ M5.begin();
+ M5.Lcd.setRotation(3);
+ M5.Lcd.fillScreen(ORANGE);
+ M5.Lcd.setCursor(10, 50, 4);
+
+ WiFi.begin(ssid, password); // 接続開始
+ while (WiFi.status() != WL_CONNECTED) { // 接続中...
+ M5.Beep.tone(2000); delay(200);
+ M5.Beep.mute(); delay(300);
+ M5.Lcd.print(".");
+ }
+ // 接続完了!!
+ M5.Beep.tone(4000);
+ M5.Lcd.fillScreen(GREEN);
+ M5.Lcd.setCursor(0, 40, 4);
+ M5.Lcd.setTextColor(BLACK, GREEN);
+ M5.Lcd.print(" Wifi Connected!\n ");
+ String gotip = WiFi.localIP().toString(); // m5デバイスのIPアドレス
+ M5.Lcd.println(gotip);
+ delay(1500);
+ M5.Beep.mute();
+}
+
+void loop() {
+}
\ No newline at end of file
diff --git a/week1.rst b/week1.rst
index 0e5b181..e5b6e0f 100755
--- a/week1.rst
+++ b/week1.rst
@@ -1,3 +1,7 @@
+.. |br| raw:: html
+
+
+
1週目
========================
@@ -6,10 +10,10 @@
電源の切り方・入れ方
----------------------------------------
-- 電源を切るには、電源ボタン(M5と書かれたAボタンの左側面)を、6秒間長押しします。
- 電源を入れるには、電源ボタンを、2秒間長押しします。
-- `M5StickCガイド `_ の、4ページ目、ハードウェアの概要が参考になります。
-
+- 電源を切るには、電源ボタン(M5と書かれたAボタンの左側面)を、6秒間長押しします。その際、プログラムによっては再スタートしたように見える場合がありますが、気にせずに長押し継続してください。
+- 動画をみる→ https://youtu.be/Lo1jZbAeT8Y
+- `M5StickCガイド `_ の、4ページ目、ハードウェアの概要も参考になります。
プログラムの書き込み方
--------------------------------------
@@ -25,6 +29,7 @@
- ``#include `` は、M5StickC用のコードです。 **M5StickCPlus** では、``#include `` に変更しないと、表示がおかしくなることがあります。
- 大文字と小文字は厳密に区別されます。
- 画面をつかうプログラムを書き込んだあと、画面を使わないプログラムを書き込むと、前のプログラムの画面が残ることがあります。
+- スケッチブック(プログラムを構成する、複数のソースコードを含むファイル、他のIDEではプロジェクトと呼ぶ場合もある)をコピーしたいときは、内包するフォルダごとコピーしてください。その際、フォルダ名と、メインのソースコードファイル名(拡張子以外の部分)は、一致している必要があります。
Serial通信
---------------------------------------
@@ -108,7 +113,7 @@
:linenos:
.. :emphasize-lines: 10-14
-.. note:: 参考:https://qiita.com/nnn112358/items/ea6b5e81623ba690343c
+.. note:: `変数の型一覧 `_ uint16_t は unsigned int 16bit type の略です。 |br| 参考:`M5StackのLCDディスプレイの色をRGBで指定する。 `_
ブザー(Beep)
@@ -147,7 +152,7 @@
外部のLED等を接続
-----------------------------------------
-外部のLED等は、 G0, G25, G26 に接続します。
+外部のLED等は、 G0, G25, G26 に接続します。(GROVE端子のG32, G33も利用できます。)
サンプルプログラムは、:numref:`led01` と同様です。PIN 番号を、0 / 25 / 26 に変更してください。
ブレッドボードに、抵抗とLEDを直列に接続します。
@@ -179,7 +184,7 @@
サーボモータ(サーボハット利用)
-------------------------------------------------------------
-:numref:`servohat01` は、`サーボハット `_ のサーボモータを動かすサンプルです。サーボモータの制御はPWMですので、基本的に、上のPWMとやっていることは同じです。おまけとして、LEDも点灯させました。``map関数`` は、Arduinoで使える関数で、範囲に対応する値を変換するときに使います。ここでは、サーボ制御で用いる5〜33の値を、LED制御の値0〜256に変換しています。
+:numref:`servohat01` は、`サーボハット `_ のサーボモータを動かすサンプルです。サーボモータの制御はPWMですので、基本的に、上のPWMとやっていることは同じです。おまけとして、LEDも点灯させました。``map関数`` は、Arduinoで使える関数で、範囲に対応する値を変換するときに使います。ここでは、サーボ制御で用いる5〜33の値を、LED制御の値0〜256に変換しています。
.. literalinclude:: src/servohat01.ino
:caption:
@@ -190,13 +195,13 @@
-外部のセンサを接続 (ADC)
+アナログセンサを接続 (ADC)
-----------------------------------------
ADCは、Analog to Digital Converter の意味です。
``analogRead(PIN)`` は、PIN番ピンの電圧(0~3.3V)を、0〜4095 の値で返します。一般に、抵抗値が変化するタイプのセンサは、この方法をつかって、読み取ることができます。
-.. warning:: `ADC `_ にかいてあるように、G26, G32, G33, G36のみ使えます。G26は、無線利用時には使えません。
+.. warning:: `ADC `_ にかいてあるように、G26, G32, G33, G36のみ使えます。G26は、無線利用時には使えません。
:numref:`cds01` はCdSセル(照度センサ) の値を読み取るサンプルです。
@@ -233,15 +238,17 @@
信号の読み取り
~~~~~~~~~~~~~~~~~~~~~
-赤外線リモコン受信モジュールが必要です。ここでは、`GP1UXC41QS `_ を前提に、話をすすめます。
+赤外線リモコン受信モジュールが必要です。ここでは、`GP1UXC41QS `_ を前提に、話をすすめます。
また、準備として、ライブラリマネージャにて、IRremoteESP8266 をインストールします。ちなみに、テストしたバージョンは2.7.15でした。
+.. note:: ライブラリマネージャは、「スケッチ」→「ライブラリをインクルード」→「ライブラリを管理…」で、ひらきます。
+
ファイルメニュー → スケッチ例 → IRremoteESP8266 → IRrecvDumpV2 を選択します。
-ブレッドボードに、以下の図のように配線します。Pとかいてある面が、受光器が出っ張っている面です。47Ωの抵抗を、5Vとの間に入れます。
+ブレッドボードに、以下の図のように配線します。Pとかいてある面が、受光器が出っ張っている面だとおもってください。47Ωの抵抗を、5Vとの間に入れます。
赤外線リモコン受信モジュールに接続したピンを ``const uint16_t kRecvPin = 36;`` として設定します。
-受光器にリモコンを向けて、ボタンを押すと、シリアルモニタに情報が表示されます。ここでは、Protocol : NEC , Code 0x2FD48B7 (32 Bits) と表示されたとします。
+受光器にリモコンを向けて、ボタンを押すと、シリアルモニタに情報が表示されます。ここでは、Protocol : NEC , Code 0x2FD48B7 (32 Bits) と表示されたとします。この数値(uint32_t)を覚えておきます。
.. figure:: images/ir_sensor_47owm.png
:scale: 60%
@@ -254,6 +261,8 @@
内蔵の赤外LED光はあまり強くないため、50cm程度まで近づかないと反応しない場合があります。
単体の赤外LEDを接続して用いると、距離を伸ばすことができます。
+なお、NECフォーマットではない赤外線リモコンの通信フォーマットについては、 `赤外線リモコンの通信フォーマット `_ や、スケッチ例を参考にしてください。
+
.. literalinclude:: src/irsend01.ino
:caption:
:name: irsend01
@@ -282,25 +291,189 @@
.. :emphasize-lines: 6-7, 15,19
-
-
-
Wifi 接続
--------------------------------------
+:numref:`wifi01` は、Wifi接続のサンプルです。``ssid`` と ``password`` には、環境にあわせたものを入力してください。接続すると、m5デバイスのIPアドレスを画面に表示します。本当にWifi接続できたかどうかを、PCのターミナルからpingを打つなどして、確認してみましょう。(Windowsの場合、コマンドプロンプトをひらき、ping のあとに、半角スペースと、確認したいIPアドレスを入れます)
+
+.. literalinclude:: src/wifi01.ino
+ :caption:
+ :name: wifi01
+ :language: arduino
+ :linenos:
+ :emphasize-lines: 4-5,13-18
+
+Wifi接続するだけでは、あまり意味がないので、Telnetサーバを起動する例を :numref:`telnet01` に示します。
+シリアルモニタを開いて、IPアドレスを確認したら、ターミナル(コマンドプロンプト)で、``telnet IPaddr`` と入力して、接続します。
+telnet から文字を入力すると、シリアルモニタに表示されます。
+逆に、シリアルモニタから文字を入力すると、Telnet接続しているターミナルに、文字が表示されます。
+``WiFiServer server(23)`` で、23番ポートで待ち受けるサーバを、作成しています。
+ちなみに、``WiFiMulti`` は、複数のアクセスポイントに対して、Wifi接続を試みることができる機能(クラス)です。ただし、最終的に繋がるのは1つのアクセスポイントになります。
+
+.. note:: Telnet接続を切断するときは、まずControlキーをおしながら ``]`` をおしてください。プロンプトが ``telnet>`` と表示されますので、``quit`` と打ち込むと終了します。 ``Escape character is '^]'.`` の ``^`` は、Controlキーのことです。
+
+.. literalinclude:: src/telnet01.ino
+ :caption:
+ :name: telnet01
+ :language: arduino
+ :linenos:
+ :emphasize-lines: 29-30, 32
+
NTPサーバ (Network Time Protocol) と時刻の取得
-----------------------------------------------------------------------------------------------------------------------------------
+M5StickCPlusには、システム時間(localTime)と、RTC(リアルタイムクロック:時計の機能を備えたICのこと)の2種類の時計があります。システム時間は、システムリセット(再起動)のたびに、時刻もリセットされますが、後者のRTCはリセットされません。
+:numref:`ntp01` に、NTPサーバを使ってシステム時間の修正をしたのち、システム時間を1秒ごとに取得して、シリアルモニタに表示する例を示します。こちらは、M5のライブラリは不要です。configTime()でNTPサーバを設定しておくと、1時間に1回、NTPサーバに接続して、時刻修正します。
+
+.. literalinclude:: src/ntp01.ino
+ :caption:
+ :name: ntp01
+ :language: arduino
+ :linenos:
+ :emphasize-lines: 13
+
+.. warning:: configTime() を行わない状況で、システム時間(localTime)を取得しようとすると、取得に数秒ほど時間がかかります。
+
+:numref:`rtc01` は、RTCの時刻を表示するサンプルです。なお、5行目で ``USE_NTP`` に 1 が設定してあれば、RTCに時刻を設定します。常時Wifiネットワークに接続できない場合は、RTCを利用することが望ましいです。
+
+.. literalinclude:: src/rtc01.ino
+ :caption:
+ :name: rtc01
+ :language: arduino
+ :linenos:
+ :emphasize-lines: 5
+
+.. note:: RTCに一旦時刻を設定しておくと、初期状態で書き込まれているプログラムFactoryTest の「BMP8563 RTC Time」でも、その時刻が表示されるようになります。
WebClient
---------------------------------------
+:numref:`httpclient01` は、HTTP通信で天気予報Web APIに接続するサンプルです。HTTPClientクラスを用いると、ブラウザでURLを指定してWebページを開くように、WebサーバにGETメソッドやPOSTメソッドでリクエストを送信して、ステータスコードやレスポンスを取得することができます。このサンプルでは、シリアルコンソールに、天気予報をJSON形式で表示します。JSON(ジェイソン)とは、Javascriptのオブジェクトの形式でデータを表現する記法です。
+
+
+.. literalinclude:: src/httpclient01.ino
+ :caption:
+ :name: httpclient01
+ :language: arduino
+ :linenos:
+.. :emphasize-lines: 5
+
+.. warning:: https (SSL) 通信をする場合は、スケッチ例→HTTPClient→BasicHttpsClient を参照して、WiFiClientSecure クラスを使用してください。
+
+Google Spreadsheet にデータを送信する
+--------------------------------------------------------
+
+HTTP通信で、サーバにデータを送信して格納したいとおもっても、適当なサーバを準備するのは手間がかかることがあります。
+
+Google Apps Scriptを用いると、HTTP通信で Google Spreadsheet にデータを書き込んだり、読み取ったりするWebサービスを作成し、WebAPIとして公開することができます。
+
+(Google Spreadsheetのメニューで) 拡張機能→Apps Script または、ツール→スクリプトエディタ で、:numref:`doget` を「コード.gs」に書き込みます。15行目で「シート1」の一番下の行に、配列array の要素を追加します。19行目のgetRange(1,3).getValue()は、スプレッドシートのC1(3列1行)の値を取得しています。ここに「=average(C2:C200)」のようにしておくと、データの平均値を取得することもできます。
+
+作成した「コード.gs」について、「公開」→「ウェブアプリケーションとして導入...」で、Webアプリとして公開できます。(なお、「新しいエディタ」では表示が異なるため、できない可能性があります。)
+
+作成できたかどうかをパソコンから確認するには、コマンドラインから、以下のように入力します(ただし、curlが必要です。val1=10 と val2=20 のあいだは&記号ですので、シェルでバックグラウンド処理されないように、URLをシングルクォートで囲っています) ::
+
+ curl -L 'https://script.google.com/macros/s/XxXxXxXxXx/exec?val1=10&val2=20'
+
+
+.. literalinclude:: src/doget.js
+ :caption:
+ :name: doget
+ :language: javascript
+ :linenos:
+ :emphasize-lines: 14
+
+
WebServer
----------------------------------------
+すこし長いですが、:numref:`httpserver01` は、80番ポートでHTTPでの通信を待ち受け(listenし)て、クライアントからの接続情報(ヘッダ情報)を返すWebサーバのシンプルな例です。クライアント(ブラウザ)からのリクエスト行のうち、 ``GET`` または ``POST`` ではじまる行があれば、変数 ``meth`` に格納します。つまり、変数 ``meth`` には、ブラウザで発行したリクエストのURLが含まれることになります。POSTメソッドで送信されていれば、リクエストボディに記述されたデータを変数 ``post`` に格納します。 クライアント(ブラウザ)に返す「レスポンス」は、 ``client.println()`` で送信します。最後に、``client.stop()`` で、サーバ側から接続を切断します。
+
+.. literalinclude:: src/httpserver01.ino
+ :caption:
+ :name: httpserver01
+ :language: arduino
+ :linenos:
+ :emphasize-lines: 18,22,26,43,80
+
+センサデータを返すだけなら問題ありませんが、クライアントからのデータを GET / POST で受信して処理する場合は、 ``key1=val1&key2=val2`` のような文字列を要素に分解する必要がでてきます。あまり深入りしませんが、正規表現で文字列を照合・抽出する ``Regexp`` や、抽出した結果をハッシュ/辞書として保存する ``Dictionary`` ライブラリを導入すると、複雑なリクエストやデータを扱いやすくなるでしょう。
+:numref:`regexp01` に、RGBの色指定文字列などのリクエスト文字列をパースして辞書に追加するプログラムの断片を示します。**注意:このプログラムは、単体では動作しません**
+
+.. literalinclude:: src/regexp01.ino
+ :caption:
+ :name: regexp01
+ :language: arduino
+ :linenos:
+
+
+MQTT
+-------------------------------------------
+
+MQTT(Message Queue Telemetry Transport) は、センサデータをデバイス間で共有・流通させるときなどに使われる、軽量のメッセージ送受信プロトコルです。
+ここで、「軽量」とは、HTTPに比べて、ヘッダ部分のデータが少ない、という意味です。
+MQTTでは、サーバのことを「ブローカ」と呼びます。ここでは、データを送信するPublisherと、データを受信するSubscriberの2つのデバイスと、ブローカの3つの構成要素で説明します。
+
+Publisherは、ブローカに接続しデータを送信します。このとき、 **「トピック」** と呼ばれる、データの登録先を文字列で指定します。また、retainデータとして送信するかどうかも指定します。retainとは「保持・維持」の意味で、最後に送ったデータをブローカに残しておきたい場合、rateinデータとします。
+Subscriberは、ブローカに接続し、トピックをサブスクライブ(購読)します。このとき、トピックに書き込まれているretainデータがあれば、最初にそのデータを受信します。retainデータがなければ、Publisherが新しいデータをトピックに送信したタイミングで、データを受信します。
+このような通信手段(プロトコル)を、パブリッシュ/サブスクライブモデル と呼びます。`参考サイト:
+IoT初心者向け!「MQTT」について簡単にまとめてみる `_
+
+
+MQTT Publish
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+:numref:`mqtt01pub` は、MQTT Publisherのサンプルです。実験用ブローカ(mqtt.istlab.info)に接続して、 ``office/temp`` というトピックにデータ(シリアルコンソールで送信した文字列)を書き込みます。 **パスワードはここには書けませんので、講義ポータル資料を参照してください。**
+
+
+.. literalinclude:: src/mqtt01pub.ino
+ :caption:
+ :name: mqtt01pub
+ :language: arduino
+ :linenos:
+
+
+MQTT Subscribe
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+:numref:`mqtt01sub` は、MQTT Subscriberのサンプルです。実験用ブローカ(mqtt.istlab.info)に接続して、 ``office/temp`` というトピックを購読します。 **パスワードはここには書けませんので、講義ポータル資料を参照してください。** JSON形式のデータを処理するときは、ArduinoJsonをつかってパージング/deserialize すると便利です(78行目以降にサンプルがあります)。
+
+.. literalinclude:: src/mqtt01sub.ino
+ :caption:
+ :name: mqtt01sub
+ :language: arduino
+ :linenos:
+ :emphasize-lines: 38-39
+
+トピック名とワイルドカード
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+サブスクライブするときのトピック名には、ワイルドカードが指定できます。例えば、 ``office/+``
+と指定すると、 ``office/temp`` にも ``office/humid`` にもマッチします。`参考:MQTT の仕様 `_
+
+
+mosquitto コマンド例
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+mosquitto は、オープンソースのMQTT Broker/Client 実装の1つです。参考までに、mosquitto クライアントを使用するコマンド例を示します。::
+
+ #(パブリッシュ。retainなし: -m "メッセージ" )
+ mosquitto_pub -h mqtt.istlab.info -u ex1 -P PASS -t office/temp -m "data or message"
+ #(パブリッシュ。retainあり: -r )
+ mosquitto_pub -h mqtt.istlab.info -u ex1 -P PASS -t office/temp -m "data or message" -r
+ #(retainデータを削除。-n : send a null (zero length) message.)
+ mosquitto_pub -h mqtt.istlab.info -u ex1 -P PASS -t office/temp -n -r
+ #(サブスクライブ)
+ mosquitto_sub -h mqtt.istlab.info -u ex1 -P PASS -t office/temp
+
+3つ目の例に示すように、長さ0の非retainデータを送信すると、retainデータを削除できます。
+
+
+
+Bluetooth Serial Protocol Profile (SPP)
+--------------------------------------------
Bluetooth Low Energy
--------------------------------------
@@ -308,10 +481,6 @@
アドバータイズ
-Bluetooth Serial Protocol Profile (SPP)
---------------------------------------------
-
-
Preference
-------------------------------------------
@@ -328,7 +497,3 @@
- RTC(BM8563)
- OTA
-参考リンク
-------------------------------------------
-
-- M5StickC非公式日本語リファレンス
\ No newline at end of file
diff --git a/week2.rst b/week2.rst
index e0c1731..b454a9d 100755
--- a/week2.rst
+++ b/week2.rst
@@ -3,3 +3,15 @@
1週目で学んだことを活かして、組み合わせてみましょう。
+
+複数人で書いたプログラムを統合する
+------------------------------------------------------------------------------
+
+- Arduino IDEでは、プログラムを複数のファイル(たとえば、main.ino / sub.ino / hoge.cpp / hoge.h ) に分割して記述することができます。複数ファイルに分割することで、関数定義を機能別にまとめることができ、管理しやすくなります。
+- Arduino IDEでは、プログラムを構成する、複数のファイル(スケッチブック)を、1つのフォルダに入れて管理します。(スケッチブックのことを、他のIDEでは「プロジェクト」と呼ぶ場合もある)
+- スケッチブックに別のファイル(hogehoge.ino) を追加するには、右上にあるシリアルモニタをひらくアイコンの、下の「▼」ボタンからメニューをひらき、「新規タブ」を選択し、ファイル名(拡張子.ino をのぞいた、hogehogeの部分のみ)を入力します。
+- スケッチブックをコピーしたいときは、内包するフォルダごとコピーしてください。**その際、フォルダ名と、メインのソースコードファイル名(拡張子以外の部分)は、一致している必要があります。**
+- 複数のファイルを置いたときの挙動について: ``***.ino`` ファイルの内容は、単純にメインのタブ(フォルダ名と同じinoファイル)にマージされます。``***.cpp`` や ``***.c`` という拡張子でファイルを作成した場合は、``***.h`` を作成する必要があります。`参考:Properly using separate tabs with Arduino IDE `_
+- Git を利用すると、複数人で作業したファイルを統合しやすいです。
+
+
|
|