赤外線送信+温湿度計のローカル接続デバイスの作成
はじめに
ESP32を利用して赤外線送信をローカルのWebサーバーからできるようにしてみました。元々Raspberry Pi 3bにて個人的に製作し、1年ほど運用しておりましたが、3bでこの機能を継続することは役不足と感じ、別のマイコンを利用しての再構築を考えていました。また、冬になり暖房で肌が乾燥してきたため、追加で部屋の湿度を知りたいと思っていました。そこで、温湿度計(DHT11)を追加してみました。
概要
大まかな構成はこんな感じなります。ユーザーはブラウザからIRコードの送信や温湿度計の情報の受信を行います。ESP32 DevKit CはWiFiへ接続してサーバーIPの取得、SDカードからの各種情報の読み取り、温湿度計の情報の取得、ブラウザのボタンに対応する赤外線コードの取得、送信を行います。IRコードの追加、削除やSSIDの変更など、変更頻度の高いデータをすべてSDカードに集約しています。
回路
以下の材料を使用しました。
- ESP32 DevKit C x1
- DHT11 x1
- ユニバーサル基板 x1
- LED取付基板 x1
- 1K抵抗 x1
- 10K抵抗 x1
- 33K抵抗 x4
- 33Ω抵抗 x7
- 940nm赤外線LED(OSI5LA5A33A-B) x7
- microSDモジュール x1
- 2SD2012 (トランジスタ) x1
図のように結線します。センサとSDは3.3V、LEDは5Vへ接続します。
温湿度計
回路図左上のDHT11です。IOポートは電源電圧と同じ3.3Vでプルアップします。
microSDモジュール
SPIで接続します。信号線(CS,CLK,MOSI,MISO)をすべて3.3Vでプルアップします。
赤外線LED
赤外線LEDの駆動回路です。赤外線LEDは940nmのものを使い、33Ωの抵抗を並列に配線しています。
プログラム
プログラムはArduino IDEで書き込むものと、SDカードに配置するhtml,js,cssの2つあります。ESP32とクライアント(ブラウザ)との主な送受信処理はこんな感じになります。
Arduino IDEのコード
使用するライブラリ
プログラムは以下のライブラリを使っています。最初にインストールが必要です。
- Arduino Json 6.17.2
- DHT sensor library for ESPx 1.17.0
- SDFat 2.0.4
- IRremoteESP8266 2.7.14
コード
Arduino のコードは少し長めなので、説明はコード内に記述しました。
ESP32に書き込むコード
#include <WiFi.h>
#include <ArduinoJson.h>
#include <SdFat.h>
#include <IRsend.h>
#include "DHTesp.h"
// 4KBキャッシュ
const uint16_t SD_LOAD_BUFFERSIZE = 4096;
// 赤外線LEDの出力ピン
const uint8_t GPIO_IR_OUT_PIN = 4;
// 温湿度計の入力ピン
const uint8_t GPIO_DHT11_PIN = 22;
// SDのSPI選択ピン
const uint8_t CHIP_SELECT_SD = 5;
// 赤外線データフォルダ
const char* IR_JSON_DIR = "data/";
// サーバーの応答タイムアウト時間(ms)
const long timeoutTime = 5000;
// SDFatライブラリ
SdFat SD;
// IRremote (送信)
IRsend irsend(GPIO_IR_OUT_PIN);
// DHTXXの温湿度計センサライブラリ
DHTesp dht;
// ウェブサーバーをポート80に設定
WiFiServer server(80);
// 現在の起動経過時間
static uint32_t currentTime = millis();
// 1つ前の起動経過時間
static uint32_t previousTime = 0;
/**
* ブラウザへの応答タイムアウト判定
*/
bool timeout(){
previousTime = currentTime;
return (timeoutTime < (currentTime - previousTime));
}
/**
* 温湿度計センサの値を取得
*/
String getSensorValue(){
TempAndHumidity values = dht.getTempAndHumidity();
// 失敗した場合、空の文字列を返却
if (dht.getStatus() != 0) {
Serial.println("DHT11 error status: "
+ String(dht.getStatusString()));
return "";
}
Serial.println(" T:" + String(values.temperature)
+ " H:" + String(values.humidity));
// {"temperature":数値,"humidity":数値} を返す
return ("{\"temperature\":" + String(values.temperature)
+ ", \"humidity\":" + String(values.humidity) + "}");
}
/**
* 取得したURLに応じたファイルを読み込み、ブラウザへ
* 応答を返す。
*/
void loadUrl(String url, WiFiClient *client){
// SDからの読み込みバッファを確保
char readBuffer[SD_LOAD_BUFFERSIZE] = {0};
unsigned short counter = 0;
File file = SD.open(url, FILE_READ);
if (!file){
// 失敗時は下の文字列がブラウザに表示される。
client->println("<p>File could not load<p>");
return;
}
// ファイル読み込み
while (file.available()) {
readBuffer[counter] = file.read();
counter++;
if(counter == SD_LOAD_BUFFERSIZE){
// バッファが最大に達した場合、書き込み (応答を返す)
client->write(readBuffer, SD_LOAD_BUFFERSIZE);
counter = 0;
}
}
// 最後に読み込んだバッファを書き込み (応答を返す)
if(counter > 0) client->write(readBuffer, counter);
file.close();
// 送信は最後に空行が必要
client->println();
}
/**
* ヘッダー内からURLを抽出
*/
String parseUrl(String *header){
unsigned int first = header->indexOf("GET ");
unsigned int last = header->indexOf(" HTTP/1.1");
if (first >= 0 && last >= 0 && last > first) {
return header->substring(first + 4, last);
}
return "";
}
/**
* ブラウザのリクエストが赤外線コードの送信要求かどうか判定
* フォーマット: ?sig=<デバイス名>&<機能>
*/
bool parseIrFunc(String url, String *device, String *func){
unsigned int first = url.indexOf("?sig=");
unsigned int second = url.indexOf("&");
if (first >= 0 && second >= 0 && second > first) {
*device = url.substring(first + 5, second);
*func = url.substring(second + 1);
return true;
}
return false;
}
/**
* ブラウザのリクエストがセンサの更新要求かどうか判定
* フォーマット: ?req=<センサ名>&<時刻>
*/
bool parseSensorUpdate(String url, String *device){
unsigned int first = url.indexOf("?req=");
unsigned int second = url.indexOf("&");
if (first >= 0 && second >= 0 && second > first) {
*device = url.substring(first + 5, second);
return true;
}
return false;
}
/**
* ブラウザからの要求に対して応答を返す。(レスポンスヘッダー)
*/
void sendContentType(String type, WiFiClient *client){
client->println("HTTP/1.1 200 OK");
client->println("Content-type:" + type);
client->println("Connection: keep-alive");
client->println(); // 最後に空行が必要
}
/**
* ヘッダーの1行分のバッファを取得
*/
String readLine(WiFiClient *client){
String cline = "";
char ch;
while (client->connected()) {
ch = client->read();
if (ch == '\n') {
break;
} else if (ch != '\r') {
cline += ch;
}
}
Serial.println("Header : " + cline);
return cline;
}
/**
* データの種類を取得
*/
String parseDataType(String path) {
String dataType = "text/plain";
if (path.endsWith("/")) {
path += "index.html";
}
if (path.endsWith(".htm")
|| path.endsWith(".html")) {
dataType = "text/html";
} else if (path.endsWith(".css")) {
dataType = "text/css";
} else if (path.endsWith(".js")) {
dataType = "application/javascript";
} else if (path.endsWith(".ico")) {
dataType = "image/x-icon";
}
return dataType;
}
/**
* 文字列を16進数に変換
*/
uint32_t stringToHex(String num){
String candidate = num;
candidate.replace("0x","");
candidate.toUpperCase();
unsigned int len = candidate.length();
unsigned int array_max = len - 1;
uint32_t result = 0;
char c;
for(int i = 0; i < len; i++){
c = candidate.charAt(i);
if (c >= '0' && c <= '9') {
result |= ((c - '0') << (4 * (array_max - i)));
}
else if (c >= 'A' && c <= 'F') {
result |= (((c - 'A') + 10) << (4 * (array_max - i)));
}
}
Serial.print("[stringToHex] result : 0x");
Serial.println(result, HEX);
return result;
}
/**
* WiFiのSSIDとパスワードを取得
*/
void getWifiKeys(String &ssid, String &password){
// readline buffer
String readBuffer;
char ch;
// config.jsonを開く
File file = SD.open("/config.json", FILE_READ);
if (!file){
Serial.println("[Config] file could not load.");
return;
}
while (file.available()) {
ch = file.read();
if (ch == '\n') {
DynamicJsonDocument doc(readBuffer.length() + 4011);
DeserializationError error = deserializeJson(doc, readBuffer);
// Test if parsing succeeds.
if (error) {
Serial.print("size: ");
Serial.println(readBuffer.length(), DEC);
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return;
}
const char* key = doc["ssid"];
const char *type = doc["password"];
ssid = key;
password = type;
readBuffer = "";
} else if (ch != '\r') {
readBuffer += ch;
}
}
}
/**
* 赤外線コードを送信
*/
void sendIr(String &device, String &func){
// readline buffer
String readBuffer;
char ch;
// 送信周波数を38kHzに設定
const uint8_t IR_SEND_FREQ = 38;
// フォルダ名 + デバイス名.jsonを開く
File file = SD.open(IR_JSON_DIR + device + ".json", FILE_READ);
if (!file){
Serial.println("[SendIr] file could not load.");
String out = "file : " + *IR_JSON_DIR + device + ".json";
Serial.println(out);
return;
}
while (file.available()) {
ch = file.read();
if (ch == '\n') {
// JSONフォーマットのファイルを取得
DynamicJsonDocument doc(readBuffer.length() + 4011);
DeserializationError error = deserializeJson(doc, readBuffer);
if (error) {
Serial.print("size: ");
Serial.println(readBuffer.length(), DEC);
Serial.print("deserializeJson() failed: ");
Serial.println(error.c_str());
return;
}
// 送信コードのキーを取得
const char* key = doc["key"];
// ブラウザから送信されたキーと同じかどうか判定
if(func.equals(key)){
// キーのタイプ(NEC,生コード)を取得
const char *type = doc["type"];
Serial.print("type : ");
Serial.println(type);
if(strcmp(type, "raw") == 0){
JsonArray codejson = doc["code"].as<JsonArray>();
std::vector<uint16_t> code;
for(JsonVariant v : codejson) {
code.emplace_back(v.as<uint16_t>());
Serial.print(v.as<uint16_t>(), DEC);
Serial.print(",");
}
Serial.println("");
// IR送信を実行
irsend.sendRaw(&code[0], codejson.size(), IR_SEND_FREQ);
Serial.println("[sendIr] Send raw code");
}else if(strcmp(type, "nec") == 0){
// IR送信を実行
irsend.sendNEC(stringToHex(doc["code"].as<String>()));
Serial.println("[sendIr] Send nec code");
}
break;
}
readBuffer = "";
} else if (ch != '\r') {
readBuffer += ch;
}
}
}
void setup() {
Serial.begin(115200);
// LEDの初期化
pinMode(GPIO_IR_OUT_PIN, OUTPUT);
digitalWrite(GPIO_IR_OUT_PIN, LOW);
// 温湿度計の初期化
dht.setup(GPIO_DHT11_PIN, DHTesp::DHT11);
// SDカードの初期化
if (!SD.begin(CHIP_SELECT_SD, SD_SCK_MHZ(25))) {
Serial.println("SD card initialize failed.");
return;
}
// SDからWiFiのSSIDとパスワードを取得
String ssid, password;
getWifiKeys(ssid, password);
Serial.println("SSID : " + ssid);
// Serial.println("Password : " + password);
// WiFi接続を確立
WiFi.begin(ssid.c_str(), password.c_str());
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
// ローカルのIPアドレスの表示
Serial.print("WiFi connected. IP: ");
Serial.println(WiFi.localIP());
// サーバー開始
server.begin();
}
void loop(){
// クライアントの取得 (取得失敗対策のため、5回リトライ)
WiFiClient client;
int i = 0;
for(; i < 5; i++){
client = server.available();
if(client) break;
delay(50);
}
if(i == 5) return;
Serial.println("Connection started.");
// レスポンスヘッダーの読み取りに必要なバッファ
String header = "";
String currentLine = "";
// 接続中 && タイムアウト時間を超えていない
currentTime = millis();
while (client.connected() && timeout() == false) {
currentTime = millis();
if (!client.available()) continue;
currentLine = readLine(&client);
header += (currentLine + '\n');
if (currentLine.length() == 0) {
// ヘッダーを最後まで読み込んだ場合
String url = parseUrl(&header);
sendContentType(parseDataType(url), &client);
// センサ更新要求への応答
String sensor;
if(parseSensorUpdate(url, &sensor)){
String response = getSensorValue();
client.println(response.c_str());
Serial.println("Response: " + response);
break;
}
// 赤外線コードの送信 (応答は何でもいい)
String device, func;
if(parseIrFunc(url, &device, &func)){
Serial.println("Remote Device : " + device);
Serial.println("Function : " + func);
sendIr(device, func);
client.println("OK");
break;
}
// ページ読み込みへの応答
if (url.endsWith("/")) {
url += "index.html";
}
loadUrl(url, &client);
break;
}
}
// 接続を終了する (ブラウザへの応答完了)
client.stop();
Serial.println("Connection closed.\n");
}
SDカード内に配置するコード
SDカードには、UIとなる部分とESP32にデータを送信する部分を作成します。見た目はこんな感じになります。
使用するライブラリ
SDの読み込み速度やSPIFFSなどを利用することも想定し、軽量で簡単に十分なUIを作れるPure.cssを採用しました。
フォルダ構成
SDカードにはファイルを下のように配置しました。appフォルダがリモコン操作する機器を入れたものになります。cssフォルダはUIの補助、jsフォルダにはリモコン操作の実行処理やメニューバーの実装が含まれています。dataフォルダは赤外線送信のコードが含まれています。今回は、ダミーのコードを作成し、送受信のテストをしてみます。
ルートフォルダ
├ index.html (ホーム画面)
├ config.json (WiFi設定)
├app
│ └ dummy
│ └ index.html (赤外線送信選択UI)
├css
│ ├ layouts.css (赤外線送信UIの調整)
│ ├ side-menu.css (サイドメニュー)
│ └ pure-min.css (Pure.css本体)
├js
│ ├ ui.js (サイドメニュー)
│ └ signal.js (赤外線送信命令の送信)
└data
└ DUMMY.json (赤外線送信命令に紐づけられたデータ値リスト)
ホーム画面 (ルートフォルダのindex.html)
script部分のupdateSensorで温湿度計の更新を行っています。具体的には、10秒ごとにこの関数が定期的に実行され、結果を得るとhtml上のtemp,temp_text,humid,humid_textに該当するidの要素が更新されます。
updateSensor内で利用されているXMLHTTPRequestは、ESP32のサーバー上では動作します。PC上にあるファイルのhtmlを開いて実行した場合は、CORSというエラーが発生します。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="IR send server for ESP32">
<title>Remote Panel</title>
<link rel="stylesheet" href="./css/pure-min.css">
<link rel="stylesheet" href="./css/side-menu.css">
<link rel="stylesheet" href="./css/layouts.css">
<script>
window.onload = function (){
updateSensor('dht11');
}
function updateSensor(device) {
var xhr = new XMLHttpRequest();
xhr.timeout = 2000;
xhr.responseType = 'text';
xhr.onload = function (e) {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
var tempprogress = document.getElementById('temp');
var temp = document.getElementById('temp_text');
var humidprogress = document.getElementById('humid');
var humid = document.getElementById('humid_text');
// 結果を読み取り
const parsed = JSON.parse(xhr.responseText);
tempprogress.value = Number(parsed.temperature);
temp.innerHTML = Number(parsed.temperature).toFixed(1) + ' ℃';
humidprogress.value = Number(parsed.humidity);
humid.innerHTML = Number(parsed.humidity).toFixed(1) + ' %';
} else {
console.error(xhr.statusText);
}
}
};
xhr.ontimeout = function(e) {
xhr.abort() ;
}
xhr.open('GET', '?req=' + device + '&' + new Date().getTime(), true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.setRequestHeader('X-Content-Type-Options','nosniff');
xhr.send();
}
window.addEventListener('load', function () {
// 10秒間隔で更新を設定
setInterval(function () { updateSensor('dht11'); }, 10000);
});
</script>
</head>
<body>
<div id="layout">
<!-- Menu toggle -->
<a href="#menu" id="menuLink" class="menu-link">
<!-- Hamburger icon -->
<span></span>
</a>
<div id="menu">
<div class="pure-menu">
<a class="pure-menu-heading" href="#">HOME</a>
<ul class="pure-menu-list">
<!-- ボタン定義 -->
<li class="pure-menu-item">
<a href="/app/dummy/" class="pure-menu-link">テスト</a>
</li>
</ul>
</div>
</div>
<div id="main">
<div class="content">
<h2 class="content-subhead">部屋</h2>
<div class="pure-g room">
<!-- disp 1 -->
<div class="pure-u-1-2">
<label for="temp">温度</label>
<meter style="width: 10vw; max-width: 100px;" id="temp"
min="-20" max="80"
low="10" high="30" optimum="25"
value="20">
</meter>
</div>
<div class="pure-u-1-2">
<div id="temp_text"> - ℃</div>
</div>
</div>
<div class="pure-g room">
<!-- disp 2 -->
<div class="pure-u-1-2">
<label for="humid">湿度</label>
<meter style="width: 10vw; max-width: 100px;" id="humid"
min="0" max="100"
low="33" high="66" optimum="80"
value="90.8">
</meter>
</div>
<div class="pure-u-1-2">
<div id="humid_text"> - %</div>
</div>
</div>
<h2 class="content-subhead">デバイス</h2>
<div class="pure-g">
<div class="pure-u-1-2">
<button class="index-button pure-button" onclick="location.href='/app/dummy/';">テスト<br>(Dummy)</button>
</div>
</div>
</div>
</div>
</div>
<script src="js/ui.js"></script>
</body>
</html>
Wifi設定 (ルートフォルダのconfig.json)
””の中を置き換えて使用します。
config.json
{"ssid":"SSID","password":"パスワード"}
jsonを読み込むためには、行の最後に改行が必要です。
赤外線送信選択UI (app/dummy/index.html)
ボタンでsendSignalを呼び出すようにして、後述のsignal.jsに定義されたsendSignalBaseを通してESP32にリクエストを送信します。
app/dummy/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="IR send server for ESP32">
<title>Remote Panel</title>
<link rel="stylesheet" href="../../css/pure-min.css">
<link rel="stylesheet" href="../../css/side-menu.css">
<link rel="stylesheet" href="../../css/layouts.css">
<script src="../../js/signal.js"></script>
<script>function sendSignal(key){ sendSignalBase('DUMMY', key); }</script>
</head>
<body>
<div id="layout">
<!-- Menu toggle -->
<a href="#menu" id="menuLink" class="menu-link">
<!-- Hamburger icon -->
<span></span>
</a>
<div id="menu">
<div class="pure-menu">
<a class="pure-menu-heading" href="../../index.html">HOME</a>
<ul class="pure-menu-list">
<!-- ボタン定義 -->
<li class="menu-item-divided pure-menu-item pure-menu-selected">
<a href="#" class="pure-menu-link">テスト</a>
</li>
</ul>
</div>
</div>
<div id="main">
<div class="content">
<div class="pure-g">
<!-- row 1 -->
<div class="pure-u-1-3">
<button class="button-green pure-button" onclick="sendSignal('KEY_1 ')">テスト1</button>
</div>
<div class="pure-u-1-3">
<button class="button-lightgray pure-button" onclick="sendSignal('KEY_2')">テスト2</button>
</div>
<div class="pure-u-1-3">
<button class="button-lightgray pure-button" onclick="sendSignal('KEY_3')">テスト3</button>
</div>
<!-- row 2 -->
<div class="pure-u-1-1">
<p style="text-align: right"><br>Dummy</p>
</div>
</div>
</div>
</div>
</div>
<script src="../../js/ui.js"></script>
</body>
</html>
UI調整ファイル (css/layouts.css)
センサ部分の表示サイズの設定と、ボタンの色設定です。
layouts.css
.room
{
width: 100%;
height: 100%;
margin: 0.5em;
color: white;
font-size: 1rem;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
text-align: center;
}
.index-button {
width: 98%;
height: 90%;
padding-bottom: 0.3em;
margin: 1em;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
background: rgb(200, 200, 200); /* this is a gray */
}
.button-green,
.button-lightgray
{
width: 98%;
height: 90%;
padding-bottom: 0.3em;
margin: 0.5em;
color: white;
font-size: 0.6rem;
border-radius: 4px;
text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
}
.button-green {
background: rgb(28, 184, 65); /* this is a green */
}
.button-lightgray {
background: rgb(200, 200, 200); /* this is a light gray */
color: black;
}
サイドメニュー (css/side-menu.css)
https://purecss.io/layouts/side-menu/の説明にこのファイルへのリンクがあります。このコードをコピーし、以下の部分を改変します。
-
bodyのかっこ内にbackground-color: #2c2c2c;を追加
-
#menuかっこ内に-webkit-overflow-scrolling: touch;の追加
-
#menu .pure-menu-selected, #menu .pure-menu-heading{}の部分を以下に変更
#menu .pure-menu-selected { background: #1f8dd6; } #menu .pure-menu-heading { background: transparent; }
-
.menu-link span,.menu-link span:before,.menu-link span:after {}部分のpointer-events: none;の削除
Pure.css本体 (css/pure-min.css)
公式サイトのリンクを辿って中身をそのままコピーしてきます。
サイドバー選択処理 (js/ui.js)
https://purecss.io/layouts/side-menu/ の説明にリンクがあるので、本体をコピーしてきます。
リモコン送信処理 (js/signal.js)
sendSignalBaseを呼び出すと、ESP32側に現在のパス+?sig=デバイス名&送信リモコンキー名が送信されます。
signal.js
function sendSignalBase(device, key){
var xhr = new XMLHttpRequest();
xhr.open('GET', '?sig=' + device +'&' + key );
xhr.timeout = 1000 ;
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.responseType = 'document' ;
xhr.ontimeout = function(e) {
xhr.abort() ;
}
xhr.send();
}
リモコン送信コード (data/DUMMY.json)
送信コードです。今回はNECと生コードで実験します。コードの説明を次に示します。
キー名 | 値の説明 |
---|---|
type | 値の種類 (rawかnec) |
key | ブラウザから送られるキー |
code | 赤外線コードの値 |
DUMMY.json
{"type":"nec","key":"KEY_1","code":"0x12345678"}
{"type":"nec","key":"KEY_2","code":"0x56781234"}
{"type":"raw","key":"KEY_3","code":[9000,4500,560,350,560,560,560,560,560,560]}
jsonを読み込むためには、行の最後に改行が必要です。
実験
ArduinoのIRremoteのサンプル、IRrecvDumpをそのまま利用して、Arduino Leonardoで今回作成したデバイスの送信コードを受信してみます。実験結果は下の通りで、NECのコードは一致し、Rawのコードはタイミングが少々違いますが、赤外線リモコンとしては許容範囲であると思います。(受信ICは秋月のSPS-442-1です。)
Ready to receive IR signals at pin 11
12345678
Decoded NEC: 12345678 (32 bits)
Raw (68): 9050 -4400 650 -500 650 -450 600 -550 650 -1600 650 -500 600 -500 650 -1600 650 -500 550 -550 650 -500 600 -1700 550 -1600 650 -500 600 -1650 600 -500 650 -500 600 -550 600 -1600 600 -550 600 -1650 600 -500 650 -1650 600 -1600 600 -500 650 -500 600 -1650 600 -1700 550 -1650 600 -1650 550 -550 650 -500 600 -500 600
56781234
Decoded NEC: 56781234 (32 bits)
Raw (68): 9050 -4400 650 -500 650 -1550 700 -450 650 -1600 650 -450 650 -1600 650 -1600 650 -500 650 -450 650 -1600 650 -1600 650 -1600 650 -1600 600 -500 650 -500 600 -500 650 -500 600 -500 650 -500 600 -1650 600 -500 650 -500 600 -1650 600 -500 650 -500 600 -500 650 -1600 650 -1600 600 -550 600 -1650 600 -500 650 -500 600
9D334F57
Unknown encoding: 9D334F57 (32 bits)
Raw (10): 9100 -4400 600 -300 700 -450 650 -450 700
おわりに
- SPIFFSでアップロードする話が少し出てきたと思いますが、開発中だったコードをアップロードして試すと結構速度が遅かったので、SDを採用することになりました。
- ブレッドボードで試すと接触のせいか、時々SDが読めなかったりします。
- たまにレイアウトが崩れます。ブラウザ更新すれば直るのですが、原因はまだわかりません。
-
karakirimu
さんが
2021/02/14
に
編集
をしました。
(メッセージ: 初版)
-
karakirimu
さんが
2021/02/14
に
編集
をしました。
(メッセージ: 章番号削除)
-
karakirimu
さんが
2021/02/14
に
編集
をしました。
(メッセージ: 目次削除)
ログインしてコメントを投稿する