6.5秒で起動する日本語対応Vimマシンを作った

これは何?

Raspberry Pi は高性能で比較的安く、面白いボードですが、標準のRaspberry Pi OSだと起動時間が遅いのがネックです。

起動時間を早くする方法はいろいろあるのですが、ここではBuildrootを使ってシンプルなイメージを作る事で、高速起動を実現してみました。

シンプルなイメージということで、今回は「日本語入力ができるVim環境」の構築目指すことにしました。

※と言ってもBuildrootを普通に使っただけでそこまでの最適化は行っていないです。

(今値段を見てびっくり、半導体不足の影響ですね、、今は買わないほうがよさそうです・・)

Buildrootによるイメージの作成

f:id:inajob:20220331222650p:plain

先人の知恵を借りてBuildrootを使ってイメージを作成します。

以下の記事の内容が非常に簡潔でそのまま真似しました。

qiita.com

そのまま、と書きましたが実際にはWindows PCの上で動くDockerの中で実行しました。

コマンドを列挙するとこんな感じです。

# dockerでubuntuの環境を作成
$ docker run --rm -it ubuntu bash

# apt-get update
# apt-get install wget make build-essential gcc g++ bzip2 cpio unzip rsync bc libncurses-dev file
# wget https://buildroot.org/downloads/buildroot-2022.02.tar.xz
# tar xJvf buildroot-2022.02.tar.xz
# cd buildroot-2022.02
# make raspberrypi0_defconfig

ここまでが準備です。

必要なパッケージをインストールして、buildroot本体を取得し展開します。

makeを使ってRaspberry Pi Zeroの設定を読み込みます。

他のRaspberry Pi向けのイメージを作る際はmake list-defconfigsを実行してボードを探してみてください。

次に細かい設定を行います。胃かを実行するとCUIで動くメニューが起動します。

# make menuconfig

キーボードを使っていくつか設定を変更します。

f:id:inajob:20220331221730p:plain

今回は最低限の日本語入力ができることを目的としているため以下の設定変更を行いました。

  • Toolchain
    • C libraryを muslに変更
  • Target packages
    • Show packages that are also provided by busybox のチェックを入れる
    • Text editors and viewers
      • vim にチェックを入れる
    • Graphic libraries and applications (graphic/text)
      • fbterm にチェックを入れる
    • Development tools
      • git にチェックを入れる(これはあってもなくても良い)

このあとmakeを実行するとビルドが始まります(数時間かかります)

# make

ビルドが完了すると output/images/sdcard.img が生成されます。

以下のようなコマンドでコンテナからイメージファイルをホストマシンに取り出します。

$ docker cp コンテナのID:/root/buildroot-2022.02/output/images/sdcard.img ./

後は適当なツールを使ってこのイメージをSDカードに焼きます。 (自分はRaspberry Pi Imagerを使いました)

この状態から日本語入力を実現するためのアイデア

普通に考えるとuimなどの日本語変換の仕組みをインストールするのですが、慣れないBuildrootで、これを実現するのはちょっと面倒そうです(やってみると簡単なのかもしれませんが)。

ということで、今回はvim上で動作するskk.vimというのを試してみることにしました。

日本語表示・入力のための下準備

ここからの操作はext3をマウントできる仕組みが必要です。自分は今回使うのとは別のRaspberry Piに外付けSDカードドライブを取り付けてこの作業を実施しました。 (以降~などのパスが出てきますが、これはSDカードを使ってブートした時のパスを表しています、ホストのRaspberry Piのパスではありません。)

日本語表示はfbterm、日本語入力はvim上で動作するSKKで実現します。

日本語表示に必要なttfファイルを~/.fontsに配置します。

自分は源真ゴシック (げんしんゴシック) | 自家製フォント工房 を使いました。ダウンロードしたのちに等幅フォントの「GenShinGothic-Normal.ttf」だけを取り出し上記パスに配置しました。

fbtermの設定(~/.fbtermrc)でGen Shin Gothic Normalを指定することで、このフォントが利用できます (利用できるフォント名は fbterm -v を実行すると確認できます。)

次にvim上で動作するSKKの設定です。

今回は以下を利用します。

github.com

以下のページを参考にセットアップを実施します。

qiita.com

~/.vim以下にgitリポジトリpluginディレクトリをそのままコピーするだけです。

SKKの辞書も上記記事のまま、wgetで取得しホームディレクトリ直下に解凍しておきます。

今回は上記記事を真似てSKK-JISYO.Lを使いました。

~/.vimrcにも上記記事そのままの設定を書きました

let skk_large_jisyo = '~/SKK-JISYO.L'
let skk_auto_save_jisyo = 1

ブート、日本語入力

さて、ここまで準備出来たら、SDカードをアンマウントしRaspberry Pi Zeroに挿入します。

HDMIケーブルでディスプレイに接続、USB-OTGアダプタを経由してキーボードを接続し、電源USBを接続します。

f:id:inajob:20220331222505p:plain

起動するとHDMIケーブルで接続したモニタにコンソールが表示されます。

私の環境では6.5秒程度でログイン待ちコンソールが表示されました。

f:id:inajob:20220331222526p:plain

ログインコンソールにrootと入力するとログインできます。

さらにfbtermと入力し日本語が表示できるようにします。

そして満を持してvimを実行し、挿入モードに入ってCtrl+Jを押してしばらく待つとskk.vimの日本語入力モードに入ります。

f:id:inajob:20220331222545p:plain

ということで、ここまでで 高速起動する日本語入力できる環境を作ることが出来ました。

まとめ

Buildrootを初めて使いましたが、特に工夫しなくても6.5秒で起動するイメージが作れる素晴らしいものでした。

Raspberry Pi 3/4など、もっと性能の良いボードを使えばさらに高速化できると思います。

まだまだ最適化の余地はありますし、SDカードの種類などでも起動の速度は異なると思います。

さらに、vimだけあれば動作するSKK実装を使うことで、簡単に日本語入力環境を作ることが出来ました。

自作のポメラ的なガジェットを作る際にはこの知見が役立ちそうです。

この記事をご覧になっている方で、似たような挑戦をしたことがある方は、ぜひ https://twitter.com/ina_ani に教えてください。

エンジニアパパの2歳までのクリエイティブなDIY育児の記録

このブログでもちょこちょこクリエイティブな育児情報を発信してきましたが、それらは割と大掛かりなものでした。

 

そこで今回は、記事にするほどでもない日々の小粒なDIY事例を紹介しようと思います。

 

同じようなコンセプトで生後2か月の時に書いた記事はこちら。

inajob.hatenablog.jp

うちの娘ももうすぐ2歳となりますが、ここまでに行ってきた小粒なDIY事例を紹介していきます。

 

3Dプリント失敗作を入れたマラカス

あまり遊んでくれず

f:id:inajob:20220327224149p:plain

おむつのパッケージの赤ちゃんを切り取っておもちゃ箱に貼り付ける

じっと見ていた

f:id:inajob:20220327224233p:plain

なぜか某アニメ風の顔をいろいろな育児グッズに描く(妻)

特に娘は反応せず

f:id:inajob:20220327224307p:plainf:id:inajob:20220327224524p:plain

うんこ棒の反対側を保護するキャップを3Dプリンーーで作る

f:id:inajob:20220328083525p:plain

良かった写真を額縁に入れて飾る(妻)

家にいてふと目に留まるとなごむ

f:id:inajob:20220327224340p:plain

足置きとしての技術書

足が地に着くほうが落ち着くらしいです。

f:id:inajob:20220327224413p:plain

トンネルを自作

結構遊んでくれました。

f:id:inajob:20220327224616p:plain

牛乳パックで積み木

これも結構遊んでくれました

f:id:inajob:20220328083613p:plain

コンセントカバーの隙間を3Dプリンターを使って埋める

別にこの隙間があっても困ることはないですが、不格好だったので・・

f:id:inajob:20220327225604p:plainf:id:inajob:20220327225623p:plain

ニューブロックで親が遊ぶ

親の方が夢中になるパターン

f:id:inajob:20220327224715p:plainf:id:inajob:20220327224740p:plain

気付くと真面目なメモを書き始める落書き大会

娘のために描いてたはずが、突然真面目なアイデアメモを書き始めたりもします。

f:id:inajob:20220327224805p:plain

水で絵が描けるシートが便利

f:id:inajob:20220327224827p:plain

これです。壁とかに落書きされないので安心です。

ドアノブをやたらと開け始めたので対策

ドアノブを外すと、90度回転させて取り付けることが出来ます。こうなると通常のノブより開けるのが難しくなります。(今の所これを開けることはないです

f:id:inajob:20220327224850p:plain

言葉遊びかるたを自作(妻)

f:id:inajob:20220327224908p:plain

緩衝材に顔を描いておもちゃにする

あまり遊んでくれず

f:id:inajob:20220327224939p:plain

マスキングテープを駆使して娘に年賀状を書いてもらう(妻)

悩みの種の年賀状のデザインを娘の落書きで済ませるという作戦。

マスキングテープで白を残した部分のおかげでデザインっぽくなった。

f:id:inajob:20220327225038p:plain

引き出しにイラストを描く(妻)

保育園とかでも良く見かけるアレ

f:id:inajob:20220327225055p:plain

ブロックで親が遊ぶ

親の方が熱中するシリーズ

f:id:inajob:20220327225119p:plainf:id:inajob:20220327225138p:plain

f:id:inajob:20220327225153p:plain

一口ゼリーの容器でテントウムシを作る(妻)

これはよく遊んでいた。コスパの良いおもちゃです。

f:id:inajob:20220327225216p:plain

縄跳びをぶつ切りにしてポットん落としを作る(妻)

ベビーシッターさんに教えてもらったおもちゃ

よく遊んでいる

f:id:inajob:20220327225250p:plain

これを並べて絵のようなものを作る

親の方が遊ぶパターン

f:id:inajob:20220327225316p:plainf:id:inajob:20220327225334p:plain

f:id:inajob:20220327225353p:plainf:id:inajob:20220327225410p:plain

洗面所下の収納をむやみに開かないようにする部品を3Dプリンターで作る

今の所これで開けられなくなりました。

f:id:inajob:20220327225457p:plain

まとめ

生後2か月ごろから 2歳くらいの間に作ったDIY系の事例をざざっと紹介しました。

ここまでの所、このように親も楽しみながら育児出来ています。

 

子供への影響とかそういうのもありますが、まずは親が楽しんで育児をするのが大切だと思っています。

 

引き続きクリエイティブに育児をしていきたいと思います。

キッチン水切り棚の導入と3Dプリンターによる不具合修正

キッチン水切り棚

皆さんの家は洗った食器を置く棚、どのようなものを使っていますか?

我が家はもともと、シンプルな水受けの付いた棚だったのですが、子供が生まれたこともあり、皿の数が増え、少し手狭になってきました。

そこで、いわゆる「水切り棚」を購入することにしました。

ここで言う「水切り棚」というのは、キッチンシンクの上に固定できる棚で、棚から落ちた水がそのままシンクに落下するため、水受けが不要となり、かつスペースが節約できるという便利収納家具のことです。

初回購入のミス

比較的大物家具という事で、長さを計ったりして、良さそうなものを注文したのですが、棚の脚間の内径と外形を取り違えたせいで、絶妙に我が家のシンク上に設置できない棚を購入してしましました・・

幅も足らなければ、高さも大きすぎて、シンク上収納と干渉してしまうという残念な結果です。

返品なども考えたのですが、ちょうど工作室の棚に良いという事に気付いたので、返品せずに活用することにしました。

f:id:inajob:20220320121334p:plain

これ、意外と良いですよ。

2度目の正直

初回購入でミスしたので、今度はしっかりと寸法を測って、適合する商品を探しました。

要件は・・

  • シンク幅制限
  • シンク高さ制限
  • できれば2段

f:id:inajob:20220320121419p:plain

どうやら我が家の要件だと、海外製の安い水切り棚では対応するサイズが無く、少し高めの日本製のものがよさそうだという事がわかりました。

という事で、2度目はこちらを購入。

この商品は横幅、高さともに可変という事で、我が家の要件にマッチしました。

棚部分は、フラットなものとかご状のものをそれぞれ選択できるのですが、フラットなものは端の囲いがなく、食器が落下しそうだったので、かご4つタイプを選択しました。

棚のサイズは良し!しかし・・・

設置してみたところ棚のサイズは問題なく、無事にキッチンに設置することが出来ました。

しかし、新たな問題として、かごと皿の相性問題が発覚しました。

この棚に付属しているかごの幅が、絶妙に我が家の皿とマッチしません。

小さな皿はすり抜けて落ちてしまうし、大きな皿はかごの幅が足りず入りません。

f:id:inajob:20220320121452p:plain f:id:inajob:20220320121514p:plain f:id:inajob:20220320121538p:plain

またしても失敗か・・・

と思ったのですが、代替となりそうな棚も見つからないし、何とか工夫して乗り切ることにしました。

Try1 100均の皿置きを重ねる

収納で利用していた皿置きを棚の上に載せてみました。これで一応皿を収納することが出来ました。

f:id:inajob:20220320121605p:plain

しばらくこれで運用していたのですが、皿が高い位置に収納されてしまうため、上の収納と干渉してしまい、出し入れするのが難しいことがわかりました。

Try2 3Dプリンタでシンデレラフィットの皿置きを作る

Try1 は結構いい感じだったのですが、皿置きの幅と棚の幅が合わないために、無駄にスペースを取ってしまっていたのが問題だと考えました。

そこで我が家の3Dプリンタを活用して、棚のサイズにぴったりの皿置きを作る事にしました。

この棚の問題は大きく2つあります

  • 棚の幅が狭いのに、深すぎて、大きな皿が奥まで入らない
  • 棚の網の間隔が広すぎて、皿が倒れたり、小さな皿が落下する

これを解決するために、以下の対策を考えました

  • 皿置きに高さを持たせることで負傘を底上げする
  • 網の間隔を狭くする、横網により落下を防ぐ

ということで、OpenSCADでささっと立体を設計し、、3Dプリンタで印刷! f:id:inajob:20220320121636p:plain

意外と大きくなってしまったので、形状は単純なのですが印刷に5時間ほどかかる大作になってしまいました・・

f:id:inajob:20220320121655p:plain

何とか使えるものになった

ということで、使い勝手が微妙だった水切り棚に、3Dプリンタで作った皿置きを組み合わせる事で、何とか使い物になるソリューションが完成しました。

f:id:inajob:20220320121724p:plain

まとめ

色々失敗も多かった水切り棚の購入ですが、いろいろと試行錯誤して、それなりに使いやすいものが出来ました。

3Dプリンタはこういうちょっとした生活の工夫が出来て便利です。

TVチューナー(EX-BCTX2)が時々HDDを見失う対策と録画一覧をDLNAで引っこ抜く話

これは何?

我が家ではモニタ一体型TVではなく、TVチューナーをPCモニタにつなげています。

inajob.hatenablog.jp

製品としてはEX-BCTX2です。

まぁ、いわゆるnasne的なTVチューナーです。

この製品はHDDを内蔵しておらずUSB接続HDDを外付けします。

しかし、どうもこれが曲者で、我が家では、1か月に1度くらいの頻度で接続しているHDDを見失うことがありました。

この現象が起きると、録画済みの番組が見れないだけでなく、録画そのものが行われなくなるため、せっかく見たくて録画予約していた番組の録画が失敗しとても悲しい気持ちになります。

前からこの現象には悩まされてきたのですが、頻度が低かったので特に対策していませんでした。

しかし、先日「ダイの大冒険」を録画し損ねて、とても悔しかったので、その気持ちが熱いうちに対策を考えることにしました。

ステータスページなどは無いのか?

ひとまずHDDを見失うのはあきらめて、「HDDを見失ったことに気付く」というのが出来るのかというアプローチで調査を始めました。

このTVチューナーはIPアドレスを持つので、そこにブラウザでアクセスするなどして、HDDの様子がわかるのであれば、スクレイピングなどで、監視が出来そう・・と思ったのですが、、

表示されるのはファームウェアのバージョンやIPアドレスの情報だけで、HDDの接続状態は含まれていませんでした。

f:id:inajob:20220213203427p:plain

メディアプレイヤーから見ると・・?

このチューナーは専用のソフト(Windowsだと「テレキングリモート」「テレキングプレイ」)を使って視聴するのですが、なぜか録画した番組一覧をWindowsのメディアプレイヤーから取得することが出来ることに気付きました。 (※当たり前ですが、著作権保護機能により映像を見ることはできません)

f:id:inajob:20220213204003p:plain

これは、何らかの標準的なプロトコルで、録画した番組情報を取得できるのだろう、とあたりをつけました。

この方法がわかれば、録画した番組情報が取得でき無くなる==HDDの認識が出来なくなっている、と外部のプログラムから気付くことが出来ます。

UPnPDLNA

ちょっと調べるとすぐにわかりました、まずこのTVチューナーはUPnPでサービスを広報する仕組みがありました。 そして、そのアドレスに対してDLNAと呼ばれる仕様でアクセスすることで、メディアサーバーとしてある程度の操作が出来そうという事がわかりました。

UPnP

まずはUPnPを使ってみます。

丁度我が家のLANにはRaspberryPiが常時起動しているので、ここからコマンドを実行してTVチューナーのサービスを調べてみることにします。

下記のようにgupnp-toolsパッケージに含まれるgssdp-discoverというコマンドでこれを調べることが出来ました。 (USNに含まれるuuidを公開するのがまずいのかどうかよくわかっていないのでマスクしています、多分大丈夫だと思うけど・・)

$ sudo apt-get install gupnp-tools
...
$ gssdp-discover -i eth0 --timeout=3
...
Using network interface eth0
Scanning for all resources
Showing "available" messages
resource available
  USN:      uuid:XXXXXXXXXX::upnp:rootdevice
  Location: http://192.168.1.15:55958/drgd/
  USN:      uuid:XXXXXXXXXX
  Location: http://192.168.1.15:55958/drgd/
resource available
  USN:      uuid:XXXXXXXXXX::urn:schemas-digion-com:device:DragD:1
  Location: http://192.168.1.15:55958/drgd/
resource available
  USN:      uuid:XXXXXXXXXX::urn:schemas-digion-com:service:DragPlusLRManager:1
  Location: http://192.168.1.15:55958/drgd/
resource available
  USN:      uuid:XXXXXXXXXX::upnp:rootdevice
  Location: http://192.168.1.15:55959/public/
resource available
  USN:      uuid:XXXXXXXXXX
  Location: http://192.168.1.15:55959/public/
resource available
  USN:      uuid:XXXXXXXXXX::urn:schemas-iodata-jp:device:NetworkTunerCommand:1
  Location: http://192.168.1.15:55959/public/
resource available
  USN:      uuid:XXXXXXXXXX
  Location: http://192.168.1.15:55247/dms/
resource available
  USN:      uuid:XXXXXXXXXX::urn:schemas-upnp-org:device:MediaServer:1
  Location: http://192.168.1.15:55247/dms/
resource available
  USN:      uuid:XXXXXXXXXX::urn:schemas-upnp-org:service:ContentDirectory:1
  Location: http://192.168.1.15:55247/dms/
resource available
  USN:      uuid:XXXXXXXXXX::urn:schemas-upnp-org:service:ConnectionManager:1
  Location: http://192.168.1.15:55247/dms/
resource available
  USN:      uuid:XXXXXXXXXX::urn:schemas-digion-com:service:X_AccessControl:1
  Location: http://192.168.1.15:55247/dms/
resource available
  USN:      uuid:XXXXXXXXXX::urn:schemas-digion-com:service:X_DeviceConfiguration:1
  Location: http://192.168.1.15:55247/dms/
resource available
  USN:      uuid:XXXXXXXXXX::urn:schemas-dlpa-jp:service:X_DtcpPlus:1
  Location: http://192.168.1.15:55247/dms/
...

これで出てきたHTTPのアドレスにはブラウザでアクセスすることが出来ました。

今回目をつけたのはContentDirectoryというものです。どうも、ここにうまいことリクエストすることで、録画した番組一覧などを取得できるようです。

DLNA

ここからはDLNAという仕様に則って通信を行えば良さそうです。 少し調べるといくつか参考になりそうな情報がありました。 DLNAと言っても、その実態はHTTPのGET/POSTのリクエストのようで、Curlなどでも実行できるものでした。

まずはPOSTのリクエストボディを作ります。

読む限りSOAPのようです(よく知らんけど・・)。

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <m:Browse xmlns:m="urn:schemas-upnp-org:service:ContentDirectory:1">
      <ObjectID>0</ObjectID>
      <BrowseFlag>BrowseDirectChildren</BrowseFlag>
      <Filter>*</Filter>
      <StartingIndex>0</StartingIndex>
      <RequestedCount>200</RequestedCount>
      <SortCriteria></SortCriteria>
</m:Browse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

なんとなく意味するところとしては、

  • ContentDirectoryというサービスにBrowseというActionを要求している。
  • ObjectIDというのが操作対象で、0だとルートを指す。
  • BrowseFlagをBrowseDirectChildrenとするとその操作対象の直下にあるリソース一覧を要求することとなる。
  • Filterは*以外の例を見なかった
  • StartIndexは大量のデータの途中から読む際につかうIndex
  • RequestedCountは取得したい数、しかしこの数取れるわけではないようだ
  • SortCriteria は空文字以外の例を見なかった

という感じ。

さて、これを踏まえてCurlのコマンドを呼び出します。

$ curl -v -H "Content-Type: text/xml; charset=\"utf-8\""  -H "SOAPAction: \"urn:schemas-upnp-org:service:ContentDirectory:1#Browse\"" --data-binary @request.xml http://192.168.1.15:55247/dms/control/ContentDirectory

ヘッダにContent-TypeSOAPActionというものを付与しないとエラーが返るようでした。

HTTPのアドレスは UPnPで見つけた http://192.168.1.15:55247/dms/ にアクセスするとそれっぽいURLが書いてありました。

さて、これを実行するとルートのリソース直下にある子供のリソースが取得できるはずです・・

*   Trying 192.168.1.15:55247...
* Connected to 192.168.1.15 (192.168.1.15) port 55247 (#0)
> POST /dms/control/ContentDirectory HTTP/1.1
> Host: 192.168.1.15:55247
> User-Agent: curl/7.73.0
> Accept: */*
> Content-Type: text/xml; charset="utf-8"
> SOAPAction: "urn:schemas-upnp-org:service:ContentDirectory:1#Browse"
> Content-Length: 899
>
* upload completely sent off: 899 out of 899 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: Linux/3.0.8 UPnP/1.0 DiXiM/4.0
< Date: Sun, 13 Feb 2022 09:03:53 GMT
< Connection: close
< EXT:
< Content-Type: text/xml; charset="utf-8"
< Transfer-Encoding: chunked
<
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:BrowseResponse xmlns:u="urn:schemas-upnp-org:service:ContentDirectory:1">
<Result>&lt;DIDL-Lite xmlns=&quot;urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/&quot; xmlns:upnp=&quot;urn:schemas-upnp-org:metadata-1-0/upnp/&quot;
xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot; xmlns:dlna=&quot;urn:schemas-dlna-org:metadata-1-0/&quot; xmlns:dixim=&quot;urn:schemas-digion
-com:metadata-1-0/dixim/DIDL-Lite/&quot; xmlns:microsoft=&quot;urn:schemas-microsoft-com:WMPNSS-1-0/&quot; xmlns:lamprey=&quot;http://www.lampreynet
works.com/schema/lamprey_1.0/&quot; xmlns:arib=&quot;urn:schemas-arib-or-jp:elements-1-0/&quot; xmlns:dtcp=&quot;urn:schemas-dtcp-com:metadata-1-0/&
quot; xmlns:dlpa=&quot;urn:schemas-dlpa-jp:metadata-1-0/&quot; xmlns:xsrs=&quot;urn:schemas-xsrs-org:metadata-1-0/x_srs/&quot;&gt;
&lt;container id=&quot;root/XXXXXXXXXX/&quot; parentID=&quot;0&quot; restricted=&quot;0&quot; childCount=&quot;11&quot;&gt;
&lt;dc:title&gt;USB2:大容量&lt;/dc:title&gt;
&lt;upnp:containerUpdateID&gt;3802&lt;/upnp:containerUpdateID&gt;
&lt;upnp:class&gt;object.container&lt;/upnp:class&gt;
&lt;/container&gt;&lt;container id=&quot;root/LIVE_TUNER/&quot; parentID=&quot;0&quot; restricted=&quot;1&quot; childCount=&quot;3&quot;&gt;
&lt;dc:title&gt;ライブチューナー&lt;/dc:title&gt;
&lt;upnp:class&gt;object.container&lt;/upnp:class&gt;
&lt;arib:objectType&gt;&lt;/arib:objectType&gt;
&lt;upnp:containerUpdateID&gt;350&lt;/upnp:containerUpdateID&gt;
&lt;/container&gt;&lt;/DIDL-Lite&gt;
</Result>
<NumberReturned>2</NumberReturned>
<TotalMatches>2</TotalMatches>
<UpdateID>3810</UpdateID>
</u:BrowseResponse>
</s:Body>
</s:Envelope>
* Closing connection 0

うげ、なんだこれ・・

と思いつつそれっぽい情報も含まれているので注意深く読んでみます。

どうやらこれは、XMLの中にさらに文字列としてXMLが入ってしまっているようです。

しかしよく見ると「USB2:大容量」「ライブチューナー」という文字が見えます。

この「USB2:大容量」というのは自分が外付けHDDにつけた名前なので、どうやらルートにあるリソースが出ているようです。

さらに良く見るとこの外付けHDDのIDがroot/XXXXXXXXXX/である事もわかります(よくわからないので一応マスクしています。)

次はObijectIDをこの外付けHDDのIDにして、再度同じコマンドを実行すると、今度は外付けHDD内のディレクトリ一覧を取得できました。

ディレクトリ一覧ですが、その中に「すべて」という名前のディレクトリがあり、そのIDで同じコマンドを実行するとどうも、すべての録画データを取得できるようでした。(確かに純正アプリの「テレキングリモート」もそういう挙動をします)

f:id:inajob:20220213212516p:plain

という事で、無事DLNAを使ってTVチューナで録画した番組の一覧を取得することが出来ました。

HDDの死活監視を設定する

という事で、ちょっとやりすぎた感もありますがDLNAを使って外付けHDD内の録画データの一覧を取得できました。

HDDが認識できていないときは、このデータが取得できないはずなので、これを利用して外付けHDDが認識できないときにアラートを上げることが出来るようになりました。

雑に書いたスクリプトはこんな感じ・・

まずはおおもとのシェルスクリプト。これをcronで実行します。

#!/bin/bash

cd `dirname $0`

MAX_RETRY=5
n=0
until [ $n -ge $MAX_RETRY ]
do
# ====================
n=$[$n+1]
curl -v -H "Content-Type: text/xml; charset=\"utf-8\""  -H "SOAPAction: \"urn:schemas-upnp-org:service:ContentDirectory:1#Browse\"" --data-binary @request.xml http://192.168.1.15:55247/dms/control/ContentDirectory > get.txt

python check.py && break

echo "ERROR"
sleep 10

# ====================
done

if [ $n -ge $MAX_RETRY ]; then
  echo "failed: ${@}" >&2
  # SLACKなどにHDDが認識できない旨を通知
  exit 1
else
  echo "OK"
fi

リクエストに使うXML文書(ObjectIDはマスクしています)

<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><m:Browse xmlns:m="urn:schemas-upnp-org:service:ContentDirectory:1"><ObjectID xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">「外付けHDD内のすべてのリソースを表すID」</ObjectID><BrowseFlag xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">BrowseDirectChildren</BrowseFlag><Filter xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string">*</Filter><StartingIndex xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui4">0</StartingIndex><RequestedCount xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="ui4">200</RequestedCount><SortCriteria xmlns:dt="urn:schemas-microsoft-com:datatypes" dt:dt="string"></SortCriteria></m:Browse></SOAP-ENV:Body></SOAP-ENV:Envelope>

curlで得たXMLを解析しちゃんと番組データが入っているかを確認するpythonスクリプト

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import html
import xml.etree.ElementTree as ET

count = 0

with open("get.txt", encoding="utf8") as f:
    s = f.read()
    x = html.unescape(s)
    #print(x)
    root = ET.fromstring(x)
    print(root) # Envelope
    print(root[0]) # Body
    print(root[0][0]) # BrowseResponse
    print(root[0][0][0]) # Result
    print(root[0][0][0][0]) # DITL-Lite
    for item in root[0][0][0][0]:
        print(count, item[0].text) # title
        count = count + 1
    print("count", count)

さて、これで外付けHDDの死活監視を実現できました。

シェルスクリプトで、謎にループしているのは、初回のリクエストは失敗し、その後HDDがスピンアップして安定するとリクエストが成功するためです。

このスクリプトを仕込んでから、まだ1度もHDDの認識を失敗したことがないのでわからないですが、この定期的な外付けHDDのスピンアップのおかげで、認識しなくなる問題も発生頻度が下がっているかもしれません。

蛇足: 録画しているすべての番組を一覧する

ここまでで、表題の問題は解決しましたが、せっかく録画番組一覧が取得できるなら、何かに応用したいな、、と思って取得したデータを見ていました。

しかし、「すべて」のデータを取得しようとしても1度のリクエストでは数十件分のデータしか返却されていないことに気付きました。

どうやら、StartingIndexを取得したデータの個数ずつずらして何度もリクエストすることが必要のようです。

いわゆるWebアプリのAPIの「ページング」的なやつですね。

ここまで来たら、やってしまおうという事で、このページングをすべてたどって、録画データのタイトル一覧を出力するプログラムを書いてみました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import html
import xml.etree.ElementTree as ET
import urllib.request
import sys

url = 'http://192.168.1.15:55247/dms/control/ContentDirectory'
headers = {
        'Content-Type': 'text/xml; charset="utf-8"',
        'SOAPAction': '"urn:schemas-upnp-org:service:ContentDirectory:1#Browse"'
        }

def createRequestBody(objectID, startIndex):
  return '''<?xml version="1.0"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
  <SOAP-ENV:Body>
    <m:Browse xmlns:m="urn:schemas-upnp-org:service:ContentDirectory:1">
      <ObjectID>%s</ObjectID>
      <BrowseFlag>BrowseDirectChildren</BrowseFlag>
      <Filter>*</Filter>
      <StartingIndex>%s</StartingIndex>
      <RequestedCount>0</RequestedCount>
      <SortCriteria></SortCriteria>
</m:Browse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>''' % (objectID, startIndex)

def browse(objectID, startIndex):
  reqbody = createRequestBody(objectID, startIndex)
  req = urllib.request.Request(
          url,
          reqbody.encode("utf8"),
          method="POST",
          headers = headers
          )
  count = 0
  with urllib.request.urlopen(req) as res:
      s = res.read().decode("utf8")
      x = html.unescape(s)
      root = ET.fromstring(x)
      return root

def getItems(root):
    body = root.findall('{http://schemas.xmlsoap.org/soap/envelope/}Body')[0]
    browseResponse = body.findall('{urn:schemas-upnp-org:service:ContentDirectory:1}BrowseResponse')[0]
    return browseResponse.findall('Result')[0].findall('{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}DIDL-Lite')[0]

root = browse(0, 0)
# find USB disk
usbID = -1
for item in getItems(root):
    if item[0].text.find('USB') != -1:
        usbID = item.attrib['id']

root = browse(usbID, 0)
# find ALL directory
allID = -1
for item in getItems(root):
    if item[0].text.find('すべて') != -1:
        allID = item.attrib['id']

# list all contents
index = 0
while True:
    root = browse(allID, index)
    body = root.findall('{http://schemas.xmlsoap.org/soap/envelope/}Body')[0]
    browseResponse = body.findall('{urn:schemas-upnp-org:service:ContentDirectory:1}BrowseResponse')[0]
    numberReturned = int(browseResponse.findall('NumberReturned')[0].text)
    totalMatches = int(browseResponse.findall('TotalMatches')[0].text)
    # print(index, numberReturned, totalMatches)

    items = getItems(root)
    for i, x in enumerate(items):
        title = x.findall('{http://purl.org/dc/elements/1.1/}title')[0]
        start = x.findall('{http://purl.org/dc/elements/1.1/}date')[0]
        print(index + i, start.text, title.text)
    index = index + numberReturned
    if index + numberReturned == totalMatches:
        break

実行すると、何度もリクエストを行い、すべての録画番組のタイトルを取得します。

f:id:inajob:20220213212235p:plain
出力したデータをgrepして特定の番組を取り出している

まだ残る謎

さて、ここまでTVチューナのDLNA機能を使って、録画番組一覧を取得してきましたが、やっていく中で以下のような疑問が出てきましたが、まだ未解決のままです。

何か情報をご存知の方は、ぜひ教えてください。

  • 「テレキングリモート」から録画番組の削除を行っても、DLNA経由での呼び出しではデータが残り続ける。一度「テレキングリモート」で録画番組の一覧を取得すると(このときものすごく遅い)、以降DLNA経由での呼び出しでもデータが正しく消えるようになる。
  • 「テレキングリモート」のUIでは視聴済みの番組にはマークがつくが、DLNAのレスポンスを見る限りそのようなデータが入っていない

DLNAでできるのかは不明ですが、以下のようなこともやってみたいです

  • 放送が終了しているのに残り続けている繰り返し予約の検知
  • 録画予約の取得
  • 録画予約の実施
  • 録画された番組と繰り返し予約エントリの紐づけの取得

まとめ

ということで、TVチューナーで外付けHDDが突然認識されなくなる問題を、外形監視により検知できるようになりました。 まだ一度も発動していないのでスクリプトにはバグがあるかもしれません。

また、副産物として、TVチューナで録画した番組のタイトル一覧を取得できるようになったので、これも何かに使ってみたいです。

例えば、「テレキングリモート」より一覧性の高い録画番組ビューアとか?(ただし再生はできない・・)

参考

DLNA周りの参考となる記事が少なかったのですが、結局以下のページを丸々真似する感じになりました。

記事を書いてくださった方に感謝します。

3Dプリンタと無料のツールで「うんこクッキー」を作った

f:id:inajob:20220122080901p:plain

事の発端

1月某日、我が家の電子レンジが壊れました。これを機に新しいものを買おう・・という事になりいろいろと吟味した結果・・

オーブンレンジを買いました!

オーブン機能をつけたのは、「お菓子」を作りたかったから!

ということで早速クッキーを作る事にしました。

3Dプリンタで型を作る

クッキーの型は市販のものも購入していたのですが、せっかく家に3Dプリンタがあるので、オリジナルの型も作ってみることにしました。

今回作るのは、みんな大好き「うんこ」です!

unko.svg

まずはSVG画像でうんこのシルエットを作ります。作図にはInkscapeを利用しました。 ベジェ曲線を使った作図は少しコツが必要ですが、慣れればこのくらいは簡単に作れます。

f:id:inajob:20220121215523p:plain

unko.scad、unko.stl

SVG画像をもとに3Dデータを作成します。ツールはOpenSCADを利用しました。 offsetで少し輪郭を大きくして、元のデータとdifferenceすることで、クッキー型の3Dデータを作成します。

ソースコードはこんな感じです。

module unko()
  import("unko.svg", center = false, dpi = 96); // unko.svgの読み込み

linear_extrude(height=20) // 型の厚さは20cm
difference(){ // 膨らませたものから元のデータの差をとる
  offset(3)  // unko.svgのデータを3mm膨らませる
    unko();
  unko();
}

完成したらSTLデータとしてエクスポートします。

f:id:inajob:20220121215543p:plain

unko.gcode

Silc3rでSTLファイルをG-Codeファイルに変換します。

f:id:inajob:20220121215602p:plain

3Dプリンタに転送して印刷

3Dプリンタにデータを送って印刷を開始し、後は待つだけです。 今回は手元にあったPLAのフィラメントを使いましたが、おそらく厳密には健康に良くない物質が混ざる危険があるような気がします。 真似して実施する場合は自己責任でお願いします。

f:id:inajob:20220121215632p:plain

クッキーの製作

今回は市販のクッキーの素を使いました。

クッキー型で生地を抜くとこんな感じです。

f:id:inajob:20220121215759p:plain

で、電子レンジのオーブンで加熱。大体30分ほどで完成です。

f:id:inajob:20220121215834p:plain

おまけ

Twitterのアイコンもクッキーにしてみたらいい感じに出来ました!

f:id:inajob:20220121215907p:plain

感想

オリジナルの形のクッキーを作るというのは思った以上にテンションが上がりました。 データを用意する方法も、はじめのSVGを作るところ以外は機械的な作業なので、思った以上に簡単でした。

まだうちの子供は1歳なので、あまり楽しめていない感じでしたが、もう少し大きくなったら、子供の好きなものの形のクッキーを作ってあげようと思っています。

子供がを持つことで、3Dプリンタも今までと違った使い方をするようになってきました。折角子供を授かったので、この環境の変化をポジティブにとらえ、このような楽しい活動をどんどんやっていこうと思っています。

2021まとめ

f:id:inajob:20211231203539p:plain

ピックアップニュース

娘(1歳)がコロナに罹患

なんといっても今年は、これが一番のニュースでした。24日間の隔離はとても大変でしたが、家族全員大事が無くて良かったです。

inajob.hatenablog.jp

育児と仕事の両立

1月から長い9か月の育休を終え、仕事に復帰しました。妻は4月から復帰で、2オペで育児しつつ仕事と両立させています。 時短勤務などを駆使して、なるべく無理しないように努めています。

インターネットを活用したパパ友との交流も活発に行うことが出来ました。

パパ育コミュ | 子育てパパが集う場所 に参加し、たくさんの子育てを楽しむパパと交流したり、育児Scrapboxを公開し育児ノウハウの共有を行うこともできました(自由参加)。

inajob.hatenablog.jp

toICNの開発

クラウドファンディングで購入したインスタコード専用の楽譜変換ツール「toICN」を作り、これが、そこそこ利用され、OSSとしての開発も、小規模ながら体験することが出来ています。

インスタコードはまだまだアップグレードが続いているので、今後も開発をやっていきます!

inajob.hatenablog.jp

ステータス

  • 引っ越してから5年目
  • 会社に入ってから12年目(来年で13年目)
  • 結婚して5年ちょっと
  • 娘が生まれて1年目

今年学んだスキル

育児全般(引き続き)

育児は学びの連続です。娘もいつの間にか歩けるようになり、大人のいう事も少しずつ分かるようになってきて、また少し言葉もしゃべるようになってきました。来年もどんどん成長するんだろうなぁ・・

オクラの育て方

今年はミニトマトに加えて、オクラとスイートバジルも栽培しました。 inajob.hatenablog.jp

自作キーボードづくりの一通りの流れ

ALLPCB39というキーボードを設計しました。

inajob.hatenablog.jp

怒涛の基板設計

ALLPCBさんで、無料のクーポンが毎月利用できるという事で、基板設計を毎月行い 3枚の基板を試作できました。

  • CheapSeqDuo

  • ALLPCB39
    • 前述のキーボード
  • PiPoPa
    • 携帯電話型のArduino互換ボード。まだ記事を書いていない・・
    • ALLPCBのUselessコンテストの「BestIdea」入賞した

勢いで設計した割には3枚ともそこそこ面白いものが出来て満足しています。また記事を書かねば・・

CH451制御

グリッド状のスイッチとLEDを制御できる便利ICであるCH451の試作基板を作り、制御の方法を学びました。(同時押しが検出できないこともわかり、お蔵入りですが・・

今年買ったもの

  • ガス給湯器
  • 電動コーヒーミル

(追記予定)

感想

育児と仕事の両立というのは思った以上にハードで、自分の趣味の活動や、勉強に使える時間が限られてきたと感じた1年でしたが、そんな中でも、このブログの毎月更新や、もう一つのinajobがいろいろ紹介するサイトです – inajobのいろいろレビュー の平日ほぼ毎日更新が出来たのは、育休中に育児に向けた生活のリズムや、夫婦の役割分担がうまくできたからだと思っています。

こうやって記事にして見直してみると、いろいろ挑戦したが、アウトプットできていない作品が結構あるなと感じました。子育てなどで可処分時間が減っている中で、インプット・アウトプットのバランスをもう少し考えていく必要があるなと思っています。

育児はまだまだ長期戦ですが、息切れしないように気を配りつつ、自分のやりたいこともうまく両立していきたいと考えています。

月別生データ

1月

  • 育休から復職
  • Arduboy FX Modをもらう
  • 保育園決定

  • M5Paper使ってみる

inajob.github.io

inajob.github.io

  • clubhouse初体験
  • 夜間断乳頑張る

2月

  • 娘用電子工作

inajob.hatenablog.jp

  • duktape, emscriptenなどで遊んでいた
  • 1週間のレシピを考案するソフトを試作してた
  • HACHIBARのLiPo化
  • Graphviz Live Previewの開発

inajob.github.io

3月

  • igeta試作
  • ひな祭り
  • パパ育の電子書籍でアイコンを書いてもらったり
  • クラシルでレシピを決め始める

inajob.hatenablog.jp

  • Meowbit

inajob.github.io

  • コンセントカバー

  • M5StackとUSB-Host-Shield
  • ノートパソコンスタンド

inajob.github.io

  • HACHIBARを増やす
  • おうちサイネージにランダム画像を出すようにする

4月

  • 慣らし保育
  • RakuChord,HACHIBARの新バージョン発注(まだ倉庫に眠っている・・)
  • TTGO T-Watch Keyboard

inajob.github.io

  • LibreCADを試すが、合わない
  • 手挽のコーヒーミル購入
  • ぼにぎり
  • 毎月風邪をひく生活に・・
  • Aliから謎の荷物が届く事件

5月

  • テープカッターチャレンジ

inajob.hatenablog.jp

  • 冷蔵庫調味料棚をDIY

  • パンダのうんこ 購入
  • HACHIBAR少し販売再開
  • igeta組み立て
  • mmf21
  • ヨトウムシにやられる
  • ironingを試す
  • カニカルキーボード

inajob.github.io

6月

  • PVBフィラメントを試す
  • カニカルキーボードデビュー

inajob.hatenablog.jp

  • 役員トレカを作ってみる
  • CheapSeqのケースを作る
  • Arduboy FX Mod組み立て

inajob.hatenablog.jp

  • 3Dプリンタでキーキャップを作ってみる
  • FitBitに腕をやられる
  • おもしろいのを3Dプリントしたらちょっとバズる
  • コードリーディングメモの記事が盛り上がる

qiita.com

7月

inajob.github.io

  • オクラ収穫開始

inajob.hatenablog.jp

  • ニューブロックで遊ぶ
  • CheapSeqDuoの設計
  • Type-C to VGA
  • MCE3開発

github.com

  • SONICWARE LIVEN 8bit warps LVN-010 -- ほとんど遊べていない・・もったいない

8月

inajob.hatenablog.jp

  • 新しいロボット掃除機
  • toICN実装開始
  • 高解像度ディスプレイモジュール

inajob.github.io

9月

  • World Makerのクローズドベータ当選
  • インスタコードが届く

inajob.hatenablog.jp

  • 5000フォロワー達成
  • PiPoPaの設計・実装
  • 大量のPLAフィラメントを購入

  • 水で落書きできるシート
  • ABAさんのゲームをRaspberryPiで動かそうとしてみる
  • XDAキーキャップ

inajob.github.io

  • ALLPCB39とM5Stackの組みあわせ
  • RaspberryPi4スターターキット

inajob.github.io

10月

  • toICNひと段落

inajob.hatenablog.jp

  • 給湯器の予防交換
  • 電子計り

inajob.github.io

  • 貧者のUSBチェッカー

  • インスタコードのボリュームの固定具を3Dプリント
  • TodayTwitの開発

11月

  • ALLPCB Useless Contest 受賞(何賞だったかな?)
  • ALLPCB39の記事

inajob.hatenablog.jp

12月

  • 3DPCB

inajob.hatenablog.jp

  • おうちサイネージのオープン化

  • クリスマスオーナメントを作る

  • 怒涛の帰省

参考

3Dプリンタで基板のようなもの(3DPCB)を作ってみた

基板のようなもの?

下記の記事で紹介していた方法が、前から気になっており、今回はこの方法でオリジナルの基板のようなものを作ってみました。

inajob.github.io

この手法は Developing 3DPCB – 3D Printed Circuit Board with lots of potential. – Johan von Konow で紹介されており、「3DPCB」と呼ばれています。

基板設計

基板の設計は通常のプリント基板と同様にKiCADで行います。

こんな風に論理回路図を書いて・・

f:id:inajob:20211202213528p:plain

基板を書きます。

f:id:inajob:20211202213553p:plain

この時KiCADを「レガシーツールセット」に切り替えて、「配線時の角度を45度単位に制限」のチェックを外して、自由な角度に配線できるようにしておくのがポイントです。

45度に制限しても良いのですが、実際に配線をするときに導線を曲げる必要があり面倒です。なるべく直線で配線します。

f:id:inajob:20211202213743p:plain

3Dプリンタの精度にもよりますが、私は2mmの配線幅としました。

データ作成

基板の片面にのみ配線したので、その面のデータをdxfで出力します。

f:id:inajob:20211202214044p:plain

ドリルデータはいつも通りに出力します。

ドリルデータはそのままだとCADソフトに読み込ませることが出来ないので、下記スクリプトでDXFに変換します。 (元となるソースコードDRL NC file to DXF · GitHub を参考にしています。)

import ezdxf
from sys import argv
import re

if len(argv) < 2:
    print("Need filename")

in_filename = argv[1]
out_filename = in_filename[:-4] + ".dxf"

doc = ezdxf.new(dxfversion="R2010")

doc.layers.add('holes', color=2)
msp = doc.modelspace()

tools = {}
current_tool = None
for line in open(in_filename).readlines():
    line = line.strip()

    match = re.match("^T(\d+)C(.+)$", line)
    if match:
        tool_id = int(match.group(1))
        size = float(match.group(2))
        tools[tool_id] = size
        continue
    match = re.match("^T(\d+)$", line)
    if match:
        current_tool = int(match.group(1))
        continue
    match = re.match("^X(.+)Y(.+)$", line)
    if match:
        x = float(match.group(1))
        y = float(match.group(2))
        size = tools[current_tool]

        print(x/100,y/100,size/2*50*4)
        msp.add_circle((x/100,y/100), radius=size/2*50*4)

print(out_filename)
doc.saveas(out_filename)

3DPCBの作成

OpenSCADを使います。

difference(){
  translate([0,0,-1])
  // 雑に外形線を描く
  linear_extrude(height=3){
    polygon([[0,50],[40,20],[25,20],[50,-20],[30,-20],[15,-20],[10,-40],[0,-40]]);
    mirror([-1,0,0])
      polygon([[0,50],[40,20],[25,20],[50,-20],[30,-20],[15,-20],[10,-40],[0,-40]]);
  }
  // 配線をくりぬく
  linear_extrude(height=3)
    translate([0,40,0]){
      import("out/christmas-B_Cu.dxf");
    }
  
  // ドリル穴をくりぬく
  translate([0,0,-2])
  linear_extrude(height=2-0.3)
  translate([0,40,0]){
    scale([1/4, 1/4, 1/4])
    import("out/christmas.dxf");
  }
  
  // ドリル穴の上部にはんだ付けしやすいように大き目の穴をくりぬく
  translate([0,0,0])
  linear_extrude(height=6)
  translate([0,40,0]){
    scale([1/4, 1/4, 1/4])
    offset(5)
    import("out/christmas.dxf");
  }
}

これでこんな立体が生成できます。

こちらははんだ付け面。

f:id:inajob:20211202214615p:plain

部品配置面はこんな感じ。

f:id:inajob:20211202214647p:plain

ドリル穴部分は裏面には小さな穴をあけつつ、表面にははんだ付けしやすいように、大きな穴をあけています。

dxfデータはドリル穴しかないですがOpenSCADのoffsetを使うことでこの穴を大きくして大きな穴のデータを用意しています。

今回は配線面を下にして、部品実装面を上に3Dプリントしようとしていたので、オーバーハング部分の造形をうまい事やるために、1層だけあえて穴をふさぐようにデータを作っています。

f:id:inajob:20211202215846p:plain

(このテクニックは FDM(FFF)3Dプリンタで印刷する機械部品をモデリングするときの、たくさんの小さな工夫|はるかぜポポポ|note この神記事を参考にしています)

3Dプリント

あとは3Dプリンタが出力するのを待つだけです。

厚さは3mmでそこまで大きくないので、40分ほどで完成しました。

部品取り付け穴は1層分だけふさがっているので、キリで突いて穴を貫通させます。

部品の取り付け

今回の部品はLEDと抵抗です。部品取り付け穴に部品を差し込んで、溝に沿って余った足を折り曲げて配線します。

配線すべき個所には溝があるため、迷うことなく配線できました。これがこの3DPCBを使う利点ですね。

PLAは熱に弱いので、はんだ付けは素早く行います。と言っても普通にはんだ付けしてる分にはそこまで気を遣う必要もなさそうでした。 (PLAが溶けたとき独特の甘い匂いが少し出ます)

そんなこんなでできた基板がこちら!

f:id:inajob:20211203072456p:plain

まとめ

挑戦してみたかった3DPCBを試してみました。

PCBほどではありませんが、ユニバーサル基板に実装するよりは簡単でミスが少ない基板が出来たように思います。

今回は試していないですが、配線面を下にして3Dプリントすることが出来ることもわかったので、部品実装面にも造形できそうです。

参考とした記事には具体的なツールの紹介などは無かったのですが、使い慣れたKiCAD, OpenSCADを組み合わせる事で比較的「職人芸」なしに3DPCBが作れることが分かったのも大きな収穫でした。

リアランスなどはPCBには遠く及びませんが、家にある3Dプリンタでこういうものが作れるのは意外と使えるテクニックのように思いました。