でんげき☆ Network Service

Raspberry Pi 4 で運用実験中 Connect checker

カテゴリ「情報」に属する投稿20件]

(M5) ATOM Lite でまぁまぁスマートなリモコンを作ろう

202307171653523-admin.png
 
今年の初めに…かれこれ 18 年くらい住んでたボロアパートをぶっ壊すってんで追い出されて今度は高級マンションの最上階に引っ越してきました!(>_<)w そんな高級マンションには…ご入居前リフォームで 2022 年製のエアコンに交換されていたのはいいんだけどちょっと使いづらいんですよね
何が使いづらいかっていうと…リモコンでのタイマーセットが不便なんですよ 以前のボロアパートで使ってたエアコンはリモコンに RTC(時計) が実装されていたのでタイマーセットも絶対時間で行うことができていたんですよ それがご新居のエアコンのリモコンには RTC(時計) が付いてないもんだでタイマーセットしようとすると「7時間後」って感じの相対時間での設定になるんですよ 不便ですよね(-_-;)
そんな訳なんでもちっとタイマーセットしやすいリモコンをどうにかするっていうか…せっかくなんでスマホちゃん等と連動させて絶対時間でエアコンの On/Off を制御できるリモコンを作ってみることにしました

昨年に「安いじゃん!」って思って買っておいた (M5) ATOM Lite が手元にあるんでそれを使うことにしました 今買うなら (M5) ATOMS3 Lite  のほうがいいのかも知れないけど未確認です汗
それと CGI っていうか Perl が使える Web サーバが必要です 今回は Raspberry Pi OS にセットアップした Apache を使ってるけど…プロバイダなどで供されている CGI の使える Web サーバでも大丈夫だと思います

そうそう今回使う (M5) ATOM Lite には RTC が載っていません それ即ち時計が使えないって事なんでその辺をどうにかしなければなりません
そこで一定時間(今回は約30秒)ごとに Web サーバにアクセスして…アクセスされた Web サーバが現在時刻(とエアコン設定内容)を返しそれにより時刻の判定を行うようにしました

  :

それでは最初に (M5) ATOM Lite の方から初めていきましょう 開発環境は Linux っていうか Snap 版の Arduino IDE を使いました まぁ Arduino IDE であれば Windows 版とかでも同じく使えるとは思いますが未確認です汗 そんな Arduino IDE の設定は M5StickC Plus の開発環境を構築する が参考になると思います ボードを追加した後に「M5Stack-ATOM」を設定すれば ok です

お次は (M5) ATOM Lite 向けのライブラリのインストールですかね これは「ツール」メニューから「ライブラリを管理…」を選んで検索窓に必要なキーワードを入れて出てきたものをインストールってする感じですかね

20230717165352-admin.png
m5atom」と入れて出てきた「M5Atom」をインストールする

202307171653522-admin.png
irremoteesp8266」と入れて出てきた「IRremoteEsp8266」をインストールする

202307171653521-admin.png
heatpumpir」と入れて出てきた「HeatpumpIR」をインストールする

これでライブラリ類の準備も完了ですかね それではコードを見ていきましょう…っていうか手探りの「どうにか動く」テスト的なものだったのが意外と問題なく動いてしまって「あーなんか書き直すのもめんどくさいなー」って気分が昂ぶってきたんでそんなテスト的なものをそのまま載せています 動作未保証なのはもちろんとして…諸々をきちっと書き直して使ってください それだけが私の望みです汗

  :

----- Start of remcon.ino -----
#include <M5Atom.h> // Atom のヘッダファイルを準備
#include <WiFiMulti.h>
#include <HTTPClient.h>
#include <MitsubishiHeatpumpIR.h>

const char* url = "http://user:pass@hoge.orz/remocon/read.cgi"; // 現在時刻&設定内容の場所
const char* ssid = "WiFi_ssid";  // SSID
const char* password = "WiFi_pass";  // パスワード

const uint16_t kIrLed = 12; // 赤外線 LED 送信用のポート番号
IRSenderBitBang irSender(kIrLed); // 赤外線LEDが接続されているピン番号
HeatpumpIR *airCon = new MitsubishiFDHeatpumpIR();  // 三菱エアコンのインスタンス

// FastLED ライブラリの設定(CRGB構造体)
CRGB dispColor(uint8_t r, uint8_t g, uint8_t b) {
  return (CRGB)((r << 16) | (g << 8) | b);
}

WiFiMulti WiFiMulti;  // Wi-Fi を使いたい

void setup() {
  Serial.begin(115200);
  Serial.println("");

  M5.begin(true, false, true);  // 本体初期化(UART有効, I2C無効, LED有効)
  M5.dis.drawpix(0, dispColor(0, 0, 0));  // LED全消灯(赤, 緑, 青)

  WiFiMulti.addAP(ssid, password); // 「SSID」と「パスワード」を設定しておく
  Serial.print("Fi-Fi Connecting.");
}

void loop() {
  while (WiFiMulti.run() != WL_CONNECTED) {
    Serial.print(".");
    M5.dis.drawpix(0, dispColor(255, 0, 0));  //LED(赤)
    delay(500);
  }
  Serial.print("ADDR: ");
  Serial.println(WiFi.localIP());

  M5.dis.drawpix(0, dispColor(20, 20, 20)); // LED(白)

  HTTPClient http;
  http.begin(url);
  Serial.print("GET: ");
  int httpCode = http.GET();  // GET メソッドでファイルを取得
  Serial.println(httpCode);
//  Serial.println(http.errorToString(httpCode));

  if (httpCode > 0) {
    String htdoc = http.getString();  // 取得したファイルの内容
    Serial.println(htdoc);

//  YYYY/MM/DD HH:YY:MM(WDAY)\ts?,HH:MM e?,HH:MM m?
//  現在年/月/日 時:分:秒(曜日)\t開始フラグ,時:分 終了フラグ,時:分 モード
    String now_hour = htdoc.substring(11, 13);  // 現在時を取得
    String now_min = htdoc.substring(14, 16);   // 現在分を取得

    String st = htdoc.substring(25, 27);        // 開始フラグを取得
    String ed = htdoc.substring(34, 36);        // 終了フラグを取得

    String st_hour = htdoc.substring(28, 30);   // 開始時
    String st_min = htdoc.substring(31, 33);    // 開始分

    String ed_hour = htdoc.substring(37, 39);   // 終了時
    String ed_min = htdoc.substring(40, 42);    // 終了分

    String md = htdoc.substring(43, 45);        // モードを取得

    if (st == "s1" && now_hour == st_hour && now_min == st_min) {
      if (md == "m1") {
        Serial.println("Cool");
        M5.dis.drawpix(0, dispColor(0, 0, 255));  //LED(青)
        airCon->send(irSender, POWER_ON, MODE_COOL, FAN_1, 28, VDIR_DOWN, HDIR_AUTO); // 冷房
      } else if (md == "m2") {
        Serial.println("Hot");
        M5.dis.drawpix(0, dispColor(255, 255, 0));  //LED(黄)
        airCon->send(irSender, POWER_ON, MODE_HEAT, FAN_1, 16, VDIR_DOWN, HDIR_AUTO); // 暖房
      } else {
        Serial.println("Dry");
        M5.dis.drawpix(0, dispColor(0, 255, 0));  //LED(緑)
        airCon->send(irSender, POWER_ON, MODE_DRY, FAN_1, 28, VDIR_DOWN, HDIR_AUTO); // 除湿
      }
    }

    if (ed == "e1" && now_hour == ed_hour && now_min == ed_min) {
      Serial.println("--- Stop ---");
      M5.dis.drawpix(0, dispColor(255, 0, 255));  //LED(紫)
      airCon->send(irSender, POWER_OFF, MODE_COOL, FAN_1, 28, VDIR_DOWN, HDIR_AUTO); // 電源 Off
    }

  } else {  // ファイルの取得に失敗している
    Serial.println("Error");
  }

  delay(30000);
}
----- End of remcon.ino -----
※デバッグ用のシリアル通信のアレが残っているんで実稼働時には消したほうがいいかも知れないです

冒頭の #include <MitsubishiHeatpumpIR.h> は…まぁ見ての通り三菱社製のエアコン制御をする時に使うものです これが他社製であれば
#include <DaikinHeatpumpIR.h>  // ダイキン社製
#include <FujitsuHeatpumpIR.h>  // 富士通社製
#include <HitachiHeatpumpIR.h>  // 日立社製
#include <PanasonicHeatpumpIR.h>  // パナソニック社製
#include <ToshibaHeatpumpIR.h>  // 東芝社製
こんな感じになるんじゃないかと思います(未確認) 詳しくは Aruduino IDE の関連フォルダ Arduino/libraries/HeatpumpIR/ を眺めてみるといいかと思います

  :

データ(と時間)を取ってくる Web サーバの設定と… Wi-Fi 接続に関する設定を行います
const char* url = "http://user:pass@hoge.orz/remcon/read.cgi"; // 現在時刻&設定内容の場所
const char* ssid = "WiFi-ssid";  // SSID
const char* password = "WiFi-pass";  // パスワード

保安上っていうか恐らくベーシック認証なりをかけて運用すると思うのですが…その際に ID とパスワードの入力を自動化しておかねばなりません そんなときは http://user:pass@hoge.orz/remcon/read.cgi って感じで URL の前に「user:pass@」を付ければ自動で認証してくれます この書き方は一般的なブラウザでも(警告が出るかもだけど)使えます
その次に Wi-Fi の SSID とパスワードの設定を行います 上記のベーシック認証と併せて平文のパスワードを記すことになるので取り扱いには十分注意してください

  :

HTTPClient http;
http.begin(url);
int httpCode = http.GET();  // GET メソッドでファイルを取得

Wi-Fi 接続が確立したら目的のサーバからデータを取ってきます 後でも説明しますが…ここで得られるデータは次のようになっています

YYYY/MM/DD HH:YY:MM(WDAY)[タブ(\t)]s?,HH:MM e?,HH:MM m?
[タブ(\t)] の前半がアクセスした時点での日時
[タブ(\t)] の後半がエアコンの開始/終了の時刻とモードを指しています
例えば 12 時 34 分に作動させたい場合は s1,12:34 となります その次が e1,23:45 だったら 23 時 45 分に電源を切る動作となります これが s0 もしくは e0 だった場合は何も動作しないようにしています
行末の m? は動作モードを指しています m1 が冷房 m2 が暖房 m3 が除湿です 
http.GET(); した際のバッファサイズが不明だったんで…できるだけ切り詰めて必要最小限の内容にしました バッファに余裕があればもっと見やすい内容にするのもアリかもしれませんね

  :

そして実際に赤外線で信号を送信する部分です
airCon->send(irSender, POWER_ON, MODE_COOL, FAN_1, 28, VDIR_DOWN, HDIR_AUTO); // 冷房

各パラメータの意味は以下のようになっています
// airCon->send( irSender, POWER_**, MODE_**, FAN1, 温度設定, VDIR_** , HDIR_** ); // 設定項目 ※温度設定は「整数値」で
//  Arduino/libraries/HeatpumpIR/HeatpumpIR.h
//
// ・POWER (電源)
//    POWER_ON (on)
//    POWER_OFF (off)
// ・MODE (モード)
//    MODE_AUTO (自動)
//    MODE_COOL (冷房)
//    MODE_HEAT (暖房)
//    MODE_DRY (除湿)
//    MODE_ON (不明)
//    MODE_OFF (不明)
// ・FAN (風量)
//    FAN_AUTO
//    FAN_1 (弱)
//    FAN_2
//    FAN_3
//    FAN_4
//    FAN_5 (強)
// ・VDIR (風向高さ)
//    VDIR_AUTO
//    VDIR_UP
//    VDIR_MUP
//    VDIR_MIDDLE
//    VDIR_MDOWN
//    VDIR_DOWN
// ・HDIR (風向左右)
//    HDIR_AUTO
//    HDIR_LEFT
//    HDIR_MLEFT
//    HDIR_MIDDLE
//    HDIR_MRIGHT
//    HDIR_RIGHT

エアコンの設定温度は整数値で指定する必要があるようです 最近は 0.5℃ 間隔で設定できるエアコンが増えてる感じですが…そこまで細かい制御には対応できないっぽいです また今回のプログラムでは設定温度を即値の決め打ちしています 使い勝手を高めるのであればこの辺も外部から変更できるようにすべきですね

だいたいこんな感じですかね これを delay(30000); して約 30 秒間隔のループで回しています なにかの都合…例えば http.GET(); した際にサーバの返答が遅れたりするとループ間隔が長くなり指定された時間を通り越してしまうかも?って思ってこのくらいの間隔にしました まぁこの辺は各環境によりけりで程よい設定にしてもらえればなぁって思います

  :

さて…お次は UI っていうか Web サーバ側の云々ですかね 用意するのは 4 つのファイル(index.html, index.css, index.cgi, read.cgi)とスクリプトが生成する 1 つのファイル(data.txt)です

----- Start of index.html -----
<!DOCTYPE html>
<html lang="ja">
<head>
   <meta charset="UTF-8">
   <!-- ビューポートの設定 -->
   <meta name="viewport" content="width=device-width,initial-scale=1">
   <!-- スタイルシートの読み込み -->
   <link href="./index.css" rel="stylesheet">
   <title>M5 Atom Lite タイマー予約</title>
</head>
<body>

<center><div class="red"></div></center>
<center><form action="./index.cgi" method="get">
   <div>
      <label for="name"><a href="./index.cgi">M5 Atom Lite タイマー予約</a></label>
   </div>
   <div><hr class="double" width=80% noshade></div>
   <div>
      <label><input class="st" type="checkbox" name="st" value="1">「入り」を使う</label>
   </div>
   <div>
      <select name="st_hour">
         <option class="st_hour" value="00">00</option>
         <option class="st_hour" value="01">01</option>
         <option class="st_hour" value="02">02</option>
         <option class="st_hour" value="03">03</option>
         <option class="st_hour" value="04">04</option>
         <option class="st_hour" value="05">05</option>
         <option class="st_hour" value="06">06</option>
         <option class="st_hour" value="07">07</option>
         <option class="st_hour" value="08">08</option>
         <option class="st_hour" value="09">09</option>
         <option class="st_hour" value="10">10</option>
         <option class="st_hour" value="11">11</option>
         <option class="st_hour" value="12">12</option>
         <option class="st_hour" value="13">13</option>
         <option class="st_hour" value="14">14</option>
         <option class="st_hour" value="15">15</option>
         <option class="st_hour" value="16">16</option>
         <option class="st_hour" value="17">17</option>
         <option class="st_hour" value="18">18</option>
         <option class="st_hour" value="19">19</option>
         <option class="st_hour" value="20">20</option>
         <option class="st_hour" value="21">21</option>
         <option class="st_hour" value="22">22</option>
         <option class="st_hour" value="23">23</option>
      </select>時
      <select name="st_min">
         <option class="st_min" value="00">00</option>
         <option class="st_min" value="10">10</option>
         <option class="st_min" value="20">20</option>
         <option class="st_min" value="30">30</option>
         <option class="st_min" value="40">40</option>
         <option class="st_min" value="50">50</option>
      </select> 分
   </div>
   <div><hr class="double" width=80% noshade></div>
   <div>
      <label><input class="ed" type="checkbox" name="ed" value="1">「切り」を使う</label>
   </div>
   <div>
      <select name="ed_hour">
         <option class="ed_hour" value="00">00</option>
         <option class="ed_hour" value="01">01</option>
         <option class="ed_hour" value="02">02</option>
         <option class="ed_hour" value="03">03</option>
         <option class="ed_hour" value="04">04</option>
         <option class="ed_hour" value="05">05</option>
         <option class="ed_hour" value="06">06</option>
         <option class="ed_hour" value="07">07</option>
         <option class="ed_hour" value="08">08</option>
         <option class="ed_hour" value="09">09</option>
         <option class="ed_hour" value="10">10</option>
         <option class="ed_hour" value="11">11</option>
         <option class="ed_hour" value="12">12</option>
         <option class="ed_hour" value="13">13</option>
         <option class="ed_hour" value="14">14</option>
         <option class="ed_hour" value="15">15</option>
         <option class="ed_hour" value="16">16</option>
         <option class="ed_hour" value="17">17</option>
         <option class="ed_hour" value="18">18</option>
         <option class="ed_hour" value="19">19</option>
         <option class="ed_hour" value="20">20</option>
         <option class="ed_hour" value="21">21</option>
         <option class="ed_hour" value="22">22</option>
         <option class="ed_hour" value="23">23</option>
      </select>時
      <select name="ed_min">
         <option class="ed_min" value="00">00</option>
         <option class="ed_min" value="10">10</option>
         <option class="ed_min" value="20">20</option>
         <option class="ed_min" value="30">30</option>
         <option class="ed_min" value="40">40</option>
         <option class="ed_min" value="50">50</option>
      </select>分
   </div>
   <div><hr class="double" width=80% noshade></div>
    <div>
      <input class="md" type="radio" name="md" value="1">冷房
      <input class="md" type="radio" name="md" value="2">暖房
      <input class="md" type="radio" name="md" value="3">除湿
   </div>

   <div><hr class="hrline" width=80% noshade></div>
   <div>
      <input class="submit" type="submit" value="予約">
   </div>
</form></center>

</body>
</html>
----- End of index.html -----
index.html ってファイル名なんで…デフォルトで表示されるファイルっぽいですが実際にはそのような用途には使っていません デザインしやすいようにファイルを分割してあるだけで実際に表示は index.cgi が行います

----- Start of index.css -----
/* ボディ */
body {   /* 一般的な表示部 */
   margin: 0 ;
   padding: 0 ;
   font-size: 20px ;
   line-height: 3 ;
}

input {   /* チェックボックス */
   width:         25px;
   height:         25px;
   -moz-transform:      scale(1.4);
   -webkit-transform:   scale(1.4);
   transform:      scale(1.4);
}

select {   /* プルダウンメニュー */
   font-size: 30px ;
}

.hrline {   /* 罫線(?) */
   border: none;
   background-color: #fff;
   border-width: 1px 0 0 0;
   border-top: double;
   border-color: black;
}

.submit {   /* 送信ボタン */
   width:         100px;
   height:         40px;
   -moz-transform:      scale(1.4);
   -webkit-transform:   scale(1.4);
   transform:      scale(1.4);
}

.red {   /* 設定変更した */
   font-size: 40px ;
   color: #ff0000;
}

/* スマホだけに適用するCSS */
@media screen and ( max-width:479px ) {
   body {   /* 一般的な表示部 */
      font-size: 20px ;
   }

   input {   /* チェックボックス */
      width:         25px;
      height:         25px;
      -moz-transform:      scale(1.4);
      -webkit-transform:   scale(1.4);
      transform:      scale(1.4);
   }

   select {   /* プルダウンメニュー */
      font-size: 25px ;
   }

   .hrline {   /* 罫線(?) */
      border: none;
      background-color: #fff;
      border-width: 1px 0 0 0;
      border-top: double;
      border-color: black;
   }
   .submit {   /* 送信ボタン */
      width:         100px;
      height:         40px;
      -moz-transform:      scale(1.4);
      -webkit-transform:   scale(1.4);
      transform:      scale(1.4);
   }

   .red {   /* 設定変更した */
      font-size: 40px ;
      color: #ff0000;
   }
}
----- End of index.css -----
でっか文字な表示にしたくて見様見真似で用意しました 見様見真似の即興ってことで CSS について何一つ理解していません汗 おかしな所しかないと思うけど…まぁその辺は生温かい目で見守って頂けると幸いに思います(瀧汗

----- Start of index.cgi -----
#!/usr/bin/perl

$datafile = "./data.txt";   # ATOM Lite が読みにくるファイル
$htmlfile = "./index.html";   # ベースとなる HTML ファイル

if ($ENV{'REQUEST_METHOD'} eq 'POST') {   # POST メソッドで送られてきた場合
   read(STDIN, $query, $ENV{'CONTENT_LENGTH'});
} else {   # GET メソッドで送られてきた場合
   $query = $ENV{'QUERY_STRING'};
}

#### ATOM Lite が読みにくるデータファイルを作成する ####
open (DATA, $datafile);
$data = <DATA>;   # 既存のデータファイルを読み内容を取り出しておく
if ($data =~ /s(\d),(\d\d):(\d\d) e(\d),(\d\d):(\d\d) m(\d)/) {
   $in{'st'} = $1;
   $in{'st_hour'} = $2;
   $in{'st_min'} = $3;
   $in{'ed'} = $4;
   $in{'ed_hour'} = $5;
   $in{'ed_min'} = $6;
   $in{'md'} = $7;
}
close (DATA);

if ($query) {   # データを受信していた場合の処理
   open (DATA, ">", $datafile);   
   foreach $data (split(/&/, $query)) {   # 「&」で分割して全て処理する
      ($key, $value) = split(/=/, $data);   # 「name=value」のそれぞれに分割して連想配列を作っていく
      $value =~ s/\+/ /g;   # 以下ざっくりとした URL デコードする
      $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack('C', hex($1))/eg;
      $value =~ s/\t//g;
      $in{"$key"} = $value;   # value = $in{"$name"} の連想配列が出来上がる
   }
   # 送られてきた内容を適用してデータファイルを更新
   if (!($query =~ /st=/)) { $in{'st'} = '0'; }   # チェックボックスの挙動がアレなんで個別対応(汗
   if (!($query =~ /ed=/)) { $in{'ed'} = '0'; }   # チェックボックスの挙動がアレなんで個別対応(汗
   if ($in{'st'}) { print DATA "s1,"; } else { print DATA "s0,"; }
   if ($in{'st_hour'}) { printf DATA "%02d:", $in{'st_hour'}; } else { print DATA "00:"; }
   if ($in{'st_min'}) { printf DATA "%02d ", $in{'st_min'}; } else { print DATA "00 "; }
   if ($in{'ed'}) { print DATA "e1,"; } else { print DATA "e0,"; }
   if ($in{'ed_hour'}) { printf DATA "%02d:", $in{'ed_hour'}; } else { print DATA "00:"; }
   if ($in{'ed_min'}) { printf DATA "%02d ", $in{'ed_min'}; } else { print DATA "00 "; }
        if ($in{'md'} eq "1") { print DATA "m1\n"; } elsif ($in{'md'} eq "2") { print DATA "m2\n"; } else { print DATA "m3\n"; }
   close (DATA);
}

open (IN, $htmlfile);   # 以下ベースとなる HTML ファイルを読み込み設定時間などを反映させる

print "Content-type: text/html; charset=UTF-8\n\n";
@lines = <IN>;
foreach (@lines) {
   if ($query) { s/(<div class="red">)(<\/div>)/$1設定しました$2/; }

   if ($in{'st'} eq "1") { s/(class="st")/$1 checked/; }
   if ($in{'st_hour'}) { s/(class="st_hour" value=")($in{'st_hour'}")/$1$2 selected/; }
   if ($in{'st_min'}) { s/(class="st_min" value=")($in{'st_min'}")/$1$2 selected/; }
   if ($in{'ed'} eq "1") { s/(class="ed")/$1 checked/; }
   if ($in{'ed_hour'}) { s/(class="ed_hour" value=")($in{'ed_hour'}")/$1$2 selected/; }
   if ($in{'ed_min'}) { s/(class="ed_min" value=")($in{'ed_min'}")/$1$2 selected/; }
        if ($in{'md'}) { s/(name="md" value=")($in{'md'}")/$1$2 checked="checked"/; }

   print $_;
}
close (IN);
----- End of index.cgi -----
実際に表示を受け持つ index.cgi です 表示を受け持ちつつ…フォームで入力された値を解釈して data.txt を生成します そしてそのフォームで入力された値 index.html の内容を書き換えて表示するようになっています こうしておけば以前の設定内容を次回の設定に引き継げるようになるので便利かなーって思いまして
そんな訳なんで .htaccess で Web サーバの設定変更が可能な場合は DirectoryIndex index.cgi としてデフォルトで読み出されるファイルに設定しておくといいでしょう

あとこの index.cgi は GET メソッドでデータを受けれられるので…極端な話パラメータ付きの URL を直打ちすることでも設定が可能です 例えば 08 時 20 分冷房 で作動させ 19 時 30 分 に電源を切る場合だと
http://user:pass@hoge.orz/remocon/index.cgi?st=1&st_hour=08&st_min=20&ed=1&ed_hour=19&ed_min=30&md=1
こんな感じの URL になります それ即ち wget や curl などのコマンドでアクセスすることが可能となり…さらに cron などを使えば毎日の動作や毎週の動作といった感じで自動化することも可能になります いや実際に試してはないですけどたぶん大丈夫だと思います汗

----- Start of read.cgi -----
#!/usr/bin/perl

print "Content-type: text/plain; charset=UTF-8\n\n";

# 現在日時の表示
@wdays = ( "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat");
($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time());
printf "%04d/%02d/%02d %02d:%02d:%02d(%s)\t", $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $wdays[$wday];

$datafile = "./data.txt";   # ブラウザで設定した内容のファイル
open (DATA, $datafile);
print <DATA>;   # ファイルの 1 行目を読み取り表示する
close (DATA);
----- End of read.cgi -----
(M5) ATOM Lite がアクセスしてくるファイル(CGI)です 現在日時を整形して表示して…あとは index.cgi が生成した data.txt の内容をくっつけて送信してくれます

  :

ごちゃごちゃ長くなったんで以下にまとめときます 「名前を付けてリンク先を保存」とかすれば手間がなくていいと思いますたぶん
remcon.ino   (M5) ATOM Lite 用 Arduino Sketch ファイル
index.html   表示のベースとなる HTML ファイル
index.css   でっか文字にするためのアレ
index.cgi   ページ表示しつつデータファイルを作成する
read.cgi   (M5) ATOM Lite が読みに来る先のやつ
data.txt  index.cgi が作成するデータファイル

20230717165352-admin.jpg
そして後はいい感じに設置して完了ですかね (M5) ATOM Lite の赤外線はかなり飛距離が短いんで…エアコンにへばりつけるような設置になっちゃうのかな

あーあと余談っていうか注意事項ですが (M5) ATOM Lite が読みに来る read.cgi は数十秒に 1 回のペースで 24 時間 365 日延々とアクセスがあります そのままの Web サーバだと…そのアクセス毎にログファイルが蓄積されることになります もし可能なら Web サーバの設定を変更して…このアクセスに対してのみログの書き込みを抑制するなどしたほうがいいと思います ざっくり SetEnvIf nolog  なんて感じのキーワードでぐぐれば必要な情報に辿り着けるとイイナ!

  :

そんな感じで最後までご覧いただきありがとございました汗 ざっくりテスト用に作ったのをイジくりまわした代物なのでいろいろおかしな部分はありますが…手っ取り早く動作を体感してみたい!って時には役立つかもです それにしても 1,000円 ちょっとで買える (M5) ATOM Lite はホントすごいですよね きちんと筐体に収められていつつ… Wi-fi で接続を確立して HTTP でデータを取ってくることができる 赤外線 LED でリモコンのような動作もさせられる 欲を云えば RTC(時計) が欲しかったかなって感じではあるけど…それもどっかのサーバに接続して時刻を取得すればそれほど問題にはならないんでまぁいいかな♪ なんかそんなかんじで #Arduino #M5StickC

情報 <19935文字>

MakeMKV の使い方…のもうちょっと前のメモ

20220925101846-admin.png
 
DVD や BD をアレするのに便利な MakeMKV ですが…そうそう毎日毎日頻繁に使うものじゃないんでイザ使おうと思うとなんかポップアップが出て使えない!ってことはありませんか? 私はあります でもまぁそうそう毎日毎日頻繁に使うものじゃないんでレアな出来事ではあるのですが(汗

202209251018461-admin.png
では頻繁にアップデートされる MakeMKV の更新方法からですかね なんか Windows 版とかではバイナリで供されているらしいんで…それを入れればいいらしい?って感じなんですが Linux 版はソースコードでの提供なんでその都度コンパイルしなくちゃいかんっぽいでうね

202209251018462-admin.png 202209251018463-admin.png
そんな訳で MakeMKV のページに行ってみて Downloadforum page. と進み…

202209251018464-admin.png
makemkv-oss-*.tar.gz makemkv-bin-*.tar.gz の 2 つをダウンロードしてそれぞれ適当なフォルダに解凍しておきます

202209251018465-admin.png
それでは makemkv-oss から処理していきましょう makemkv-oss を解凍したフォルダに移動して…

./configure
make
sudo make install


を順番に実行します 環境にもよるだろうけど…ちょっと時間がかかります
つーかまぁこの辺の何行かになってるのを一行づつコピペするのは面倒なんで…

./configure && make && sudo make install

こんな感じで「&&」でコマンドをつなげて実行するのもアリですかね

※なお MakeMKV の初回のインストールの際には sudo apt-get install build-essential pkg-config libc6-dev libssl-dev libexpat1-dev libavcodec-dev libgl1-mesa-dev qtbase5-dev zlib1g-dev を真っ先に実行しておいたほうがいいらしいです

  :

202209251018466-admin.png
お次は makemkv-bin を処理しましょう makemkv-bin を解凍したフォルダに移動して…

make
sudo make install


を順番に実行します これも先程と同じく「&&」でコマンドをつなげて実行していいと思います

make && sudo make install

202209251018467-admin.png 202209251018468-admin.png
途中で重要な注意事項的なものが表示されるので…カーソルキーの↑↓やスペースキーなどを使い読んだ後に「q」で抜けて了解した場合に「yes」を入力して次に進みます

  :

これで MakeMKV のアップデート(インストール)は完了です makemkv-*.tar.gz を展開しておいたフォルダは削除しても問題ないんで…まぁ邪魔くさいので消しておいたほうが無難でしょう

202209251018469-admin.png
そしてお次は「期限切れ」となってるこれをどうにかしましょう

2022092510184610-admin.png 2022092510184611-admin.png
News and Announcements 内の MakeMKV is free while in beta と進み「The current beta key is」の内容を…

2022092510184612-admin.png
MakeMKV を起動 → ヘルプ(H) → 公認(R) を選び…出てきた入力欄にコピペして「OK」すれば完了です

2022092510184613-admin.png
あとは MakeMKV を再起動すれば使用できるようになってます ありがたいですね(>_<)w

それほど面倒な手順ってほどでもないんだけど…リンク先とか忘れがちなんでその辺の対策メモってかんじでまとめておきました ご利用は自己判断でお願いします(汗 #Ubuntu

情報 <1542文字>

Python の使い方メモ Vol.1

20220703053348-admin.png
 
時既に Perl の時代は終わり…世は Python が覇者となっている!って事なんでぼちぼち勉強しようかなーって思いつつちっとも重い腰が上がらないことはありませんか? 私はあります いやむしろずっとそんな感じです(瀧汗
まぁまぁそんな話はともかく…なんか動画サイトのアレをゴニョゴニョするのに便利な yt-dlp ってのがあるのですが…それが Python で書かれているらしいんですよね そんな訳で手っ取り早いインストール法と…過去のバージョンに戻したい!って時とかの操作法のメモなんかを少々ってかんじで

以降のメモは Python 3 を対象にしています
$ python --version
Python 3.8.10

Python モジュールのインストール ※ここでは yt-dlp をインストールする
$ sudo python -m pip install yt-dlp
もしくは…
$ sudo pip install yt-dlp
でもいいみたい? 違いがよく判ってないんで調べておくメモ

何かの都合で古いバージョンに戻したくなった時 ※ここでは yt-dlp を対象とする
$ pip install yt-dlp==
ERROR: Could not find a version that satisfies the requirement yt-dlp== (from versions: 2021.1.15, 2021.1.15.post1, 2021.1.16, 2021.1.20, 2021.1.24, 2021.1.24.post1, 2021.1.29, 2021.2.4, 2021.2.9, 2021.2.15, 2021.2.19, 2021.2.24, 2021.3.1, 2021.3.3, 2021.3.3.1, 2021.3.3.2, 2021.3.7, 2021.3.15, 2021.3.21, 2021.3.24, 2021.3.24.1, 2021.4.3, 2021.4.11, 2021.4.22, 2021.5.11, 2021.5.20, 2021.6.1, 2021.6.8, 2021.6.9, 2021.6.23, 2021.7.7, 2021.7.21, 2021.7.24, 2021.8.2, 2021.8.10, 2021.9.1, 2021.9.2, 2021.9.25, 2021.10.9, 2021.10.10, 2021.10.22, 2021.11.10, 2021.11.10.1, 2021.12.1, 2021.12.25, 2021.12.27, 2022.1.21, 2022.2.3, 2022.2.4, 2022.3.8, 2022.3.8.1, 2022.3.8.2, 2022.4.8, 2022.5.18, 2022.6.22, 2022.6.22.1, 2022.6.29)
ERROR: No matching distribution found for yt-dlp==

こんな感じでモジュール名の後ろに「==」を付ければバージョンが列挙される裏技みたいな感じ??

その中から指定のバージョンをインストールする ※ここでは yt-dlp を対象とする
$ sudo pip install yt-dlp==2022.5.18
こうすることで過去のバージョンとかでもインストールできます

ざっくり使った感じだとこの 2022.5.18 が TVer も abema もアレできていい感じですね

基礎中の基礎の前の初歩中の初歩な内容すぎて申し訳ないです… #Python #Ubuntu

情報 <1581文字>

Twitter(ツイッター)の隠しコマンド?

20220508181222-admin.png
 
Twitter(ツイッター)使ってますか? 私は使ってます…っていうかわりとべったりです(汗 そんな Twitter のホーム画面っていうかタイムラインに混入してくる「おすすめ」とか「〇〇さんが□□しました」系の推し(?)が本当にウザいですよねって事でその辺をどうにかできるかもしれない隠しコマンドがあるようなんで設定してみたメモです

そんな設定は「ミュートするキーワード」で行います ブラウザ版を使っている場合は→ https://twitter.com/settings/muted_keywo... にアクセスすることにより直接「ミュートするキーワード」にいけます

スマホ(Android)の場合は…

202205081812229-admin.png
左上のアイコンをタップしてメニューを出す

202205081812228-admin.png
下の方にある「設定とプライバシー」をタップする

202205081812227-admin.png
そのままメニューをたぐってもいいんだけど…判りやすく検索窓に「ミュート」と入力する

202205081812226-admin.png
出てきた「ミュートとブロック」をタップする

202205081812225-admin.png
「ミュートするキーワード」でたどり着けます

20220508185124-admin.png 202205081812224-admin.png
右下にあると思われる「+」でミュートするキーワードを設定するんだけど…この中で「ホームタイムライン」をチェックして「すべてのアカウント」を選び期間は「再度オンにするまで」にしとくと良いようです なおこの辺の設定はアカウントに対して行われてるようなんで PC ブラウザで設定すればそれがスマホアプリに反映されるし逆もまた然りって感じっぽいです 操作しやすいもので設定すればいいと思います
その辺の下準備が済んだところでミュートするキーワードです

ハイライト系
suggest_recap
suggest_recycled_tweet
generic_activity_highlights

おすすめユーザー系
who_to_follow_entry
suggest_who_to_follow

おすすめのツイート系
suggest_activity

○○さんがいいねしました系
suggest_activity_tweet
tweet-context with-icn

○○さんがフォローしています系
suggest_pyle_tweet
suggest_sc_tweet

ハッシュタグのおすすめ系
suggest_grouped_tweet_hashtag

他人のリプ表示をやめる系
filter:follows -filter:replies

過去のRT表示をやめる系
suggest_recycled_tweet_inline

よくわからないけど一応メモ
ActivityTweet
RankedOrganicTweet

この中で必要なものを選んで個々に設定すればいいようです。

202205081812223-admin.png
あーあとホーム画面の右上にあるアイコンをタップして…

202205081812222-admin.png
「最新のツイートがタイムラインに表示されます」って感じに設定しておく必要もあるようです

これで余計なものに邪魔されにくい平穏なタイムラインを楽しめます 「邪魔だな」って思ったおすすめなんかをちょっとづつ指定して試してみるといいでしょう
つーかまぁしょーみリスト管理しててホーム画面に来ることは滅多にないんでアレなんだけど(汗

  :

ミュートとは直接の関係はないんだけど…検索する時に便利なキーワードなどなどもせっかくなんでメモ

202205081812221-admin.png
そういや Android アプリ版の Twitter で実装されているんだけど…各人のプロフィールの右上にある「虫メガネ」アイコンですね これをタップすると「その人のツイートに限定した」検索ができるようです 検索クエリ的には from:@アカウント名 って感じですかね
その検索の応用っていうか…例えば「リツイートがやたらと多い人なんだけどその人が発したツイートだけを見たい!」って時なんかはその検索欄に「 」←全角スペースを入力するといいようです

その他にリプライのみを表示させたい場合は filter:replies を指定して…逆にリプライを除去したい場合は -filter:replies を指定します
メディア等が含まれたツイートのみを表示させたい場合は filter:links を指定して…逆にメディア等が含まれたツイートを除去したい場合は -filter:links を指定します

 検索するツイートの期間を絞り込みたい場合は…開始を since:YYYY-MM-DD と指定して終了を until:YYYY-MM-DD として設定します ※ YYYY は西暦, MM は月, DD は日を指します
コピペしやすいようにっていうか…そこそこ使い勝手がいい 1 年単位で列挙しておきます
since:2024-01-01 until:2024-12-31
since:2023-01-01 until:2023-12-31
since:2022-01-01 until:2022-12-31
since:2021-01-01 until:2021-12-31
since:2020-01-01 until:2020-12-31
since:2019-01-01 until:2019-12-31
since:2018-01-01 until:2018-12-31
since:2017-01-01 until:2017-12-31
since:2016-01-01 until:2016-12-31
since:2015-01-01 until:2015-12-31
since:2014-01-01 until:2014-12-31
since:2013-01-01 until:2013-12-31
since:2012-01-01 until:2012-12-31
since:2011-01-01 until:2011-12-31
since:2010-01-01 until:2010-12-31

・・とまぁこんな感じですかね またなにか思いついたら追加していくかも #Twitter

※追記※
だいぶん前に作ったやつだけど…そこそこ詳細に Twitter 検索できるようなやつ

情報 <2586文字>

続・モニタ無しでラズパイを VNC 接続できるとこまでセットアップする

20220429192311-admin.png
 
以前に 完全モニタ無しで Raspberry Pi 4 を VNC 接続できるとこまでセットアップするメモ なんてのを紹介していたんだけど…それから月日が流れ Raspberry Pi OS のバージョンが上がったっていうか色々とセットアップ方法が変更になったんでその辺をざっくり検証してみましたってメモです

20220429185131-admin.jpg 202204291851316-admin.png
そんな今回は某案件的な話で貸与して頂けた Raspberry Pi 400 を使用して 64-bit 版Raspberry Pi OS with desktop をモニタ無しっていうか SSH でログインしつつの VNC 接続できる所まで試してみました

今回の Raspberry Pi OS のバージョンアップの大きな変更っていうか…今までデフォルトで「pi」ってユーザが用意されていたのが今回からそれが廃止になったようです それに伴いパスワードを設定したユーザが不在(?)って事になり dd コマンドなりでイメージを書き込んだ後に boot パーティションのルートフォルダにファイル名が「ssh」もしくは「ssh.txt」のファイルを新規作成しただけでは SSH でログインできなくなってました

202204291851315-admin.png
それじゃどうしよう?って話なんですが…まぁ結果から云うとそれをどうにかする機能が追加された新しい Raspberry Pi Imager を使うって事らしいです
これの右下にある「歯車」のマークをクリックすると…

202204291851314-admin.png
「詳細な設定」として Pi OS の諸々の設定ができるようになります ユーザの設定だけでなく SSH の有効化や Wi-Fi の設定も一気に行えるんでこれは便利ですね!(>_<)w

  :

・・とまぁこれで話が終わっちゃうのも少々物足りない気がしたっていうか Pi Imager を使うことなくそれらの設定をどうにかする手法を模索してみました
まずはイメージファイルを dd なりで書き込んだものと Pi Imager を使って書き込んだものを比較してみました その結果 /boot/cmdline.txt に変更が加えられてたのと /boot/firstrun.sh が生成されていた事が判りました

それでは /boot/cmdline.txt から見ていきましょう

----- オリジナルの /boot/cmdline.txt -----
console=serial0,115200 console=tty1 root=PARTUUID=50a67c3e-02 rootfstype=ext4 fsck.repair=yes rootwait quiet init=/usr/lib/raspi-config/init_resize.sh splash plymouth.ignore-serial-consoles
----------

----- Pi Imager が作った /boot/cmdline.txt -----
console=serial0,115200 console=tty1 root=PARTUUID=50a67c3e-02 rootfstype=ext4 fsck.repair=yes rootwait quiet init=/usr/lib/raspi-config/init_resize.sh splash plymouth.ignore-serial-consoles systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target
----------

これを見る限り Pi Imager を使わずにどうにかする時にはオリジナルの /boot/cmdline.txt の末尾に systemd.run=/boot/firstrun.sh systemd.run_success_action=reboot systemd.unit=kernel-command-line.target を付ければよさそうです

ただし「root=PARTUUID=50a67c3e-02」の部分ですかね…これは配布されるディスクイメージのバージョン毎に異なる値を設定する必要があるようです
その辺の PARTUUID を調べるには lsblk コマンドを使うといいらしい? 試しに実際に USB メモリに書き込んだものと…ディスクイメージを直接マウントしたものとで確認してみました

$ lsblk -o TYPE,KNAME,PARTUUID,LABEL,SIZE,VENDOR
TYPE KNAME    PARTUUID    LABEL    SIZE VENDOR
disk sdg                          29.9G silicon
part sdg1     50a67c3e-01 boot     256M
part sdg2     50a67c3e-02 rootfs   3.7G
rom  sr0                          1024M HL-DT-ST
part loop39p1 50a67c3e-01 boot     256M
part loop39p2 50a67c3e-02 rootfs   3.7G

これを見る限り /rootfs に設定された PARTUUID をコピペすればいいようですね この辺は dd なりでイメージを書き込んだ場合には気にする必要はなさそうだけど /boot/cmdline.txt を全て自作で用意したいような場合には気をつけたほうがいいのかな?って思いました

  :

お次は /boot/firstrun.sh です 長くてややこしいですが頑張って見ていきましょう

----- Pi Imager が作った /boot/firstrun.sh -----
#!/bin/bash

set +e

CURRENT_HOSTNAME=`cat /etc/hostname | tr -d " \t\n\r"`
echo HostName >/etc/hostname
sed -i "s/127.0.1.1.*$CURRENT_HOSTNAME/127.0.1.1\tHostName/g" /etc/hosts
FIRSTUSER=`getent passwd 1000 | cut -d: -f1`
FIRSTUSERHOME=`getent passwd 1000 | cut -d: -f6`
if [ -f /usr/lib/userconf-pi/userconf ]; then
   /usr/lib/userconf-pi/userconf 'UserName' '$5$Salt$xxxxxxxxxx..'
else
   echo "$FIRSTUSER:"'$5$Salt$xxxxxxxxxx..' | chpasswd -e
   if [ "$FIRSTUSER" != "UserName" ]; then
      usermod -l "UserName" "$FIRSTUSER"
      usermod -m -d "/home/UserName" "UserName"
      groupmod -n "UserName" "$FIRSTUSER"
      if grep -q "^autologin-user=" /etc/lightdm/lightdm.conf ; then
         sed /etc/lightdm/lightdm.conf -i -e "s/^autologin-user=.*/autologin-user=UserName/"
      fi
      if [ -f /etc/systemd/system/getty@tty1.service.d/autologin.conf ]; then
         sed /etc/systemd/system/getty@tty1.service.d/autologin.conf -i -e "s/$FIRSTUSER/UserName/"
      fi
      if [ -f /etc/sudoers.d/010_pi-nopasswd ]; then
         sed -i "s/^$FIRSTUSER /UserName /" /etc/sudoers.d/010_pi-nopasswd
      fi
   fi
fi
systemctl enable ssh
cat >/etc/wpa_supplicant/wpa_supplicant.conf <<'WPAEOF'
country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
ap_scan=1

update_config=1
network={
   ssid="Wi-Fi_SSID"
   psk=0123456789abcdefxxxx..
}

WPAEOF
chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf
rfkill unblock wifi
for filename in /var/lib/systemd/rfkill/*:wlan ; do
  echo 0 > $filename
done
rm -f /etc/localtime
echo "Asia/Tokyo" >/etc/timezone
dpkg-reconfigure -f noninteractive tzdata
cat >/etc/default/keyboard <<'KBEOF'
XKBMODEL="pc105"
XKBLAYOUT="jp"
XKBVARIANT=""
XKBOPTIONS=""

KBEOF
dpkg-reconfigure -f noninteractive keyboard-configuration
rm -f /boot/firstrun.sh
sed -i 's| systemd.run.*||g' /boot/cmdline.txt
exit 0
----------

この中で書き換えるべき点はホストネームとなる HostName と作成するユーザ名 UserName & パスワード $5$Salt$xxxxxxxxxx.. と Wi-Fi に接続するための Wi-Fi_SSID &パスワード 0123456789abcdefxxxx.. の箇所でしょうか
その辺を踏まえた上で…もちっと再設定しやすいように改変してみました

----- 改変版 /boot/firstrun.sh -----
#!/bin/bash

USER='UserName'
PASS='$5$Salt$xxxxxxxxxx..'
WIFISSID='Wi-Fi_SSID'
WIFIPASS='0123456789abcdefxxxx..'
HOST='HostName'

set +e

CURRENT_HOSTNAME=`cat /etc/hostname | tr -d " \t\n\r"`
echo $HOST >/etc/hostname
sed -i "s/127.0.1.1.*$CURRENT_HOSTNAME/127.0.1.1\t$HOST/g" /etc/hosts
FIRSTUSER=`getent passwd 1000 | cut -d: -f1`
FIRSTUSERHOME=`getent passwd 1000 | cut -d: -f6`
if [ -f /usr/lib/userconf-pi/userconf ]; then
   /usr/lib/userconf-pi/userconf "$USER" "$PASS"
else
   echo "$FIRSTUSER:$PASS" | chpasswd -e
   if [ "$FIRSTUSER" != "$USER" ]; then
      usermod -l "$USER" "$FIRSTUSER"
      usermod -m -d "/home/$USER" "$USER"
      groupmod -n "$USER" "$FIRSTUSER"
      if grep -q "^autologin-user=" /etc/lightdm/lightdm.conf ; then
         sed /etc/lightdm/lightdm.conf -i -e "s/^autologin-user=.*/autologin-user=$USER/"
      fi
      if [ -f /etc/systemd/system/getty@tty1.service.d/autologin.conf ]; then
         sed /etc/systemd/system/getty@tty1.service.d/autologin.conf -i -e "s/$FIRSTUSER/$USER/"
      fi
      if [ -f /etc/sudoers.d/010_pi-nopasswd ]; then
         sed -i "s/^$FIRSTUSER /$USER /" /etc/sudoers.d/010_pi-nopasswd
      fi
   fi
fi
systemctl enable ssh
cat >/etc/wpa_supplicant/wpa_supplicant.conf <<WPAEOF
country=JP
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
ap_scan=1

update_config=1
network={
   ssid="$WIFISSID"
   psk=$WIFIPASS
}

WPAEOF
chmod 600 /etc/wpa_supplicant/wpa_supplicant.conf
rfkill unblock wifi
for filename in /var/lib/systemd/rfkill/*:wlan ; do
  echo 0 > $filename
done
rm -f /etc/localtime
echo "Asia/Tokyo" >/etc/timezone
dpkg-reconfigure -f noninteractive tzdata
cat >/etc/default/keyboard <<'KBEOF'
XKBMODEL="pc105"
XKBLAYOUT="jp"
XKBVARIANT=""
XKBOPTIONS=""

KBEOF
dpkg-reconfigure -f noninteractive keyboard-configuration
rm -f /boot/firstrun.sh
sed -i 's| systemd.run.*||g' /boot/cmdline.txt
exit 0
----------

こんな感じでしょうか 設定すべき項目を最初の方に集めてありそこを書き換えればいいようにしてみました
その中で注意すべき点と云いますか…パスワードですね
USER='UserName'
PASS='$5$Salt$xxxxxxxxxx..'

ログインするユーザのパスワードは暗号化されたものを指定する必要があるようです これは openssl passwd コマンドで生成できます

$ openssl passwd -5 -salt=Salt password
-5 オプションは SHA256 アルゴリズムでの暗号化を行います この他にも SHA512 で暗号化する -6 オプションや MD5 で暗号化する -1 オプションとかがあるようです
-salt を指定すると更に複雑な暗号化を行うことができるらしい? 本来なら -salt=$RANDOM のようにランダムに生成した文字列を指定するのが推奨されているようだけどお好みの文字列でも暗号化できます まぁなんか面倒くさそうだな…って感じなら -salt オプションを省略しても問題はないんですが汗

この /boot/firstrun.sh でユーザのパスワードを指定する際には必ず暗号化されたパスワードを使用する必要があり…平文のパスワードは使えません どうしても平文のパスワードを使いたい場合は /boot/firstrun.sh/rootfs/usr/lib/userconf-pi/userconf 内の「chpasswd -e」の「-e」を削除すればできなくもないけど… /rootfs/usr/lib/userconf-pi/userconf は ext4 ファイルシステムなので OS によっては読み書きできないかもだしアクセス権が root なのでその辺もどうにかしないといけないので色々と面倒です

そんな訳なんで…初ログイン後にすぐ別のパスワードに書き換えることを前提に仮的なパスワードを作っておきました
$ openssl passwd -5 -salt=pass pass として作成した $5$pass$Rl0JZvvs0bt2Lu8dfAumW11vh2yrd.LfOysdTyQ7Yt5 って感じのセキュリティのかけらもない意識低すぎて気絶しそうなパスワードですが実際にこれを設定した後に「pass」なるパスワード文字列でログインできます

※ これでログインしたら直ちに passwd コマンドなりで別のパスワードを再設定してください!

ユーザ名の設定の他に Wi-Fi についての設定というかパスワードについても説明しておきます
WIFISSID='Wi-Fi_SSID'
WIFIPASS='0123456789abcdefxxxx..'

ここでも暗号化されたパスワードを使用します これは wpa_passphrase コマンドで生成できます

$ wpa_passphrase "Wi-Fi_SSID" "password"
network={
   ssid="Wi-Fi_SSID"
   #psk="password"
   psk=bd3f1ad8bd45873a14351f4209ab719147a960587508a8c89ef776cd9b43cefb
}
こんな感じで 256bit PSK アルゴリズムを用いた暗号化パスワードが生成されます このパスワードを WIFIPASS='0123456789abcdefxxxx..' の部分に貼り付けてやれば ok です
ちなみに #psk="password" って平文のパスワードがコメントして出てるっていうかパスワードをダブルクォート「""」で囲んでやれば平文のパスワードを指定できるんで… WIFIPASS='"password"' って指定も ok です ただしこの設定はシステムが可動した後も /etc/wpa_supplicant/wpa_supplicant.conf 内に残り続けることになるんでリスクがあります 平文のパスワードは危険を承知した上でご使用ください

  :

まぁそんなこんなで上記の /boot/cmdline.txt と /boot/firstrun.sh を用意したらラズパイの電源を入れて起動します 数分ほど待った後に SSH でログインできます ログインした後は概ね 完全モニタ無しで Raspberry Pi 4 を VNC 接続できるとこまでセットアップするメモ の手順で VNC 接続できますが…その辺を切り取って手短に解説しておきます

まずは SSH でログインして $ sudo raspi-config 設定ツールを起動する キーボードの矢印キーで移動してエンターキーで決定ってスタイルです Tab キーを使う場面もあります ※ 画像は古いバージョンのものなので少々表示が異なる場合があります

202110151828034-admin.png
3 Interface Options を選ぶ

202110151828035-admin.png
I3 VNC を選ぶ

202110151828036-admin.png
VNC サーバを有効にするけどいいの?と聞かれるんで <Yes> を選ぶ

202110151828037-admin.png
設定完了

お次は vncserver-x11 ファイルを編集する
$ sudo nano /root/.vnc/config.d/vncserver-x11
末尾に以下の 3 行を追加します
2021101518280310-admin.png
Authentication=VncAuth
Encryption=AlwaysOff
Password=


VNC サーバを再起動する
$ sudo systemctl restart vncserver-x11-serviced

VNC のパスワードを設定する
$ sudo vncpasswd -service

VNC サーバを再起動する
$ sudo systemctl restart vncserver-x11-serviced

202204291851313-admin.png
以前はこの後に /boot/config.txt を編集する必要があったけど…今回はこの作業をする事なく VNC 接続できました

202204291851312-admin.png
せっかくなんで日本語化の設定も行っておきましょう

202204291851311-admin.png
Raspberry Pi Configuration の Location タブの Set Locate ボタンを押して Language と Character Set を設定して OK ボタンを押下する
その後に再起動することにより設定が反映され表示が日本語になります

あーあとお好みにより $ sudo apt install ibus-mozc でローマ字かな漢字変換っていうかインプット・メソッドをインストールしておくのもいいかも知れません これも再起動後に有効になります

20220429185131-admin.png
既に設定した後なんで心もち表示が違うかもだけど…右上のインジケータにあるキーボードマークをクリックして Mozc を選べば ok です

  :

まぁだいたいこんな感じですかね 今後のバージョンアップなりで使えなくなるかもな資料ですが…とりあえず /boot/firstrun.sh が何をしていたのかをざっくり知ることができてよかったです
つーか世間的に Ubuntu Desktop 22.04 LTS on a Raspberry Pi が話題のようですね…これはいっぺん試してみなきゃ!(>_<)wって感じで今回はこの辺で #[Raspberry Pi]

※ 追記 ※
上記の話とは概ね関係ないんだけど…ラズパイの Bootloader イメージを Raspberry Pi Imager を使わずにダウンロードしたい場合は https://downloads.raspberrypi.org/eeprom... に記された "SD Card Boot", "USB Boot", "Network Boot" のそれぞれの url を参照するといいらしい?(無保証 ちょっと調べてみたら判明した気がしたんで今後のもしものために記載しておくメモ

情報 <9939文字>

M5StickC Plus の開発環境を構築する

20220326153456-admin.jpg
 
elchika公式 @elchika_info さんがリツイートキャンペーンを行っていたのですが…

それに応募したら大当選しちゃいました!!

202203261507198-admin.jpg
そんな訳で M5StickC Plus を頂きました! 本当にありがとございます!!(>_<)w
こないだから Arduino Pro Micro をぼちぼちとイジり始めてマイコンへの興味が盛り上がってきた所なんで…さらに見識を深めることができそうで嬉しいです!(>_<)w

  :

そんな M5StickC Plus の外観をざっくり見てみましょう

202203261507197-admin.jpg 202203261507191-admin.jpg
裏面には I/O の説明が所狭しと書かれています 何かとよく使う(と思われる)ボタンや LED のピン番号なんかも書かれているんで…ド忘れした時とかでも困らないと思います

20220326150719-admin.jpg
USB は Type-C のようです

202203261507195-admin.jpg 202203261507194-admin.jpg
USB を挿して電源が供給されると起動するようです 何やらセンサーの状況が見れるようなものとかマイクのテストや赤外線の送信(?)のようなものを「M5」ボタンで切り替えながら楽しめるようです

202203261507193-admin.jpg
ちなみに電源の ON/OFF は側面のボタンを長押しすることにより操作できるようです 電源を ON する場合には 2 秒位の長押しで電源を OFF にする場合は 6 秒くらい長押しするといいようです

  :

そんな訳なんで動作チェックがてら開発環境を整えていきましょう M5StickC Plus (M5Stackシリーズ) の開発環境は Arduino IDE を使ってどうにかできるようなんで手軽っちゃぁ手軽ですよね
その辺の詳細な手順については 公式 に詳しく書かれていたんで…それを実際に試していきたいと思います

2022032615071914-admin.png
まずは Arduino IDE の「ファイル」→「環境設定」を開きます

2022032615071913-admin.png
その中の「追加ボードマネージャのURL」に https://m5stack.oss-cn-shenzhen.aliyuncs... を追加して「ok」します

2022032615071912-admin.png
お次に「ツール」→「ボード」→「ボードマネージャ」を開きます

2022032615071911-admin.png
検索欄に「m5stack」と入れて出てきた「M5Stack」をインストールする

2022032615071910-admin.png
これで「ツール」→「ボード」とした際に「M5Stick-C Plus」を選べるようになります

  :

使用ボードの設定が済んだらライブラリをインストールします

202203261507199-admin.png
「スケッチ」→「ライブラリをインクルード」→「ライブラリを管理」を開きます

202203261507198-admin.png
検索欄に「m5stickcplus」と入れて出てきた「M5StickCPlus」をインストールします

202203261507197-admin.png
なんか関連したライブラリ(?)も一緒にインストールするか聞かれるんだけど…よく判んなかったんで全部入れちゃいました(汗

202203261507196-admin.png
ちなみに…もし間違ったライブラリを入れちゃった時にそれを消したい場合は「ファイル」→「環境設定」で確認できる「スケッチブックの保存場所」で確認できるフォルダを開いて libraries フォルダを開いて

202203261507195-admin.png
該当するライブラリが入っているフォルダを削除して Arduino IDE を再起動すると消すことができるようです

202203261507194-admin.png 202203261507193-admin.png
まぁとにかくコレで M5StickC Plus のライブラリをインクルードできるようになりました

  :

それじゃ実際にコードを書いてコンパイルして M5StickC Plus に書き込んでみましょう まぁ本来なら LED を点滅させる…いわゆる「Lチカ」を試すべきトコロなんですが液晶ディスプレイが搭載されているって事なんでそれに文字を出力させてみましょう

----------

# include <M5StickCPlus.h>

void setup(){
  M5.begin(); // M5StickC Plus の初期化?
}

void loop() {
  M5.Lcd.setTextSize(3);  // フォントサイズ
  M5.Lcd.setRotation(3);  // 画面表示の向き
  M5.Lcd.print("Hello World");  // お決まりのやつ
}

----------

202203261507191-admin.png
ざっくりこんな感じですかね これをマイコンボードに書き込んでみましょう

202203261507192-admin.jpg
無事に「Hello World」と表示されて大成功です!

  :

ちなみにコンパイル時に…我が家の環境では以下のようなエラーが発生しました

Traceback (most recent call last):
  File "/home/nekoyama/.arduino15/packages/m5stack/tools/esptool_py/3.1.0/esptool.py", line 38, in <module>
    import serial
ModuleNotFoundError: No module named 'serial'
exit status 1
ボードM5Stick-C-Plusに対するコンパイル時にエラーが発生しました。


これは Python にシリアル通信するための pyserial モジュールが入ってない時に発生するらしいので…

202203261507192-admin.png
pip install pyserial としてインストールしておけばいいようですメモ

  :

20220326150719-admin.png
さらにちなみに初回起動時に動いていたセンサーの状況が見れるようなものとかマイクのテストや赤外線の送信(?)のようなものは「ファイル」→「スケッチ例」→「M5StickCPlus」→「FactoryTest」で再び入れ直せるようです 他にも参考になりそうなものが用意されているんで…ざっと目を通しておくといいかもです

  :

Arduino Pro Micro と M5StickC Plus を比べると…すぐに使える I/O が豊富でいろいろ興味を駆り立てられてしまいますね! 特に ESP32 ってやつですかね Wi-Fi でコネクションを確立したりその後に HTTP としてサーバからドキュメンをを取ってこれたりもできるようなんで Web サーバと連携させて M5StickC Plus をリモートで操作するようなものとか作れるかもしれんですね!
そんな感じで引き続きぼちぼちイジっていきたいと思います #Arduino #M5StickC

情報 <2732文字>

Android と PC のための adb メモ

20220321135913-admin.png
 
Android Debug Bridge(adb)なるものがあります Debug Bridge なんて聞くと「なんか面倒くさそうだな…」って思いがちだけど…一般利用者的な使い方でも「知っておくと得をする」ような便利な機能があるっていうか忘れがちな使い方をまとめて忘れ物防止メモって感じで書き留めておきます

  :

一般的には Android 端末と PC を USB ケーブルで繋ぎます なんかここんとこ Wi-Fi を使ってケーブル無しで繋ぐ手法が確立されてきてるようだけど…その話は後述って感じで

2022032113591336-admin.png 2022032113591335-admin.png
まずはスマホ側の設定ってことで「設定」メニューから「システム」→「端末情報」と進みます ※機種により若干の操作法の違い有り

2022032113591334-admin.png
その中にある「ビルド番号」を連打する

2022032113591333-admin.png
既に有効にしてあったんでアレだけど…これで「開発者向けオプション」を有効にできます

※機種によっては「設定」メニューからいきなり「デバイス情報」って入る場合や「詳細設定」の中に入っている場合とかがあるようです まぁその辺はそれっぽい項目を選びつつ「ビルド番号」を探し出してそれを連打してください(汗

2022032113591332-admin.png 2022032113591331-admin.png
次に「設定」メニューから「システム」→「詳細設定」と進むと「開発者向けオプション」が現れます ※これも機種によって進むべきメニュー項目が違う場合があるんで…あちこち探して辿り着いてください(汗

2022032113591330-admin.png 2022032113591329-admin.png
「開発者向けオプション」内にある「USBデバッグ」をオンにして許可する これで端末側の設定は概ね完了です

  :

お次は PC 側の設定ってことで…本来 adb ってやつは Android Studio って開発ツールに含まれているものなんでそれをセットアップする必要があるんだけど単に adb だけを使いたい人向けにシンプルなパッケージが用意されているんでそれを入れることにします

2022032113591328-admin.png
sudo apt install adb

なお動作検証っていうか…基本的に xubuntu 20.04 LTS を USB メモリに書き込んだものをお試しモードっていうかライブ起動して諸々を試しています 画像の撮り忘れなんかで…本番環境の Ubuntu MATE 20.04 LTS を使う場合もありますがその辺はご了笑ください汗

2022032113591327-admin.png
インストールが完了したら先ほどの「USBデバッグ」をオンにした Android と PC を USB ケーブルで繋ぎ adb start-server してみましょう daemon started successfully と出れば成功です
この操作は省略してもいいのですが…接続した Android 機と PC とのやりとりを管理するデーモン(adbd)の起動を明確に確認できるので知っておいていいと思います
ちなみにデーモン(adbd)を終了する際には adb kill-server します いろいろな端末を繋ぎつつ作業してると稀に動作が不安定になる場合とかあるようなので…いったんデーモンを終了させて再び起動させるとうまくいく時があります

2022032113591326-admin.png
そんな adb ですが 1 台の PC に複数台の Android を接続することができます 接続した Android(のシリアル) は adb devices で確認することができます

2022032113591325-admin.png
そのシリアルを adb の -s オプションを使って指定するとその Android 機を操作できます 画像はそれぞれの Android 内の proc フォルダ内にある cpuinfo ファイルの末尾 10 行くらいを表示させた一例です
なお PC に 1 台だけ Android 機を接続して使用する際にはこの -s オプションは不要です 今後は説明の簡略化っていうか 1 台だけの接続にして -s オプションは省略した感じでいきます

  :

それでは adb のよく使うコマンドの説明をちょびっとだけ…

2022032113591324-admin.png
adb shell で接続した Android 機にログイン(?)できます コマンドライン上の操作で Linux 系のコマンドが使えます

2022032113591323-admin.png
adb shell コマンド名 だと PC 側の端末上で接続した Android 機のコマンドを実行できます 実行結果を PC 側のファイルにリダイレクトしたりパイプに送り込んだりできます
ちなみにここで試している pm コマンドはパッケージ・マネージャ系のコマンドですね これに list package なる引数を与えて起動するとその端末にインストールされているアプリの一覧を確認できます ここではその結果から grep を用いて必要な行だけ抽出して表示させています

2022032113591322-admin.png 2022032113591321-admin.png
パッケージの一覧を確認できたってことで…それじゃ Android 機からファイルを取ってくる adb pull を使って apk(アプリ) を抜いてみましょう
目的とするアプリの ID(?) を指定することで apk ファイルを取得できます これはブラウザを使い Google Play で目的のアプリを表示させたアドレス欄にも表示されているんでその辺を参考にして grep で絞り込むといいでしょう
そうして出てきた中から /data/app/〜/base.apk を選択してコピーして adb pull /data/app/〜/base.apk として実行すると PC 側に base.apk として持ってこれます

この apk ファイルはそのまま Android 機にインストールすることができます インストールする場合は目的の Android 機に繋ぎ替えた後に adb install base.apk とすれば ok です

2022032113591320-admin.png 2022032113591319-admin.png
モノは試しに Fire HD 10 タブレット (10インチHDディスプレイ) 32GB - Alexa搭載 にインストールしてみました もともと Amazon apps 版の フェアリードール が入ってたんだけど…それとは別にインストールすることができました
Amazon Fire には Google Play が入ってないんだけどこの手法を使えばアプリのインストールが可能です…が多くの場合ハードやセキュリティの制約などで入れられないことが多いです まぁどうしてもの非常時に入ったらイイナ!って感じで覚えておくといいかもです(汗

  :

アプリのバックアップの別の手法として adb backup アプリID ってのもあるようですね ただこれは機種ごとのセキュリティ設定が強く影響するのか…同じアプリでも G 社の Android だとうまくいくけど S 社のそれだとうまくいかないなんてことがあるようです
まぁもしうまくバックアップすることができたとして…出来上がった *.ab ファイルを展開するツールが存在するっぽい?
android-backup-extractor ってものらしく abe.jar ってのがそれらしいです Java 環境が必要で java -jar abe.jar unpack 解凍元.ab 解凍先.tar で展開できるようです

  :

Android 機のスクリーンショットを PC で撮ってそのファイルを PC に保存するなんてこともできるようです

2022032113591311-admin.png
例えば adb exec-out screencap -p > ss$(date +%Y%m%d%H%M%S).png なんてすると実行した日時を付加した ss が PNG で PC に直接保存できます スマホの「電源 + 音量ダウン」のボタン同時押しがそれなりに使いにくいんで…微妙なタイミングを要するスクリーンショットを撮りたい時などに重宝すると思います

  :

Android 機の画面を PC 上で表示させられる scrcpy ってのがあるらしいです これは sudo apt install scrcpy で入れることができるようだけど…

2022032113591318-admin.png
我が家の環境ではエラーが出て動きませんでした

2022032113591317-admin.png
それじゃ別の手法で!ってことで snap 版の scrcpy を sudo snap install scrcpy で入れてみました ちなみに apt で入れる scrcpy の方が PATH の優先順位が高いんで /snap/bin/scrcpy って感じのフルパス指定で起動してみます

2022032113591316-admin.png
snap 版の scrcpy だとうまく動きました! 思ってたより動きもスムーズでいい感じです!(>_<)w
そんな snap 版の scrcpy を毎回毎回フルパス指定で起動するのも面倒だな…って事なんで snap の別名設定を使ってみることにします

2022032113591315-admin.png
設定は sudo snap alias 元のコマンド名 新しいコマンド名 でできるようなんで sudo snap alias scrcpy Scrcpy って感じで頭文字を大文字にしてみました エイリアスを確認する際には snap aliases とするようです

  :

同様に Android 機の音声を PC 上で再生させられる sndcpy ってのがあるらしいです これは rom1v / sndcpy の Get the app から sndcpy-v1.1.zip (※20220321現在) を落としてきて展開して…その中の sndcpy (シェルスクリプト) を実行すればいいようです

2022032113591314-admin.png
先ほどの Scrcpy と sndcpy を同時に使ってみた例 音声は 0.5 秒くらい(?)遅れてきてるみたいですね あーあと sndcpy を使用する場合には PC に vlc (メディアプレイヤ) を入れておく必要があるようです
スマホとかの小さい画面とショボいスピーカーの音声を PC 側に出すと見やすくて音もよくてとてもゴキゲンです! 追加投資は USB ケーブルだけ!って手軽さがいいですね!

  :

そんな USB ケーブルで PC と Android 機を繋ぎっぱなしってのもなんか邪魔くさいんで…その辺をネットワーク (Wi-Fi等) でどうにかする手法があるようです ※使用するにあたり…最初の設定段階では USB ケーブルで繋いでおく必要があります

2022032113591313-admin.png 2022032113591312-admin.png
まず adb tcpip 5555 で listen するポートを 5555 に設定します ポート番号は 5555~5585 の範囲で奇数番号のポートが使用できるらしい?
次に adb connect 192.16x.x.x32 で Android 機の IP アドレスを指定して接続します Android 機の IP アドレスは「設定」→「ネットワークとインターネット」→「Wi-Fi」→「接続済みのAP」→「詳細設定」等で確認できます ※使用機種によりメニュー構成が若干違います

connected to 192.16x.x.x32:5555 などと表示され接続したら…この時点で USB ケーブルを抜いても ok です
あとは adb shell なりの adb コマンドをネットワーク経由で使用できます

  :

さらに最近では最初からネットワークで全てが完了する「ワイヤレス デバッグ」も使用できるようになりつつあります ※ Android OS 11 以降で adb も新しいものが必要になります

202203211359136-admin.png
sudo apt install adb で入れたものは Ver. 1.0.39 で…これは古いので使えません じゃぁどうしよう?ってことで…それじゃ Android Studio を入れてみましょう

202203211359135-admin.png 202203211359134-admin.png
Android Studio のダウンロードページ のダウンロードボタンを押下して…お決まりの了解したぜ!チェックした後にダウンロードします ダウンロードしたファイルを解凍して android-studio/bin/studio.sh を実行するとインストールが開始します 本気で使う気がないのなら…まぁこの辺は適当でいいと思います(汗

202203211359136-admin.png
標準的なインストールを行うと ~/Android/Sdk/platform-tools/ の中に adb が用意されるのでこれを使ってみます Ver. 1.0.41 でした

2022032113591310-admin.png
お次は Android OS 11 以降の設定を行います 「設定」→「システム」→「開発者向けオプション」→「ワイヤレス デバッグ」をオンにして…その項目をタップする

202203211359139-admin.png
タップしたらワイヤレス デバッグの設定に入れるので…「ペア設定コードによるデバイスのペア設定」をタップする

202203211359138-admin.png
すると必要な情報が表示されるので…これをもとに adb で接続設定していきます なお古い adb と混同するをアレなんで ~/Android/Sdk/platform-tools/adb って感じのフルパスでコマンドを起動しています

202203211359133-admin.png
先ほど表示されてた IP アドレスとポート番号で ~/Android/Sdk/platform-tools/adb pair 192.16x.x.x31:43015 って感じで実行します するとペアリング・コードを聞かれるのでそれを入力します ※ここでは 226441 でした これでペアリング設定は完了です

202203211359132-admin.png 202203211359137-admin.png
お次は実際に接続します これは ~/Android/Sdk/platform-tools/adb connect 192.16x.x.x31:40643 って感じで実行します 先ほどのペアリング設定のポート番号とは別のポート番号になるので注意してください

202203211359131-admin.png
成功すればこれでワイヤレス接続が完了しています 後は ~/Android/Sdk/platform-tools/adb shell するなり色々をネットワーク経由で行えます 少々手順が多くて面倒かなーって思いつつ…まぁ慣れてしまえば USB ケーブルを接続するより楽ちんかなーってイメージです
ただし先述の scrcpy や sndcpy は(20220321現在)対応していない感じでした この辺が早く対応してくれればなーって思います

※ 追記 ※
テストした環境の PATH 設定の都合で Ver. 1.0.39 の adb が参照されていたので…その辺をどうにかしたら sndcpy は動きました Android Studio で入れた Ver. 1.0.41 を優先的に使えるよう PATH を以下のように設定しました
export PATH="/home/$USER/Android/Sdk/platform-tools:$PATH" ※ 標準的(?)な Android Studio のインストールを行った場合
一時的な設定ならコマンドラインで上記のように実行するもよし…再起動後とかも永続的に使いたいのであれば ~/.profile を編集して…最終行辺りに上記のパス設定を追加しておくといいでしょう

ちなみに scrcpy は apt 版(Ver. 1.12.1)と snap 版(Ver. 1.23)のどちらもワイヤレス環境では動作しませんでした(-_-;)

  :

そんなこんなな adb の使い方いろいろでした 他にも音量の操作やら特定のイベントシグナルの送信などなどアプリ開発のデバッグに有用な機能が用意されているのですが…まぁ末端ユーザでは概ね必要のない機能なのでその辺はもっとプロの方が発する情報をご参照くださいってことで #Android #Ubuntu #コマンドヘルプ

情報 <6454文字>

Arduino とマトリクスキーボード

202202201020325-admin.jpg
 
以前にどうにかしてたっていうか「Arduino Leonardo (Pro Micro) でマルチメディアキーボードを作ろう!」の後ぐらいにいろいろ調べてたら…なんかマトリクス・キーボードっていうの? 少ないピン数で多くのスイッチを読み取れる方法があるらしい??ってのを見つけて気になってたんで勉強がてらその辺を試してみることにしました

そんなマトリクスってのを日本語的に云うと…碁盤の目みたいなものって感じになるっぽい? それをキーボード的に実装すると…例えば横方向にマイコンから何本かの出力線を這わせておいてそれと何本かの入力線をスイッチを介してあたかも碁盤の目のように配置したもの?ってなるらしい??

202202201020324-admin.jpg 20211113072346-admin.jpg
そんな感じで Arduino VKLSVAN Pro Micro USB ATmega32U4 (※以下 Arduino と略す)を使って実際に作ってみました ちなみに Pro Micro のピン配列図はこんな感じです つーかブレッドボード(?)が狭くて碁盤の目になってなくてアレですよね(汗

202202201020325-admin.png
回路図的なものにするとこんな感じです つーか回路図なんて描いたことないんで正しくない図かもしれませんが…まぁ概念的な絵だと思って見てください(滝汗

202202201020324-admin.png
ここでは Arduino の 6 番ピンと 7 番ピンを出力として使い 14 番ピンと 15 番ピンを入力として使います
そして 6 番ピンのみに信号を出力します その状態で A もしくは B のスイッチが押されればそれぞれ対応した 14 番ピンか 15 番ピンで信号を読み取ることができます それにより A もしくは B のスイッチが押されたかどうかを特定できます
※なんか PULLUP 回路の都合上っていうの?
※普段は HIGH にしてあって…「出力する」としたものを LOW にすることで動作する仕組みです ちょっとややこしいですね(-_-;)

202202201020323-admin.png
引き続き今度は 7 番ピンにのみ信号を出力します その状態で C もしくは D のスイッチが押されればそれぞれ対応した 14 番ピンか 15 番ピンで信号を読み取ることができます それにより C もしくは D のスイッチが押されたかどうかを特定できます
この一連の動作を繰り返すことにより碁盤の目のように配置されたスイッチを特定していく…そんな構造のキーボードをマトリクスキーボードと呼ぶらしいです

それでは実際にスケッチを書いて動作を確認してみましょう

----------

#include "Keyboard.h"

void setup() {
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(14, INPUT_PULLUP);
  pinMode(15, INPUT_PULLUP);

  digitalWrite(6, HIGH);  // 6 番ピンを HIGH にしておく
  digitalWrite(7, HIGH);  // 7 番ピンを HIGH にしておく

  Serial.begin(9600);
  Keyboard.begin();
}

void loop() {
// -------------------------------- //
  digitalWrite(6, LOW); // 6 番ピンを LOW にしてスキャン開始
  if (digitalRead(14) == LOW) { // 14 番ピンは LOW になっている?
    Serial.print("A\n");  // LOW なら A のスイッチが押されている
    Keyboard.print("A\n");
  }
  if (digitalRead(15) == LOW) { // 15 番ピンは LOW になっている?
    Serial.print("B\n");  // LOW なら B のスイッチが押されている
    Keyboard.print("B\n");
  }
  digitalWrite(6, HIGH);  // 6 番ピンを HIGH にしてスキャン終了
// -------------------------------- //
  digitalWrite(7, LOW); // 7 番ピンを LOW にしてスキャン開始
  if (digitalRead(14) == LOW) { // 14 番ピンは LOW になっている?
    Serial.print("C\n");  // LOW なら C のスイッチが押されている
    Keyboard.print("C\n");
  }
  if (digitalRead(15) == LOW) { // 15 番ピンは LOW になっている?
    Serial.print("D\n");  // LOW なら D のスイッチが押されている
    Keyboard.print("D\n");
  }
  digitalWrite(7, HIGH);  // 7 番ピンを HIGH にしてスキャン終了
// -------------------------------- //
  delay(100); // 適当に待って繰り返し
}

----------

202202201020322-admin.png
シリアルモニタとテキストエディタを並べて動作テストしてみましょう スイッチに対応した文字が表示されれば成功です ちなみに今回はシリアルモニタとテキストエディタの両方に出力するようにしてますが…初期のテスト段階ではシリアルモニタだけを使ったほうがいいかもですね

  :

さてそんな感じでマトリクス・キーボードの大まかな動作を理解できた気になったんで…ぼちぼちマルチメディアキーボード的なやつを作るか!って思いたいトコロなんだけどスイッチ類を買い揃えて配線したりとかとかしょーみ面倒くさいなぁと思ってたんですよね そう思いつつ某Aマゾンを眺めてたら気になるものを見つけました

Ren He 5個セット 16キー 4*4 マトリックス フィルム ボタン キーボード マトリックススイッチ キーパッド メンブレン式 マトリックスキーボード Arduinoに対応

かなり怪しげな感じだけど 5 個入りなのに 600 円でお釣りがきちゃうお値段に釣られてポチっちゃいました 初期不良でいくつか動かなかったとしても十分にお値打ちっポイネ!!

202202201020323-admin.jpg
なんか適当なプチプチに包まれた荷姿で届くと思ってたのに…郵便受けをギリ通る厚みのプラケースに入れられて来ましたw やるじゃんww

202202201020321-admin.png
ちなみにデータシートの類は入ってなかったんで自力で配線を解析する必要があったけど…まぁわりと素直な作りだったんで簡単に判明してよかったです そして Arduino と接続したピン番号とかも

20220220102032-admin.jpg 202202201020321-admin.jpg 202202201020322-admin.jpg
実際に接続してみました まぁ毎度のごとく「接続例」って感じなんで…この辺は各人のお好みでどうにかしてもらえばいいかと思います

それでは早速スケッチを書いて動作を確認してみましょう

----------

const byte KEYOT[] = {4, 5, 6, 7};  // 出力ピンの設定
const byte KEYIN[] = {14, 15, 18, 19};  // 入力ピンの設定
const int WAIT = 100; // ちょっと待たせる

//  関数 keyscan() がキーの状態を読み取った値を入れておく配列
byte SW[sizeof(KEYOT)][sizeof(KEYIN)];

const byte ON = LOW;  // LOW と HIGH を…
const byte OF = HIGH; // ON と OF の別名で定義しておく

void setup() {
  for (byte i = 0; i < sizeof(KEYOT); i++) {
    pinMode(KEYOT[i], OUTPUT);  // 出力ピンのモード設定
    digitalWrite(KEYOT[i], HIGH); // 出力ピンを HIGH に設定
  }
  for (byte i = 0; i < sizeof(KEYIN); i++) {
    pinMode(KEYIN[i], INPUT_PULLUP);  // 入力ピンを PULLUP モードに設定
  }
  Serial.begin(9600); // シリアル通信を開始
}

void loop() {
  keyscan();  // キーの状態を読み取る
// -------------------------------- //
  if (SW[0][0] == ON) { Serial.println("1"); }
  if (SW[0][1] == ON) { Serial.println("2"); }
  if (SW[0][2] == ON) { Serial.println("3"); }
  if (SW[0][3] == ON) { Serial.println("A"); }
// -------------------------------- //
  if (SW[1][0] == ON) { Serial.println("4"); }
  if (SW[1][1] == ON) { Serial.println("5"); }
  if (SW[1][2] == ON) { Serial.println("6"); }
  if (SW[1][3] == ON) { Serial.println("B"); }
// -------------------------------- //
  if (SW[2][0] == ON) { Serial.println("7"); }
  if (SW[2][1] == ON) { Serial.println("8"); }
  if (SW[2][2] == ON) { Serial.println("9"); }
  if (SW[2][3] == ON) { Serial.println("C"); }
// -------------------------------- //
  if (SW[3][0] == ON) { Serial.println("*"); }
  if (SW[3][1] == ON) { Serial.println("0"); }
  if (SW[3][2] == ON) { Serial.println("#"); }
  if (SW[3][3] == ON) { Serial.println("D"); }
// -------------------------------- //
  delay(WAIT);
}

void keyscan() {  // キーの状態を読み取る関数
  for (byte i = 0; i < sizeof(KEYOT); i++) {
    digitalWrite(KEYOT[i], LOW);  // 特定の出力ピンを LOW にする
    for (byte j = 0; j < sizeof(KEYIN); j++) {
      if (digitalRead(KEYIN[j]) == ON) {  // 入力ピンの状態を読む
        SW[i][j] = ON;  // キーが押されている
      } else {
        SW[i][j] = OF;  // キーは離されている
      }
    }
    digitalWrite(KEYOT[i], HIGH); // LOW にした出力ピンを HIGH に戻す
  }
}

----------

冒頭にある

const byte KEYOT[] = {4, 5, 6, 7};  // 出力ピンの設定
const byte KEYIN[] = {14, 15, 18, 19};  // 入力ピンの設定
const int WAIT = 100; // ちょっと待たせる

この辺と…あとは loop() 内を書き換えるだけでいいように書いてみました 使用するピン数が増減しても概ね追従できると思います
あーあと setup() で使用したい機能を開始させるような辺りの書き換えも必要ですかね

それと…話がややこしくなるとアレなんで今回はシリアル通信のみで動作チェックしています

キーの状態を読み取る keyscan() 関数ってのがありますが…グローバル変数を使っているので引数も返り値もありません
読み取った値は SW[][] って二次元配列にセットしています

あと PULLUP の特性っていうか普段が HIGH となっていて…スイッチが押されると LOW になるって辺りが何となく直感的にイメージしにくいように思えたんで
const byte ON = LOW;  // LOW と HIGH を…
const byte OF = HIGH; // ON と OF の別名で定義しておく
って感じでスイッチが押されてる ON ってのとスイッチが離されてる OF って感じの別名で定義してあります

  :

そして loop() 内で keyscan() を呼んでキーの状態を読み取っておいて…あとは個々の SW[][] の状態に応じてお好みの文字(列)を出力させるなり何なりをすればいいでしょう
将来的にっていうかもし「1」が押されてたらキーボードとして文字を出力して「#」が押されてたらマウスの右クリックを…って感じで様々な処理を行えるよう少しばかり長ったらしく書いてあります

この辺の「やること」の種類が決まりきっているのであれば

void loop() {
  keyscan();
// -------------------------------- //
  const char* str[] = {
    "1\n", "2\n", "3\n", "A\n",
    "4\n", "5\n", "6\n", "B\n",
    "7\n", "8\n", "9\n", "C\n",
    "*\n", "0\n", "#\n", "D\n",
  };
  for (byte i = 0; i < sizeof(KEYOT); i++) {
    for (byte j = 0; j < sizeof(KEYIN); j++) {
      if (SW[i][j] == ON) {
        Serial.print(str[sizeof(KEYOT) * i + j]);
      }
    }
  }
// -------------------------------- //
  delay(WAIT);
}

こんなふうに for() で回すように書けばもうちょっとスッキリするかもしれんですね これはまぁ「やること」の内容によっていろいろ書き方があるよねって事で参考までに

  :

あーあと後半の中華クソ安 16 キーボードでの実験ではテキストエディタへの文字出力を行っていないんですが…これを実際に行うと不具合が出るものがあります

20220220102032-admin.png
キーボード上の「*」 なんですけどね シリアルモニタではちゃんと「*」が出力されているのに…テキストエディタ上では「(」になってしまいます
※別のスケッチでテストした際の画像です

これは #include "Keyboard.h" した際のキーボードレイアウトが KeyboardLayout_en_US になってて…まぁいわゆる US キーボードってやつですかね これが日本で一般的に使われている JIS キーボードとレイアウト(キーの配置)が違うから起こる現象のようです
アルファベットと数字に関しては問題ないのですが…記号はその多くが US と JIS とで配列が違うんで問題になります

202202201033061-admin.png
先ほどの「*」を例にすると… US キーボードでは「*」が「8」のキーに割り当てられています

20220220103306-admin.png
そのキーコードを JIS キーボードに当てはめると「8」のキーにあるのは「(」って事になるので正しく表示されないのです

困っちゃいますよね…そこそこ数が出回ってる(と思われる) JIS キーボードなんだで Keyboard.begin(KeyboardLayout_ja_JP); くらい通るようにしといてほしいのに!って思うんだけどそうはいきません(-_-;)

どうしたもんかなーって思いつつ調べてたら…何やら Keyboard.h を複製した後に JIS キーボード向けに書き換える手法を見つけました→ Arduino Leonardoで\記号を打つ:メガギガテラス:So-netブログ
実際に試してみたところ…これでバッチリ動作しました! まぁあくまで自己責任で!って感じになるんだけど…自作キーボードでいろいろしたい!って場合にはとても有用な情報だと思うんで参考にしてみてください

  :

キーボード自作って話になると耳にするマトリクスキーボードってやつですね 今までは何となく漠然と聞き流していたんですが…今回はその辺に踏み込んでその原理をほんのり理解できた気になれてよかったです マイコン側のピン数を減らしつつ…より多くのキーを読み取るテクニックは今後も色々と使えそうなんで今後もより掘り下げた勉強をしていきたいなって思いました

あと…マトリクスキーボードの構造的な問題っていうか特定条件の複数キーの同時押しをした際に正常に動きません 今回の中華クソ安 16 キーボードを使った実験を例にとると…縦軸に配されたキー(例えば「1と4」とか「AとBとC」とか)を同時押しすると「どれも押されてない」って判定されます まぁなんか読みたいキーとは別のスイッチを介して HIGH が逆流してくる(?)ことにより誤動作するらしく…そんな逆流を防ぐためにダイオードを入れるって事らしいんですが今回は既成品でその辺をどうにもできかねる感じなんでそのままにしてあります(汗

まぁその辺を踏まえた上で…この中華クソ安 16 キーボードでマルチメディアキーボード的なものを作るとしますかね 程々に硬い板に貼り付けて配線の取りまとめをすればいいだけって感じの見栄えよりお手軽さ!って感じのアレですが(瀧汗

そんなこなんで今回も長々とお疲れさまでした! 安いのに遊び甲斐のある Arduino をもっと色々と使い倒していきタイネ!(>_<)w #Arduino

情報 <7955文字>

Arduino の シリアル通信

20220130153543-admin.png
 
Arduino VKLSVAN Pro Micro USB ATmega32U4 (※以下 Arduino と略す)で開発してる際のデバッグとかで「変数がどうなってるのか」を見たくなることはありませんか? 私はあります いやむしろ見ないとやってられません!!(>_<)wって感じなんだけど Arduino にはモニタ出力がありません

20220130153543-admin.jpg
なんか LCD ディスプレイとやらを接続すれば文字なんかを表示させられるっぽいけど…接続とか面倒そうだし手が出ないなぁと思ってたけど何やら「シリアル通信」ってので PC と文字のやりとりができるらしいじゃん!? まじか!!

そんなシリアル通信は Arduino IDE から使えるようです

202201301535436-admin.png
[メニュー] → [ツール] → [シリアルモニタ] と操作するか…

202201301535435-admin.png
もしくはツールバーにある虫メガネみたいなアイコンを押下すると出てきます

202201301535434-admin.png
出てきた「シリアルモニタ」はこんな感じです
我が家の環境では「改行コード = LFのみ」で「通信速度 = 9600bps」が初期設定となっていました

通信速度は Arduino 側で設定した速度とシリアルモニタで設定する速度は同じものにしておく必要がある…らしいんですが軽くイジってみた感じでは設定値に関係なく動作しているっぽい感じなんで気にする必要はないのかもしれません

改行コードは Linux 系が「LF」で Mac 系(の昔のやつ?)が「CR」で… Windows 系が「CR+LF」と聞いたことがあるので機種ごとに適切なものに変更しておいたほうがいいの?
ざっくり使ってみた感じでは… Arduino からシリアルモニタに送られてくる場合の改行コードは設定の影響を受けないっぽいです シリアルモニタから Arduino に送信する際に「送信ボタンを押した」もしくは「エンターキーを押した」時に改行コードをどのようにするかの設定となるようです なおここでの解説は改行コードを「LFのみ」に設定してあるものとします

  :

それでは早速と言うか Arduino からシリアルモニタに文字を送信してみましょう シリアル通信を開始するには以下のように記述します
Serial.begin(speed) ってやつですね speed は BPS で… 300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 115200 なんかがよく使われがちですがそれ以外の値も指定できるようです ここでの解説では「9600」を使用するとします

その後は Serial.print() や Serial.println() を使ってシリアルモニタに数値や文字列を送信できます Serial.print() は C で云うトコロの sprintf() を意識した作りとなってるようですが…書式を全て解説するのもアレなんでその辺は手を抜いてざっくりサンプルを載せておきます

----------

void setup() {
  Serial.begin(9600); // シリアル通信を開始する
}

void loop() {
  unsigned long time = millis() / 1000; // 動作時間を秒の単位で取得
  String h = String(time / 3600); // 「時」を文字列にして取得
  String m = String((time % 3600) / 60); // 「分」を文字列にして取得
  String s = String(time % 60); // 「秒」を文字列にして取得

  Serial.println(String(h + ":" + m + ":" + s)); // 整形して送信
  delay(1000); // 1 秒待って繰り返し
}

----------

202201301535431-admin.png
これは Arduino が動作開始してからの時間を延々と表示し続けるってものです 動作開始してからの時間をミリ秒で取得できる millis() 関数を使い…そこで得られた変数の値を人の目で確認しやすい(と思われる)「時:分:秒」に整形して表示しています こんな感じで変数の内容を確認できるんでデバッグなどでは大いに助かりますね!

  :

シリアルモニタには Arduino に対して文字を送信する機能も備えられています それを Arduino 側から Serial.read() などを使って読み取るわけですが…基本的に「1文字づつ」送られてくる扱いになるようです
例えば「a10Z」と送信すれば「a」と「1」と「0」と「Z」に「設定した改行コード」が加えられて順番に送られてきます

あと文字は「文字コード」として送られてきます 「a」だと「97 (0x61)」となり「1」だと「49 (0x31)」となり「0」だと「48 (0x30)」となり「Z」だと「90 (0x5A)」で…最後に「改行コード(LF)」が「10 (0x0A)」って感じになります
デバッグ時に「ループ回数の変数をプログラム実行中に指定の値に変更したい」とかの場合だとシリアルモニタから送られてきた「文字」を「数値」に変換する必要があります

----------

void setup() {
  Serial.begin(9600); // シリアル通信を開始する
}
 
void loop() {
  if (Serial.available() > 0) // 文字(列)が送信されてきた?
  {
    byte data = (Serial.read()); // 送信されてきた文字を読み込む
    if (data >= '0' && data <= '9') { // 文字は数字か?
      data = data - '0'; // 文字コードを数値に変換
      Serial.print("数字が入力された [" + String(data) + "]\n");
    } else {
      if (data == 10) { // 改行コードは表示に不都合があるんで
        data = 0; // 何も表示しないようにしておく
      }
      Serial.print("数字以外 [" + String((char)data) + "]\n");
      data = 0; // 数字以外は「0」と扱うことにする
    }

    for (int i=1; i <= data; i++){ // 送信された数値の数だけループしてみる
      Serial.print(String(data) + " 回ループの " + String(i) + " 回目\n");
    }
  }
}

----------

202201301535433-admin.png
シリアルモニタに適当な適当な文字(列)を入力して「送信」します

202201301535432-admin.png
送信した文字に「数値」があればその数だけ for() のループ回数を変更します 「数値」以外の文字は処理しないようにしてあります
なお「1文字づつ」送られてくるってのが少々厄介で…「10以上の数値」を扱う場合にはひと工夫必要です どうしたらいいんだろう? 先に送られてきた数値を10倍してそこに新たに送られてきた数値を足すって流れになるのでしょうか ちょっと大掛かりになってくるっていうか面倒なんで今回はスルーしときますが概ねそんな流れになると思います(汗

そんなこんなな Arduino のシリアル通信なお話でした しょーみもっと早く知っとっけばよかった!って思いつつ書いてましたとさ(瀧汗 #Arduino

  :

※ 追記 ※
「いやいや Arduino でも sprintf() って使えるじゃん?」ってタレコミを頂いんたんでよくよく調べたらありました(汗 参照している Arduino リファレンス にはその辺が載ってなかったんで…無いものとばっか思ってました

他にも数値かどうか?を判定する isDigit() や改行の判定に使ってたものを isPrintable() に置き換えられるようなものもありました
上で説明してる数値を変数に代入して云々…で書いてるものもこんな感じでできそうです

    if (data >= '0' && data <= '9') { // 文字は数字か?
        ↓
    if (isDigit(data)) { // 文字は数字か?

----

      if (data == 10) { // 改行コードは表示に不都合があるんで
        ↓
      if (!isPrintable(data)) { // 改行コード等は表示に不都合があるんで

いやはや勉強になりました! 興味本位でなんとなく始めた Arduino ってことでまだまだ知らないことだらけなんで…こうして知識が広がっていくのは楽しいです どもありがとございました!(>_<)w

情報 <3825文字>

Windows の BAT ファイルで CGI しよう

20220103162756-admin.png
 
ぺけぽんっていうか Windows XP の頃にあまりにも怒り心頭になって勢い Microsoft に三行半を叩きつけてからずっと Linux な生活の日々なんですが…まぁそうは云いつつ Windows も使える事をアピールしておかないとオファーが来ないんでって感じなんで今回は Windows 的な話題っていうか BAT ファイルを CGI にしちゃおう!って解説です(汗

つーか初っ端から言い訳を並べるっていうか…我が家の Windows 機は 2015 年くらいに発売された 2 万円くらいの 8 型 Windows 10 タブレットって事なんでものすっごい低スペックです リソースなんて OS を起動するだけで使い果たしちゃう感じなんで新たにアプリケーションとか入れる余裕が全くありません なんか Windows でも CGI を動かす時は Perl や Python なんかを導入してるようですが…そんなんが入る空きなんてこれっぽっちも無いんで「それじゃ今あるもので何とかするか」ってことで標準的に使える BAT ファイルを引っ張り出してきた感じですかね(泪

世間的には Windows の Web サーバは Apache や IIS がよく使われている感じ?らしいですが…もちろんその辺を入れる余裕がないんで軽量コンパクトな Web サーバとして有名だった AN HTTPD を microSD に入れて一時的なテスト環境をざっくり構築してみました ただ AN HTTPD はかなり前に開発が終了しちゃってるらしく正規のルートでは配布されていないらしい? まぁとりあえず anhttpd download なるキーワードでぐぐってみたらどうにかなったけどお約束っていうかこの辺は自己責任でお願いします

  :

あーあと殆ど大半の方には関係のない話っていうか Windows の BAT ファイルを Linux で作ろうとすると色々と不都合が発生するようです…いや発生しました ごくシンプルにっていうか @echo off とだけ書いた BAT ファイルすら動かない致命的な不都合でありつつ原因に気付くまで結構かかりました orz

まずは改行コードです Windows 系列の改行コードは「CR+LF」なのに対して Linux 系列の多くは「LF」のみとなっていて…この改行コードの違いが問題になります ちなみに Mac OS では「CR」な改行コードを吐くものもあるとか??
Windows のみで開発していれば(ほぼ)気にする必要はないようですが…他の OS 上で書いた場合は改行コードの違いに要注意です

お次は文字コードです これもうっかりやらかしたっていうか Linux 上で何気に UTF-8 で書いた BAT ファイルを Windows に持ってきてテストしたらうまく動きませんでした
この文字コードの問題は改行コード以上に呪わしいっていうか…例えば Windows 標準の「メモ帳」を使用したとしてもバージョンの違いによりデフォルトの文字コードが Shift-JIS (ANSI) だったり UTF-8 だったりするので注意する必要があります

202201031627564-admin.png
なお我が家の Microsoft Windows [Version 10.0.19042.1348] に付属のメモ帳ではデフォルト文字コードが UTF-8 だったようですが…いつの間にかメモ帳も進化したのか保存時に文字コードを選択できるようになってました ちなみに ANSI ってやつが Shift-JIS っぽいですね

つーかそもそもコマンドプロンプトで使用する文字コードが Shift-JIS (ANSI) だって保証がどこにも無いみたいです 我が家は(たぶん)何も設定していないので Shift-JIS (ANSI) になってましたが…これを他の文字コードに設定することも可能のようです

202201031627563-admin.png
単に現在の文字コードを確認するだけなら「コマンドプロンプト」のタイトルバーを右クリックして「プロパティ」を参照するといいでしょう
もし他の文字コードに変更したい場合は chcp コマンドを使います 単に chcp とした場合には現在の設定値を表示します 変更したい場合は chcp 番号 という感じで番号を指定します 代表的なものだと「ANSI/OEM 日本語;日本語 (shift-jis) が 932」で「EUC 日本語が 51932」で「Unicode (UTF-8) が 65001」となるようです この辺の コードページ識別子の一覧 が公式(?)にあったんで参考になるかも? ちなみになんか面倒くさそうだったんで実際に変更して試してないんで今後は Shift-JIS (ANSI) で話を進めます(瀧汗

  :

さて…その辺の注意事項もろもろを無事にクリアしたところで実際に BAT ファイルを CGI として動かしてみましょう

202201031627561-admin.png 202201031627562-admin.png
とりあえず AN HTTPD に CGI の設定をしましょう…っていうか特に何もしなくても動くっぽい?
我が家では拡張子「.bat」に対する「実行プログラム」を「空欄」もしくは「c:\windows\system32\cmd.exe /c」で動作しました テストする環境が無いんで IIS (Internet Information Services) 等では試せてないのですが…おそらくそっちも「.bat」に対する設定で「(空欄)」もしくは「c:\windows\system32\cmd.exe /c」を設定すれば動くのかなーって思ってます この辺はどなたか実際に試して教えて頂きたい感じですね ※ご連絡頂いたんで…本記事の最下部くらいに設定法などを転載してあります

そんな設定が済んだ所で…それじゃ実際に BAT ファイルを作って試してみましょう 以下のバッチファイルを適切な改行コードと文字コードで保存した後に Web サーバのドキュメントルート以下に配置してブラウザからアクセスしてみましょう

----- bat_cgi01.bat -----

@echo off

REM ブラウザに「Content-type:」を出力する(単純なTEXTファイル用)
echo Content-type: text/plain; charset=Shift_JIS
echo.

REM Windows のバージョンを表示 ※空行も表示されている
ver

REM 環境変数に設定されている CPU の種類を表示
echo %PROCESSOR_IDENTIFIER%

echo.

REM 現在日時を date と time コマンドで表示
echo. | date
echo. | time

echo.

REM 必要な所だけ抽出してみる
echo. | date | find "現在の日付"
echo. | time | find "現在の時刻"

echo.

REM 環境変数でも現在日時を取得できるっぽい??
echo %DATE%
echo %TIME%

echo.

REM 日付が設定されている変数から「年月日」を取得してログファイル的なファイル名を作ってみる
echo %DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log

----------

202201031627569-admin.png
正しく動作すればこんな感じの内容がブラウザに表示されます
今回は単純なテキストを表示するのに適した「Content-type: text/plain; charset=Shift_JIS」で表示させてみました コマンドプロンプトを別の文字コードに設定している場合はここの「charset=」以降を適当なものに変更する必要があると思います

Linux の Web サーバだと実行属性がどうのこうのとかありますが Windows にはそんなものは無いので「.bat」の拡張子のものであればそれだけで動きます ちなみに大文字/小文字の区別も無いのでブラウザのアドレス欄で「BAT_CGI01.BAT」なんて大文字で指定しても動きます
※少なくことも我が家の AN HTTPD を使用した環境では動きました
※テストした環境が FAT32 ファイルシステムの microSD だったから大文字/小文字の区別がない? これが NTFS ファイルシステム上だと大文字/小文字を区別するの? どうなの??

さて…うまく動きましたか? 無事に動いたのなら次にいってみましょう

----- bat_cgi02.bat -----

@echo off

REM ブラウザに「Content-type:」を出力する(HTML出力用)
echo Content-type: text/html; charset=Shift_JIS
echo.

REM HTML なページとしてのお約束的なヘッダ部
echo ^<!doctype html^>
echo ^<html^>^<head^>^<title^>今日の運勢的な?^</title^>^</head^>^<body^>

echo 今日の運勢は…^<font color=red^>

REM コマンドの実行結果を変数に取り込む
for /f "usebackq delims=" %%A in (`echo. ^| time ^| find "現在の時刻"`) do set RAND=%%A

REM 「%RAND:~-1%」で変数の末尾 1 文字を取り出す
if %RAND:~-1% == 0 echo [%RAND:~-1%] ☆大吉☆
if %RAND:~-1% == 1 echo [%RAND:~-1%] 中吉
if %RAND:~-1% == 2 echo [%RAND:~-1%] 中吉
if %RAND:~-1% == 3 echo [%RAND:~-1%] 吉
if %RAND:~-1% == 4 echo [%RAND:~-1%] 吉
if %RAND:~-1% == 5 echo [%RAND:~-1%] 吉
if %RAND:~-1% == 6 echo [%RAND:~-1%] まぁまぁ
if %RAND:~-1% == 7 echo [%RAND:~-1%] 凶
if %RAND:~-1% == 8 echo [%RAND:~-1%] 凶
if %RAND:~-1% == 9 echo [%RAND:~-1%] 大凶(-_-;)

echo ^</font^>です^<br^>

REM HTML なページとしてのお約束的なフッタ部
echo ^</body^>^</html^>

----------

202201031627568-admin.png
正しく動作すればこんな感じの内容がブラウザに表示されます 何度かロリードして試してみたってイメージでw
今回はホームページな表示をさせるために「Content-type: text/html; charset=Shift_JIS」を設定してみました これで文字装飾などの「タグ」が使えるようになるのですが…この「タグ」に使用する「<」と「>」はそのまま使うとファイルにリダイレクト/インリダイレクトするための記号として解釈されてうまく動かなくなるのでそれをエスケープするための記号「^」を使用しています コマンドの実行結果を変数に取り込むバッククオート内ではパイプに使用する記号「|」なんかも誤動作するんでエスケープする必要があります

そんなこの CGI はありがちな「おみくじ CGI」です 標準コマンドである time の出力の中から「現在の時刻」が含まれる行を変数に取り込み…その文字列の末尾(この場合 0〜9 になる)を取り出しその値に対して処理を振り分けています
ざっと見た感じ time は 1/100 秒の値まで表示している感じなんで…その値をざっくりした乱数値として使用しています ちなみに大まか作ってから気づいたのですが…バッチファイル内で乱数となる変数 %RANDOM% なんてのもあるようです これは 0~32767 の得られるようですね たぶん動作はこちらのほうが軽いと思います

さてさて…うまく動きましたか? 無事に動いたのなら次にいってみましょう

----- bat_cgi03.bat -----

@echo off

REM ブラウザに「Content-type:」を出力する(PNG画像用)
echo Content-type: image/png
echo.
goto MAIN

REM 時間を判定して画像を表示する用のサブルーチン
REM call :SUB <時間> <ファイル名>
REM  <時間> は 0~23 で指定
REM  <ファイル名> は表示させたい PNG 画像を指定
:SUB
echo. | time | find " %1:" > NUL
if %errorlevel% == 0 type %2
exit /b

REM サブルーチンを呼んだりするメイン的な処理
:MAIN
call :SUB 0 img_shinya.png
call :SUB 1 img_shinya.png
call :SUB 2 img_shinya.png
call :SUB 3 img_shinya.png
call :SUB 4 img_shinya.png
call :SUB 5 img_asa.png
call :SUB 6 img_asa.png
call :SUB 7 img_asa.png
call :SUB 8 img_asa.png
call :SUB 9 img_asa.png
call :SUB 10 img_hiru.png
call :SUB 11 img_hiru.png
call :SUB 12 img_hiru.png
call :SUB 13 img_hiru.png
call :SUB 14 img_hiru.png
call :SUB 15 img_hiru.png
call :SUB 16 img_hiru.png
call :SUB 17 img_hiru.png
call :SUB 18 img_yoru.png
call :SUB 19 img_yoru.png
call :SUB 20 img_yoru.png
call :SUB 21 img_yoru.png
call :SUB 22 img_yoru.png
call :SUB 23 img_yoru.png

----------

以下の画像を bat_cgi03.bat と同じフォルダ内に用意しておいてください
img_asa.png img_hiru.png img_yoru.png img_shinya.png
※画像は いらすとや さんから拝借しました いつもありがとございます

202201031627567-admin.png
今度は少し変わり種っていうか…正しく動作すればこんな感じの内容がブラウザに表示されます(表示は時間帯によって変化します)
今回は HTTP ヘッダとして「Content-type: image/png」出力しています これは一体何なのかと云うと…ブラウザから見たこの CGI (bat_cgi03.bat) 自体が画像となっている事を意味しています
つまりブラウザから見れば「同じファイル名」なのに「アクセス毎に違う画像」を表示できることなんですね

※なお本来はテキストデータを扱う type コマンドでバイナリデータを標準出力に流しています ざっとテストした感じではうまく動いているっぽいけど…どうなんだろうね? なんか type コマンドの仕様に「EOF」が出現したら動作を終了する的なものがあるらしいんだけど…ねぇ?
※バイナリを扱える copy コマンドで出力先を CON にして試してもみたんだけど…こちらは明確に誤動作していました 確か CON デバイスは改行コードの置換などそんな動作を行うんだっけ? 何も変換とかの動作をしない…シンプルに全てのデータをそのまま標準出力に流してくれるものがあればいいのですが

話を元に戻して… CGI (bat_cgi03.bat) 自体が画像ファイルとして動作することのメリットを考えてみましょう
例えば迷惑系の HTML メールですかね その中の画像として他のサーバにある bat_cgi03.bat を呼ばれたとしましょう するとその他の場所にあるサーバ内ではアクセスログが記録されます それ即ち「メールが読まれたこと」を記録することになります

もちろんそれだけでは大きな脅威にならないかもだけど…そこには面白い使い方があるんですよ

ブラウザから見た bat_cgi03.bat は単なる画像ファイルだけど実際にはプログラム的な動作をしています そしてプログラムの動作には「引数」を与えることができます
先ほどの bat_cgi03.bat の呼び出しに対して bat_cgi03.bat?HOGEHOGE と云った感じで「引数」を与えて呼び出しそれを解釈できるようになります この引数の部分にメールを受信した人を特定する一意のデータ(この場合はHOGEHOGE)を付けておき…その一意のデータと送信したメールアドレスを紐づけておけば「誰がメールを読んだのか」を知ることができます

例として先ほどの bat_cgi03.bat の末尾にログを収集するものを付加してみましょう

----- bat_cgi04.bat -----

@echo off

REM ブラウザに「Content-type:」を出力する(PNG画像用)
echo Content-type: image/png
echo.
goto MAIN

REM 時間を判定して画像を表示する用のサブルーチン
REM call :SUB <時間> <ファイル名>
REM  <時間> は 0~23 で指定
REM  <ファイル名> は表示させたい PNG 画像を指定
:SUB
echo. | time | find " %1:" > NUL
if %errorlevel% == 0 type %2
exit /b

REM サブルーチンを呼んだりするメイン的な処理
:MAIN
call :SUB 0 img_shinya.png
call :SUB 1 img_shinya.png
call :SUB 2 img_shinya.png
call :SUB 3 img_shinya.png
call :SUB 4 img_shinya.png
call :SUB 5 img_asa.png
call :SUB 6 img_asa.png
call :SUB 7 img_asa.png
call :SUB 8 img_asa.png
call :SUB 9 img_asa.png
call :SUB 10 img_hiru.png
call :SUB 11 img_hiru.png
call :SUB 12 img_hiru.png
call :SUB 13 img_hiru.png
call :SUB 14 img_hiru.png
call :SUB 15 img_hiru.png
call :SUB 16 img_hiru.png
call :SUB 17 img_hiru.png
call :SUB 18 img_yoru.png
call :SUB 19 img_yoru.png
call :SUB 20 img_yoru.png
call :SUB 21 img_yoru.png
call :SUB 22 img_yoru.png
call :SUB 23 img_yoru.png

REM 最後にログを保存する
REM 例)[日 時],[引数],[相手ホスト名(相手IPアドレス)],[ブラウザ名]
echo [%DATE% %TIME%],[%QUERY_STRING%],[%REMOTE_HOST%(%REMOTE_ADDR%)],[%HTTP_USER_AGENT%] >> %date:~0,4%%date:~5,2%%date:~8,2%.log

----------

202201031627566-admin.png
これで「アクセス日時」「引数」「相手ホスト名(相手IPアドレス)」「ブラウザ名」がログとして記録されるようになります

202201031627565-admin.png
たったこれだけの仕組みで「単に HTML メールを見ただけ」なのにいろいろ収集できるんで…画像ファイルとして動作する CGI には大きな意義があると思います

※なお最近のメーラは「外部コンテンツを読み込まない」的なオプションがデフォルトでオフになっていると聞きます しかしその意味をよく判らないまま「便利さを求めて」読み込むような設定に変更してる人も多いと聞きます
※まぁもちろん外部コンテンツの全部が全部にそのような動作が仕掛けられている事は無いとは思いますが…そのような危険性があることを覚えておきたいですね

  :

なんて感じで長々とお疲れさまでした 久しぶりのバッチファイルっていうか…思い返せば X68000 の Human68k の AUTOEXEC.BAT の頃以来ですかね(汗 なんか今回いろいろ調べてて「サブルーチンなんて使えてたっけ??」とか新しい発見があってよかったです
今回 Web サーバ以外は最初から用意されている(と思われる)標準的なコマンドをどうにか組み合わせて機能を実現してみました それ故にっていうか…久しぶりだったんでいろいろ無駄な部分とかもあるけどその辺はご了笑くださいませ
まぁしょーみ今さらバッチファイルを CGI に使おうなんて奇特な人も居ないだろうで全く問題ないだろうけどww

あと今回テストに使った Web サーバは一時的なものなので WAN に公開していません つーか使いたくないんで年に数回くらいしか電源を入れることがない代物なんでその辺はお察しください… #Windows #CGI

  :

今回使用した bat_cgi*.bat と img*.png をひとまとめにした配布セットを用意しました
  bat_cgi.zip
なおこれを使用した際に発生したあらゆる利益・不利益について当方は一切の責任を負いませんって定型文でよろしくお願いします

  :

※ 追記 ※
今回は AN HTTPD のみでの動作テストだった訳ですが…有志のえらいひとが IIS (Internet Information Services) でのテストをしてくれて問題点と解決法をご連絡頂けたんでここに転載させていただきます

まずは IIS の設定から
202201101418043-admin.png 202201101418042-admin.png
「CGI」の設定で「起動ごとに新しいコンソールを使用する」を「True」にする

202201101418041-admin.png
「ハンドラー マッピング」の設定で「マネージハンドラーの追加」をして

20220110141804-admin.png
「要求パス」に「*.bat」を
「実行可能ファイル」に「c:\windows\system32\cmd.exe /c %s」を
「名前」には「判りやすそうな適当な名前」を設定します

あとは上の方で示してる BAT ファイルの内容的な問題っていうか…その BAT ファイルが参照/作成するファイルへのパスですかね
どうも IIS の場合だとカレントディレクトリが「BAT ファイルを実行した場所」ではなく「物理パス(ドキュメント ルート)」となるらしく…その辺を考慮してないと誤作動となるようなのでその辺を改良します まぁ基本的には BAT ファイルを実行した場所を示す変数である「%~dp0」を追加します

----- bat_cgi01.bat -----
REM 日付が設定されている変数から「年月日」を取得してログファイル的なファイル名を作ってみる
echo %DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log

  ↓

REM 日付が設定されている変数から「年月日」を取得してログファイル的なファイル名を作ってみる
echo %~dp0%DATE:~0,4%%DATE:~5,2%%DATE:~8,2%.log
----------

----- bat_cgi03.bat -----
REM 時間を判定して画像を表示する用のサブルーチン
REM call :SUB <時間> <ファイル名>
REM  <時間> は 0~23 で指定
REM  <ファイル名> は表示させたい PNG 画像を指定
:SUB
echo. | time | find " %1:" > NUL
if %errorlevel% == 0 type %2
exit /b

  ↓

REM 時間を判定して画像を表示する用のサブルーチン
REM call :SUB <時間> <ファイル名>
REM  <時間> は 0~23 で指定
REM  <ファイル名> は表示させたい PNG 画像を指定
:SUB
echo. | time | find " %1:" > NUL
if %errorlevel% == 0 type %~dp0%2
exit /b
----------

----- bat_cgi04.bat -----
上記 bat_cgi03.bat のサブルーチン部と同じく if %errorlevel% == 0 type %~dp0%2 と直して…

REM 最後にログを保存する
REM 例)[日 時],[引数],[相手ホスト名(相手IPアドレス)],[ブラウザ名]
echo [%DATE% %TIME%],[%QUERY_STRING%],[%REMOTE_HOST%(%REMOTE_ADDR%)],[%HTTP_USER_AGENT%] >> %date:~0,4%%date:~5,2%%date:~8,2%.log

  ↓

REM 最後にログを保存する
REM 例)[日 時],[引数],[相手ホスト名(相手IPアドレス)],[ブラウザ名]
echo [%DATE% %TIME%],[%QUERY_STRING%],[%REMOTE_HOST%(%REMOTE_ADDR%)],[%HTTP_USER_AGENT%] >> %~dp0%date:~0,4%%date:~5,2%%date:~8,2%.log
----------

こんな感じに修正します これで正しく動くようになるようです
わざわざのご連絡ありがとございました!(>_<)w

情報 <11014文字>

DASHBOARD

■全文検索:

複合検索窓に切り替える

■複合検索:

  • 投稿者名:
  • 投稿年月:
  • #タグ:
  • カテゴリ:
  • 出力順序:

■ハッシュタグ:

■カテゴリ:

■日付検索:

■機器状態:

Raspberry Pi 4 Status

編集

RSSフィード