M5StickC で毎朝照明を自動で点灯する

最近寒いのでカーテンを遮光・遮熱のものに変えた。 遮熱のほうはあまり実感がないが遮光のほうはばっちりで、 昼間でもカーテンを閉めれば部屋がかなり暗くなる。

寝てる間は暗くてよいのだが、 朝になっても明るさで自然に目が覚めるということがなくなった。 時計を見ないと大まかな時間すら分からないのは思いの外不便だ。 そこで、以前購入した M5StickC を使って 毎朝自動で照明を点灯させることを思いついた。

使用する照明器具とリモコン

私の部屋の照明はNECライティング(現ホタルクス)の LEDシーリングライトHLDX0801 を使用している。 昨年買い換えたのだが、 これはシーリングライトとしてはかなりの薄型で、 引掛シーリングと合わせても高さ6cmくらいしかない。 以前使用していたものは高さ15cmほどあったため、 圧迫感がなくなり気に入っている。 唯一の不満点はリモコンが付属していないことで、別途 RE0206 を購入し使用している。

リモコンの信号解析

まずは点灯時に RE0206 から発信される赤外線信号を調べるため Grove 接続の IR ユニット を購入した。 M5StickC 単体でも赤外線の発信はできるが、受信はできないため、 リモコンの信号を調べる場合は別途購入する必要がある。

M5StickC/M5Stack Fireで赤外線リモコンを作ろうと四苦八苦したのでメモ - カワリモノ息子の技術メモ的な〜 を参考に Arduino IDE から IRremoteESP8266 をインストールし、スケッチ例の IRvecvDumpV3 を開く。 kRecvPin を 33 に変更して M5StickC へ書き込んだあと、 IR ユニットへ向けてリモコンのボタンを押すと シリアルモニタへ信号が表示された。

赤外線リモコンの信号にはいくつか種類があるらしいが、 NEC製品なので当然NECフォーマットだった。

スケッチの作成

M5StickC で設定時刻に赤外線信号を送信するスケッチを作成した。

saasan/m5stickc-ir-timer: M5StickCで設定時刻に赤外線信号を送信する

#include <M5StickC.h>
#include <WiFi.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#include "wifi-ssid.h"

// -----------------------------------------------------------------------------
// 定数
// -----------------------------------------------------------------------------
// 赤外線LEDのピン番号
// M5StickC内蔵の赤外線LEDを使用する場合は9
// Grove接続のIRユニットを使用する場合は32
const uint8_t IR_SEND_PIN = 9;
// 赤外線送信するデータ
const uint64_t IR_SEND_DATA = 0x41B6659A;
// GMTからの時間差(秒)
const long JST = 9 * 60 * 60;
// NTPサーバ
const char *NTP_SERVER = "ntp.nict.jp";
// 電源ボタンが1秒未満押された
const uint8_t AXP_WAS_PRESSED = 2;
// ボタンが長押しされたと判定する時間(ms)
const uint32_t BUTTON_PRESSED_MS = 500;
// 最後にボタンを押してから画面を省電力にするまでの時間(ms)
const unsigned long SCREEN_OFF_MS = 3000;
// 通常時の画面輝度
const uint8_t SCREEN_ON_BRIGHTNESS = 12;
// 省電力時の画面輝度
const uint8_t SCREEN_OFF_BRIGHTNESS = 8;

// -----------------------------------------------------------------------------
// 変数
// -----------------------------------------------------------------------------
// IRremoteESP8266のIRsendクラス
IRsend irsend(IR_SEND_PIN);
// 現在時刻
struct tm now;
// 赤外線送信する時刻の時間
int timer_hour = 7;
// 赤外線送信する時刻の分
int timer_min = 0;
// 赤外線送信済みならtrue
bool ir_sent = false;
// 最後にボタンが押された時間
unsigned long button_pressed_millis = 0;

// -----------------------------------------------------------------------------
// 関数
// -----------------------------------------------------------------------------
// 現在時刻を表示
void showCurrentTime() {
    char message[50];

    sprintf(
        message,
        " now:\n  %04d/%02d/%02d %02d:%02d:%02d\n timer:\n  %02d:%02d",
        now.tm_year + 1900,
        now.tm_mon + 1,
        now.tm_mday,
        now.tm_hour,
        now.tm_min,
        now.tm_sec,
        timer_hour,
        timer_min);

    M5.Lcd.setCursor(0, 0);
    M5.Lcd.print(message);
}

// 無線LAN接続
void connectWiFi(const char *ssid, const char *passphrase) {
    M5.Lcd.printf("Connecting to %s", ssid);
    WiFi.begin(ssid, passphrase);
    while (WiFi.status() != WL_CONNECTED) {
        delay(1000);
        M5.Lcd.print(".");
    }
    M5.Lcd.print("\nconnected!");
    delay(500);
    M5.Lcd.fillScreen(BLACK);
}

// 画面の輝度を上げる
void screenOn() {
    M5.Axp.ScreenBreath(SCREEN_ON_BRIGHTNESS);
    button_pressed_millis = millis();
}

// 画面の輝度を下げる
void screenOff() {
    M5.Axp.ScreenBreath(SCREEN_OFF_BRIGHTNESS);
}

void setup() {
    M5.begin();

    // 画面の表示設定
    M5.Lcd.setRotation(1);
    M5.Lcd.setTextFont(2);
    // ピンモードの設定
    pinMode(IR_SEND_PIN, OUTPUT);
    // 無線LANへ接続
    connectWiFi(WIFI_SSID, WIFI_PASSPHRASE);
    // NTPの設定
    configTime(JST, 0, NTP_SERVER);

    button_pressed_millis = millis();
}

void loop() {
    M5.update();

    // ボタンAが押されたらアラーム時刻の時間を変更
    if (M5.BtnA.wasPressed() || M5.BtnA.pressedFor(BUTTON_PRESSED_MS)) {
        timer_hour++;
        if (timer_hour > 23) timer_hour = 0;

        screenOn();
    }
    // ボタンBが押されたらアラーム時刻の分を変更
    if (M5.BtnB.wasPressed() || M5.BtnB.pressedFor(BUTTON_PRESSED_MS)) {
        timer_min++;
        if (timer_min > 59) timer_min = 0;

        screenOn();
    }
    // 電源ボタンが押されたらリセット
    if (M5.Axp.GetBtnPress() == AXP_WAS_PRESSED) {
        esp_restart();
    }

    // 現在時刻を取得
    getLocalTime(&now);
    showCurrentTime();

    // 最後にボタンを押してから時間が経過している場合は画面を省電力化
    if ((millis() - button_pressed_millis) > SCREEN_OFF_MS) {
        screenOff();
    }

    if (now.tm_hour == timer_hour && now.tm_min == timer_min && now.tm_sec == 0) {
        // 赤外線送信していなければ電源ON信号を送信
        if (!ir_sent) {
            irsend.sendNEC(IR_SEND_DATA);
            ir_sent = true;
        }
    } else {
        ir_sent = false;
    }

    delay(100);
}

IR_SEND_DATA の 0x41B6659A が上記で解析した点灯するための信号で、 これを IRsend クラスの sendNEC メソッドで送信している。

タイマーの時刻は ボタンA(正面の「M5」ボタン)で時、 ボタンB(側面の小さいボタン)で分を変更できるようにした。

送信するリモコンの信号を変えればいろいろと応用できそう。

消してしまった WSL 環境のバックアップからファイルを取り出す

PC を新しいものへ移行する際に WSL 環境の中身を移行するのを忘れていた。 というか、別の環境に最新のファイルがあると思い込んでいたので敢えて移行しなかったのだ。 実際は自分の旧 PC の WSL 環境にあったものが最新のファイルで、 それに気付いたときにはディスクの初期化が済んでいた。

旧 PC の Ubuntu 内には作業中のファイルがあり、 これが消えてしまうと10時間ほどかけた作業が無駄になってしまう。 こんなこともあろうかと、旧 PC のディスクを Disk2vhd で VHDX ファイル化したものは残しておいたので、 このバックアップファイルから必要なファイルを取り出すことにした。

旧 PC のバックアップから Ubuntu のディスクイメージを探す

まずは旧 PC のバックアップから Ubuntu のディスクイメージを探す。 VHDX ファイルをダブルクリックすると自動でマウントされるので その中を探したところ以下のパスにディスクイメージがあった。

\Users\ユーザー名\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\ext4.vhdx

このファイルをマウントして中身を取り出して完了かと思ったら、 ファル名通りファイルシステムが ext4 だからなのか Windows ではマウントできなかった。

しかたないので新 PC の WSL へ新しいディストリビューションを入れて ext4.vhdx を置き換えることにする。

2022/08/27 追記

2021/12/26 にリリースされた 21.07 から 7-Zip が VHDX ファイルへ対応しています。 ファイルシステムが ext4 でも 7-Zip で開いてファイルを取り出せることを確認済みです。

Alpine WSL をインストールする

ext4.vhdx からファイルを取り出すため Alpine WSL をインストールした。 これはおそらく新 PC で使ってないディストリビューションならなんでもいい。 新 PC でも既に Ubuntu を使い始めていたのでそれ以外で、 インストール作業に時間をかけたくないこともあり 軽量なことで有名な Alpine Linux を選択した。

インストール後、念のため一度起動して初期設定を行っておく。

WSL のサービスを停止して ext4.vhdx を置き換える

WSL のサービスが起動したままだとファイルがロックされていて置き換えられない。 コマンドプロンプトか PowerShell を管理者権限で起動し、 以下のコマンドで WSL のサービスを止める。

net stop LxssManager

次に以下のパスにある Alpine WSL の ext4.vhdx を旧 PC のバックアップから取り出したもので置き換える。

\Users\ユーザー名\AppData\Local\Packages\36828agowa338.AlpineWSL_my43bytk1c4nr\LocalState\ext4.vhdx

最後に以下のコマンドで WSL のサービスを起動する。

net start LxssManager

必要なファイルを取り出す

あとは alpine コマンドで起動し旧 PC の Ubuntu 環境へ入ってファイルを取り出すことができた。

最近知った Bash の便利機能

cd -

一つ前のディレクトリへ戻る。

pushd / popd で2つのディレクトリを往復することがあったけどこっちのほうが速い。

Ctrl + u, Ctrl + y

Ctrl + u でカーソル位置から行頭まで切り取る。 Ctrl + y で切り取ったものを貼り付ける。

git commit -m "hoge" とか書いたところで git add してないことに気付いたりとか、 コマンド入力中に別のコマンドを先に実行したくなったときに便利。

ちなみに Ctrl + k ならカーソル位置から行末まで切り取れる。

スタートメニューから消えた Dell Command | Update を復活させる

Dell Command | Update を実行したあとにスタートメニューから Dell Command | Update が消えることがある。

しばらく待っていれば自動で復活することもあれば復活しないこともある。 復活しないときは一度アンインストールして Dell のサイトからダウンロード したものを入れ直していたが、けっこう手間がかかって面倒だった。 そもそもアプリの一覧に残ってるならもっと簡単に復活できるんじゃないか?

そう思ってスタートメニューだけ復活させる方法がないか調べたら以下の方法で復活できた。

  1. 管理者権限で PowerShell を起動する。
  2. 以下のコマンドを実行する。

    $ManifestPath = (Get-AppxPackage -AllUsers -Name "DellInc.DellCommandUpdate").InstallLocation + "\Appxmanifest.xml"
    Add-AppxPackage -Path $ManifestPath -Register -DisableDevelopmentMode
    

※Get-AppxPackage に -AllUsers オプションを付けて実行するのに管理者権限が必要。

参考 : Add-AppxPackage (AppX) | Microsoft Docs

USB CABLE CHECKER 2 で USB Type-C ケーブルの性能を確認する

先日 RAVPower 製 90W USB PD 充電器 RP-PC128 を買いました。 同じく RAVPower 製の 61W USB PD 充電器 PR-PC112 を持っていたのですが、 65W を要求する Dell 製ノート PC に給電しながら電源を入れると 途中で電力が足りない旨メッセージが表示されて起動が一時停止したり、 Windows 10 起動後も電力不足の通知が表示されるという状態で煩わしかったためです。

eMarker 付きのケーブルを持っていなかったので RAMPOW という謎のメーカーの PD 3.0 / USB 3.1 Gen2 対応を謳う USB Type-C ケーブルと Bit Trade One の USB CABLE CHECKER 2 (ADUSBCIM) もついでに購入しました。

RAMPOW は Web サイト を見ても本社所在地や電話番号等が一切書かれておらず怪しげなメーカーという印象だったため、 ケーブルを使用する前に念の為 USB CABLE CHECKER 2 で確認しようという寸法です。 (※購入後に気付いたのですが、RAMPOW の Amazon出品者プロフィール に住所と電話番号が書かれていました。中国深センの会社のようです。)

RAMPOW 製ケーブル RAD03

さっそく RAMPOW 製ケーブルを確認してみます。

RAMPOW 製ケーブル RAD03

USB CABLE CHECKER 2 の表示は以下の通り。

GND+VBUS=188mΩ
CC:DOWN1K/E-MARKED
SHELL-GND SHORT(A&B)

表示の意味については 取扱説明書 に記載されています。

[DOWN1K/E-MARKED]

Cプラグ内にGND-VCONN間に接続された1kΩの抵抗器を持ちます。

これにより接続先USB機器にEマーカーIC内蔵ケーブルということを通知します。

[SHELL-GND SHORT(SIDE)]

プラグシェルがGNDと導通している場合表示されます。()内は導通している側のコネクタがA,Bどちらかを表します。

両側のコネクタが導通している場合はA&Bと表示されます。

なお、タイプC-Cケーブルでは規格でGNDとシェルが接続されることが定められています。

CC:DOWN1K/E-MARKED は eMarker 内蔵、 SHELL-GND SHORT(A&B) は両側のプラグシェルが GND と導通していることを示しています。

eMarker が内蔵されているため 3A を超える電流が流せます。 connection のランプも全点灯しており、PD 3.0 / USB 3.1 Gen2 対応を謳うケーブルとして特に問題なさそうです。

RAVPower 製充電器 RP-PC128 付属ケーブル

購入時に気付いてなかったのですが、 RP-PC128 にも 1.5m の Type-C ケーブルが付属していたためこれも確認してみます。

RAVPower 製 90W USB PD 充電器 RP-PC128 付属ケーブル

GND+VBUS=170mΩ
CC:DOWN1K/E-MARKED
SHELL-GND SHORT(A&B)

eMarker が内蔵されているため 3A を超える電流が流せます。 connection のランプは USB 2.0 の部分と CC (Configuration Channel) が点灯しています。 USB 2.0 対応の PD ケーブルとして使えるようです。

電源ケーブルとして使うならこれで十分ですね。 RAMPOW のケーブルいらなかったのでは?

Lenovo Yoga Book C930 付属ケーブル

手元にあったほかの USB Type-C ケーブルも確認してみます。

Lenovo Yoga Book C930 付属ケーブル

GND+VBUS=212mΩ
SHELL-GND SHORT(A&B)

eMarker が内蔵されていないため 3A までの電流しか流せません。 connection のランプは USB 3.2 が一部点灯しています。

USB 3.0, 3.1 では TX1/RX1 と TX2/RX2 のどちらかが繋がっていれば良いようです。 USB 3.2 Gen 2x2 では TX1, TX2, RX1, RX2 のすべてを使用してより高速な通信を行うため、このケーブルは利用できません。

SBU (Sideband Use) は USB では使用されず、 DisplayPort 出力や Thunderbolt などの Alternate Mode に使われる信号線のようです。

USB 3.1 対応の PD ケーブルとしては使えそう。

One-Netbook OneMix3S 付属ケーブル

One-Netbook OneMix3S 付属ケーブル

GND+VBUS=225mΩ

SHELL-GND SHORT(A&B) の表示がない……。 規格に従ってないケーブルのようです。 これは使わないほうがよさそう。

まとめ

RAVPower 製 90W USB PD 充電器 RP-PC128 と RAMPOW 製ケーブル RAD03 の組み合わせで、 65W を要求する Dell 製ノート PC が問題なく充電できました。

RP-PC128 は MacBook 付属の 61W USB-C 充電器と比べて サイズがひと回り小さい (65 x 65 x 32 mm) ため持ち運びやすく、 90W の大出力により充電できる機器の幅が広がります。

また、電力に余裕があるため、BUFFALO の 5-in-1 ドッキングステーション LUD-U3-CGD/N を間にはさんでも安定して動作しています (61W 出力の PR-PC112 を使用したときは有線 LAN の通信がブツブツ切れていた)。 ケーブル1本で電源、ディスプレイ、有線 LAN、キーボードが繋がるのは便利です。

今回購入した RAMPOW 製ケーブルは問題ありませんでしたが、 手持ちの OneMix3S 付属ケーブルが規格違反だったのは意外でした。 USB Power Delivery では高電圧、大電流が流れるため、 ケーブル購入の際は信頼できるメーカーのものを選んだほうがよさそうです。

参考サイト