<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.0.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2024-12-31T15:19:40+09:00</updated><id>/feed.xml</id><title type="html">muo-ya</title><subtitle>いろんなメモ書き</subtitle><entry><title type="html">Linux向けSMC読み書きツール（I/Oポートアクセス）をRustへ移植した</title><link href="/2024/12/24/smc-rw-linux-rust.html" rel="alternate" type="text/html" title="Linux向けSMC読み書きツール（I/Oポートアクセス）をRustへ移植した" /><published>2024-12-24T12:02:00+09:00</published><updated>2024-12-24T12:02:00+09:00</updated><id>/2024/12/24/smc-rw-linux-rust</id><content type="html" xml:base="/2024/12/24/smc-rw-linux-rust.html">&lt;p&gt;本記事は&lt;a href=&quot;https://qiita.com/advent-calendar/2024/rust&quot; class=&quot;link&quot;&gt;Rust Advent Calendar 2024&lt;/a&gt; Series 3、19日目のエントリです&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1&quot;&gt;&lt;/a&gt;動機&lt;/h2&gt;
&lt;p&gt;私は日常の外出時用PCとしてMacBook 2016を使っています。このPCはmacOSのサポートが切れていることもあってUbuntuで使っているのですが、とっくにビンテージ扱いのPCにおいてはバッテリー寿命をのばすために充電上限を設定し、適宜設定更新する必要があります&lt;/p&gt;
&lt;p&gt;もちろんこのような端末固有のきめ細かな制御ツールはUbuntu標準機能に入っていないので、以前GNOME拡張として実装しました&lt;a id=&quot;fnb-itawaly&quot; href=&quot;#fn-itawaly&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;。しかしUbuntu 22.04からUbuntu 24.04へアップデートするとGNOME拡張のサイドロード方法が変わったようで動作しなくなってしまいました&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-itawaly&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] &lt;a href=&quot;https://github.com/muojp/itawaly/&quot; class=&quot;link&quot;&gt;https://github.com/muojp/itawaly/&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;個人的にはあまりGNOME Desktop固有の事情に深入りしたくないので、他の方法で書き直すことにしました。PCのI/Oポートを叩く都合上、root権限を付与した単機能のコンパクトなバイナリで実際のI/Oポートオペレーションをおこないつつ、高水準側に便利機能を実装してなんらかのプロセス間通信で連携させるのですが、この低水準側で従来使っていたSmcDumpKey.c（&lt;a href=&quot;https://github.com/floe/smc_util/blob/master/SmcDumpKey.c&quot; class=&quot;link&quot;&gt;https://github.com/floe/smc_util/blob/master/SmcDumpKey.c&lt;/a&gt;）には若干データ出力上の不具合があり、どうせ手を入れるならリライトもアリか、と考えました&lt;/p&gt;
&lt;p&gt;あまりCのコードをメンテし続けたくはないので、I/Oポートアクセスのための最小限のコードだけをCの世界に残しつつRustで書き直しました&lt;/p&gt;
&lt;p&gt;コードは&lt;a href=&quot;https://github.com/muojp/smc-rw-linux/&quot; class=&quot;link&quot;&gt;https://github.com/muojp/smc-rw-linux/&lt;/a&gt;にあります&lt;/p&gt;
&lt;p&gt;ちなみにRustでのSMCアクセスについて、Swiftで書かれたmacOS用の実装やRustのmacOS用クレート&lt;a id=&quot;fnb-smc-crate&quot; href=&quot;#fn-smc-crate&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*2&lt;/a&gt;は見かけたのですが、Linux向けに書かれた実装はサッと見つけられませんでした&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-smc-crate&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*2] &lt;a href=&quot;https://crates.io/crates/smc&quot; class=&quot;link&quot;&gt;https://crates.io/crates/smc&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2&quot;&gt;&lt;/a&gt;実装方針&lt;/h2&gt;
&lt;p&gt;以前はCで書かれたユーティリティーをビルドしてGNOME拡張のJavaScriptコードからプラットフォーム呼び出しで利用していました。I/Oポートアクセス権限の都合上、便利機能の提供側とI/Oポートアクセス部分は別プロセスにしたいので、構成としては今回もさほど変わりません&lt;/p&gt;
&lt;p&gt;Rustでの実装は次のような方針で進めました&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ccでC側とのインタフェースを用意し、IoPortRw traitでOS差分を吸収&lt;/li&gt;
&lt;li&gt;それを実装するLinuxIoPortRwに実際のI/Oポートアクセスのunsafe部分を置き&lt;/li&gt;
&lt;li&gt;これらを利用する低水準側操作としてSmcPrimitive traitを定義&lt;/li&gt;
&lt;li&gt;今回作ったMacBook用実装はDefaultSmcRw（リポジトリ内にデバイス製造企業名を残したくなかったので曖昧な命名にしています）として実装&lt;/li&gt;
&lt;li&gt;高水準側はSmcOperationのデフォルト実装として作り、テスト範囲を低水準側と切り離しやすくする。実際のテストはmockallクレートでやっていく&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3&quot;&gt;&lt;/a&gt;実装上のポイント&lt;/h2&gt;
&lt;p&gt;Linuxのsys/io.hはaarch64環境には存在しません。動作ターゲットのMacBook 2016はIntelアーキテクチャですが私は普段の開発をaarch64のLinux環境でしているので、アーキテクチャ依存部分を分離する必要がありました&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;#if defined(__x86_64__)

#include &amp;lt;sys/io.h&amp;gt;

void port_outb(unsigned char value, unsigned short port) {
        outb(value, port);
}

unsigned char port_inb(unsigned short port) {
        return inb(port);
}

int port_ioperm(unsigned long from, unsigned long num, int turn_on) {
        return ioperm(from, num, turn_on);
}

#else

void port_outb(unsigned char, unsigned short) {
}

unsigned char port_inb(unsigned short) {
        return 0;
}

int port_ioperm(unsigned long, unsigned long, int) {
        return 0;
}

#endif
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;若干乱暴ですがこれでクロスターゲットのテスト可能範囲をだいぶ増やせました&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3-4&quot;&gt;&lt;/a&gt;テスト戦略&lt;/h2&gt;
&lt;p&gt;前述のようにtraitをSmcPrimitiveとSmcOperationと低水準・高水準に分けて実装し、SmcOperationのデフォルト実装に高水準APIを実装することで、低水準側をmockにしたテストをしやすくしました。この分離がなければ無限にダミーI/Oポート応答シナリオを書くことになってなかなか不毛です&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;#[cfg(test)]
    mock! {
        SmcTest {}
        impl SmcPrimitive for SmcTest {
            type IoPort = MockIoPortRw;
            fn new(io_port_rw: MockIoPortRw) -&amp;gt; Self;
            fn wait_read(&amp;amp;self) -&amp;gt; libc::c_int;
            fn send_byte(&amp;amp;self, cmd: u8, port: u16) -&amp;gt; Result&amp;lt;libc::c_int, String&amp;gt;;
            fn send_argument(&amp;amp;self, key: [u8; SMC_KEY_NAME_LEN]) -&amp;gt; Result&amp;lt;(), (usize, String)&amp;gt;;
            fn recv_byte(&amp;amp;self) -&amp;gt; Result&amp;lt;u8, ()&amp;gt;;
        }
    }
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;アプリケーション開発の中でも自動テスト部分ではやはり特殊なことをするケースがそれなりにあって（典型的には多くの人が初めてリフレクションやRTTIを意識するのはテスト文脈ではないでしょうか）、テスト補助ライブラリが複雑な状態に基づくテストケースをテストターゲットの設計へあまり踏み込まずに書ききれる筋力を持つか否かというのは言語自体の書き味と同じくらい重要な場合もあります。もちろん、testableにするためのプログラム構造変更がよりよい設計につながるケースも多いため、テストの窮屈さは設計改善のシグナルのひとつでもありますが、「ここはこれでよし」と定めた際にその方針通り走り切れることもまたテスト補助ライブラリの出来と言語の記述力次第というか。&lt;/p&gt;
&lt;p&gt;今回、I/Oポートへの入力値に応じて出力が想定どおりに変化することを確認するためにArcを介した共有メモリでのデータのやり取りが必要になったり、テストの並列実行時にmock生成部分がスレッドセーフではない挙動をする（flakyに失敗する）ためmockのセットアップ部分へMutexベースのロックを利用したりと細かな手当は必要となりましたが、逆に言えばそれだけで済んだのでmockallは今回の用途では十分に実用域で開発を強力に支えてくれると感じました&lt;/p&gt;
&lt;p&gt;とはいえautomockでは足りずmock!{}ブロックを書く必要が生じるのはmockall設計上のイレギュラーパターンに半分足を突っ込んでいる状態で、あまりサンプルが豊富ではないしコンパイルエラー時のメッセージも分かりづらくなるためあまり複雑化しないほうが良い気配はありました&lt;/p&gt;
&lt;p&gt;また、今回のテストコードを書いている際にpanic関連の挙動についての学びがありました。文脈としては、不正な長さのデータを投入した際に想定通り&lt;code class=&quot;inline-code tt&quot;&gt;Err()&lt;/code&gt;が戻ってくることを確認するのに加えて、高水準側（ほぼmainメソッド直下）においては想定通りpanicで実行を止めてくれることを確認したかったので、次のようなコードを書きました&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;let hook = panic::take_hook();
panic::set_hook(Box::new(|_| {

}));
data.data_len = 16;
let result = std::panic::catch_unwind(|| {
    let mut short_buf = [0u8; 8];
    smc_rw.read_smc(SMC_GET_KEY_TYPE_CMD, [0x01, 0x02, 0x03, 0x04], &amp;amp;mut short_buf[0..data.data_len as usize])
});
panic::set_hook(hook);

assert!(result.is_err());
match result {
    Err(panic_msg) =&amp;gt; {
        if let Some(msg) = panic_msg.downcast_ref::&amp;lt;String&amp;gt;() {
            assert_eq!(&amp;quot;range end index 16 out of range for slice of length 8&amp;quot;, msg);
        }
    }
    Ok(_) =&amp;gt; panic!(&amp;quot;should receive an error&amp;quot;)
}
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;もともとは&lt;code class=&quot;inline-code tt&quot;&gt;std::panic::catch_unwind&lt;/code&gt;で単純に情報取得していたのですが、stdoutに出てくるbacktraceが割と邪魔だと気付いたので抑制したいと思ったのがきっかけで対応を練りました。処理としてはpanic時のフックを一時的にほぼ空の実装へ置き換えてテスト実行、その後にもとのフックへ戻しています&lt;/p&gt;
&lt;p&gt;想定通りbacktraceが出力されなくなって自動テストの出力が汚れることはなくなり、さらに副作用として、手元PCでこのテストケースだけ150msほどかかっていたのが数msまで短縮されました。やはりbacktraceの生成は普通に重い処理なんだな、、という実感がありました（&lt;code class=&quot;inline-code tt&quot;&gt;cargo test&lt;/code&gt;時は通常Debugビルドで動いているという都合もあるでしょう）&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3-4-5&quot;&gt;&lt;/a&gt;まとめ&lt;/h2&gt;
&lt;p&gt;今回の用途は、低水準のI/Oを叩きに行くコンパクトなユーティリティーをそこそこ見通しよく書けてメモリ安全性を確保しやすくテストコードもそれなりに書きやすくコンパイル後のバイナリが許容可能なサイズに収まる&lt;a id=&quot;fnb-binary-size&quot; href=&quot;#fn-binary-size&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*3&lt;/a&gt;、というRustとCargo/クレートエコシステムの良さを活かしやすいものでありました&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-binary-size&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*3] リリースバイナリが667KiBで決して小さくはないのですが、そのあたりは別エントリに書いたので参照ください。&lt;a href=&quot;https://b.muo.jp/2024/12/22/rust-release-binary-size&quot; class=&quot;link&quot;&gt;https://b.muo.jp/2024/12/22/rust-release-binary-size&lt;/a&gt; ちなみにbuild-stdでビルドすると348KiB、CLI処理用のclapが大半を占めます&lt;/p&gt;&lt;/div&gt;</content><author><name></name></author></entry><entry><title type="html">Rust製ツールのリリースバイナリを無理なくコンパクトにする</title><link href="/2024/12/22/rust-release-binary-size.html" rel="alternate" type="text/html" title="Rust製ツールのリリースバイナリを無理なくコンパクトにする" /><published>2024-12-22T20:17:00+09:00</published><updated>2024-12-22T20:17:00+09:00</updated><id>/2024/12/22/rust-release-binary-size</id><content type="html" xml:base="/2024/12/22/rust-release-binary-size.html">&lt;p&gt;シンプルな機能のRustプログラムを書いていたら、リリースビルドでも420KB超と予想以上にサイズが大きくなった。バイナリサイズの削減がどの程度現実的なのかをざっと検証してみる&lt;a id=&quot;fnb-min-sized-rust&quot; href=&quot;#fn-min-sized-rust&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1&quot;&gt;&lt;/a&gt;初期状態の確認&lt;/h2&gt;
&lt;p&gt;releaseビルドの初期状態で429104 bytes&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;cargo bloat&lt;/code&gt;で中身を確認すると、大半が標準ライブラリ由来、特にpanic時のハンドラから呼ばれていると思しきbacktrace生成用のライブラリが上位を占める&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ cargo bloat --release -n 10
...

 File  .text     Size Crate Name
 0.9%   6.3%  14.7KiB   std std::sys::backtrace::_print_fmt::
 0.7%   5.2%  12.1KiB   std std::backtrace_rs::symbolize::gimli::Context::new
 0.6%   4.0%   9.4KiB   std gimli::read::dwarf::Unit&amp;lt;R&amp;gt;::new
 0.6%   4.0%   9.3KiB   std miniz_oxide::inflate::core::decompress
 0.4%   2.9%   6.9KiB   std addr2line::ResUnit&amp;lt;R&amp;gt;::find_function_or_location::
...
&lt;/pre&gt;
&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2&quot;&gt;&lt;/a&gt;バイナリサイズの削減&lt;/h2&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-2-1&quot;&gt;&lt;/a&gt;Cargo.tomlへの定番設定&lt;/h3&gt;
&lt;p&gt;Cargo.tomlに以下を追加する&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;[profile.release]
# opt-level = &amp;quot;z&amp;quot;
lto = true
codegen-units = 1
strip = true
panic = &amp;quot;abort&amp;quot;
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;panic = &amp;quot;abort&amp;quot;&lt;/code&gt;がキー！という書き方をしているblog記事類も見つかるが、ここへの期待度はさほど高くない。よほどきれいにstripされない限りは周辺ライブラリの容量削減は見込めないため&lt;/p&gt;
&lt;p&gt;これらの設定を追加後にリビルドして326040 bytes、最初の429104 bytesと比較すると24%の削減&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;opt-level = &amp;quot;z&amp;quot;&lt;/code&gt;指定を有効化するとさらに9KBほど減るが、速度を犠牲にする判断をしたくないケースも多い気はするので今回は最終的にコメントアウトした。典型的には短めの固定長配列に対するループのアンロール処理を無効化することでサイズを削るのだろう&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-2-1-2-2&quot;&gt;&lt;/a&gt;stdのコンパクト版ビルド&lt;/h3&gt;
&lt;p&gt;2018年頃には確立していた手法として、stdの実行時用コンパクト版をビルドする方法がある&lt;a id=&quot;fnb-xargo&quot; href=&quot;#fn-xargo&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*3&lt;/a&gt;。MSVCでいうところのVC++ Runtime debug版とrelease版のようなもので、Rustでは通常debug時でもrelease時でも同一のプリビルトstdライブラリを静的リンクしているが、ビルド時にソースからstdを構築してバンドルする仕組み（概念としてはstd Aware Cargoと表現されている&lt;a id=&quot;fnb-std-aware-cargo&quot; href=&quot;#fn-std-aware-cargo&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*2&lt;/a&gt;）によって不要なものを削ってバイナリサイズを削れる。当初はXargoで実現されていた機能だが、現在はnightly版の&lt;code class=&quot;inline-code tt&quot;&gt;build-std&lt;/code&gt;を使用する&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ cargo +nightly build -Z build-std=std,panic_abort -Z build-std-features=panic_immediate_abort --release
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;ここで指定している&lt;code class=&quot;inline-code tt&quot;&gt;-Z&lt;/code&gt;オプション群はnightly版のCargoでしかまだ使えない（2024年12月時点）ので、rustupから予めnightly版のtoolchainとstdビルド用のrust-srcを導入しておく必要がある&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;build-std-features=panic_immediate_abort&lt;/code&gt;指定でCargo.toml側の&lt;code class=&quot;inline-code tt&quot;&gt;panic = &amp;quot;abort&amp;quot;&lt;/code&gt;指定を補完しているのは良い&lt;/p&gt;
&lt;p&gt;これで26KBまで劇的に削減できる&lt;/p&gt;
&lt;p&gt;なお、stdをビルドする都合上リリースビルドの所要時間は伸びる。手元の古いPCでは30秒程度のオーバーヘッドが発生するが、リリースビルドをそう頻繁にやるわけではないので開発体験を悪化させる性質のものではない。配布バイナリのサイズ最適化という文脈では許容できる範囲と考えられる&lt;/p&gt;
&lt;p&gt;ただ、build-stdは前述のように2024年時点でもnightlyの機能である。std-aware Cargoの仕様策定には複雑な要件が絡み、安定化のためのディスカッションポイントはどれもパワーが必要そうで、すでに数年間議論が止まっていたり当初の議論リード者の関与が薄くなっている点も複数ある。バイナリサイズ削減がビジネス価値につながる企業が急にスポンサーとして付く、のような大幅加速イベントが発生しない限り、2030年ぐらいまでnightlyのままであっても驚きはない状況に見える&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;build-std&lt;/code&gt;はまだpreliminaryでexperimentalな存在という扱いであることには注意が必要。開発段階で各種自動テストを通していても言語ランタイム/標準ライブラリが差し替えられた状態でそれらの結果が信用できるか、というのは現状での各種リリースバイナリをbuild-std版のstdライブラリと静的リンクして出力することを躊躇するに十分な理由で、そう考えるとよほど特殊な事情がない限りはnightly範囲外のCargo.toml設定群で満足しておくのが無難ではある&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-2-1-2-2-3&quot;&gt;&lt;/a&gt;実行環境に応じた検討事項&lt;/h2&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-2-1-2-2-3-3-1&quot;&gt;&lt;/a&gt;配布サイズと実行時のトレードオフ&lt;/h3&gt;
&lt;p&gt;配布時のサイズをさらに小さくする選択肢としてはUPXによる実行可能ファイルの圧縮&lt;a id=&quot;fnb-upx&quot; href=&quot;#fn-upx&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*4&lt;/a&gt;やlzipなどの一般的な圧縮形式の利用が考えられる。UPXは実行時にメモリ上でバイナリを展開する必要があり、実際の配布サイズとしては一般的な圧縮形式と大きな差は出ないため、見かけ上の容量削減（自己満足とも言える）に留まる可能性がそれなりに高い。たしかに今回のターゲットでは26KB→15KBぐらいに縮みはしたが作っているツールの性質的には特にUPXは不要だと感じた&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-2-1-2-2-3-3-1-3-2&quot;&gt;&lt;/a&gt;WebAssembly向けの考慮&lt;/h3&gt;
&lt;p&gt;wasm環境での実行を想定する場合、stdをカスタムビルドするよりも&lt;code class=&quot;inline-code tt&quot;&gt;no_std&lt;/code&gt;を選択するほうが現実的なケースも多そう。しかしstdの要素は案外あちこちで必要になるのでその都度代替実装を探したり書いたりとしていくハードルは結構高い&lt;a id=&quot;fnb-nostd-playbook&quot; href=&quot;#fn-nostd-playbook&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*5&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-min-sized-rust&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] &lt;a href=&quot;https://github.com/johnthagen/min-sized-rust&quot; class=&quot;link&quot;&gt;https://github.com/johnthagen/min-sized-rust&lt;/a&gt; というチュートリアルが丁寧に各手法を紹介しているので参考になる。結果としてはこの内容を辿っていった&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-std-aware-cargo&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*2] &lt;a href=&quot;https://github.com/rust-lang/wg-cargo-std-aware&quot; class=&quot;link&quot;&gt;https://github.com/rust-lang/wg-cargo-std-aware&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-xargo&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*3] Xargoは2018年以降アクティブな開発は行われていない。 &lt;a href=&quot;https://github.com/japaric/xargo/issues/193&quot; class=&quot;link&quot;&gt;https://github.com/japaric/xargo/issues/193&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-upx&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*4] Ultimate Packer for eXecutables &lt;a href=&quot;https://github.com/upx/upx&quot; class=&quot;link&quot;&gt;https://github.com/upx/upx&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-nostd-playbook&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*5] wasm向けのno-stdについては&lt;a href=&quot;https://hackmd.io/@alxiong/rust-no-std&quot; class=&quot;link&quot;&gt;https://hackmd.io/@alxiong/rust-no-std&lt;/a&gt;が出発点としてよさそう&lt;/p&gt;&lt;/div&gt;</content><author><name></name></author></entry><entry><title type="html">ケーブルボックス内のM4 Mac miniの温度管理</title><link href="/2024/11/28/mac-mini-temperature.html" rel="alternate" type="text/html" title="ケーブルボックス内のM4 Mac miniの温度管理" /><published>2024-11-28T00:00:00+09:00</published><updated>2024-11-28T00:00:00+09:00</updated><id>/2024/11/28/mac-mini-temperature</id><content type="html" xml:base="/2024/11/28/mac-mini-temperature.html">&lt;p&gt;今年は久々に手元の作業用Macを買い替えた。個人的には視界の中にMacがあっても特にテンション上がらないので、Mac mini本体は大きめのケーブルボックスの中に設置している。この運用なら発売前から話題になっていた電源ボタン問題とも無縁である&lt;/p&gt;
&lt;p&gt;ケーブルボックス内に隔離するとエアフロー確保はそれなりに課題だと思いつつ、冬場なのでひとまず深く考えず後回しにしていた。けれど、年末の大掃除の文脈でレイアウト変更を考えるのも良いなと思ったので、少し考えることにした&lt;/p&gt;
&lt;p&gt;まずはケーブルボックス内で実際どれぐらいの温度で動作しているのかを知るところから。Macのシステム情報を取得・可視化する方法はいろいろあるが、Intel Mac用のものなど入り乱れていて Apple SiliconのMac、とりわけ手元のM4 Mac miniで正常動作するのがどれなのかよくわからない。いくつか試した結果、無事に動作したのがStats&lt;a id=&quot;fnb-stats&quot; href=&quot;#fn-stats&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;だった&lt;/p&gt;
&lt;p&gt;StatsはHomebrewからインストールできる&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ brew install stats
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;デフォルトではシステムトレイ上にいろいろなシステム情報が表示されるのだけど、CPU使用率やネットワーク速度類が頻繁に画面上で表示更新されていると気が散るので、温度に特化させることにした&lt;/p&gt;
&lt;p&gt;データ更新頻度はデフォルトで1秒間隔になっている（表示上は3秒間隔がデフォルトだが実際は1秒）けれど、そんなに頻繁な更新は必要ないので1分間隔にしておく&lt;/p&gt;
&lt;p&gt;ポップアップ通知の機能もついているので、ちょっとやばいかなという条件として Airport 50度、Average CPU 75度を設定しておいた&lt;/p&gt;
&lt;p&gt;ちなみにStatsにはファン速度のコントローラーもついているので、静音性よりも放熱を優先したい場合は手動でファン速度を上げられる。これは便利&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-stats&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] &lt;a href=&quot;https://github.com/exelban/stats&quot; class=&quot;link&quot;&gt;https://github.com/exelban/stats&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1&quot;&gt;&lt;/a&gt;2024-12-31追記&lt;/h2&gt;
&lt;p&gt;温度を1日取ってみたらまあまあ上がっていて本体寿命によくなさそうだったので即ケーブルボックスから取り出して室内のすみっこに置いた&lt;/p&gt;
&lt;p&gt;Statsはそれ以来起動していない。1週間ぐらいは状況を確認することになるかなと思ったけれど、状況が可視化されたら思いの外一瞬でカタがついた。ありがたや&lt;/p&gt;</content><author><name></name></author></entry><entry><title type="html">このblogの下回りシステムを少しメンテした</title><link href="/2024/11/27/blog-system-maintenance.html" rel="alternate" type="text/html" title="このblogの下回りシステムを少しメンテした" /><published>2024-11-27T09:54:00+09:00</published><updated>2024-11-27T09:54:00+09:00</updated><id>/2024/11/27/blog-system-maintenance</id><content type="html" xml:base="/2024/11/27/blog-system-maintenance.html">&lt;p&gt;このblogはRe:VIEW記法で書いたエントリをJekyll+Netlifyで静的サイトとして書き出している&lt;/p&gt;
&lt;p&gt;ちょっと古めのアーキテクチャだとは感じるけれどRe:VIEW記法で文書を作るのには慣れているので今から変える理由も特にないかなという&lt;/p&gt;
&lt;p&gt;さて久々に新エントリを書いたらNetlifyでビルドエラーを吐いた。Python系の依存解決でエラー&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;7:50:45 AM:   Downloading funcparserlib-0.3.6.tar.gz (30 kB)
7:50:45 AM:   Installing build dependencies: started
7:50:46 AM: Failed during stage 'Install dependencies': dependency_installation script returned non-zero exit code: 1
7:50:46 AM:   Installing build dependencies: finished with status 'done'
7:50:46 AM:   Getting requirements to build wheel: started
7:50:46 AM:   Getting requirements to build wheel: finished with status 'error'
7:50:46 AM:   error: subprocess-exited-with-error
7:50:46 AM:   × Getting requirements to build wheel did not run successfully.
7:50:46 AM:   │ exit code: 1
7:50:46 AM:   ╰─&amp;gt; [1 lines of output]
7:50:46 AM:       error in funcparserlib setup command: use_2to3 is invalid.
7:50:46 AM:       [end of output]
7:50:46 AM:   note: This error originates from a subprocess, and is likely not a problem with pip.
7:50:46 AM: error: subprocess-exited-with-error
7:50:46 AM: × Getting requirements to build wheel did not run successfully.
7:50:46 AM: │ exit code: 1
7:50:46 AM: ╰─&amp;gt; See above for output.
7:50:46 AM: note: This error originates from a subprocess, and is likely not a problem with pip.
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;funcparserlib-0.3.6&lt;/code&gt;、やけに古い&lt;/p&gt;
&lt;p&gt;そもそもなんでPython?というのをすっかり忘れていたのだけど、数年前に自分へのクリスマスプレゼントとしてblockdiagで図を埋め込めるようにしたのだった&lt;a id=&quot;fnb-old-post&quot; href=&quot;#fn-old-post&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;。すっかり忘れていた&lt;/p&gt;
&lt;p&gt;Netlifyでは&lt;code class=&quot;inline-code tt&quot;&gt;runtime.txt&lt;/code&gt;にPythonのバージョンを書けるのだけど見てみるとバージョン&lt;code class=&quot;inline-code tt&quot;&gt;3.8&lt;/code&gt;、今年の10月にサポートが切れている&lt;/p&gt;
&lt;p&gt;極力最新にするのも良いけれど手元で使っているデスクトップのUbuntu 22.04デフォが&lt;code class=&quot;inline-code tt&quot;&gt;3.10&lt;/code&gt;系なので一旦それにあわせておく&lt;/p&gt;
&lt;p&gt;pipのバージョン指定を手動で細かくやるのも不毛なので、手元環境でvenvを作り直してそこへ入ったバージョンを採用する&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;python3 -m venv .venv
source .venv/bin/activate
pip install blockdiag
pip freeze &amp;gt; requirements.txt
echo '3.10' &amp;gt; runtime.txt
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;として再度デプロイする。どうもまだ図のビルドでエラーを吐いている&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;blockdiag==3.0.0
funcparserlib==2.0.0a0
pillow==11.0.0
webcolors==24.11.1
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;ちゃんと今風な依存関係になっているけれど何がまずいのか、デプロイログを見てみる&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;9:33:56 AM: INFO jekyll: blockdiag -a -T svg -o ./images/html/2019-12-25-diagram1.svg /tmp/review_graph20241127-5628-tmushg
9:33:56 AM: ERROR: 'FreeTypeFont' object has no attribute 'getsize'
9:33:56 AM: ERROR jekyll: failed to run command for id 2019-12-25-diagram1: blockdiag -a -T svg -o ./images/html/2019-12-25-diagram1.svg /tmp/review_graph20241127-5628-tmushg
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;調べるとPillow 9系から10系以上（現在の推奨は11系）へバージョンアップした際の非互換性FAQらしい&lt;/p&gt;
&lt;p&gt;セキュリティ上Pillow 9系を使い続けたくはないが、ここに時間をかけすぎたくもないので一旦撤退がてら9.5.0を使っておくことにした&lt;/p&gt;
&lt;p&gt;ひとまず図は再生成されたので一旦よしとする&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-old-post&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] &lt;a href=&quot;https://b.muo.jp/2019/12/25/jekyll-review-blockdiag&quot; class=&quot;link&quot;&gt;https://b.muo.jp/2019/12/25/jekyll-review-blockdiag&lt;/a&gt;に当時の記事がある&lt;/p&gt;&lt;/div&gt;</content><author><name></name></author></entry><entry><title type="html">VS Codeのホイールボタンペーストを無効化する</title><link href="/2024/11/24/vscode-center-click-paste.html" rel="alternate" type="text/html" title="VS Codeのホイールボタンペーストを無効化する" /><published>2024-11-24T00:00:00+09:00</published><updated>2024-11-24T00:00:00+09:00</updated><id>/2024/11/24/vscode-center-click-paste</id><content type="html" xml:base="/2024/11/24/vscode-center-click-paste.html">&lt;p&gt;私のメイン環境はUbuntu 22.04 LTSであり、VS Codeの設定としてはほぼデフォルトのままで運用している&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1&quot;&gt;&lt;/a&gt;ホイールボタンペーストの問題&lt;/h2&gt;
&lt;p&gt;Linuxデスクトップ環境では、ホイールボタンクリックでクリップボードの内容がペーストされる挙動がデフォルトである。この挙動はLinuxデスクトップ環境における長年の伝統だが、個人的にはあまり直感的でもなければ便利でもない。ホイールボタンクリックは新タブでリンクを開くボタンだと教育されて育った世代なので、ペースト挙動は世間常識から外れていてLinuxデスクトップ環境が一般に普及しない要因のひとつではないかとさえ考えている&lt;/p&gt;
&lt;p&gt;人生においてテキストを「ペーストしよう」と思ってホイールボタンをクリックする瞬間が存在するだろうか？と考えてみると、自分の場合にはエディタ作業に誤ってホイールボタンをクリックしてしまうことでコードの途中に意図しないペーストが発生して生産性を下げる懸念のほうが大きいと感じるし、うっかり画面外でペーストしたことに気付かずセンシティブな情報をgit pushした日には面倒が大きすぎる&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2&quot;&gt;&lt;/a&gt;設定による解決&lt;/h2&gt;
&lt;p&gt;VS Codeの設定で以下のように変更する&lt;a id=&quot;fnb-vscode-issue&quot; href=&quot;#fn-vscode-issue&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;と、この問題を解決できる&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;&amp;quot;editor.selectionClipboard&amp;quot;: false
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;設定はsettings.jsonへ直接記述するか、設定画面から変更する&lt;/p&gt;
&lt;p&gt;この設定により、以下の操作が無効化される：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;テキスト選択時の自動クリップボードコピー&lt;/li&gt;
&lt;li&gt;ホイールボタンクリックによるペースト&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通常のCtrl+C / Ctrl+Vによるコピー＆ペースト操作は従来通り利用できる&lt;/p&gt;
&lt;p&gt;この設定変更はVS Code内でのみ有効であり、他のアプリケーションでの挙動には影響しない。OS全体でこの挙動を無効化する方法もあるが、経験上あまりうまくいったことがない（ホイールボタン自体を無効化するなど副作用が派手で不便なことが多い）ので今回はVS Code内での対応に留めておく&lt;/p&gt;
&lt;p&gt;ちなみに個人的にはFirefoxに関して同様の設定をしている。Notionのリンクを新タブで開こうとしてクリップボードを適当なページへぶちまけるリスクは現代において思いのほか現実的で危ういので、これもおすすめする&lt;a id=&quot;fnb-firefox&quot; href=&quot;#fn-firefox&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*2&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-vscode-issue&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] &lt;a href=&quot;https://github.com/microsoft/vscode/issues/14610&quot; class=&quot;link&quot;&gt;https://github.com/microsoft/vscode/issues/14610&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-firefox&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*2] &lt;a href=&quot;https://www.reddit.com/r/firefox/comments/a6i0ba/ysk_you_can_disable_the_disabling_of_copy_and/?rdt=41407&quot; class=&quot;link&quot;&gt;https://www.reddit.com/r/firefox/comments/a6i0ba/ysk_you_can_disable_the_disabling_of_copy_and/?rdt=41407&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;</content><author><name></name></author></entry><entry><title type="html">12インチMacBook（2016）をUbuntu 22.04で使う</title><link href="/2023/11/26/macbook-12inch-linux.html" rel="alternate" type="text/html" title="12インチMacBook（2016）をUbuntu 22.04で使う" /><published>2023-11-26T00:00:00+09:00</published><updated>2023-11-26T00:00:00+09:00</updated><id>/2023/11/26/macbook-12inch-linux</id><content type="html" xml:base="/2023/11/26/macbook-12inch-linux.html">&lt;p&gt;一年の終わりが近い&lt;/p&gt;
&lt;p&gt;ので、毎年のように書いている自分の作業環境の話を2023年版にアップデートしておく&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1&quot;&gt;&lt;/a&gt;920g、黄金時代のMacBook&lt;/h2&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1&quot;&gt;&lt;/a&gt;MacとmacOS&lt;/h3&gt;
&lt;p&gt;まずmacOSのライフサイクルについておさらいすると、macOSは毎年6月のWWDCあたりで発表され、10月頃のiPhone発売と近い時期にリリースされ、初期リリースから33ヶ月ほどでセキュリティーfixが止まる&lt;/p&gt;
&lt;p&gt;セキュリティーfixが止まった頃には次のOSのメジャーバージョンがリリースされているのでそちらへアップデートしろということになるが、非対応デバイスは切り捨てられる&lt;a id=&quot;fnb-oclp&quot; href=&quot;#fn-oclp&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;歴史的にMacの発売からセキュリティーfixが終了するまでの期間は約7年で、それぐらい経つうちには数回バッテリーがヘタり、バッテリーのGenius Barでの修理受付が終了し、互換バッテリー在庫も枯渇していく&lt;a id=&quot;fnb-kbd&quot; href=&quot;#fn-kbd&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*2&lt;/a&gt;、というライフサイクルである&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-oclp&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] OpenCore Legacy Patcherを利用して新macOSを無理やり旧Macへインストール派閥も存在するがHackintoshの類なのでここでは扱わない&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-kbd&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*2] ちなみに12インチMacBookはキーボードが故障しやすいので交換用パーツを一式手元に抑えてある&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;あまり知られていないが、もう8年近くも重量1kgを下回るMacBookは発売されていない&lt;/p&gt;
&lt;p&gt;いっぽうで2023年にリリースされたmacOS 14（Sonoma）では全世代の12インチMacBookがサポートを打ち切られ、2022年リリースのmacOS 13（Ventura）の2025年7月最終アップデート（見込み）によって全てのサポートが終了する&lt;/p&gt;
&lt;p&gt;これがMacBookにおける2025年問題である&lt;/p&gt;
&lt;p&gt;サポートの切れたOSを日常利用するのはセキュリティー上望ましくないので12インチMacBookユーザーはそろそろ逃げ道を本格検討する必要があり、その際に有力な選択肢がLinuxである&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2&quot;&gt;&lt;/a&gt;MacとLinux&lt;/h3&gt;
&lt;p&gt;12インチMacBookのようなクラシックなデバイスはLinux、とりわけUbuntuでの利用と相性が良い&lt;/p&gt;
&lt;p&gt;現代のUbuntu標準GNOME Desktopは非力なCPUでも十分実用的に動作するし、メモリを一定抑制しやすいようにFirefoxを常用するなどすれば&lt;a id=&quot;fnb-low-memory&quot; href=&quot;#fn-low-memory&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*3&lt;/a&gt;日常的な作業における支障は少ない&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-low-memory&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*3] Appleが低レンジMacにおいて「メインメモリ8GBでいいでしょ？」と設計する裏側にはmacOSのメモリ圧縮機能とSSDへのスワップアウト性能の良さを前提としている部分があり、デスクトップLinuxは必ずしもこの思想と相性が良くないためChromeは正直きびしい部分がある&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;年が明けたらUbuntu 24.04が近づいてくる&lt;/p&gt;
&lt;p&gt;Ubuntuの非サーバー構成インストールではインストール後も定期的にkernelバージョンが上がっていくのでメジャーなLTS更新だからといって基盤部分で大きな変化はないことが多い&lt;a id=&quot;fnb-ubuntu-lts&quot; href=&quot;#fn-ubuntu-lts&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*4&lt;/a&gt;が、メジャーなアップデートではPipeWireやWayland、Flutterの利用領域拡大によって確実に前向きな変化が起きるので早い段階でバージョンアップするようにしている&lt;/p&gt;
&lt;p&gt;OSのメジャーバージョンアップは何かとトラブルを引き起こして忌避されがちであるが、個人的にはdist-upgradeせずにOS新規インストール&amp;amp;Homeディレクトリの式年遷宮をする派&lt;a id=&quot;fnb-apt-snap&quot; href=&quot;#fn-apt-snap&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*5&lt;/a&gt;なので、次の遷宮機会時にはこれまでMac標準のAPFSへ割り当てていたディスク領域を大幅にshrinkさせてLinux用に再割当てするのが楽しみ、という程度のお気楽な2年に1度のイベントである&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-ubuntu-lts&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*4] 逆にLTSライフサイクル中を通じてkernelの新規featuresも取り込まれていきほぼ最新を使い続けられるというメリットがあり、Ubuntuを積極的に選ぶ要素のひとつでもある&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-apt-snap&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*5] どのみちapt/snapで導入できないものは数えるほどしかインストールしない&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;さて、Ubuntuでの利用を前提として12インチMacBookのLinuxでのドライバサポート状態をレビューしておく&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2&quot;&gt;&lt;/a&gt;12インチ MacBook Early 2016（MacBook 9,1）のLinuxドライバサポート状況&lt;/h2&gt;
&lt;p&gt;内蔵キーボード、トラックパッド、Wi-Fiまでは標準ドライバで賄える&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-2-1&quot;&gt;&lt;/a&gt;オーディオ出力&lt;/h3&gt;
&lt;p&gt;スピーカーと有線イヤフォンの利用にはクセがある&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Linux標準ドライバだと「イヤフォンジャック経由の出力ができる」&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/leifliddy/macbook12-audio-driver&quot; class=&quot;link&quot;&gt;https://github.com/leifliddy/macbook12-audio-driver&lt;/a&gt;の非公式ドライバだと「スピーカーからの出力ができる」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらを手動または半自動で切り替えることになる&lt;/p&gt;
&lt;p&gt;個人的には本体スピーカーから音を出したい場面がないので詳しく調べていない&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-2-1-2-2&quot;&gt;&lt;/a&gt;バッテリー管理&lt;/h3&gt;
&lt;p&gt;前述のように本体内蔵バッテリーはサポートの切れたMacを延命する重要な要素で、バッテリー寿命をのばすためには充電系のコントロールが欠かせない&lt;/p&gt;
&lt;p&gt;Mac内部の設定値を適宜いじることでmacOSには及ばないもののそれなりの延命は期待できる&lt;/p&gt;
&lt;p&gt;GNOME Desktopから手軽にバッテリー充電上限を設定できるように&lt;a href=&quot;https://github.com/muojp/itawaly&quot; class=&quot;link&quot;&gt;https://github.com/muojp/itawaly&lt;/a&gt;シェル拡張を自作したので、機会があれば試してみてほしい&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-2-1-2-2-2-3&quot;&gt;&lt;/a&gt;Bluetooth&lt;/h3&gt;
&lt;p&gt;厄介その1がBluetoothで、標準サポートされていない&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/leifliddy/macbook12-bluetooth-driver&quot; class=&quot;link&quot;&gt;https://github.com/leifliddy/macbook12-bluetooth-driver&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ここのBluetoothドライバを導入すれば動作する&lt;/p&gt;
&lt;p&gt;kernel更新の都度カーネルモジュールをビルドして組み込むのは若干面倒であるが、作業量自体が多いわけではなく基本的に2-3分で終わる&lt;/p&gt;
&lt;p&gt;HIDたとえばトラックボールやHHKB Proを接続して使う分には問題ないが、SBCの音声ストリームですらかなり途切れ途切れになるためBluetoothヘッドセットを接続してBGMを流すような用途にはほぼ使えない&lt;/p&gt;
&lt;p&gt;おそらくBT通信チップの初期化シーケンスでネゴシエーションしている動作モードが低ビットレート版のもので 本来は接続機器にあわせてモード変更コマンドを発行する必要があるのだろう（高レートで固定すると消費電力へ響くというパターンもありうる）&lt;/p&gt;
&lt;p&gt;このあたりは非公式情報によって構築されたドライバの弱みポイントではあり、オーディオ出力やオンライン会議用の利用については内蔵BT利用を諦めてUSB接続のBluetoothドングルを利用するほうが格段に安定する&lt;/p&gt;
&lt;p&gt;ちなみに2015年モデル（MacBook 8,1）は前述の非公式ドライバを導入してもBluetoothモジュールが動作しないためBluetoothドングルが必須である&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-2-1-2-2-2-3-2-4&quot;&gt;&lt;/a&gt;Webカメラ（FaceTime）&lt;/h3&gt;
&lt;p&gt;厄介その2がWebカメラである&lt;/p&gt;
&lt;p&gt;2011年以降のMacはFaceTimeを搭載している&lt;/p&gt;
&lt;p&gt;これらの多くはFaceTime HDと呼ばれているが12インチMacBookではどうも縦解像度480が上限のようで、どうひいき目に評してもSDである&lt;/p&gt;
&lt;p&gt;この手の内蔵カメラモジュールは内部で適当なUSBインタフェースへ接続されるのが普通かと思っていたらFaceTime HDは内部がPCIe接続になっている&lt;/p&gt;
&lt;p&gt;なぜPCIe？と思うが仮に60fps 1080pを24bit ベタRGBで伝送しようとすると約3Gbps必要であり（実際にはFaceTime HDは720pの時代が長くフレームレートも15-20fps程度なので500Mbpsない程度）、内蔵のUSB 2.0へぶら下げるのには多少荷が重い&lt;/p&gt;
&lt;p&gt;Wi-Fi/BluetoothモジュールがPCIe x1接続になっているパターンは今でもそれなりに見かけるし、特にプラグアンドプレイを前提とせず電源管理の柔軟性を確保したいデバイスなら悪くない選択肢なのだろう&lt;/p&gt;
&lt;p&gt;さて、Linux kernel自体にFaceTimeサポートのコードは取り込まれていないが、&lt;a href=&quot;https://github.com/patjak/facetimehd&quot; class=&quot;link&quot;&gt;https://github.com/patjak/facetimehd&lt;/a&gt;の非公式ドライバによって動作することが知られている&lt;a id=&quot;fnb-no-official-support&quot; href=&quot;#fn-no-official-support&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*6&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-no-official-support&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*6] 初期化シーケンスで必要となるファームウェアの再配布問題を考えるとおそらくFaceTime/HDサポートが標準へ取り込まれることはないだろう&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;12インチMacBookについては少々特殊で、前述の非公式ドライバをそのままビルドして組み込んでも初期化でコケて動作しない&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/patjak/facetimehd/issues/196#issuecomment-569858722&quot; class=&quot;link&quot;&gt;https://github.com/patjak/facetimehd/issues/196#issuecomment-569858722&lt;/a&gt;のパッチを当てると無事に動作する&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-2-1-2-2-2-3-2-4-3&quot;&gt;&lt;/a&gt;ChromeOS Flexという選択肢&lt;/h2&gt;
&lt;p&gt;仮にAppleがまだ最新OSでデバイスをサポートしているとしてもOS挙動が重すぎて使いたくない、というのはよくある話である&lt;/p&gt;
&lt;p&gt;いっぽうChromeOSと廉価版Chromebook群によるPC業界の学びは、用途を十分に絞り込めば現代でもメインメモリ4GB+16GB eMMCという貧弱な構成から十分に実用的なPC環境を構築・維持できるということである&lt;/p&gt;
&lt;p&gt;そして2020年にGoogleがCloudReadyを買収し、2022年にChromeOS Flexを公開したことにより非Chromebookへの限定的なChromeOSサポートが拡大しており、古いMacへChromeOS Flexをインストールするという道も現実味を帯びてきている&lt;/p&gt;
&lt;p&gt;これが一筋縄でいかないのはChromeOSの構造に由来する&lt;/p&gt;
&lt;p&gt;ChromeOSは枯れたLinux kernelにモダンなユーザーランドを組み合わせることでアップデート速度とメンテナンスコストのバランスを取るようになっているため、つまりは搭載しているLinux kernelが極端に古かったりする&lt;/p&gt;
&lt;p&gt;MacでLinuxを使う際にはこのあたりの問題を踏みやすい（なにぶんほとんどのハードウェア要素の詳細が非公開で有志による解析と非公式ドライバの開発に依存している）&lt;/p&gt;
&lt;p&gt;12インチMacBookは特にMac側のアーキテクチャ分断のモダン側の崖っぷち部分に位置しており、同機が発売された時点のLinux標準ドライバでは内蔵SPIインタフェースのキーボードが利用できない、内蔵NVMe SSDへアクセスできない、といった典型的なペリフェラルサポートにまつわる問題があった&lt;/p&gt;
&lt;p&gt;これらは2017年頃までに概ね解消してLinux kernel 5.3周辺で大きく改善したが、ChromeOSのベースバージョンが問題となる&lt;/p&gt;
&lt;p&gt;1年ぐらい前に試した際には&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ChromeOS 105：Linux 5.10ベース&lt;/li&gt;
&lt;li&gt;ChromeOS Flex：Linux 4.4あたりベース&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;であり&lt;a id=&quot;fnb-chromeos-base-kernel&quot; href=&quot;#fn-chromeos-base-kernel&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*8&lt;/a&gt;、ChromeOS FlexのインストーラーをUSBドライブから起動した時点で何も操作できなくなり電源ボタン長押し（これはOSサポートと関係なくPC/AT的な挙動で通る）するしかなくなる状態だった&lt;/p&gt;
&lt;p&gt;Chrome OS Flexのベースバージョンも今年の半ばにはLinux 5.10まで上がっている&lt;a id=&quot;fnb-base-version&quot; href=&quot;#fn-base-version&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*7&lt;/a&gt;ので、おそらくBluetooth・カメラを除けば12インチ用MacBookの安定稼働環境として実用的な状態になっているのではないだろうか&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-base-version&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*7] &lt;a href=&quot;https://www.reddit.com/r/ChromeOSFlex/comments/145166o/what_kernel_does_chrome_os_flex_use/?rdt=38176&quot; class=&quot;link&quot;&gt;https://www.reddit.com/r/ChromeOSFlex/comments/145166o/what_kernel_does_chrome_os_flex_use/?rdt=38176&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-chromeos-base-kernel&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*8] 厳密には出荷時期の古いChromebookでは最新ChromeOSユーザーランドに古いkernelを組み合わせて動作し続けるケースがあるので正確ではない&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;ChromeOSはすごい&lt;/p&gt;
&lt;p&gt;特にArmベースでWidevineのきちんとしたプロファイルが通って各種動画ストリーミングサービスを問題なく視聴できる環境としてはArmbian系よりもChromeOS系のほうが苦労せずに済むという点から考えてもなかなか強い環境である（あるいは用途を絞り込んでSTB向けAndroidを動かす、という二択）&lt;/p&gt;
&lt;p&gt;今後もLinux kernelへ各種コミュニティ開発ドライバが取り込まれていくと 最終的にChromeOS Flexがビンテージ/クラシックPC用の最も手軽でアップデートを受け続けられる環境になる可能性がある&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-2-1-2-2-2-3-2-4-3-4&quot;&gt;&lt;/a&gt;まとめ&lt;/h2&gt;
&lt;p&gt;Appleが1kg未満のMacを新規発売するのと手元12インチMacBookが全台朽ち果てるのとどちらが早いか、来年もご期待ください&lt;/p&gt;</content><author><name></name></author></entry><entry><title type="html">冬を乗り切れるか不安になってきたので意識低いホームオートメーションをする</title><link href="/2022/12/22/nature-remo-zenity.html" rel="alternate" type="text/html" title="冬を乗り切れるか不安になってきたので意識低いホームオートメーションをする" /><published>2022-12-22T00:00:00+09:00</published><updated>2022-12-22T00:00:00+09:00</updated><id>/2022/12/22/nature-remo-zenity</id><content type="html" xml:base="/2022/12/22/nature-remo-zenity.html">&lt;p&gt;最近寒すぎてこの冬を乗り切れるか怪しい&lt;/p&gt;
&lt;p&gt;弊宅にはNature Remoが導入済みであり、超簡易的にエアコンのP制御を組みリモートワーク環境の生産性確保を試みているが、契約している電力会社がどんどん燃料調達に関する値上げをしている中ではこの冬エアコンをまともに使える気がしない&lt;/p&gt;
&lt;p&gt;そうなると時代は灯油だ オイルを燃やして暖を取るほかない&lt;/p&gt;
&lt;p&gt;ここで問題になるのがIoT的な電源制御であり、廉価帯の石油ファンヒーターはもちろん上位モデルでも基本的にリモートで電源ONにする機能は入っていないように見受けられる（安全上の配慮、法令によるものと自主規制の組み合わせだろう）&lt;/p&gt;
&lt;p&gt;致し方ないので点火は手動でおこなうものとする&lt;/p&gt;
&lt;p&gt;問題はいつ点火するかであり、リモートワーカーとしては自分のパフォーマンスが落ちてくる前にこれを適切におこなう必要がある&lt;/p&gt;
&lt;p&gt;基本的にリモートワークのパフォーマンスは室温による関数であり、室温をほどよく把握しつづけることがパフォーマンスにつながる&lt;/p&gt;
&lt;p&gt;Nature Remoのスマホアプリを開けば室温が分かるが、アプリを四六時中開いているのは効率悪い&lt;/p&gt;
&lt;p&gt;室温計を追加購入したくはない、となるとやるべきは作業中のPC上に室温をほどよく表示することである（&lt;span class=&quot;imgref&quot;&gt;図1&lt;/span&gt;）&lt;/p&gt;
&lt;div id=&quot;id_2022-12-22-nature-remo-gnome&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2022-12-22-nature-remo-gnome.png&quot; alt=&quot;Nature Remoで取得した室温をGNOMEで表示する&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図1: Nature Remoで取得した室温をGNOMEで表示する
&lt;/p&gt;
&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1&quot;&gt;&lt;/a&gt;意識の低いホームオートメーションとはなにか&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;全世界で自分だけ使えればいい（自分の特殊ニーズを満たせればよい）&lt;/li&gt;
&lt;li&gt;全自動は目指さない。自分を操作インタフェースとして使うことで目的を達成できるなら積極的に使う&lt;/li&gt;
&lt;li&gt;bashと定番コマンド群でさらっと書く&lt;/li&gt;
&lt;li&gt;書いたら書いた通りに動き、簡単に捨てられる&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2&quot;&gt;&lt;/a&gt;Nature RemoのAPIから室温を取り出す&lt;/h2&gt;
&lt;p&gt;先人の知見にありがたく乗っかるものとする&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://blog.yuu26.com/nature-remo-api-tutorial/&quot; class=&quot;link&quot;&gt;https://blog.yuu26.com/nature-remo-api-tutorial/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;複数のNature Remo端末を登録している場合はデバイスIDで更にフィルタするのが無難&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;  TEMPERATURE=`curl -s -X GET &amp;quot;https://api.nature.global/1/devices&amp;quot; -H &amp;quot;Authorization: Bearer $NATURE_TOKEN&amp;quot; \
  | jq &amp;quot;.[] | select(.id == \&amp;quot;$DEVICE_ID\&amp;quot;) | .newest_events.te.val&amp;quot;`
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;こんな感じだろう&lt;/p&gt;
&lt;p&gt;APIから取れる値の更新間隔は1分か2分ごとであるように思うが今回はひとまず気にせず30秒間隔でデータを取得しておく&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3&quot;&gt;&lt;/a&gt;ZenityでGUI表示する&lt;/h2&gt;
&lt;p&gt;手元のデスクトップ環境はUbuntu 22.04であり、標準のGNOME Desktopで動作している&lt;/p&gt;
&lt;p&gt;この環境においてざっくり可視化はどうすればいいか&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;curl&lt;/code&gt;で値を取って&lt;code class=&quot;inline-code tt&quot;&gt;jq&lt;/code&gt;で加工する、だけだとターミナルのタブを1枚専有したりいちいち見に行かなきゃいけなくて面倒なのでみんなのzenityでGUI化する&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://help.gnome.org/users/zenity/stable/progress.html.ja&quot; class=&quot;link&quot;&gt;Zenity - プログレスバー&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;公式ドキュメントが充実しているので機能カタログを読んでいく&lt;/p&gt;
&lt;p&gt;数値変化をそこそこ視覚的に分かりやすく表現し、認知の負荷を下げるのが目的であり、やりたいことを実現するには単発プログレスバーを表示しっぱなしにしておけばいい&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;標準入力をあけっぱなしにして新しい値を適宜受け取るという扱いやすい仕様&lt;/li&gt;
&lt;li&gt;行頭に# を入れてやるとラベル更新、なければ数値更新という割り切った仕様も良い&lt;/li&gt;
&lt;li&gt;OKとかCancelとかは別に要らないけどわざわざ外すほどでもない&lt;/li&gt;
&lt;li&gt;複数の値をプログレスバー表示したい（CO2濃度とか）、となったらまあ適宜ちゃんとやればよい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;要求を満たすものはこんな感じ、コマンドからさらっとGUIを制御できるというだけで満点である&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;(
while :
do
  TEMPERATURE=15
  PROGRESS=0
  echo &amp;quot;$PROGRESS&amp;quot;
  echo &amp;quot;# $TEMPERATURE&amp;quot;
  sleep 30
done
  ) |
zenity --progress \
  --title=&amp;quot;Room temperature&amp;quot; \
  --text=&amp;quot;fetching...&amp;quot; \
  --percentage=0
&lt;/pre&gt;
&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3-4&quot;&gt;&lt;/a&gt;入力値を適当にクランプする&lt;/h2&gt;
&lt;p&gt;プログレスバーを扱う際に面倒なのは想定外の範囲の入力値の処理であり、つまりクランプが必要&lt;/p&gt;
&lt;p&gt;bash script類で小数込みの値をクランプするのがまあまあ面倒そう、と調べてみたらawkを使う策が提示されておりすばらしい&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://stackoverflow.com/a/27688507&quot; class=&quot;link&quot;&gt;https://stackoverflow.com/a/27688507&lt;/a&gt;&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;  P=`awk '$0&amp;lt;15{$0=15}$0&amp;gt;30{$0=30}1' &amp;lt;&amp;lt;&amp;lt; &amp;quot;$TEMPERATURE&amp;quot;`
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;これで15度から30度の範囲外の値を上限値と下限値へクランプできる&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3-4-5&quot;&gt;&lt;/a&gt;bcで気温をプログレスバーの0-100値へマップする&lt;/h2&gt;
&lt;p&gt;シンプルな数値計算でもbash組み込みのオペレータで演算すると面倒&lt;a id=&quot;fnb-bash-operators&quot; href=&quot;#fn-bash-operators&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;というのは非常に有名事案につき、bcを使う&lt;/p&gt;
&lt;p&gt;bcの素晴らしい点は&lt;code class=&quot;inline-code tt&quot;&gt;-l&lt;/code&gt;オプションにより小数込みの演算を通せるところである&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;  PROGRESS=`echo &amp;quot;(100/15)*($P-15)&amp;quot; | bc -l`
&lt;/pre&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-bash-operators&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] bash含めてシェルスクリプトを正しく書くのは想像の何倍も困難だと知っているので厳密な理解をしようという気持ちはとうに放り捨てている&lt;/p&gt;&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3-4-5-6&quot;&gt;&lt;/a&gt;出力微調整やデータの扱いやすさを検討して仕上げる&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;念のため時刻・温度のペアでログをローカルディレクトリへ書き出すようにしておく&lt;ul&gt;
&lt;li&gt;あとからGoogle Sheetsあたりへ放り投げるようにすればクラウド化もできて完璧&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;データとしてはTSVにしておくと楽&lt;/li&gt;
&lt;li&gt;dateをそのまま使うと行末改行が入るので一旦パラメータに受けて回避してる&lt;ul&gt;
&lt;li&gt;tabの出力は&lt;code class=&quot;inline-code tt&quot;&gt;echo&lt;/code&gt;に&lt;code class=&quot;inline-code tt&quot;&gt;-e&lt;/code&gt;オプションが必要&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;zenityで表示したダイアログ側の後始末をしていないのでダイアログを閉じても最大30秒ほどプロセスが残り、次回の温度取得が走ってから終了するが特に問題ないのでそのままにしておく&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3-4-5-6-7&quot;&gt;&lt;/a&gt;スクリプト全文&lt;/h2&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;#!/bin/bash

LOGFILE='/home/user/templog.txt'
NATURE_TOKEN='NATURE_REMO_TOKEN'
DEVICE_ID='DEVICE_UUID'

(
while :
do
  TEMPERATURE=`curl -s -X GET &amp;quot;https://api.nature.global/1/devices&amp;quot; -H &amp;quot;Authorization: Bearer $NATURE_TOKEN&amp;quot; | jq &amp;quot;.[] | select(.id == \&amp;quot;$DEVICE_ID\&amp;quot;) | .newest_events.te.val&amp;quot;`
  P=`awk '$0&amp;lt;15{$0=15}$0&amp;gt;30{$0=30}1' &amp;lt;&amp;lt;&amp;lt; &amp;quot;$TEMPERATURE&amp;quot;`
  PROGRESS=`echo &amp;quot;(100/15)*($P-15)&amp;quot; | bc -l`
  echo &amp;quot;$PROGRESS&amp;quot;
  echo &amp;quot;# $TEMPERATURE&amp;quot;
  echo -n `date +%Y-%m-%dT%H:%M:%S` &amp;gt;&amp;gt; $LOGFILE
  echo -e &amp;quot;\t$TEMPERATURE&amp;quot; &amp;gt;&amp;gt; $LOGFILE
  sleep 30
done
  ) |
zenity --progress \
  --title=&amp;quot;Room temperature&amp;quot; \
  --text=&amp;quot;fetching...&amp;quot; \
  --percentage=0
&lt;/pre&gt;
&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3-4-5-6-7-8&quot;&gt;&lt;/a&gt;その他&lt;/h2&gt;
&lt;p&gt;.NET 7も出たことだしMAUIで組めばいいのでは、と思うところはあるがまだLinuxが公式サポート環境に入っていないので今回はナシである（たぶん問題なくMonoで動く範疇ではある）&lt;/p&gt;</content><author><name></name></author></entry><entry><title type="html">最近のUbuntuでDiffImgをビルドする</title><link href="/2021/01/12/build-diffimg-ubuntu-2010.html" rel="alternate" type="text/html" title="最近のUbuntuでDiffImgをビルドする" /><published>2021-01-12T12:10:00+09:00</published><updated>2021-01-12T12:10:00+09:00</updated><id>/2021/01/12/build-diffimg-ubuntu-2010</id><content type="html" xml:base="/2021/01/12/build-diffimg-ubuntu-2010.html">&lt;p&gt;diffは大事。テキストでも画像でも。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1&quot;&gt;&lt;/a&gt;OSS画像diffツールの昨今&lt;/h2&gt;
&lt;p&gt;OSSの画像diffツールとしては&lt;a href=&quot;http://sourceforge.net/projects/diffimg/&quot; class=&quot;link&quot;&gt;DiffImg&lt;/a&gt;が有名。本家プロジェクトは昔懐かしSourceForge（以下SF）にホストされており、近年更新されていない。Ubuntu用のビルドは&lt;a href=&quot;https://launchpad.net/~dhor/+archive/ubuntu/myway?field.series_filter=&quot; class=&quot;link&quot;&gt;https://launchpad.net/~dhor/+archive/ubuntu/myway?field.series_filter=&lt;/a&gt;にあるが、16.04を最後にビルドされていない。このため、20.10（Groovy Gorilla）のような先鋭的Ubuntu環境においては自前でビルドする必要がある&lt;a id=&quot;fnb-fn-1804&quot; href=&quot;#fn-fn-1804&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;。&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-1804&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] 先鋭的どころか、18.04（Bionic Beaver）や20.04（Focal Fossa）といった近年のLTS勢でも状況は同じはず&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;さて、ソースにバグフィックス類の手を加えてコツコツとメンテされているGitHub上のリポジトリが存在する（&lt;a href=&quot;https://github.com/sandsmark/diffimg&quot; class=&quot;link&quot;&gt;https://github.com/sandsmark/diffimg&lt;/a&gt;）ので、今回はこれを使う。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2&quot;&gt;&lt;/a&gt;ビルド環境を整える&lt;/h2&gt;
&lt;p&gt;ビルドにあたり、依存関係を揃える。DiffImgはQtにがっつり依存しているため、Qtもろもろをインストールする。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ sudo apt install libopencv-dev libfreeimage-dev qt5-default \
  libqwt-qt5-dev qt5-qmake qttools5-dev-tools
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;DiffImgのドキュメントには&lt;code class=&quot;inline-code tt&quot;&gt;libqwt-dev&lt;/code&gt;を入れろとあるが、Ubuntu 18.04あたりでパッケージの命名ルールが変わったようで、最新版には存在しない。このため、&lt;code class=&quot;inline-code tt&quot;&gt;libqwt-qt5-dev&lt;/code&gt;を指定するのがよい。これに気付かず、ソースからlibqwtをインストールして遠回りをした。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3&quot;&gt;&lt;/a&gt;ソースコード取得・ビルド&lt;/h2&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ git clone https://github.com/sandsmark/diffimg.git
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;forkのコード差分を信用すべきではないと考える（まったくもって正しい疑い方だと思う）場合はちゃんと差分を確認するか、SFのコードを拾ってきてほしい。&lt;/p&gt;
&lt;p&gt;普段どおりのCMake作法でビルドする。今回はfork版（CMakeでビルドが通るように手を加えられている）を使っているのですんなり行ったけれど、fork版をメンテしているSF版ではすんなりいかないかもしれない。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ mkdir build
$ cd build/
$ cmake ..
-- The CXX compiler identification is GNU 10.2.0
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found Qwt: /usr/lib/libqwt-qt5.so (found version &amp;quot;6.1.4&amp;quot;)
-- Configuring done
-- Generating done
-- Build files have been written to: /home/muo/workspace/diffimg/build
$ make -j
...
[100%] Built target diffimg
&lt;/pre&gt;
&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3-4&quot;&gt;&lt;/a&gt;インストール&lt;/h2&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;diffimg&lt;/code&gt;バイナリが生成されるので、そのまま使うなり&lt;code class=&quot;inline-code tt&quot;&gt;sudo make install&lt;/code&gt;するなり好きにやる。うまくビルドできていれば、無事に画像diffをとれる（&lt;span class=&quot;imgref&quot;&gt;図1&lt;/span&gt;）。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ diffimg i_183.png i_188.png
&lt;/pre&gt;
&lt;/div&gt;
&lt;div id=&quot;id_2021-01-12-diffimg&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2021-01-12-diffimg.png&quot; alt=&quot;DiffImgを実行したところ&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図1: DiffImgを実行したところ
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;これで、老眼入りつつある👀でもやっていける。&lt;/p&gt;</content><author><name></name></author></entry><entry><title type="html">Amazon’s Choiceマーク付きのFullHD 60fps対応HDMIキャプチャデバイスが治安悪い</title><link href="/2021/01/12/hdmi-capture.html" rel="alternate" type="text/html" title="Amazon's Choiceマーク付きのFullHD 60fps対応HDMIキャプチャデバイスが治安悪い" /><published>2021-01-12T12:10:00+09:00</published><updated>2021-01-12T12:10:00+09:00</updated><id>/2021/01/12/hdmi-capture</id><content type="html" xml:base="/2021/01/12/hdmi-capture.html">&lt;p&gt;ZoomやMeetでのリモート会議の場面が増える今日この頃。コンデジ（RX100M5）からPCにHDMI入力して映像を良い感じにするぞ！ということでUSB 3.0接続のHDMIキャプチャデバイスを購入しました。&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Chilison HDMI キャプチャーボード ゲームキャプチャー USB3.0 ビデオキャプチャカード 1080P60Hz ゲーム実況生配信、画面共有、録画、ライブ会議に適用 小型軽量 Nintendo Switch、Xbox One、OBS Studio対応 電源不要（アップグレードバージョン）&lt;/b&gt;という長い名前の品で、Amazon's Choiceマークつき2,400円ほどです。&lt;/p&gt;
&lt;div id=&quot;id_2021-01-12-hdmi-capture-5&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2021-01-12-hdmi-capture-5.jpg&quot; alt=&quot;Chilison HDMI キャプチャーボード&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図1: Chilison HDMI キャプチャーボード
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;しばらく前に1,000円未満で買えるお手軽HDMIキャプチャデバイスが流行りましたが、USB 3.0対応品もこれほど安くなったんですね。&lt;/p&gt;
&lt;p&gt;商品説明には次の記載があります。&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;【1080P 60fpsに更新され】HD解像度ビデオ録画とストリーミングの高品質体験を保証するために、USB 3.0インターフェイスを採用し、1080p/60fpsの高品質で録画できます。最大入力解像度：3840x2160 @ 60Hz、最大出力/録画解像度：1920x1080 @ 60Hz。（ご注意：一部の説明書はまた更新されなかったですが、キャプチャーは全部更新済みました、ご不便をかけて申し訳ございませんでした。）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;日本語が若干アレですが、ひとまず気にせずやっていきましょう。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1&quot;&gt;&lt;/a&gt;そもそもUSB 3.0機器ではない&lt;/h2&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1&quot;&gt;&lt;/a&gt;USB3. 0という名前のUSB 2.0機器&lt;/h3&gt;
&lt;p&gt;USBコネクタはUSB 3.0-Aに見えます。ちゃんと青いし、3.0の追加端子も見えます（&lt;span class=&quot;imgref&quot;&gt;図2&lt;/span&gt;）。&lt;/p&gt;
&lt;div id=&quot;id_2021-01-12-hdmi-capture-6&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2021-01-12-hdmi-capture-6.jpg&quot; alt=&quot;USBコネクタ&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図2: USBコネクタ
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;しかし、&lt;code class=&quot;inline-code tt&quot;&gt;lsusb&lt;/code&gt;（および&lt;code class=&quot;inline-code tt&quot;&gt;lsusb -t&lt;/code&gt;）の結果は次のとおりです。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;Bus 006 Device 028: ID 534d:2109 MacroSilicon USB3. 0 capture
...
/:  Bus 06.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 480M
    |__ Port 2: Dev 28, If 1, Class=Video, Driver=uvcvideo, 480M
    |__ Port 2: Dev 28, If 4, Class=Human Interface Device, Driver=usbhid, 480M
    |__ Port 2: Dev 28, If 2, Class=Audio, Driver=snd-usb-audio, 480M
    |__ Port 2: Dev 28, If 0, Class=Video, Driver=uvcvideo, 480M
    |__ Port 2: Dev 28, If 3, Class=Audio, Driver=snd-usb-audio, 480M
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;初っ端からおかしいです。&lt;/p&gt;
&lt;p&gt;残念なお知らせですが、私が買ったHDMIキャプチャデバイスは&lt;b&gt;USB 3.0対応機器&lt;/b&gt;ではなく&lt;b&gt;USB3. 0という名前のUSB 2.0機器&lt;/b&gt;です。親に向かってなんだそのUSB3. 0は、と突っ込みたくなるところですが、冷静に結果を受け止めましょう。&lt;/p&gt;
&lt;p&gt;ちなみに、同じデバイスをUSB-Cハブ上のUSB 3.0ポートに挿した場合（カスケード状態）は&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;Bus 004 Device 026: ID 534d:2109 MacroSilicon USB 2.0 Hub
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;と出力されます。すわ返品か！？と噴き上がりそうになりますが、ぐっとこらえます。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2&quot;&gt;&lt;/a&gt;PCのUSBポートは正常&lt;/h3&gt;
&lt;p&gt;のっけから結果にインパクトがありすぎて、PC側USBポートの不具合を疑いたくなるところです。なぜ過去の購入者が気付いていないのか、レビューは全部サクラだったのか、全く分かりませんが、ひとまずPC側が正常であることを最低限確認しておきます。まともなUSB 3.0対応UVCカメラの例として、手元にあったIntel RealSense SR300での出力例を示します。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;Bus 007 Device 003: ID 8086:0aa5 Intel Corp.
...
/:  Bus 07.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/2p, 10000M
    |__ Port 2: Dev 2, If 0, Class=Hub, Driver=hub/2p, 5000M
        |__ Port 1: Dev 3, If 0, Class=Video, Driver=uvcvideo, 5000M
        |__ Port 1: Dev 3, If 1, Class=Video, Driver=uvcvideo, 5000M
        |__ Port 1: Dev 3, If 2, Class=Video, Driver=uvcvideo, 5000M
        |__ Port 1: Dev 3, If 3, Class=Video, Driver=uvcvideo, 5000M
        |__ Port 1: Dev 3, If 4, Class=Vendor Specific Class, Driver=, 5000M
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;ちゃんとSuperSpeed対応であることがわかります。前述のUSB-Cハブ以下にぶら下げても同様の結果でした。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2&quot;&gt;&lt;/a&gt;キャプチャ対応範囲の仕様を確認する&lt;/h2&gt;
&lt;p&gt;v4l2-ctlコマンドで調べた結果、キャプチャデバイスとしての仕様は次の通りです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;M-JPEG出力では1920x1080 60fpsまで対応&lt;/li&gt;
&lt;li&gt;YUYV 4:2:2（YUV422）ベタ出力では720x480 30fpsまで対応（1920x1080時は5fpsまで）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;詳しくは記事末尾の付録に記載したので、興味のある方は「UVCでの対応フォーマット一覧チェック」を参照してください。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3&quot;&gt;&lt;/a&gt;動作テスト&lt;/h2&gt;
&lt;p&gt;今回はJavaScript動作のストップウォッチをPC画面に表示し、それをカメラで撮影してHDMIで取り込みます。PC画面が60fps表示されていることは、あらかじめカメラの録画映像およびスマフォのスローモーション撮影で確認済みです。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3-3-1&quot;&gt;&lt;/a&gt;1920x1080 60fpsキャプチャできる…?&lt;/h3&gt;
&lt;p&gt;デバイスの主張を信じると、無圧縮YUV422はともかくM-JPEGなら1080p60を出せるはずです。実際にキャプチャしてみます。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ ffmpeg -f v4l2 -input_format mjpeg -i /dev/video4 -c:v copy o.mkv
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;結果のフォーマットを確認すると、次の通りです。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;Input #0, video4linux2,v4l2, from '/dev/video4':
  Duration: N/A, start: 210724.995835, bitrate: N/A
    Stream #0:0: Video: mjpeg (Baseline), yuvj422p(pc, bt470bg/unknown/unknown),
    1920x1080, 60 fps, 60 tbr, 1000k tbn, 1000k tbc
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;なんだ、ちゃんと1920x1080 60fpsと書いてありますね。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3-3-1-3-2&quot;&gt;&lt;/a&gt;1920x1080 60fpsキャプチャできてる（できてない）&lt;/h3&gt;
&lt;p&gt;疑り深いので、キャプチャしたフレームをいくつか抜き出して確認します。&lt;/p&gt;
&lt;div id=&quot;id_2021-01-12-hdmi-capture-rx100m5-1080p&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2021-01-12-hdmi-capture-rx100m5-1080p.jpg&quot; alt=&quot;RX100M5から取り込んだ映像の連続キャプチャ&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図3: RX100M5から取り込んだ映像の連続キャプチャ
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;…。&lt;/p&gt;
&lt;p&gt;確かに秒間60枚キャプチャされていますが、同じ画像が2枚ずつ繰り返されています。つまり、実質30fpsのものをわざわざ容量割いて60fpsで記録している状態です。なんてこったい。&lt;/p&gt;
&lt;p&gt;念のため、&lt;a href=&quot;https://b.muo.jp/2021/01/12/build-diffimg-ubuntu-2010.html&quot; class=&quot;link&quot;&gt;DiffImg&lt;/a&gt;で画像差分を確認します。&lt;/p&gt;
&lt;p&gt;画像間に差分があれば、&lt;span class=&quot;imgref&quot;&gt;図4&lt;/span&gt;のように差分が表示されるはずです。&lt;/p&gt;
&lt;div id=&quot;id_2021-01-12-hdmi-capture-1&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2021-01-12-hdmi-capture-1.png&quot; alt=&quot;差分ありの場合の参考画像&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図4: 差分ありの場合の参考画像
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;結果は、&lt;span class=&quot;imgref&quot;&gt;図5&lt;/span&gt;のとおり差分なしでした。&lt;/p&gt;
&lt;div id=&quot;id_2021-01-12-hdmi-capture-2&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2021-01-12-hdmi-capture-2.png&quot; alt=&quot;重複フレーム間で画像差分なし&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図5: 重複フレーム間で画像差分なし
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;すわ返品か！？と噴き上がりそうになりますが、ぐっとこらえます。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3-3-1-3-2-4&quot;&gt;&lt;/a&gt;1280x720であれば60fpsキャプチャできる&lt;/h2&gt;
&lt;p&gt;いろいろと試しているうちに、60fpsきっちり撮れるケースがあることに気付きました。具体的には、Zoomで映像プレビューをかけた後でキャプチャを実行すると、重複フレームのない60fps映像を撮れます。ただし、720p（1280x720）解像度です。&lt;/p&gt;
&lt;p&gt;UVCドライバ/v4l2あたりにモード維持機能がある気配ですが、それはさておきキャプチャ時に映像サイズをhd720へ固定してやれば60fpsキャプチャが可能です。FFmpegの場合は次のように指定します。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ ffmpeg -f v4l2 -framerate 60 -video_size hd720 -input_format mjpeg \
  -i /dev/video4 -c:v copy o.mkv
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;結果は&lt;span class=&quot;imgref&quot;&gt;図6&lt;/span&gt;のとおりです。フレーム重複のない、ちゃんとした60fps映像（ただし720p）であることがわかります。&lt;/p&gt;
&lt;div id=&quot;id_2021-01-12-hdmi-capture-rx100m5-720p&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2021-01-12-hdmi-capture-rx100m5-720p.jpg&quot; alt=&quot;RX100M5から取り込んだ映像の連続キャプチャ（720p）&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図6: RX100M5から取り込んだ映像の連続キャプチャ（720p）
&lt;/p&gt;
&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3-3-1-3-2-4-5&quot;&gt;&lt;/a&gt;Chilison HDMI キャプチャーボードについて分かったこと&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;内部はUSB 2.0動作だが高級感あるUSB 3.0コネクタ（青い！）を使っている&lt;/li&gt;
&lt;li&gt;M-JPEGフォーマット 1920x1080 60fpsを流せるがデータ重複のため実際は30fps&lt;ul&gt;
&lt;li&gt;M-JPEGフォーマット 1280x720 60fpsは問題なく流せる&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;雑に言えば、&lt;b&gt;1,000円未満で売られている品と同等のものを2,500円で買えるというお得な商品&lt;/b&gt;です。&lt;/p&gt;
&lt;p&gt;われわれ購買者が成熟しないと、いつまで経っても「デバイス名にUSB3. 0って書いてるしコネクタ青いからヨシ！」というアホなことが続くのですね。&lt;/p&gt;
&lt;p&gt;ちなみに、製品のQ&amp;amp;Aには次の記載があります。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.amazon.co.jp/ask/questions/Tx1CKNSSTETK4HT/&quot; class=&quot;link&quot;&gt;https://www.amazon.co.jp/ask/questions/Tx1CKNSSTETK4HT/&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Q. 購入したら説明書にusb2.0hdmiビデオキャプチャーカードとなっていますけどusb3.0ではないのですか。A. 最近、当店のキャプチャーはUSB2.0をUSB3.0に変更中で、在庫のUSB2.0は全部売り切れないですから、USB2.0とUSB3.0の仕様が同時に販売しています。商品の説明は暫くUSB2.0に使用し、USB2.0の在庫売り切れる時、当店はすぐUSB3.0の説明を変更いたします。今後も、こんなの迷惑をお客様に与えられないように、持続的に改善します。&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;この返答後に商品ページが更新されており、私が購入した1/10時点で冒頭引用のUSB 3.0対応を謳う内容が掲載されていました。つまり、今回届いた品は購入時点の商品ページで説明されていた「USB 3.0インターフェイスを採用」という文言と一致しない、不良品ということになります。&lt;/p&gt;
&lt;p&gt;それ以前の問題として、商品ページにキャプチャ対応仕様について次のように書かれています。&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;最大入力解像度：3840x2160 @ 60Hz、最大出力/録画解像度：1920x1080 @ 60Hz。（ご注意：商品は全部アップグレード済ましたが、説明書は次回在庫補充された後から全部更新済です。）&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;しかし、そもそも1080p 60fpsでキャプチャできていない（フレームが常に重複しており30fpsしか出ていない）ので、商品説明に書かれている性能を満足しない不良品と呼ぶしかありません。&lt;/p&gt;
&lt;p&gt;すわ返品か！？&lt;/p&gt;
&lt;p&gt;不良品を売り続けても条件さえ満たせばAmazon's Choiceマークを取得できるので、購入時にAmazon's Choiceマークは信用しちゃだめですよという話でした。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3-3-1-3-2-4-5-6&quot;&gt;&lt;/a&gt;付録&lt;/h2&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3-3-1-3-2-4-5-6-6-1&quot;&gt;&lt;/a&gt;MacroSilicon 2109?&lt;/h3&gt;
&lt;p&gt;Chilison HDMI キャプチャーボードは、どのようなコアモジュールを利用しているか公表していません。しかしVendor IDがMacroSilicon該当、Product IDが0x2109である点から安直に想像すると、1,000円未満で売られている超激安HDMIキャプチャデバイスの常連であるMacroSilicon製MS2109搭載機である可能性があります。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3-3-1-3-2-4-5-6-6-1-6-2&quot;&gt;&lt;/a&gt;上限データレートを推測する&lt;/h3&gt;
&lt;p&gt;M-JPEGは圧縮度合いによって相当データ量が変わってくるので、YUV422（16bpp）出力の上限に注目します。720x480x30fpsということは、158.2MbpsがこのデバイスのUSB転送上限に近い値と考えてよさそうです。&lt;/p&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;lsusb -v&lt;/code&gt;でインタフェースディスクリプタを読むと&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;        dwMaxBitRate                196608000
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;と書かれており、187.5Mbpsがインタフェース上限なので、それなりに整合します。&lt;/p&gt;

&lt;h2 id=&quot;check-format&quot;&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3-3-1-3-2-4-5-6-6-1-6-2-7&quot;&gt;&lt;/a&gt;UVCでの対応フォーマット一覧チェック&lt;/h2&gt;
&lt;p&gt;v4l2コマンド群で対応フォーマットを調べる手順をメモがてら残します。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ v4l2-ctl --list-devices
Integrated Camera: Integrated C (usb-0000:05:00.0-2):
        /dev/video0
        /dev/video1
        /dev/video2
        /dev/video3

USB3. 0 capture: USB3. 0 captur (usb-0000:06:00.4-2):
        /dev/video4
        /dev/video5
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;※video[0-3]はノートPC内蔵のカメラです。&lt;/p&gt;
&lt;p&gt;今回のキャプチャデバイスが持つのはvideo[4-5]だと分かるので、video4の対応フォーマットを調べてみます。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ v4l2-ctl -d /dev/video4 --all
Driver Info:
        Driver name      : uvcvideo
        Card type        : USB3. 0 capture: USB3. 0 captur
        Bus info         : usb-0000:06:00.4-2
        Driver version   : 5.8.18
        Capabilities     : 0x84a00001
                Video Capture
                Metadata Capture
                Streaming
                Extended Pix Format
                Device Capabilities
        Device Caps      : 0x04200001
                Video Capture
                Streaming
                Extended Pix Format
Priority: 2
Video input : 0 (Camera 1: ok)
Format Video Capture:
        Width/Height      : 1920/1080
        Pixel Format      : 'MJPG' (Motion-JPEG)
        Field             : None
        Bytes per Line    : 0
        Size Image        : 4147200
        Colorspace        : sRGB
        Transfer Function : Default (maps to sRGB)
        YCbCr/HSV Encoding: Default (maps to ITU-R 601)
        Quantization      : Default (maps to Full Range)
        Flags             :
Crop Capability Video Capture:
        Bounds      : Left 0, Top 0, Width 1920, Height 1080
        Default     : Left 0, Top 0, Width 1920, Height 1080
        Pixel Aspect: 1/1
Selection Video Capture: crop_default, Left 0, Top 0, Width 1920, Height 1080, Flags:
Selection Video Capture: crop_bounds, Left 0, Top 0, Width 1920, Height 1080, Flags:
Streaming Parameters Video Capture:
        Capabilities     : timeperframe
        Frames per second: 30.000 (30/1)
        Read buffers     : 0
                     brightness 0x00980900 (int)    : min=-128 max=127 step=1 default=-11 value=-11
                       contrast 0x00980901 (int)    : min=0 max=255 step=1 default=148 value=148
                     saturation 0x00980902 (int)    : min=0 max=255 step=1 default=180 value=180
                            hue 0x00980903 (int)    : min=-128 max=127 step=1 default=0 value=0
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;MJPEGで1920x1080の出力に対応していますが、30fpsとありますね。すわ返品か！？と噴き上がりそうになりますが、ぐっとこらえます。&lt;/p&gt;
&lt;p&gt;対応フォーマット詳細を取得する&lt;code class=&quot;inline-code tt&quot;&gt;--list-formats-ext&lt;/code&gt;というオプションがあります&lt;a id=&quot;fnb-v4l2-usage&quot; href=&quot;#fn-v4l2-usage&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;。&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-v4l2-usage&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] &lt;a href=&quot;https://leico.github.io/TechnicalNote/Linux/webcam-usage&quot; class=&quot;link&quot;&gt;https://leico.github.io/TechnicalNote/Linux/webcam-usage&lt;/a&gt;がとても役立ちました&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ v4l2-ctl -d /dev/video4 --list-formats-ext
ioctl: VIDIOC_ENUM_FMT
        Type: Video Capture

        [0]: 'MJPG' (Motion-JPEG, compressed)
                Size: Discrete 1920x1080
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 1600x1200
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 1360x768
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 1280x1024
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 1280x960
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.020s (50.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 1024x768
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.020s (50.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 800x600
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.020s (50.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 720x576
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.020s (50.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 720x480
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.020s (50.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 640x480
                        Interval: Discrete 0.017s (60.000 fps)
                        Interval: Discrete 0.020s (50.000 fps)
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
        [1]: 'YUYV' (YUYV 4:2:2)
                Size: Discrete 1920x1080
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1600x1200
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 1360x768
                        Interval: Discrete 0.125s (8.000 fps)
                Size: Discrete 1280x1024
                        Interval: Discrete 0.125s (8.000 fps)
                Size: Discrete 1280x960
                        Interval: Discrete 0.125s (8.000 fps)
                Size: Discrete 1280x720
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 1024x768
                        Interval: Discrete 0.100s (10.000 fps)
                Size: Discrete 800x600
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 720x576
                        Interval: Discrete 0.040s (25.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 720x480
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
                Size: Discrete 640x480
                        Interval: Discrete 0.033s (30.000 fps)
                        Interval: Discrete 0.050s (20.000 fps)
                        Interval: Discrete 0.100s (10.000 fps)
                        Interval: Discrete 0.200s (5.000 fps)
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;長いのでまとめると、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;M-JPEG出力では1920x1080 60fpsまで対応&lt;/li&gt;
&lt;li&gt;YUYV（YUV2）ベタ出力では720x480 30fpsまで対応（1920x1080時は5fpsまで）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という結果です。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-1-1-1-2-2-3-3-1-3-2-4-5-6-6-1-6-2-7-7-1&quot;&gt;&lt;/a&gt;おまけ：連続画像のタイル化&lt;/h3&gt;
&lt;p&gt;本文中に掲載したような連続画像をタイルで敷き詰めたものは、ImageMagickに含まれる&lt;code class=&quot;inline-code tt&quot;&gt;montage&lt;/code&gt;コマンドで作るのが楽。&lt;/p&gt;
&lt;p&gt;デフォでは余白が入りすぎるので、こういう感じ。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ montage i_09[0-8].png -tile 3x3 -geometry +32+32 intermediate.png
$ convert intermediate.png -geometry 600x out.jpg
&lt;/pre&gt;
&lt;/div&gt;</content><author><name></name></author></entry><entry><title type="html">IPoEでネット帰宅難民になった / Rakuten Miniテザリング環境からフレッツひかりのIPoE環境へSSH/IPv6でインターネット帰宅する</title><link href="/2020/12/14/rakuten-mini-ipv6-ssh-proxy.html" rel="alternate" type="text/html" title="IPoEでネット帰宅難民になった / Rakuten Miniテザリング環境からフレッツひかりのIPoE環境へSSH/IPv6でインターネット帰宅する" /><published>2020-12-14T09:10:00+09:00</published><updated>2020-12-14T09:10:00+09:00</updated><id>/2020/12/14/rakuten-mini-ipv6-ssh-proxy</id><content type="html" xml:base="/2020/12/14/rakuten-mini-ipv6-ssh-proxy.html">&lt;p&gt;遅いネット回線でリモートワークするのは本当にしんどいです。速度改善の方法は色々ありますが、フレッツひかり回線における手軽な策として接続方式をPPPoEからIPoEへ切り替える手法が有名です。&lt;/p&gt;
&lt;p&gt;実際、私の手元でもIPoE化によって夕方時間帯の転送速度改善に一定の効果が見られました。それと引き換えに、手持ち機材ではネット帰宅&lt;a id=&quot;fnb-fn-back-to-home&quot; href=&quot;#fn-fn-back-to-home&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*1&lt;/a&gt;できなくなりました。&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-back-to-home&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*1] 出先からSSHやRDPで自宅内PCへアクセスすること&lt;/p&gt;&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1&quot;&gt;&lt;/a&gt;IPv4とv6とDS-Lite&lt;/h2&gt;
&lt;p&gt;フレッツひかりのIPoE接続はv6ネイティブです。家庭用ルーターとその下にぶら下がったデバイスはDHCPv6によって割り当てられたIPv6アドレスを使ってインターネットに出ていきます。逆に、そのままではIPv4のネットワークへ出られません。&lt;/p&gt;
&lt;p&gt;このため、IPv4側は各種のv6移行技術を利用することになり、私が契約したISPではDS-Liteを利用します。DS-Liteは家庭のルーターとISP側の提供するDS-Liteゲートウェイ（AFTR）との間でIPv6上にv4のトンネルを掘り、そこにv4パケットを流す構造です。&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-ipoe&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-ipoe.svg&quot; alt=&quot;IPoE接続時のインターネットアクセス&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図1: IPoE接続時のインターネットアクセス
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;そしてここが見落とされがちなのですが、あいにく&lt;b&gt;グローバル側からv4へのインバウンドポートを固定する方法は提供されません&lt;/b&gt;。つまり、IPoE環境においてIPv4だけでは外出先から自宅内サーバーへリモートデスクトップでログインしたり、SSH経由でVSCodeのリモート編集をしたりといった&lt;b&gt;現代における自然なインターネット呼吸ができません&lt;/b&gt;。&lt;/p&gt;
&lt;p&gt;家庭内からインターネット上のホストへとVPN接続を張ることで、外部→内部の通信を受けることは可能ですが、せっかくIPv6アドレスが割り振られているのにそれを使わずトンネル用の外部サーバーを契約するのも癪なので、家庭に転がっている機材を利用してIPv6で家庭内サーバーへとSSHできるようにしてみます&lt;a id=&quot;fnb-fn-ssh&quot; href=&quot;#fn-fn-ssh&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*2&lt;/a&gt;。&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-ssh&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*2] 自宅内のサーバーへSSHでログインしたら後はなんでも出来るので、ここをゴールとしました&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;2020年も終わろうとしているのだし、さらっとend-to-endでIPv6経由のインターネット帰宅セットを組めるだろうと実験を始めたら、想像以上にサーバー・クライアント両面でハードルがあったので、それぞれ整理してみました。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2&quot;&gt;&lt;/a&gt;元々のネットワーク構成&lt;/h2&gt;
&lt;p&gt;今回の対応をおこなう前の段階で、自宅のネットワーク機器と用途は次の通りでした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google Nest Wi-Fi（家庭内通信用）&lt;/li&gt;
&lt;li&gt;TP-Link Archer A6（IPoEルーター用）&lt;/li&gt;
&lt;/ul&gt;
&lt;div id=&quot;id_2020-12-04-network&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-network.svg&quot; alt=&quot;ネット帰宅できない状態の構成&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図2: ネット帰宅できない状態の構成
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;PCやスマフォはGoogle Nest Wi-Fiが噴いているアクセスポイントに接続する構造です。&lt;/p&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3&quot;&gt;&lt;/a&gt;自宅ゲートウェイをOpenWrtで作る&lt;/h2&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-3-3-1&quot;&gt;&lt;/a&gt;さらばArcher A6（のルーター機能）&lt;/h3&gt;
&lt;p&gt;今回、IPoE対応でそれなりのスループットが出るルーターとしてTP-Linkの激安Wi-FiルーターArcher A6を購入したのですが、この機材ではどう設定してもIPv6でぶら下がっているホストへのインバウンド通信を通せませんでした。一時期騒がれたNTT本家のひかり電話対応ホームゲートウェイのようなフレッツ網内からインバウンド通信を素通しするマズい状態を回避できるのは嬉しいのですが、ポリシーがちょっと厳しすぎます。Archer A6はDS-Liteを有効にした状態ではND-Proxy（おそらくパケットフィルタの意を含む）が基本的にONでパススルー設定にできず、ND-Proxyをうまいことオフにしつつ手元機材がIPv6アドレスを取れる状態に持っていけないかと試行錯誤しましたがv6力の低さゆえうまくいきませんでした&lt;a id=&quot;fnb-fn-weird-a6&quot; href=&quot;#fn-fn-weird-a6&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*3&lt;/a&gt;。このため、Archer A6のルーター機能は使わないことにしました。&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-weird-a6&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*3] WAN側からのpingを拒否しても普通にpingは素通しし続けるとか怪しい挙動をするので、ファームウェアの作り込みが甘いのかもしれません。エミュレータを見る限りだとEU版ならワンチャンありそうな気もするけれどJP版はメニュー少ないし謎&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;ルーターのファームウェアを書き換えることで、求める機能を実現できる可能性はありますが、残念ながらArcher A6の日本版ハード（TP-Link製品は販売地域ごとにハードウェア仕様もファームウェアも異なったりします。無線レギュレーション差を考えると当然といえば当然ですが）のソースコードは本稿執筆時点で未公開でした（&lt;span class=&quot;imgref&quot;&gt;図3&lt;/span&gt;）&lt;a id=&quot;fnb-fn-a6-gpl&quot; href=&quot;#fn-fn-a6-gpl&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*4&lt;/a&gt;。&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-no-a6&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-no-a6.png&quot; alt=&quot;TP-Link社のGPLコードセンターには日本版Archer A6のコード公開なし&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図3: TP-Link社のGPLコードセンターには日本版Archer A6のコード公開なし
&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-a6-gpl&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*4] &lt;a href=&quot;https://www.tp-link.com/jp/support/gpl-code/&quot; class=&quot;link&quot;&gt;https://www.tp-link.com/jp/support/gpl-code/&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;念のためTP-Link社へコード開示リクエストを出したうえで、こいつのルーター機能については一旦忘れておきます。代わりに、別途構築するルーターの下にぶら下げてスイッチングハブ兼有線/無線変換器として利用します。&lt;/p&gt;
&lt;p&gt;ルーター機能を使わないということはIPoE接続とLAN内へのアドレス配布を担うサーバーが別途必要になりました。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-3-3-1-3-2&quot;&gt;&lt;/a&gt;おはようRaspberry Pi 2&lt;/h3&gt;
&lt;p&gt;Archer A6の標準ファームウェアでは不可能な細かい設定をするために、多くの家庭に数枚は余っていると思われるRaspberry PiシリーズにOpenWrtをインストールします。手元では、ちょうど余っていたRaspberry Pi 2を利用しました。Ethernetが1ポートしかないのでUSB Ethernetアダプタを増設しましたが、USB 2.0のバスにもろもろがぶら下がることになるので、ネットワーク速度はそれなりに制限され、実測値で上下ともに80Mbps程度でした。&lt;/p&gt;
&lt;p&gt;Archer A6では300-700Mbps出るので、かなり遅いといえますが、Raspberry Pi 2はあくまでもコンフィグ確認用の機材なので一旦気にしないことにします。&lt;/p&gt;
&lt;p&gt;OpenWrtをインストールし、DHCPv6でアドレスを取れたらOpenWrtのリポジトリ（ここがv4-onlyだと詰むところですがv6対応で本当にありがたい）からdsliteパッケージをインストールしてDS-Lite設定をしたり、LuCIをインストールしたりと、割とありがちな環境構築をしていくと、v4/v6でインターネットへ出られる環境が整います。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-3-3-1-3-2-3-3&quot;&gt;&lt;/a&gt;/64環境と相性最悪なGoogle Nest Wi-Fi&lt;/h3&gt;
&lt;p&gt;詳しくは別の機会にしますが、今回は宅内のWi-FiルーターにGoogle Nest Wi-Fiを使いたいという事情がありました。この機材自体はIPoE非対応ながら、IPv4/v6デュアルスタックなネットワークにぶら下げることでv6機能が有効化されるという仕様なのですが、OpenWrt側でデュアルスタック化して通常のLinuxマシンからv4/v6のアドレスが共に取得出来る状態になっても「ISPがv6非対応」という非情なメッセージを吐いてv6非対応モードから動いてくれません。&lt;/p&gt;
&lt;p&gt;どうもISP側で割り当てられるアドレスが/56の場合&lt;a id=&quot;fnb-fn-hikari-denwa&quot; href=&quot;#fn-fn-hikari-denwa&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*5&lt;/a&gt;にはv6対応モードで動作するようなのですが、ひかり電話絶対契約したくない呪いを受けた人間としてはこれを自然に達成することは不可能です。&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-hikari-denwa&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*5] ひかり電話を契約するとDHCPv6で/56のアドレスが降ってくることになっています&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;次善の策としては、OpenWrt側でNAT6設定をしてデカいアドレス空間を見せてやることになります。しかしOpenWrtの&lt;a href=&quot;https://openwrt.org/docs/guide-user/network/ipv6/ipv6.nat6&quot; class=&quot;link&quot;&gt;NAT6チュートリアル&lt;/a&gt;を読みつつ設定を流し込んでみた限りではうまくアドレスを配布出来なかったので、結構厄介そうだなーと一旦投げました。さらに、NAT6ではどのみち家庭内サーバーへのアクセスに際してポートフォワードが必要です。&lt;/p&gt;
&lt;p&gt;結局&lt;b&gt;Google Nest Wi-FiはIPv4で動作させ、家庭内ネットワークは原則v4とする&lt;/b&gt;方針でほとんど問題ないのでは、と考えました。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-3-3-1-3-2-3-3-3-4&quot;&gt;&lt;/a&gt;OpenWrtのグローバル側ポート開放&lt;/h3&gt;
&lt;p&gt;前述のとおり、IPoE接続のフレッツひかりにおいてインバウンドを受けられるのはIPv6のみです。いっぽう、Google Nest Wi-Fiの制限のため家庭内の端末はIPv4となってしまいました。&lt;/p&gt;
&lt;p&gt;このため、どうにかしてOpenWrtでIPv6を受けて、家庭内へと転送することにします。&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-global-port&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-global-port.svg&quot; alt=&quot;OpenWrtのグローバル側ポート開放&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図4: OpenWrtのグローバル側ポート開放
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;まずはデフォルトで著しく開放度の低いファイアウォール設定を変更し、グローバル側でSSH用に使うポート（今回は2022としました）を通せるように&lt;code class=&quot;inline-code tt&quot;&gt;/etc/config/firewall&lt;/code&gt;へ次の内容を追記します。&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;config rule
        option name 'inssh'
        option target 'ACCEPT'
        option proto 'tcp'
        option dest_port '2022'
        option src 'wan'
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;もちろん、LuCIで同等の変更をしてもOKです。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-3-3-1-3-2-3-3-3-4-3-5&quot;&gt;&lt;/a&gt;OpenWrtでのポート転送・tcpproxy経由Google Nest Wi-Fiへの通信路確保&lt;/h3&gt;
&lt;p&gt;続いて、OpenWrtが動作しているルーター機（Raspberry Pi 2）にて、IPv6で受けた通信をIPv4へと翻訳してGoogle Nest Wi-Fiへとポート転送する必要があります。&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-v4-transfer&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-v4-transfer.svg&quot; alt=&quot;OpenWrt上でv6→v4転送&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図5: OpenWrt上でv6→v4転送
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;プロキシサーバー設定により、Google Nest Wi-Fiの特定ポートまでパケットが届くことになります。&lt;/p&gt;
&lt;p&gt;おそらくマイナーなパッケージだと思いますが、OpenWrtのリポジトリに&lt;a href=&quot;https://openwrt.org/packages/pkgdata_owrt18_6/tcpproxy&quot; class=&quot;link&quot;&gt;tcpproxy&lt;/a&gt;というIPv6対応のTCPプロキシがあるので、これを利用します。/etc/config/tcpproxyを開き、サンプル記述を参考に設定を書きます。&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;config tcpproxy
  option username 'nobody'
  option groupname 'nogroup'

config listen
  option local_port '2022'
  option remote_addr '192.168.1.100'
  option remote_port '3022'
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;code class=&quot;inline-code tt&quot;&gt;local_port&lt;/code&gt;はOpenWrtがグローバル側から着信させたいポート、&lt;code class=&quot;inline-code tt&quot;&gt;remote_addr&lt;/code&gt;はGoogle Nest Wi-FiのWAN側アドレス（これはOpenWrtの設定でDHCPの固定割り当てをしておきましょう）を指定します。&lt;code class=&quot;inline-code tt&quot;&gt;remote_port&lt;/code&gt;は、Google Nest Wi-Fiから自宅内サーバーのSSHポートへとフォワードされる予定のポート（Google Wifiアプリで設定します）を指定します。ややこしいですね。&lt;/p&gt;
&lt;p&gt;そして、tcpproxyデーモンの起動タイミングがデフォルトでは早すぎて正常に起動しないので、だいぶ後のほうへと変更します。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;# cd /etc/rc.d
# mv S50tcpproxy S97tcpproxy
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;再起動すれば、tcpproxyデーモンが起動してくることを確認できるはずです。&lt;/p&gt;
&lt;p&gt;Google WifiアプリでGoogle Nest Wi-Fi→自宅サーバーのポートフォワード設定をするのも忘れずに。ここまで一通り設定できたら、当該ポートを経由して自宅サーバーへとSSHアクセスできることも確認しておきましょう。&lt;/p&gt;
&lt;div class=&quot;column&quot;&gt;

&lt;h4&gt;&lt;a id=&quot;column-1&quot;&gt;&lt;/a&gt;設定のバックアップと書き戻し&lt;/h4&gt;
&lt;p&gt;デーモン起動タイミングの変更に関連し、LuCIのインタフェースから「設定のバックアップ」を実行して得られる.tar.gzファイル内について注意が必要です。このファイル内にパッケージ情報は残りますがrc.d以下の情報は失われてしまうので、他のルーター機へ設定を持っていく際には書き戻しついでに再度起動順の調整が必要そうです。&lt;/p&gt;
&lt;/div&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-3-3-1-3-2-3-3-3-4-3-5-3-6&quot;&gt;&lt;/a&gt;カメ専用Wi-Fiアクセスポイント&lt;/h3&gt;
&lt;p&gt;宅内を全部IPv4へ寄せた結果、もう2020年が終わるというのに静止画カメしか見れないなんて・・・、と辛い気持ちになったので、前述の激安ルーターArcher A6をブリッジモードへ落としてRaspberry Pi 2のLAN側インタフェース直下に配置しました。&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-kame&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-kame.svg&quot; alt=&quot;カメ専用Wi-Fiアクセスポイント&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図6: カメ専用Wi-Fiアクセスポイント
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;このアクセスポイントへ接続すればさらっとDHCP/DHCPv6でアドレスを取得でき、カメが動いてくれます&lt;a id=&quot;fnb-fn-kame&quot; href=&quot;#fn-fn-kame&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*6&lt;/a&gt;。かわいいね。&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-kame&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*6] &lt;a href=&quot;http://www.kame.net/&quot; class=&quot;link&quot;&gt;http://www.kame.net/&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;前述のGoogle Nest Wi-Fiは、このArcher A6の有線ポートへぶら下がる構造です&lt;a id=&quot;fnb-fn-why-no-direct-connection&quot; href=&quot;#fn-fn-why-no-direct-connection&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*7&lt;/a&gt;。もちろん、カメが動くのを見る以外に一般のIPv6ネットワークへ出ていくことも出来るので、「あ〜〜〜、なんか今日はIPv4で仕事したくないな！」とIPv4では気分が乗らない日には切り替えて使えます。この環境からはGoogle Nest Wi-Fiでポート転送しているSSHサーバー経由でないと自宅内の他の端末と通信できないので、気分は宅外作業です。&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-why-no-direct-connection&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*7] Google Nest Wi-FiをRaspberry Pi 2の直下にぶら下げたほうが速度面で無駄がなくて良いのでは、と感じるところですが、ただでさえUSB-Ethernetを無理やり増設してWAN/LANポートを確保した状態なので、これ以上Raspberry Pi 2側にポートを生やしたくありませんでした。スイッチを足すならブリッジモードのルータを置いても同じことなので、ここにArcher A6を挟めば一石二鳥という寸法です&lt;/p&gt;&lt;/div&gt;

&lt;h2&gt;&lt;a id=&quot;h20191201-1-2-3-3-1-3-2-3-3-3-4-3-5-3-6-4&quot;&gt;&lt;/a&gt;Rakuten MiniテザリングでIPv6環境へSSH&lt;/h2&gt;
&lt;p&gt;楽天モバイルの4G回線はIPv4/v6のデュアルスタックです。このため、ConnectBotのようなアプリを利用すれば前述の環境へとすんなりSSH接続できます。&lt;/p&gt;
&lt;p&gt;同様に、Rakuten Miniをテザリング端末としてLinux PCから自宅マシンへのSSHも簡単にできそうな気がします。しかし残念ながら、Rakuten Miniのテザリング環境はIPv4-onlyです（&lt;span class=&quot;imgref&quot;&gt;図7&lt;/span&gt;）。&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-rakuten-mini&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-rakuten-mini.png&quot; alt=&quot;Rakuten MiniのWi-Fiテザリング下のPCに割り当てられたアドレス&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図7: Rakuten MiniのWi-Fiテザリング下のPCに割り当てられたアドレス
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Androidにおいて、テザリングがIPv6対応か否かはAndroid OS側で規定されておらず端末ベンダー裁量なので、このようなことが起こります&lt;a id=&quot;fnb-fn-iij-ouchi&quot; href=&quot;#fn-fn-iij-ouchi&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*8&lt;/a&gt;。&lt;/p&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-iij-ouchi&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*8] 4年前にIIJの大内さんが調査されたデータが公開されています。SIMロックフリー端末なら対応、ただしDNSサーバーアドレスが取れない場合あり、など。 &lt;a href=&quot;https://www.slideshare.net/IIJ_techlog/iijmio-meeting-10-57396555&quot; class=&quot;link&quot;&gt;https://www.slideshare.net/IIJ_techlog/iijmio-meeting-10-57396555&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;外出先のPCはIPv4を使いつつ、GCPの実質無料インスタンスを中継してv6アクセス、という方法も考えましたが、残念ながらGCPにおいてv6終端は提供されているものの、Compute Engineからv6インターネットへ出ていくことは出来ませんでした。このため、帰宅ルート確保のためにAndroid端末上でプロキシサーバーを動作させ、Linux PCからはそのプロキシ経由でIPv6の世界へアクセスすることにしました。&lt;/p&gt;

&lt;h3&gt;&lt;a id=&quot;h20191201-1-2-3-3-1-3-2-3-3-3-4-3-5-3-6-4-4-1&quot;&gt;&lt;/a&gt;Servers Ultimate&lt;/h3&gt;
&lt;p&gt;Android界には&lt;a href=&quot;https://play.google.com/store/apps/details?id=com.icecoldapps.serversultimate&amp;amp;hl=ja&amp;amp;gl=US&quot; class=&quot;link&quot;&gt;Servers Ultimate&lt;/a&gt;という、HTTPサーバーどころかLAMP丸ごとAndroid端末上に立てることすら可能な万能サーバーアプリという狂ったものが存在します。いろいろ試したところ、このアプリでRakuten Mini上にプロキシサーバーを用意する方法が簡単で安定性も高かったので紹介します&lt;a id=&quot;fnb-fn-netshare&quot; href=&quot;#fn-fn-netshare&quot; class=&quot;noteref&quot; epub:type=&quot;noteref&quot;&gt;*9&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;Ultimateを謳うだけあって、Servers Ultimateには当然の如くプロキシサーバー機能も含まれています。なんと2つも。&lt;/p&gt;
&lt;p&gt;このうちHTTP Proxy Server（&lt;span class=&quot;imgref&quot;&gt;図8&lt;/span&gt;）は、端末ローカルポートを特定のHTTPサーバー/ポートの組へと転送する、HTTP限定のLocalForwardのようなものです。&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-http-proxy&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-http-proxy.png&quot; alt=&quot;HTTP Proxy Server&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図8: HTTP Proxy Server
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;IPv6テザリングに必要なのはこれではなく、Proxy Server（&lt;span class=&quot;imgref&quot;&gt;図9&lt;/span&gt;）です。&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-proxy&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-proxy.png&quot; alt=&quot;Proxy Server&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図9: Proxy Server
&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;footnote&quot; epub:type=&quot;footnote&quot; id=&quot;fn-fn-netshare&quot;&gt;&lt;p class=&quot;footnote&quot;&gt;[*9] 当初、NetShareというAndroid端末をWi-Fiホットスポット化するアプリに組み込まれているプロキシサーバーの利用を試みましたが、WebブラウザでのIPv6サイト表示は出来るもののSSHへのプロキシアクセスがうまく動作しませんでした。connect-proxyのデバッグ出力を確認する限りでは、送出しているHTTP/1.0のリクエストにNetShare組み込みプロキシサーバーが非対応なのかもしれません&lt;/p&gt;&lt;/div&gt;
&lt;p&gt;ポートを適当に指定してサーバーを起動します。&lt;/p&gt;
&lt;p&gt;あとはこのプロキシを利用してconnect-proxyでSSH接続すれば良いのですが、ひとつ考慮漏れがあります。テザリング環境という事情から、このプロキシはLinux PCから見たゲートウェイ上で動作することになりますが、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Wi-Fiテザリング時、テザリングホスト（Rakuten Mini）側のIPアドレスが固定されない&lt;/li&gt;
&lt;li&gt;Wi-FiテザリングとBluetoothテザリングでゲートウェイアドレス帯が異なる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という挙動をします。手元で確認した限り、Bluetoothテザリング時のRakuten Mini側IPアドレスは192.168.44.1に固定されますが、Wi-Fi時には192.168.43.0/24のどこかを適当に確保するようでした。&lt;/p&gt;
&lt;p&gt;こうなると、connectコマンド側のパラメータを固定せず、SSHログイン実行時のネットワーク状態にあわせてゲートウェイアドレスを指定するのが無難でしょう。具体的には、&lt;code class=&quot;inline-code tt&quot;&gt;ip route&lt;/code&gt;コマンドの出力をフィルタしてデフォルトゲートウェイアドレスを取得します。&lt;/p&gt;
&lt;div class=&quot;cmd-code&quot;&gt;
&lt;pre class=&quot;cmd&quot;&gt;$ ip route | grep default | cut --delim=' ' -f3
192.168.43.68
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;こんな感じですね。この内容を盛り込んだSSHホスト定義を&lt;code class=&quot;inline-code tt&quot;&gt;~/.ssh/config&lt;/code&gt;に追記すると、&lt;/p&gt;
&lt;div class=&quot;emlist-code&quot;&gt;
&lt;pre class=&quot;emlist&quot;&gt;Host nanopi-remote-gw-proxy
  HostName [2xxx::xxxx]
  Port 2022
  ProxyCommand connect -H `ip route | grep default | cut --delim=' ' -f3`:[Servers Ultimateのプロキシポート] %h %p
&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;無事&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-diagram1&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-diagram1.svg&quot; alt=&quot;Rakuten Miniテザリングで自宅IPoEへIPv6 SSH&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図10: Rakuten Miniテザリングで自宅IPoEへIPv6 SSH
&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;インターネット帰宅できます。&lt;/p&gt;
&lt;div class=&quot;column&quot;&gt;

&lt;h4&gt;&lt;a id=&quot;column-2&quot;&gt;&lt;/a&gt;Bluetoothテザリングのススメ&lt;/h4&gt;
&lt;p&gt;Rakuten MiniやJelly Proのような小型テザリング端末を使う場合、私はWi-Fiよりも圧倒的にバッテリー持ちのよいBluetoothテザリングを好んで使っています。バッテリー容量の少ない端末でWi-FiテザリングをONにすると3-4時間で電源が切れるというシビれる状態に陥りますが、Bluetoothテザリングなら7-8時間は問題なく使えます。通信速度は数百kbps出ればよいほうですが、ターミナル作業やGoogle Mapsの経路検索ぐらいなら十分快適に利用できます。&lt;/p&gt;
&lt;p&gt;テザリングで端末がぶら下がっていない状態なら普通に3-4日間バッテリーが保ったりします（&lt;span class=&quot;imgref&quot;&gt;図11&lt;/span&gt;）。&lt;/p&gt;
&lt;div id=&quot;id_2020-12-04-rakuten-mini-battery&quot; class=&quot;image&quot;&gt;
&lt;img src=&quot;/images/2020-12-04-rakuten-mini-battery.jpg&quot; alt=&quot;テザリング待ち受け状態で25時間経過時点&quot; /&gt;
&lt;p class=&quot;caption&quot;&gt;
図11: テザリング待ち受け状態で25時間経過時点
&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</content><author><name></name></author></entry></feed>