e-tipsmemo

ごった煮

ESPHomeとHome assistantのオートメーション

Homeassistantを経由して、物理的には、離れているが、同じネットワーク内にあるESP32デバイスを互いに連携させることを試してみる。

バイスの用意

まず、ESP32でなにかしらの回路を用意した。

  • 1つ目のESP32デバイス
    • pin2にIRセンサー
    • pin32にLED
  • 2つ目のESP32デバイス
    • pin32にLED
    • pin33をGPIOとして(LED以外の外部回路のトリガーとして使用)

これらESP32デバイスをESPhomeから以下のようにコンフィグレーションした。
(Arduino IDEに描くようなコードはまったく書いておらず、以下のyamlだけを書いた。)
上の箇条書きしているようなものが列挙されているのと、
irセンサーは「remote_receiver」として、定義し、
それが受光する信号を「binary_sensor」として解釈することを定義している。

今回はODELICのLEDシーリングライドのリモコンが手元にあったのでそれを使う。
市販されているリモコンの発する信号のフォーマットはだいたい統一されているらしいので今回は「lg」とすればよかった。(赤外線リモコンの通信フォーマット)

esphome:
  name: ir-rec
  platform: ESP32
  board: esp32dev

switch:
  - platform: gpio
    name: "LED"
    pin: 32

remote_receiver:
  pin: 
    number: 2
    inverted: true
    mode: INPUT_PULLUP
  dump: all
  
binary_sensor:
  - platform: remote_receiver
    name: "Odelic Remote Input All ON"
    lg:
      data: 0x01A3D926
      nbits: 32
      
  - platform: remote_receiver
    name: "Odelic Remote Input All OFF"
    lg:
      data: 0x01A3906F
      nbits: 32
      
# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: "**********"

ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO0_IN
  phy_addr: 1
  power_pin: GPIO16
esphome:
  name: bs-skp-sw
  platform: ESP32
  board: esp32dev

switch:
  - platform: gpio
    name: "LED"
    pin: 32

  - platform: gpio
    name: "RF_SW"
    pin: 33

ethernet:
  type: LAN8720
  mdc_pin: GPIO23
  mdio_pin: GPIO18
  clk_mode: GPIO0_IN
  phy_addr: 1
  power_pin: GPIO16

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: "**********************"

最初の書き方は以下の記事と同じ(使っているESP32のICは違うがほとんど同じ)
e-tipsmemo.hatenablog.com

このyamlをESPhomeでコンフィグレーションすると、ESPhomeに書き込むバイナリファイルが完成する。
初回の書き込みはネットワーク経由ではできないので、UART経由などで行う必要があるが、
いったんHomeassistantから認識できるようになれば、ネットワーク接続コンフィグの部分(otaやapiethernet)以外を書き換えて再コンフィグするのは、ネットワーク経由で行うことができる。

これらが完了すると
Homeassistantのオーバービューに入出力の一覧が表示される。

Output端子は、トグルボタンに対応して変更すると、GPIOの出力が変化して、LEDが光ったりする。
Input端子は、入力ステータスを表すようになっている。

オートメーション

入出力が準備完了したので、それらを連携させる。


オートメーションの作成から設定する。

画像のように、「もし『トリガー』ならば『アクション』を実行する」のような簡単な条件を設定して、常にデバイスの状態を監視しておくことができる。

実際にはデバイスの入力があると、MQTTでネットワーク経由で通信が行われて、Homeassistantがそれを受けて、条件を考慮して、MQTT経由で、デバイスの出力を行う。
のような動作だと思われる。

これで自作デバイスでオートメーション(Homeassistant経由)ができるようになった。
また、Switch botのような、Homeassistant対応のIoTデバイスを買うことで、もっと高度なオートメーションが可能になると思われる。

Switch bot温度計

Switch bot hub mini

Homeassistant対応のCO2メーター
aranet.com

QNAP NASにHomeassistantをインストールした

Homeassistantを運用するにあたってRaspberry piにHome Assistant Operating Systemをインストールして使っていた。
しかし、ネットワーク周りを整理たいので、Growiを動かしているQNAPのNAS上で動かしたいと思った。

プラン1:Container Station/Docker(ダメそう)

QNAPにはContainer StationとかいうDockerの管理ソフトのようなものをApp Centerから導入することができる。

GrowiはContainer Stationからインストール・起動することはできなかったので、
sshで接続して、直接docker compose -dしているがうまく動いていることがわかる。

これ同様に、まずはContainer Station経由でHome assistantのDockerイメージを取得して使うことができるのか試した。

ほとんど以下のサイトの通りに行った。
Installing Home Assistant (HA) on QNAP NAS - HackMD

しかし、参考サイト通りに行ったが、
なにかエラーが出てhttps://[NASIPアドレス]:8123 が出てこなかった。
それ以前に、Home assistantのDockerイメージはHomeassistant Coreというものしか含まれていないらしく
ESPHomeなどのアドオンをインストールするのに必要なHome assistant Supervisorというものがないらしい。

Raspberry piで起動しているHome assistant OSでの管理画面(Supervisorからアドオンストアにアクセスできて、ESPHomeなどをインストールできる(これがないと困る))

普通のUbuntuにDockerを入れて、ymlを書いて、docker compose -dしたときの管理画面
参考:Linux - Home Assistant

Home Assistantを使えるようにするまで その4 | Smart Life 99
ここ曰く、Containerだとやはりアドオンストアは使えない様子。
なので、別の方法を探す。

プラン2:Virtualization Station 3

QNAPのApp Centerを見ていたらVirtualization Stationというものがあった。
検索したところ仮想OSを起動できるらしいので、これ上でHome assistant OSを起動すればよいのではないかと思ったらやっている人がいた。
How to Install Home Assistant on a VM | Qnap - YouTube

Dockerよりもリソースを使用しそうだが、早く動いてくれる必要もないので。

VMにHome assistant OSをインストールできたら
コンソールを開く

ha > network info

とすると、dockerのネットワーク情報を表示してくれるので、
interface: enp0s3とかのIPアドレスにポート8123でアクセスする
アカウント作成すると、Raspberryで触っていたHome assistant OSと同じものが表示された(アドオンストアもある)


感想

Raspberry piからQNAP NASにHome assistantを乗せ換えることができそう。
Raspberry piは何か一つのことをするならお手軽だけど微妙に性能が足りないと思う時がよくある(録画なども)
複数のコンテナを云々とかになってくるとメモリが足りなくなってくるので、
やりたいことごとにRaspberry piが増えていく状況になってしまう。
最近のRaspberry 4Bなどはどうかわからないが、
素直にx86_64のi3とかにメモリをたくさん積んだマシンをサーバーにしたほうが良いのではないかと思う(最近はメモリも安いので)

たまたま持っていたQNAPのTTS-453Beを用いているが、Synologyのでも同様の機能があると思う。
メモリが足りなくなりそうだったら、推奨はされていないが、16GBにできるかもしれないらしい。

後継機種が出ている

Flexispot Q8にモニターアームを設置する

昇降机の候補として、Flexispotの購入を検討していた。
その中でもFlexispot Q8というモデルはUSB-C PDに対応しているので、
最近のノートパソコンの電源として使えると考え、調べていた。

(これから紹介する方法を再現して大変なことがあっても、全て自己責任でお願い致します)

昇降机本体

天板にこだわりはなかったので、セットを買う。
flexispot.jp


しかしこの机はほかの人のレビューにもあるように、
天板の下側に机のフレームが周囲に渡って存在するので、
なかなかモニターアームを設置できないという弱点がある。

画像は公式サイトから

モニターアーム、モニターポール

エルゴトロン LXのモニタポールは

デスククランプ部分が、66mmまでしか対応できない。(おしい)

なので、デスククランプ部分が、70mm以上のモニタポールを探した。
また、エルゴトロンのモニターアームを取り付けたいなら、35mmのポールを用意する。


これ以外にも探せばあるかもしれない。

このポールをFlexispot Q8につけるにはモニターポールの土台の位置を変更して、2つ目のネジ穴にする。


これで机のフレームを越すことができた。
しかし、この状態だとクランプするネジを全部占めてもFlexispotの天板に届くことができなかった。


木工工作

そこで、適当な木片をのこぎりで切り出して挟み込むことによって、
ポールを立てることができた。

木片の幅は大体4cm、奥行きが12cmあれば2つのクランプネジに同時に届く。
厚みは適当でよさそう。

このポールは35mmなのでエルゴトロンLXの追加アームを取り付けることが可能。

そして当然だが、モニターにはVESA対応のものを選ぶ必要がある。

耐荷重

このモニターは6kgらしく、
一方で、モニターポールは耐荷重20kgなので、
このモニターを2枚とエルゴトロンLXのモニターアーム2つだと3枚目を追加することができないかもしれない。
(モニター6kgは、おそらく土台含めての重さなので、ディスプレイだけならそこまで重くないと考えられる)
机のほうは耐荷重100kgなのでまだまだ余裕がある。

感想

事前の調査通り木片を用意するだけでモニターアームを設置することができた。
しかし、Flexispotは昇降机なので、
一番高いところにあげて、さらにモニターポールの高さが加わると、
全体として、125cm + 70cm = 195cmの高さになってしまうことに気を付けたい。
(机の上の棚などにぶつかってまう可能性がある)
また、クランプにはさむ物体はなんでもよさそうだが、十分硬い素材のものを選ばないとポールが傾く恐れがある。


最後に使用した機器一覧

(Flexispot Q8はAmazonには売っていなかった)

Tauriでlife game (Rust & React)

Tauriが来そうな気がするので、なんか書いてみる。

Github

GitHub - katakanan/tauri_life_game


TauriではGUIのフロントエンドをいろいろなフレームワークで書ける。

  • Vanilla
  • yew
  • React
  • Vue

とかいろいろだが、プロジェクトを生成するときの選択肢によって、選べる幅が変わってくるらしい。

環境設定

WindowsなのでPowershellで作業する。
Cargo でcreate-tauri-appを入れる。
yarnを入れる。

プロジェクト生成

コマンドによってテンプレートプロジェクトを生成できるが、
とりあえずReactが出てくるのか以下の選択をしたとき。

> cargo create-tauri-app
✔ Project name · react_ex2
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm)
✔ Choose your package manager · yarn
✔ Choose your UI template · React - (https://reactjs.org/)
✔ Choose your UI flavor · TypeScript

Reactにした理由は、React Nativeなどによって、盛んに開発されており、たくさんの情報があるような気がしたので。
Nextjsなどでもよいかもしれない。

YewはRustでHTMLを生成する感じのフレームワークであるが、情報がまだあんまりなくて初手つらかった。

Vanillaは素のHTMLやjavascripなので、動的な画面を作るのが大変なので、なし。

ビルド

cd react_ex2
yarn
yarn tauri dev

をしろ、との指示がある。
vscodeで開いておく。
ビルドを行うと、アプリが起動する
編集をするとホットリロードして勝手にリビルドされて起動しなおされる。

http://localhost:1420/
にアクセスするとUIだけを表示できる(ロジック側は動かない)


アプリが起動した。

Life game

とりあえず、Reactだけで、Life gameを書いてみる。
ここを写経した
React Hooks Project Tutorial - Game of Life - YouTube


動いてるっぽい。

Tauri::Command

ReactでできることをわざわざRustでやる 必要もないが、
ここからRust側に処理を切り出してみる。



基本的にHTML側のデザインに対して、ユーザーからの入力があって、onclickとかに紐づいたTypescriptのasync関数がRustの関数を呼ぶ。
そして戻り値をTypescript側で受け取ってなんやかんやする、というスタイルになる気がする。

呼ぶときの引数はRustの引数名をキーに持つオブジェクトを渡す。

Rust側からの戻り値はプリミティブな型だとそのままなことが多い。
Rust側で定義される構造体はserdeで構築された形で指定できるが、大体はDerive(serde)するとオブジェクトな形になって来るので、どんな形で返ってくるのかを確認しなければならない。

なので構造体で返すときはTypescript側で使いやすい形になるようなSerializeをしたほうがよいかも(?)

今回はRust側での盤面の状態の管理にndarray crateを使用し、そのなかのArray2を使用した
TypescriptにArray2を返すと、1次元の配列とrowとcolのサイズ、その他もろもろが渡されていた。

tauri_life_game/main.rs at 33365a18a9f3e9cbbbe1cccec27cdc5409638703 · katakanan/tauri_life_game · GitHub

ビルドがコケる

iconがないみたいなエラーがでるのでrustのところにあるconfigを変更する。

起動

 yarn tauri dev


感想

あまりRustの恩恵を受けたプロジェクトではないが、Tauriの導入にはちょうど良いかもしれない。Typescriptから呼べるRustの関数を最小限にするようなうまい設計を考える必要がある。

Tauriを使ってみる(だけ)

GUI開発をしたいので、面倒なものをいれるよりは、Windows上でやったほうがいい気がしたのでWindowsで行う。
WSL2上では、fish-shellを使っているのでnodejsを入れるのがめんどくさい

準備

Rustを入れる。
Rust をインストール - Rustプログラミング言語

nodeを入れる。
ダウンロード | Node.js

Visual StudioC++開発環境とかをいれる(msvcコンパイラとかのリンカーを使ので)
全部じゃなくてもMicrosoft Visual Studio C++ Build Toolsを入れておけばOK

Power Shellで

> npm -v
9.5.0
> npm create tauri-app
Need to install the following packages:
  create-tauri-app@3.2.1
Ok to proceed? (y) y
✔ Project name · tauri-app
✔ Choose which language to use for your frontend · Rust - (cargo)
✔ Choose your UI template · Vanilla

Template created! To get started run:
  cd tauri-app
  cargo tauri dev

npm notice
npm notice New minor version of npm available! 9.5.0 -> 9.6.2
npm notice Changelog: https://github.com/npm/cli/releases/tag/v9.6.2
npm notice Run npm install -g npm@9.6.2 to update!
npm notice
> cd .\tauri-app\
> cargo tauri dev


なんか出た。

Rust(wasm)+typescriptでやろうとしていたことが一瞬でできちゃう気がする?
ブラウザ上でも動くのだろうか?

これは生のHTMLとCSSを書くタイプのプロジェクト構成を出力する選択肢で、
ほかのを選ぶと、Reactなどをフロントエンドにフレームワークに指定できる。

\tauri> cargo create-tauri-app
✔ Project name · yfin_test
✔ Choose which language to use for your frontend · TypeScript / JavaScript - (pnpm, yarn, npm)
✔ Choose your package manager · yarn
✔ Choose your UI template · React - (https://reactjs.org/)
✔ Choose your UI flavor · TypeScript

Template created! To get started run:
  cd yfin_test
  yarn
  yarn tauri dev

Front end languageにTS/JSを選択すると後々Reactへルート分岐がある。
(別の選択肢だとReact は出てこない)
こちらの場合はcargoではなく、yarnが必要。


感想

適当にライフゲームなどを書いてみた感じ
Reactはすでに様々なプロジェクトで使われており、
情報やテンプレートが多いので、簡単にそれらしいUI構築することができた。
(結局CSSを少しいじったりもしたが、ChatGPTに聞けば案外行ける気がした)

アプリケーション作るのにHTMLとかCSSを書きたくないのでFigmaを触ってみた(が)

便利なGUIアプリケーションを作りたいなと思ってElectronとかRust-icedとかフレームワークを検討してみたことはあるが、結局GUIを整えるためにコードを何行も書くことがめんどくさいということに気づいた。

C#でアプリケーションを作るときのお手軽さぐらいでないとと思った。

C# で Windows フォーム アプリを作成する - Visual Studio (Windows) | Microsoft Learn

そこでデザインの要素を置いたらHTMLを生成してくれるようなものはないかと探した結果、
Adobe XDというものとFigma+pluginというものがあるらしい。
(ほかにもいろいろとあったが全然わからない)

Adobe XD ラーニングとサポート
Figma: コラボレーションインターフェイスデザインツール。

どうせAdobeは有料なので、Figmaの無料枠でおためしを行った。
(と思ったらFigmaAdobeに買収されていたらしい)

デザインをする

とりあえずYoutubeの動画などをみて画面をボタンがあるだけのデザインを描いてみた。

出力


Figma to HTMLとかいうものでReact向けに出力してみる。
React向けに出力して、Sandboxで表示してくれると思ったが、
なぜかSandboxが動いてくれなかったので、zipでソースコードをダウンロードする方向にした


表示

npmかyarnで依存ライブラリをインストールするといつも通り

yarn start
npm start

などで見れるようになる。

が青いボタン(予定)部分がない?

ほかのプラグインも試したが、
生成されたHTMLで表示される見た目が完全に同じにならなかったり、
無料プラグインでは生成されたHTMLコードがコピーできなかったりなど。

結論

デザインをいじりながら思っていたのは、C#GUIエディターのようなものをイメージしていたので、
デザインに対して、buttonタグを指定したり、文字のところにpタグを指定したりとかそこまでのものかと思ったら本当にデザインだけをするツールだった。

実社会では、このデザインをする人と、この完成したデザインを見ながら、それと同じ見た目のHTMLやCSSを書く別の人がいるのだと理解した。(同じ人でもいいが)

個人レベルでは、適当にインターネットからReactアプリのテンプレートコードをコピーして改造してきたほうが楽だったし、まだデザインにそこまで拘っていないので、ChatGPTに適当に聞けばそれなりのものが書ける気がすると感じた。
9+ Free React Templates - Material UI

figma違い...

Rust memmap2 LED blinking with Zynq

ZyboにUbuntu22.04のファイルシステムを入れて起動した。
PL部分にはAXI GPIOがつながっておりLEDが接続されている。
rotors.tar.goのファイルシステムにはdevmemがインストールされいた。
しかしUbuntuには入っていなかったのでdevmem2.cをどこからかダウンロードしてきてビルドする事で使える。
しかしこのままではBusyboxの時と何も変わらないのでRustで書いたアプリでdev/memを操作してみる。

環境構築

Zynq上でRustをビルドするわけにもいかないので
WSL2上でCrossコンパイルする

必要なものは

  • WSL2
  • Docker for WSL
  • Rust

これらは解説記事がとても多いのでスキップして

  • cross

cross で Rust のクロスコンパイル - Qiita
に沿ってやる
ターゲットのアーキテクチャ

~/d/r/z/devmem-rs (master)> rustup target list | grep arm
arm-linux-androideabi
arm-unknown-linux-gnueabi
arm-unknown-linux-gnueabihf
arm-unknown-linux-musleabi
arm-unknown-linux-musleabihf
armebv7r-none-eabi
armebv7r-none-eabihf
armv5te-unknown-linux-gnueabi
armv5te-unknown-linux-musleabi
armv7-linux-androideabi
armv7-unknown-linux-gnueabi
armv7-unknown-linux-gnueabihf
armv7-unknown-linux-musleabi
armv7-unknown-linux-musleabihf
armv7a-none-eabi
armv7r-none-eabi
armv7r-none-eabihf

zynqはarmv7なのでarmv7-unknown-linux-gnueabihfを入れる

memmap2

cargo new devmem-rs --bin
cd devmem-rs
cargo add memmap2

ソースコード

こんな感じで

use clap::{arg, command};
use memmap2::{MmapMut, MmapOptions};
use std::env;
use std::error::Error;
use std::fs::OpenOptions;
use std::path::PathBuf;
use std::u64;

fn mapping(addr: u64, len: usize) -> Result<MmapMut, Box<dyn Error>> {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .create(true)
        .open(PathBuf::from("/dev/mem"))?;

    let m = unsafe { MmapOptions::new().offset(addr).len(len).map_mut(&file)? };

    Ok(m)
}
fn main() {
    let matches = command!()
        .arg(arg!([ADDRESS]))
        .arg(arg!([TYPE]).default_value("b"))
        .arg(arg!([DATA]))
        .get_matches();

    let adr = match matches.get_one::<String>("ADDRESS") {
        Some(adr) => {
            let adr = adr.trim_start_matches("0x");
            u64::from_str_radix(&adr, 16).unwrap_or_default()
        }
        _ => {
            println!(
                "Usage:  devmem {{ address }} [ type [ data ] ]
        address : memory address to act upon
        type    : access operation type : [b]yte, [h]alfword, [w]ord
        data    : data to be written\n"
            );
            std::process::exit(0)
        }
    };

    let width = match matches.get_one::<String>("TYPE") {
        Some(t) => match t.as_str() {
            "b" | "B" => 1,
            "h" | "H" => 2,
            "w" | "W" => 4,
            _ => {
                println!("illigal data type '{}'", t);
                std::process::exit(0)
            }
        },
        _ => {
            unreachable!()
        }
    };

    let mut m = mapping(adr, 4).unwrap();

    let rdata = u32::from_ne_bytes(m[0..4].try_into().unwrap());
    println!("Value at address 0x{:X}: 0x{:X}", adr, rdata);

    match matches.get_one::<String>("DATA") {
        Some(s) => {
            //Write Mode
            let data = if s.starts_with("0x") {
                let s = s.trim_start_matches("0x");
                u32::from_str_radix(s, 16).unwrap_or_default()
            } else {
                u32::from_str_radix(s, 10).unwrap_or_default()
            };

            let bytes = data.to_ne_bytes();

            for i in 0..width {
                m[i] = bytes[i];
            }

            let rbdata = u32::from_ne_bytes(m[0..4].try_into().unwrap());
            println!("Written 0x{:X}: readback: 0x{:X}", data, rbdata);
        }
        None => {}
    };
}

使用


Clapによって簡単にヘルプ表示できる。

Vivadoのほうでbase addrを確認する。

AXI GPIOはbase + 0x0004に入出力方向を決めるレジスタがあり、デフォルトでは入力に設定されているので、そこに0x0を書きこむ。
LogiCORE IP AXI GPIO (v1.01b) Data Sheet (AXI) - 1.01b English

LED点灯


これでLEDが交互に光っているはず。

スイッチ入力

同様に、スイッチ入力を読み取ることもできる。