2022年2月27日日曜日

技適マーク付きESP32-CAMを入手

先日、技適ESP32への換装 を行いましたが、失敗する可能性もあったので技適取得済みのAI-Thinker製ESP32を搭載したSeeed StudioのESP32-CAMを注文してありました。先ほど届き、確認したらちゃんと技適マークが付いています。


マルツオンライン で1,414円+送料(385円)でした。ちょっと高いけど、自分で換装して失敗するリスクを考えたらまあ許容範囲でしょうか。アリエクだと送料込みで500円程度のESP32-CAMがあり、秋月の技適ESP32が320円なので計820円位で技適対応できます。

そもそも技適って本当に必要なのかな? 米国のFCCや欧州のCEを取っていれば国内でも使えるようになれば、海外の各種製品が早く安く入手できそうな気がするけど。日本だけガラパゴスにならないか心配。

下記はパッケージ写真。カメラは接続してありませんでした。

防犯カメラは、deep sleepからlight sleepに変更することで焦電センサが人を検知したらすぐに写真を撮れるようになりました。テストしていますが、時々以下のような写真になります。ダイニングでテストしていてカメラは天井を向いています。


写真の上側1/4程が切れています。microSDに保存された写真も切れているので、LINE notifyへの送信途中で切れたのではなく撮影時に切れた模様。sleepと関係しているのか、まだ原因が分かりません。

2022年2月26日土曜日

ESP32のdeep sleepは寝起きが悪い

技適品に換装したESP32-CAMと焦電センサを使ってLINEに通知する防犯カメラですが、色々とスケッチをいじって遊んでいます。

大まかな流れは、普段はdeep sleepしていて、焦電センサが人を検知したタイミングでwake upして、写真撮影し、micrSDに保存、LINE notifyへの送信するというものです。LINE notifyへの画像送付は、頻度に制限があるようなので90秒待ってからdeep sleepします。これで次にセンサが検知したら再びwake upして同じ処理を行います。

なお、90秒待つ間にもセンサが検知したら写真を撮影してmicroSDへ保存します。こちらは最短1秒毎にして、センサの前に人がいれば連続して撮影されます。

数日家の中に置いて試行運用してみましたが、どうもLINEに送られてくる写真に人物が映りません。microSDには人が映ったものも残っています。

ESP32のdeep sleep & wake upは、resetしたのと同じようにESP32の起動から動くのでアプリ処理が動くまで結構タイムラグがあるようです。

millis()関数を使い、トリガがかかってからsetup処理が始まる時間と、カメラの設定を行って撮影するまでの時間を調べてみました。

setupが動くまでに0.8秒程かかっており、さらにカメラの設定などを行い撮影するまでに約0.7秒必要で、実際の撮影はセンサが検知してから1.5秒後になっている模様。これでは、シャッタチャンスを逃しますね。なお、Serial.printでモニタしているので、その処理にも若干時間がかかっているとは思います。

ということで、deep sleepはやめて、light sleepにしようと思っています。deep sleepならバッテリ運用も可能かと思っていましたがちょっと無理そうです。

なお、LINE notifyへ写真を送るスケッチはgithhubの以下を流用さてせ頂いています。

https://github.com/fustyles/Arduino/tree/master/ESP32-CAM_Linenotify

※そのままビルドするとおねえさんの写真が送られてくる(URLが書かれている)ので注意。

2022年2月23日水曜日

ESP32-CAMを技適ESP32へ換装する動画

今週、月火は出勤だったため夜は疲れてしまい、防犯センサのスケッチはまだブラッシュアップできていません。緊急事態宣言がでないとなかなか完全な在宅勤務にはなりませんね。オミクロンもピークは過ぎたようなのでよい傾向ですが。3回目のワクチン接種はそれほど進んでいない割には感染者が減少しているのは何故でしょうか? みんな気を付けるようになったから? それとも軽症なので検査もしないから? いずれにしてももうしばらく気を付けていきます。

さて、ESP32-CAMのESP32を技適ESP32に換装する動画です。
 

スケッチは今夜あたりから手直ししてみようと思っています。

2022年2月20日日曜日

カメラモジュールESP32-CAMを技適取得済みESP32に換装

先日、人感センサーでLINEに通知する防犯センサのプロトタイプ を作りましたが、単に文字による通知だけでなく画像も送りたくなり数年前に購入したESP32-CAMというモジュールを思い出しました。Aliexpressで500円位で購入したのですが、載っているESP32が日本の技適(技術基準適合証明)を受けてなく、電波を出せないので仕舞い込んでありました。microSDスロットも付いていて、安くて便利なのですが。

技適の〒マークがありません。

アマゾンでも各種販売されていますが、そのまま電波を出して使うと電波法違反になります。単に写真を撮ってmicroSDに保存するとかの用途で、WiFiやBluetooth機能を使わなければ大丈夫です。

ネットで探してみると、技適取得済みのESP32に換装されている方がいたので、挑戦してみた次第です。

まずは両側に8ピンずつあるピンヘッダを外します。ピンヘッダは、はんだシュッ太郎で吸い取ってもなかなか抜けず苦労しました。最終的にピンヘッダを壊した。

ESP32の周りの部品が取れたり、熱で壊れたりしないようにアルミテープでマスキングします。裏面もマスクしました。

以前、アマゾンで買った安物のヒートガンで外してみます。


LowパワーでESP32の外周付近を2分半ほど加熱したら、半田が溶けて無事に外れました。このあと、パターン上に残った半田を半田吸い取り線できれいに掃除。

秋月電子で購入した技適取得済みのESP32-WROOM-32D (¥330) を載せました。ヒートガンではなく手付けです。ピンヘッダも新しいものを取り付けてあります。


また、ESP32-CAMは空きピンが一つもないので、載っているモニタ用LEDを取り外し、LEDに使っていた信号GPIO33を外部に引き出しています。引き出し先のピンはVCCピンで、元々は+3.3Vか5Vが出力されるピンですが、ジャンパ用の0オーム抵抗を外してオープンにしてから繋ぎました。

引き出したGPIO33に焦電型赤外線センサをつないでいます。


こんな感じでスマホに通知と写真が届きます。

搭載しているmicroSDには、以下のようなタイムスタンプがファイル名になった写真が保存できます。NTPサーバに繋いで撮影時の時刻を取得しています。WiFiに繋がると便利です。


付いていたカメラが挟角なので、Aliexpressで広角160度というカメラを注文してみました。届くまで3週間前後でしょうか。

スケッチはまだテスト中。今週、少しずつ仕上げる予定。また、需要があるか分かりませんが、ヒートガンで外すときの動画を撮ってあるので、そのうちアップします。

2022年2月15日火曜日

電子工作用のmicroSD購入

LINEダッシュボタンの工作で手元にある空きmicroSDを使い果たしたので、アマゾンで比較的安くて、名の通ったメーカのものを買ってみました。KIOXIA(旧東芝メモリ)の32GBです。電子工作目的では32GBも必要ないのですが、数百MBや1GBなどの小さいものは売ってないのと、16GBと値段が殆ど変わらないので、何か別の目的で使うかもとこれにしました。

届いたパッケージを見ると中国での販売用ですね。ちゃんとしたブリスタパッケージでした。



電子工作では速度はそれほど気にしないのですが、一応パッケージの裏面に最大読み取り速度100MB/sと書かれていたので、CrystalDiskMarkで測定してみました。時間がかかりそうなので1回のみ測定。


連続読み出しで80MB/sを越えているので、まずまずスペック通りでしょうか? あとは耐久性ですが、KIOXIAのブランドに期待しています。

ちなみに使っているノートPCのCドライブは、たぶん(笑)NVMe接続のM.2 SSDで以下でした。こっちは5回測定。速いのかな?


p.s.
昨夜行った MacBook Proへの乗り換え は、ほぼ無事に終わりましたが、Pythonの開発環境の一部が動かなくなりました。M1 macになったため? 色々調べてバージョンを最新版にしたりして苦戦。半日以上かかってようやくなんとか復旧しました。よかった。まだ全ては確認できてないので、明日も確認予定。

2022年2月14日月曜日

MacBookの乗り換え、再び

先日、MacBookの乗り換え(Pro→Air) をしましたが、再び乗り換えることになりました。年度末で予算が余った を調整して、新しいMacBook Proを購入することができました。年度末なのですぐに納品できるものという縛り。本日、出勤して持ち帰ってきました。全然在宅勤務にならない。

前回、タッチバーが調子悪かった ときにググったらタッチバーは壊れやすいという情報が多かったためタッチバーを搭載していない14インチMacBook Proにしました。MacBook Pro 14インチ Apple M1 Proチップ搭載モデル[2021年モデル/SSD 1TB/メモリ 16GB/10コアCPUと16コアGPU]というものです。名称が長い! メモリは32GB欲しかったのですが、納期の問題で妥協しました。


Airの薄さに慣れたためでしょうか、開けてみると分厚くてごつい感じでずっしりしています。電源がTypeCでなかったのが意外。昔のMacBookのような多ピンのコネクタが磁石でくっ付くタイプでした。これで高速充電できるらしい。


前回の失敗を踏まえてHDD経由で移行 しようと、現在Airのバックアップ中です。今夜寝る前に新MacBook Proでリストアを始めれば明日の朝には終わっていることを願っています。

p.s.
昨日作った 焦電赤外線センサを使った防犯センサ は動かしっぱなしにしていますが、誤検出はなく順調に動いています。もうしばらく動かしてみてみます。

2022年2月13日日曜日

焦電人感センサでLINEへ通知する防犯センサ

先日アップした Windows版暗号化プログラム にバグがあったので、再アップしました。暗号化対象の文字列に改行コードも含めてしまっていました。復号結果を画面で確認していたので気付きませんでした。ダメですね。もしダウンロードされている方がいたら申し訳ありませんでした。

パーツ箱に昔買った焦電型赤外線センサ PaPIRs 5m EKMC1601111 があったので、これを LINEダッシュボタン に繋いでみました。秋月で購入したものですが、今でも売っています。確認したら値段が500円になっていた。

取り合えずブレッドボードに載せて繋いでいます。パナソニックのページの使い方 を見ると、出力はFETドレイン出力でプルダウンが必要とのことなので、47KΩでプルダウンしています。出力電流は100uA以下とのことで、3.3Vで動かすと33KΩなので、少し余裕をみました。

電源を入れて30秒は不安定とのことで30秒待ってから監視を開始しましたが、どうも感度がよいのかノイズがあるのか、プルダウン抵抗が大きすぎたのか中々Lowレベルで安定しなかったので、スケッチ中にLowになるまで待つ処理を入れたらOKになりました。もう少し調整したほうがよいか。

こんな感じでスマホに監視状況が通知されます。現在、別の部屋で稼働しっぱなしにして、様子をみています。

LINE notify サービスは以下参照。

 


2022年2月12日土曜日

LINEダッシュボタン、3Dプリンタ印刷完了を通知する機能の完成

3Dプリンタの印刷終了をLINEにポストする機能をケースに組み込みました。


内部はこんな感じです。暗号化したWiFiのSSIDやパスワード、LINE notifyのトークン、およびポストするメッセージを格納したmicroSDはケースの蓋を開けないと取り外しできないようにしました。写真左側は、フォトリフレクタを収めたセンサ部です。


3Dプリンタに設置した様子です。センサ部は3Dプリンタの裏側にあり、印刷が終了してヒートベッドが奥のホームポジションに戻ると、それを検知します。


印刷を開始するとヒートベッドが最初何度が行き来するので、それが終わってヒートベッドがセンサ部を覆わなくなったらLINEダッシュボタンの電源を入れます。すると、監視開始のメッセージがポストされ、正常に監視を始めたことが分かります。

数時間後、印刷が終わるとヒートベッドが再びセンサ部を覆うので、それをトリガに印刷終了メッセージがポストされ、スマホが受け取ります。写真ではスマホを隣に並べていますが、LINEが繋がる場所ならどこにいても届きます。

回路図です。三端子レギュレータは1Aのものを使いましたが、500mA取れれば大丈夫なようです。
一度、フォトリフレクタの検出用GPIOを12に変更したのですが、センサを繋いでいるとプログラムの書き込みができなくなりました。また、センサを抜いてプログラムしてもうまく動きません。ネットで調べるとGPIO 12は曲者で、プルアップして電源を入れるとビルトインBASIC(そんなのが組み込まれているなんて初めて知った)が動作するようです。しばらく悩んでしまいました。

microSDに書き込むデータと暗号化処理は、昨日のWindows版暗号化ESP32版暗号化 の記事を参照ください。

以下、ESP32のスケッチです。一応動いていますが、無保証です。
//---------------------------------------------------------------
//                                                2022.02.12 naka
// LINE Dash Button(フォトリフレクタ版)
//
// ・WiFi ssid,password,LINE notify tokenは、暗号化してmicroSDに格納
//  ファイル名:setup.txt
//     暗号化したWiFi ssid, password, LINE notify tokenをそれぞれ1行ずつ
//     順に記載し、4行目、5行目に送るメッセージを記載。
//  (例)
//    &D/)%X/C04S+#T'?+USM
//    *%/O-J_F$##5)CK`(%
//    #VGN-Z_*-FCG+VK1/E;M<47E1UCJ.C/`-U(S#F?C06CV)%C_0$$E8W$89(0[5(U
//    動作を開始しました。
//    ボタンが押されました。
//---------------------------------------------------------------
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <SD.h>

#define SD_CS 16 // SD card chip select
#define SW    GPIO_NUM_13
#define LED_G 25
#define LED_R 27

#define ON  1
#define OFF 0

#define FILENAME "/setup.txt"

// ssid,pw,tokenを暗号化したときのキー
char* crypt_key = "Open Sesame";

// LINE Notify
const char* host  = "notify-api.line.me";
char ssid[64],password[64],token[64],message1[128],message2[128];
int sw_newest;

void setup()
{
  pinMode(SW,INPUT_PULLUP);
  pinMode(LED_G,OUTPUT);
  pinMode(LED_R,OUTPUT);
  led_green(OFF);
  led_red(OFF);
  sw_newest = digitalRead(SW);
  if (read_setup_file(FILENAME)) {
    blink_red(10);
    esp_sleep_enable_ext0_wakeup(SW, LOW);
    esp_deep_sleep_start();
  }

  // GPIO割り込みピン設定
  gpio_wakeup_enable(SW, GPIO_INTR_LOW_LEVEL);
  // 動作確認のために動作開始メッセージを送る
  wifi_connect();          // WiFi接続
  send_msg2line(message1); // LINE notifyに1行目のメッセージを送る
  wifi_disconnect();       // WiFi接続解除
  led_green(ON);

  // GPIO割り込みでのsleep復帰を有効化
  esp_sleep_enable_gpio_wakeup();
}

void loop(){
  // スリープ開始(復帰はこの後ろ)
  esp_light_sleep_start();
  
  wifi_connect();          // WiFi接続
  send_msg2line(message2); // LINE notifyに2行目のメッセージを送る
  wifi_disconnect();       // WiFi接続解除
  led_green(ON);
  while(!digitalRead(SW));
  delay(100);              // チャタリング対策
}

void led_green(int onoff) {
  if (onoff==ON) digitalWrite(LED_G,LOW);
  else           digitalWrite(LED_G,HIGH);
}

void led_red(int onoff) {
  if (onoff==ON) digitalWrite(LED_R,LOW);
  else           digitalWrite(LED_R,HIGH);
}

void wifi_connect() {
    WiFi.begin(ssid, password);
    int onoff = 0;
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        if (onoff==0) led_green(ON);
        else          led_green(OFF);
        onoff ^= 1;
    }

    // 以下5行は、普通は不要なハズ
    IPAddress gateway(192,168,0,1);
    IPAddress subnet(255,255,255,0);
    IPAddress dns1(8, 8, 8, 8); // Google DNS primery server
    IPAddress dns2(8, 8, 4, 4); // Google DNS secondary server
    WiFi.config(WiFi.localIP(),gateway,subnet,dns1,dns2);    

    led_green(ON);
}

void wifi_disconnect() {
    WiFi.disconnect(true);
    led_green(OFF);
}

void send_msg2line(char* message) {
  WiFiClientSecure client;

  led_red(ON);

  client.setInsecure();
  if (!client.connect(host, 443)) {
    blink_red(10);
    led_red(OFF);
    return;
  }
  
  String query = String("message=") + String(message);
  String request = String("") +
               "POST /api/notify HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Authorization: Bearer " + token + "\r\n" +
               "Content-Length: " + String(query.length()) +  "\r\n" + 
               "Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
                query + "\r\n";
  client.print(request);
 
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == "\r") {
      break;
    }
  }
  
  String line = client.readStringUntil('\n');
  led_red(OFF);
}

void blink_red(int no) {
  int onoff = 1;
  int i;
  for (i=0; i<no*2 ;i++) {
    delay(100);
    if (onoff==0) led_red(ON);
    else          led_red(OFF);
    onoff ^= 1;
  }
}

int read_setup_file(char* fileName) {
  char  buff[128];

  if (!SD.begin(SD_CS, SPI, 24000000)) return 1;

  File setupFile = SD.open(fileName,FILE_READ);
  if (setupFile) {
    readLine(setupFile,buff);
    decrypt(ssid,buff,crypt_key);
    
    readLine(setupFile,buff);
    decrypt(password,buff,crypt_key);
    
    readLine(setupFile,buff);
    decrypt(token,buff,crypt_key);
    
    readLine(setupFile,message1);
    readLine(setupFile,message2);
    setupFile.close();
  }

  return 0;
}

// microSDから1行読み取る
int readLine(File fp, char *buff){
  int i = 0;
  if(!fp.available()) {
    return -1;
  }
  while (fp.available()) {
    char c = fp.read();
    if (c=='\r');
    else if (c=='\n') break;
    else buff[i++] = c;
  }
  buff[i] = 0;
  
  return i;
}

void decrypt( char*a, char*b, char* key) {
  char x[128];
  int i,j,k,m;
  // ascii文字をbinaryに戻す
  for(i=0; b[i]!=0; i++) {
    x[i] = b[i] - (int)'#';
  }
  m = i - 1;

  // 6bit -> 8bit
  j = 0;
  for(i=0; j<m ; i++) {
    switch (i%3) {
      case 0:
        a[i] = (x[j]&0x3F)<<2 | (x[j+1]&0x30)>>4;
        j++;
        break;
      case 1:
        a[i] = (x[j]&0x0F)<<4 | (x[j+1]&0x3C)>>2; 
        j++;
        break;
      case 2:
        a[i] = (x[j]&0x03)<<6 | x[j+1]&0x3F;
        j += 2;
        break;
    }
  }
  m = i;
  
  k = 0;
  int n = strlen(key);
  for (i=0; i<m; i++) {
    a[i] ^= key[k++];
    k %= n;
  }
  a[i]= 0;
}
//---------------------------------------------------------------
// EOF
//---------------------------------------------------------------

パーツ箱に昔買った焦電型赤外線センサを見つけたので、防犯センサ版を作ってみようかと思っています。

2022年2月11日金曜日

LINEダッシュボタン、Windows版暗号化プログラム他

1. フォトリフレクタ固定治具


3Dプリンタの印刷完了を検知するためのフォトリフレクタを3Dプリンタに固定するための部品を3Dプリンタで作りました。一度試作して2度目でfixです。


ステッピングモータに被せて固定します。PLAのため熱が心配なので稼働中のステッピングモータを触ってみましたがほんのり温かい程度だったので大丈夫かと思います。

窪みにフォトリフレクタとLEDの電流制限抵抗、プルアップ抵抗を載せたユニバーサル基板を組み込み、ホットボンドで埋めました。前回の試作段階ではコネクタを使ってしましたが、大きくなるのでコネクタは使用せず、2芯のシールド線で電源供給と信号を取り出します。

3Dプリンタ後ろのステッピングモータに被せた様子。ちょっときつかったので、ドライヤで温めて押し込みました。PLAは室温では固いので無理に入れると割れる。


印刷が完了すると、右上に少し見えているヒートベッドが移動してフォトリフレクタの上に来ます。それをトリガにして、LINEダッシュボタンが印刷完了メッセージを送る仕掛けです。

2.消費電力測定


ESP32のsleepしない版とsleep版の消費電力(電流)を計測しました。

・sleepせず、loop内でGPIOがlowになるのを検出する版:約80mA
・light sleepでGPIO変化をトリガとしてwakeupする版:約20mA (意外に大きい)

ACアダプタで5Vを供給したときの5Vの電流を測定(基板が完成していたので、3.3Vの測定はできず)。このため3.3Vに落とすための3端子レギュレータのロスも含まれます。また、LED消灯、フォトリフレクタ消灯、WiFi未接続、microSDは接続状態です。

sleep版も意外と消費電力が大きいのは、3端子レギュレータのロスがそんなに大きいのか、sleepの使い方が悪いのか、WiFiが完全にオフになってないのか。まあ、ACアダプタ動作なのでそんなに気にしなくてもよいのですが気になる。

3. Windows版暗号化プログラム


microSDに格納するアクセスポイントのSSID、パスワード、LINE notifyのトークンを暗号化するプログラムをWindows上で動くようにしました。

まず、平文のsetup.txtファイルを用意します。文字コードはUTF-8です(Arduino-IDEがUTF-8なので)。Windows10のメモ帳はデフォルトでUTF-8のようです。
1~3行目が秘密にしたいアクセスポイント名やパスワード、LINE notifyのトークンです。4行目が起動時にポストするメッセージ、5行目がボタンを押したりするイベント発生時にポストするメッセージです。



日本語を含む場合には、ターミナルをUTF-8にしておかないと文字化けします。あらかじめ、文字コード設定コマンド chcp 65001 を実行しておきます。


ターミナル(コマンドプロンプト)から、後述のencryptソースをコンパイルしたencryptコマンドを実行します。引数は、秘密の鍵、平文のテキストファイル名です。 秘密の鍵はESP32のスケッチ内に記載したものと一致させる必要があります。

ターミナルには、暗号化した文字列と、念のため復号化した文字列、および4行目以降が表示されます。

暗号化したテキストファイルが、元のファイル名の後ろに _crypt.txt を付けたファイル名で作成されます。このファイルを setup.txt にリネームして、microSDのルートディレクトリに格納します。


暗号化プログラムのソースです。ESP32用の暗号化・復号化スケッチ に main だけ追加したものです。エラーチェックや文字長チェックなどちゃんとしていません。無保証です。

プログラムにバグがあったので修正しました('22.2.13)。
//----------------------------------------------------------------------
//  '22.02.13 bug fix
//----------------------------------------------------------------------
#include <stdio.h>
#include <string.h>

void encrypt( char*a, char*b, char* key) {
  char x[128];
  int i,j,k,m;
  k = 0;
  int n = strlen(key);
  for (i=0; b[i]!=0; i++) {
    x[i] = b[i]^key[k++];
    k %= n;
  }
  m = i;

  // 8bit -> 6bit
  // 8bit           0          1          2
  //        000000|00, 0000|0000, 00|000000
  // 6bit        0        1        2      3
  j = 0;
  for (i=0; i<m; j++) {
    switch (j%4) {
      case 0:
        a[j] = (x[i]&0xFC)>>2;
        break;
      case 1:
        a[j] = (x[i]&0x03)<<4 | (x[i+1]&0xF0)>>4; 
        i++;
        break;
      case 2:
        a[j] = (x[i]&0x0F)<<2 | (x[i+1]&0xC0)>>6; 
        i++;
        break;
      case 3:
        a[j] = x[i]&0x3F;
        i++; 
        break;
    }
  }

  // ascii文字に変換
  for (i=0; i<j; i++) {
    a[i] += (int)'#';
  }
  a[i] = 0;
  
}

void decrypt( char*a, char*b, char* key) {
  char x[128];
  int i,j,k,m;
  // ascii文字をbinaryに戻す
  for(i=0; b[i]!=0; i++) {
    x[i] = b[i] - (int)'#';
  }
  m = i - 1;

  // 6bit -> 8bit
  j = 0;
  for(i=0; j<m ; i++) {
    switch (i%3) {
      case 0:
        a[i] = (x[j]&0x3F)<<2 | (x[j+1]&0x30)>>4;
        j++;
        break;
      case 1:
        a[i] = (x[j]&0x0F)<<4 | (x[j+1]&0x3C)>>2; 
        j++;
        break;
      case 2:
        a[i] = (x[j]&0x03)<<6 | (x[j+1]&0x3F);
        j += 2;
        break;
    }
  }
  m = i;
  
  k = 0;
  int n = strlen(key);
  for (i=0; i<m; i++) {
    a[i] ^= key[k++];
    k %= n;
  }
  a[i]= 0;
}

int main(int argc, char *argv[]) {
  int i;
  if (argc!=3) {
    printf("Usage: encrypt.exe key_text file_name\n");
    return(0);
  }
  char* key  = argv[1];
  char* file = argv[2];
  char  cryptfile[128];
  strcpy(cryptfile,file);
  strcat(cryptfile,"_crypt.txt");

  FILE *fp = fopen(file,"r");
  if (!fp) {
    printf("input file open error. %s\n",file);
    return(-1);
  }

  FILE *cfp = fopen(cryptfile,"w");
  if (!cfp) {
    printf("output file open error. %s\n",cryptfile);
    return(-1);
  }

  char plain_text[128];
  char encrypted[256];
  char decrypted[256];

  for (i=0;i<3;i++) {
    fgets(plain_text,128,fp);
    strtok(plain_text,"\n\0");
    encrypt(encrypted,plain_text,key);
    fprintf(cfp,"%s\n",encrypted);
    printf("encrypted:%s\n",encrypted);

    decrypt(decrypted,encrypted,key);
    printf("decrypted:%s",decrypted);
  }
  while( fgets(plain_text,128,fp) ) {
    printf("plaintext:%s",plain_text);
    fprintf(cfp,"%s",plain_text);
  }
  
  fclose(cfp);
  fclose(fp);
  printf("\nencrypted file : %s\n",cryptfile);

}

旧?ボーランドのBCCコンパイラでコンパイルできました。個人利用は無償です。

ESP32の最終版スケッチは近日アップ予定。

2022年2月9日水曜日

LINEダッシュボタン、sleepとwakeup

オミクロン株が猛威を振るっていますが、中々完全な在宅勤務になりません。今週も昨日、今日は出勤で、大雪予報がでている明日も出勤予定。在宅したいのですがままならない。

という訳で、LINEダッシュボタンを3Dプリンタの印刷完了に使う 件の工作は進みません。出勤すると朝、昼休み、夕方の空き時間に作業できないし、夜は目が疲れるので工作したくない。ということでスケッチを少し修正しました。

ひとつは、正常にWiFiとLINE notifyサーバに繋がることを確認するために、起動時にメッセージを送るようにしたこと。このためにmicroSDカードに2つのメッセージを書いています。3Dプリンタの完了通知だけでなく、不在中にドアが開いたとかのセキュリティ監視にも使えそう。


上記はテストなので、監視開始とプリント完了の時間差が殆どありませんが、こんな感じになります。

もうひとつは、sleep と GPIO wakeup を使って印刷終了を監視するようにしたこと。想定通りに動いているので、たぶんこれでsleepしていると思う。週末にsleep版と、ずっと起きてる版の消費電力を比較確認予定。
void setup()
{
  ... 省略...
  
  // GPIO割り込みピン設定
  gpio_wakeup_enable(SW, GPIO_INTR_LOW_LEVEL);
  // GPIO割り込みでのsleep復帰を有効化
  esp_sleep_enable_gpio_wakeup();
}

void loop(){
  // スリープ開始(復帰はこの後ろ)
  esp_light_sleep_start();
  
  wifi_connect();          // WiFi接続
  send_msg2line(message2); // LINE notifyに2行目のメッセージを送る
  wifi_disconnect();       // WiFi接続解除
  while(!digitalRead(SW));
  delay(100);              // チャタリング対策
}

2022年2月6日日曜日

LINEダッシュボタン、ユニバーサル基板に実装

LINEダッシュボタンを3Dプリンタの印刷終了通知に使う ため、ESP32とmicroSDカードスロットをユニバーサル基板に載せました。小さく組むならmicroSDは使わずに、アクセスポイント情報やLINE notifyトークン、送信メッセージなどの情報はスケッチ内にコーディングしておくのがよいです。このカードスロットだけでも300円もしてESP32と値段がそれほど違わない。安いmicroSDカードも4~500円位するのでコストアップ。


配線ごちゃごちゃで汚いですが裏側です。3.3Vの三端子レギュレータと電解コンデンサなどが載っています。この面がケース表面の裏側になる予定で、LEDやスイッチはケース表面に出ます。


ESP32のピンに配線するのは細かい作業で、半田不良やショートなども心配でした。一応、テスタで導通確認などを行った後、スケッチを書き込んでテストしたら無事に動作しました。

UIを少し見直して、待機状態になったら緑色LEDが点灯するようにしてあります。また、ピンアサインなども少し変更。

あと、ACアダプタを使うのでsleepしなくてもよいかと思っていましたが、一応sleep (Deep sleepではなくlight sleepの予定)して、外部割込みでwake upして動作させるつもり。まだ書いてない。ケース加工もやらないといけない。3Dプリンタに取り付けるフォトリフレクタの固定方法も要検討です。固定治具を3Dプリンタで作るかな。

2022年2月5日土曜日

e-Taxで確定申告を済ませました

秋月に注文していた部品が朝イチで届き、LINEダッシュボタン の本ちゃんを作りたかったのですが、以前から予定していた確定申告を行いました。面倒なことは早めに済ませて、工作やDIYなどを楽しみたい。後に延ばすと気になって楽しめないので。

年に1回だけ出番があるICカードリーダとマイナンバカードを出してきました。源泉徴収票や配当の納税書類、ふるさと納税の証書など色々な書類も準備して作業です。

2時間ほどで終わりましたが、慣れない言葉や細かい数字を見たりで疲れました。期待していたより少ないですが還付されることになりました。時給換算すると結構よい(^_^)  まぁ自動的に徴収されていたものが戻ってくるだけなので、報酬という訳ではないですが。

目が疲れたので、今夜の工作やプログラムはパスです。NETFLIXで海外ドラマか映画を観てのんびりします。


2022年2月4日金曜日

LINEダッシュボタンで3Dプリント完了通知

LINEダッシュボタン に外付けスイッチ(フォトリフレクタ)を付けて、3Dプリンタの印刷完了通知を送るようにしてみました。


3Dプリンタは印刷に数時間とかかかるので、これまでは時々見に行っていました。予想時間が表示されていますがあまり当てにならないし、忘れてしまうこともありました。

仕組みですが、印刷が終わるとヒートベッドがホームポジションに戻ってくる(下記写真では左端)ので、それを検出してLINEにメッセージを送ります。


ヒートベッドが戻る位置の下にフォトリフレクタを付けています。ひとまず仮なのでマスキングテープで貼り付け。ヒートベッドの裏側は黒くて赤外線が反射しにくいので、アルミテープを貼ってよく反射するようにしました。


実際に使ってみるとすごく便利です。連続していくつも印刷したいときに、ヘッドやヒートベッドが冷める前に次の印刷に取り掛かれます。ただ準備などでうっかりフォトリフレクタを遮るとメッセージが飛ぶので、印刷を開始してから電源を入れます。


思いのほか便利なのでケースに入れて常用にしようと思います。フォトリフレクタを点灯しっぱなしにするので、電池稼働ではなくACアダプタにするつもりです。

2022年2月2日水曜日

LINEダッシュボタン、microSD対応

なかなか完全な在宅勤務にならず、今週は今日まで出勤でした。明日からは在宅予定。

先日、動画をアップしたLINEダッシュボタン ですが、WiFiのアクセスポイントとパスワード、LINE notifyのトークン、ボタンを押したときに送信するメッセージをスケッチ内に書くのではなく、microSDに格納するようにしました。プログラムを直さなくてもmicroSDの内容を書き換えれば、これらを変更できます。


平文でパスワードやトークンを書くのはリスキーなので簡単な暗号化を行いました。EXOR暗号とシーザー暗号のようなものを組み合わせています。以下のような感じで暗号化されます。
   key           :Open Sesame
   plain_ssid    :WiFi_AccessPoint_SSID
   plain_passwd  :SSID_password
   plain_token   :LINE_TOKEN_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

   crypted_ssid  :)$GF$Z_5$D#'*D;B*SS#8#SY+%CL
   crypted_passwd:*%/O-J_F$##5)CK`(#
   crypted_token :#VGN-Z_*-FCG+VK1/E;M<47E1UCJ.C/`-U(S#F?C06CV)%C_0$$E8W$89(0[5(U
EXOR暗号は2回EXOR操作すると元に戻る性質を使って、暗号化と復号化を行うものです。まず、暗号化keyと平文テキストの文字のEXORを取ります。そのままだとascii文字で表現できないので、8ビット毎の文字列(をEXORした結果)から6ビット毎に取り出してascii文字に変換します。シーザー暗号は文字をずらして暗号化しますが、これは6ビット毎に取り出して文字をずらしています。復号はこの操作の逆を行います。

暗号化/復号化のスケッチは以下です。無保証です。暗号化はC言語などで書いてPCで実行すればよいですが、今回はarduino IDEで開発し、ESP32で動かしています。
//--------------------------------------------------------------
// 簡単なXOR暗号                                    '22.01.31 naka
//  実行例)
//   key           :Open Sesame
//
//   plain_ssid    :WiFi_AccessPoint_SSID
//   plain_passwd  :SSID_password
//   plain_token   :LINE_TOKEN_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
//
//   crypted_ssid  :)$GF$Z_5$D#'*D;B*SS#8#SY+%CL
//   crypted_passwd:*%/O-J_F$##5)CK`(#
//   crypted_token :#VGN-Z_*-FCG+VK1/E;M<47E1UCJ.C/`-U(S#F?C06CV)%C_0$$E8W$89(0[5(U
//
//   decrypt_ssid  :WiFi_AccessPoint_SSID
//   decrypt_passwd:SSID_password
//   decrypt_token :LINE_TOKEN_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
//--------------------------------------------------------------
void setup() {
  char crypted_ssid[128],crypted_passwd[128],crypted_token[128];
  char dec_ssid[128],dec_passwd[128],dec_token[128];

  Serial.begin(115200);
  
  char* key          = "Open Sesame";
  char* plain_ssid   = "WiFi_AccessPoint_SSID";
  char* plain_passwd = "SSID_password";
  char* plain_token  = "LINE_TOKEN_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

  encrypt(crypted_ssid,plain_ssid,key);
  encrypt(crypted_passwd,plain_passwd,key);
  encrypt(crypted_token,plain_token ,key);

  decrypt(dec_ssid,crypted_ssid,key);
  decrypt(dec_passwd,crypted_passwd,key);
  decrypt(dec_token,crypted_token,key);

  Serial.printf("key           :%s\n\n",key);
  Serial.printf("plain_ssid    :%s\n",plain_ssid);
  Serial.printf("plain_passwd  :%s\n",plain_passwd);
  Serial.printf("plain_token   :%s\n",plain_token);
  Serial.printf("\n");

  Serial.printf("crypted_ssid  :%s\n",crypted_ssid);
  Serial.printf("crypted_passwd:%s\n",crypted_passwd);
  Serial.printf("crypted_token :%s\n",crypted_token);
  Serial.printf("\n");

  Serial.printf("decrypt_ssid  :%s\n",dec_ssid);
  Serial.printf("decrypt_passwd:%s\n",dec_passwd);
  Serial.printf("decrypt_token :%s\n",dec_token);
  Serial.printf("\n");

  sleep;
}

void loop() {
}

void encrypt( char*a, char*b, char* key) {
  char x[128];
  int i,j,k,m;
  k = 0;
  int n = strlen(key);
  for (i=0; b[i]!=0; i++) {
    x[i] = b[i]^key[k++];
    k %= n;
  }
  m = i;

  // 8bit -> 6bit
  // 8bit           0          1          2
  //        000000|00, 0000|0000, 00|000000        
  // 6bit        0        1        2      3  
  j = 0;
  for (i=0; i<m; j++) {
    switch (j%4) {
      case 0:
        a[j] = (x[i]&0xFC)>>2;
        break;
      case 1:
        a[j] = (x[i]&0x03)<<4 | (x[i+1]&0xF0)>>4; 
        i++;
        break;
      case 2:
        a[j] = (x[i]&0x0F)<<2 | (x[i+1]&0xC0)>>6; 
        i++;
        break;
      case 3:
        a[j] = x[i]&0x3F;
        i++; 
        break;
    }
  }

  // ascii文字に変換
  for (i=0; i<j; i++) {
    a[i] += (int)'#';
  }
  a[i] = 0;
  
}

void decrypt( char*a, char*b, char* key) {
  char x[128];
  int i,j,k,m;
  // ascii文字をbinaryに戻す
  for(i=0; b[i]!=0; i++) {
    x[i] = b[i] - (int)'#';
  }
  m = i - 1;

  // 6bit -> 8bit
  j = 0;
  for(i=0; j<m ; i++) {
    switch (i%3) {
      case 0:
        a[i] = (x[j]&0x3F)<<2 | (x[j+1]&0x30)>>4;
        j++;
        break;
      case 1:
        a[i] = (x[j]&0x0F)<<4 | (x[j+1]&0x3C)>>2; 
        j++;
        break;
      case 2:
        a[i] = (x[j]&0x03)<<6 | x[j+1]&0x3F;
        j += 2;
        break;
    }
  }
  m = i;
  
  k = 0;
  int n = strlen(key);
  for (i=0; i<m; i++) {
    a[i] ^= key[k++];
    k %= n;
  }
  a[i]= 0;
}

暗号化した文字列はシリアルモニタに表示されます。その文字列をメモ帳などにコピペして、ファイル名をsetup.txtとしてmicroSDのトップディレクトリに格納すればOK。


microSDはESP32の起動時に読み込みます。エラーで読み込めない場合には赤色LEDが点滅したのち、点灯して止まります。正常に読み込めれば、ボタンを押すとWiFiに接続し(その間、緑LEDが点滅)、LINE notifyにメッセージを送り(赤色LED点灯)、WiFi接続を切ります(緑と赤LED消灯)。


回路図とスケッチです。無保証です。


//---------------------------------------------------------------
//                                                2022.02.02 naka
// LINE Dash Button
//
// ・WiFi ssid,password,LINE notify tokenは、暗号化してmicroSDに格納
//  ファイル名:setup.txt
//     暗号化したWiFi ssid, password, LINE notify tokenをそれぞれ1行ずつ
//     順に記載し、4行目に送るメッセージを記載。
//  (例)
//    &D/)%X/C04S+#T'?+USM
//    *%/O-J_F$##5)CK`(%
//    #VGN-Z_*-FCG+VK1/E;M<47E1UCJ.C/`-U(S#F?C06CV)%C_0$$E8W$89(0[5(U
//    ボタンが押されました。
//---------------------------------------------------------------
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <SD.h>

#define SD_CS 16 // SD card chip select
#define SW    25
#define LED_G 33
#define LED_R 32

#define ON  1
#define OFF 0

#define FILENAME "/setup.txt"

// ssid,pw,tokenを暗号化したときのキー
char* crypt_key = "Open Sesame";

// LINE Notify
const char* host  = "notify-api.line.me";
char ssid[64],password[64],token[64],message[128];
int sw_newest;

void setup()
{
    Serial.begin(115200);
    pinMode(SW,INPUT_PULLUP);
    pinMode(LED_G,OUTPUT);
    pinMode(LED_R,OUTPUT);
    led_green(OFF);
    led_red(OFF);
    sw_newest = digitalRead(SW);
    if (read_setup_file(FILENAME)) {
      blink_red(10);
      led_red(ON);
      sleep;
    }
}

void loop(){
  if (sw_FallingEdge()) {
    wifi_connect();      // WiFi接続
    send_msg2line();     // LINE notifyにメッセージを送る
    wifi_disconnect();   // WiFi接続解除
    while(!digitalRead(SW));
    delay(100);          // チャタリング対策
  }  
}

int sw_FallingEdge() {
  int prev = sw_newest;
  sw_newest = digitalRead(SW);
  return prev & ~sw_newest;  // change 1 -> 0
}

void led_green(int onoff) {
  if (onoff==ON) digitalWrite(LED_G,LOW);
  else           digitalWrite(LED_G,HIGH);
}

void led_red(int onoff) {
  if (onoff==ON) digitalWrite(LED_R,LOW);
  else           digitalWrite(LED_R,HIGH);
}

void wifi_connect() {
    WiFi.begin(ssid, password);
    int onoff = 0;
    while (WiFi.status() != WL_CONNECTED) {
        delay(500);
        if (onoff==0) led_green(ON);
        else          led_green(OFF);
        onoff ^= 1;
    }

    // 以下5行は、普通は不要なハズ
    IPAddress gateway(192,168,0,1);
    IPAddress subnet(255,255,255,0);
    IPAddress dns1(8, 8, 8, 8); // Google DNS primery server
    IPAddress dns2(8, 8, 4, 4); // Google DNS secondary server
    WiFi.config(WiFi.localIP(),gateway,subnet,dns1,dns2);    

    led_green(ON);
}

void wifi_disconnect() {
    WiFi.disconnect(true);
    led_green(OFF);
}

void send_msg2line() {
  WiFiClientSecure client;

  led_red(ON);

  client.setInsecure();
  if (!client.connect(host, 443)) {
    blink_red(10);
    led_red(OFF);
    return;
  }
  
  String query = String("message=") + String(message);
  String request = String("") +
               "POST /api/notify HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Authorization: Bearer " + token + "\r\n" +
               "Content-Length: " + String(query.length()) +  "\r\n" + 
               "Content-Type: application/x-www-form-urlencoded\r\n\r\n" +
                query + "\r\n";
  client.print(request);
 
  while (client.connected()) {
    String line = client.readStringUntil('\n');
    if (line == "\r") {
      break;
    }
  }
  
  String line = client.readStringUntil('\n');
  led_red(OFF);
}

void blink_red(int no) {
  int onoff = 1;
  int i;
  for (i=0; i<no*2 ;i++) {
    delay(100);
    if (onoff==0) led_red(ON);
    else          led_red(OFF);
    onoff ^= 1;
  }
}

int read_setup_file(char* fileName) {
  char  buff[128];

  if (!SD.begin(SD_CS, SPI, 24000000)) return 1;

  File setupFile = SD.open(fileName,FILE_READ);
  if (setupFile) {
    readLine(setupFile,buff);
    decrypt(ssid,buff,crypt_key);
    
    readLine(setupFile,buff);
    decrypt(password,buff,crypt_key);
    
    readLine(setupFile,buff);
    decrypt(token,buff,crypt_key);
    
    readLine(setupFile,message);
    setupFile.close();
  }

  return 0;
}

// microSDから1行読み取る
int readLine(File fp, char *buff){
  int i = 0;
  if(!fp.available()) {
    return -1;
  }
  while (fp.available()) {
    char c = fp.read();
    if (c=='\r');
    else if (c=='\n') break;
    else buff[i++] = c;
  }
  buff[i] = 0;
  
  return i;
}

void decrypt( char*a, char*b, char* key) {
  char x[128];
  int i,j,k,m;
  // ascii文字をbinaryに戻す
  for(i=0; b[i]!=0; i++) {
    x[i] = b[i] - (int)'#';
  }
  m = i - 1;

  // 6bit -> 8bit
  j = 0;
  for(i=0; j<m ; i++) {
    switch (i%3) {
      case 0:
        a[i] = (x[j]&0x3F)<<2 | (x[j+1]&0x30)>>4;
        j++;
        break;
      case 1:
        a[i] = (x[j]&0x0F)<<4 | (x[j+1]&0x3C)>>2; 
        j++;
        break;
      case 2:
        a[i] = (x[j]&0x03)<<6 | x[j+1]&0x3F;
        j += 2;
        break;
    }
  }
  m = i;
  
  k = 0;
  int n = strlen(key);
  for (i=0; i<m; i++) {
    a[i] ^= key[k++];
    k %= n;
  }
  a[i]= 0;
}
//---------------------------------------------------------------
// EOF
//---------------------------------------------------------------