改造キーボードへの沼(令和版NeXTキーボードV2)

改造キーボードへの沼(令和版NeXTキーボードV2)

この記事はあくあたん工房2023アドベントカレンダーの22日目です.

どうもomzn教授(キーボード改造学)です.
まもなくクリスマスですよ.クリスマスプレゼントに自作キーボードなんか贈ったらダメですよ.

この記事は,1週間前からの続きです.

令和版NeXTキーボード V2

実は無改造のNeXTキーボードも1台所有しているのですが,こちらはジャンク品で通電しても動かなかったものです.
こいつも改造してしまうことにしましょう.

ちなみに2023年12月時点でNeXTキーボードのeBay価格はご覧の通りです.

もったいなくてハラハラしますね…

さて,今回は以下の3点を実現します.

  • パターンカットしない
  • 物理キーを互換品に変更
  • 光らせる

キー配置

今回のキーボードは前回のものよりも,多少新しい時代に製造されたものです.(それでも1990年代前半です)キー配置も似ているのですが,Returnが逆L字になり,そのあおりを食ってバックスラッシュキーがテンキーパッドに押し出されています.

ここはちょっとどうにかしてやらないと使いづらそうなところですね.
また,刻印が昔のMacintoshっぽくなってきています.

物理キー

こちらのキーボードはALPS黒軸を採用しています.ALPS黒軸はクリーム軸の後に採用されたものですが,キーのクリック感がややマイルドになっています.

キーマトリクス解析

キーボードのマトリクスパターンを解析します.目視とテスター片手に裏面のパターンを追いかけます.今回はパターンカットをしない縛りで作っていきますので,既存のマトリクスを最大限利用することになります.また,Raspberry Pi Picoを載っける基板を作り,オリジナルのコントローラを抜いたところにはめ込んでやります.

解析結果を以下の図に示します.

近いキーがコンパクトにまとまっていて,8x10に収まりそうな感じのきれいなマトリクス・・・と思ったら,モディファイアキーだけ飛び出してしまっています.なんじゃこりゃ.よく見るとR10と書いた行はGNDです.てことは,これらのキーはマトリクスではなく,ダイレクトキーになっているってことですね.

さすがにそこまで富豪なピンがあるマイコンは使っていないので,モディファイアキーもマトリクスに統合します.GNDだったパターンを追いかけてジャンパを抜いてGNDから外して1本のバスにしてしまいます.
最終的に決定したマトリクスは以下のようになります.モディファイアをどこの列に統合するかは,今回作成するPi Pico基板の都合でPCBを作りやすいように決めています.

このマトリクスをPi Picoへ変換する基板を作ります.この基板の上だけで,モディファイアキーの列の統合も行います.そのおかげで今回はほとんどジャンパワイヤを飛ばす必要がなくなりました.


設計したらElecrowに発注.基板制作費は$1.00. 送料が$15ほどで1週間弱で届きます.バグの入り込む手配線を必死でやるよりはお金で解決した方が良かろうということです.

実装

キーの裏からLEDでキーを光らせたいと思っているので,どうにかならないかといろいろ眺めましたが,結局スイッチを全部取り替えることにしました.

基板は片面パターンなので,はんだ吸い取りは簡単にできます.
しかし,古い基板なので銅箔パターン自体がもろくなっており,簡単に剥離してきます.
細心の注意を払いながら,はんだ除去とスイッチの撤去を行います.

外して…

外して…

外して…

じゃーん

キースイッチはFILCOダイレクトさんから,Matias Click Switch Gray(タクタイル)とWhite(クリック)を入手しました.テンキーだけ,Whiteでカチャカチャ言うように作ってみます.

さらに表面のジャンパもLEDストリップが通るところは邪魔になりますので撤去して裏面に再実装します.はんだ吸い取り線の消費速度が異常です.結局実装されていた部品の9割以上は外す羽目になりました.

ついでに,Caps Lock LEDを白色LEDに変更します.

続いて,光らせるためのLEDストリップを敷設します.LEDストリップは,よく見かける10mm幅のものだとキースイッチに干渉しそう(ギリギリでなんとなならないこともない)なので,今回は5mm幅のものを入手します.制御はWS2812のドライバでできるので,安心です.

1列目…

2列目…

全列…

点灯!

これで基板のほうでやることは終わりなので,キースイッチをはんだづけしていきます.
キースイッチをはんだづけしてしまうと,LEDストリップには手が出せなくなるので,接触不良のダブルチェックが必要になります.実際,後述するようにキーを全部付けた後で,一部のキーの不認識というバグを出しました.

仕上げ

キースイッチをはんだづけし終わった頃に,キーマトリクス変換基板が深圳から届きました.



基板上にはI2CとWS2812用のコネクタも引き出しています.
これを使ってRaspberry Pi Picoとキーボード基板の橋渡しをします.
基板にバグさえ無ければ,ワイヤを1本も繋ぐこと無く配線作業が完了です.

試験点灯,ヨシ!

透過OLEDもI2C経由で取り付けます.
ここらはV1と全く同じなので,簡単にできます.

後は組み上げるだけです.

完成

できました!


QMKのコードは以下に置いてあります.

RGB LightingとRGB Matrix

QMKには2種類のRGB LED制御があります.RGB Lightingはws2812のようなシリアルLEDを操作するための機能,RGB Matrixはマトリクス構造になっているLEDを制御するドライバを利用するためのものです.ただ,RGB Matrixでもws2812がサポートされています.

RGB Lightingは最初からシリアルLEDを使う前提なので,設定が楽です.通し番号だけで制御できます.また,レイヤーへの対応などがサポートされています.きれいなアニメーションが用意されています.

RGB Matrixは汎用のマトリクス制御なので,LEDとキーの対応を作ってやらねばなりません.これがまあまあ面倒で,キーマトリクス→LEDマトリクスの対応表と,LEDマトリクス→物理キーの場所の対応表の2種類を作らねばなりません.アニメーションも用意されていますが,こちらはタイピングに反応したアニメーションが利用できるのが強みです.

こんなExcelを書いて,コードに落とし込んでやります.


どちらの方法でも同じことを実現はできそうですが,自分で作ること考えると出来合いのものを使いたくなりますね.

キータイプに反応して光るのはこんな感じになります.

なお,ws2812を使う場合は,これら2つの方法を共存させることはできません.

バグとか途中の失敗とか

  • 当初,メイン基板のLEDの点灯方法を勘違いしていて,5VラインをGNDだと思っていました.そのまま最初の変換基板を作ってしまったので,大幅なやり直しが必要になり,結局再注文しました.
  • LEDストリップを敷設後,キースイッチを全てはんだづけした後で,キーマトリクステストをしたら,1行分が反応無しになりました.テスターで調べていると,LEDストリップを接続したときだけ5Vと当該行がつながってしまっていました.これは,ジャンパワイヤを付け替えたときにほんの少し表に出た部分がLEDストリップの5Vと接触していたためでした.

おわりに

古いキーボードを改造して現代によみがえらせる遊びは楽しいですよ.
私自身,20年前には持っていなかった電子工作のスキルを得て再改造を試みると,できることが増えていて嬉しかったです.

1990年代のメカニカルスイッチ採用のキーボードは打鍵感も良いので,よみがえらせる価値があります.

みなさんもジャンク屋でよさげなキーボードを見つけたら即確保!

では,Merry Christmas and a happy new year!

改造キーボードへの道(令和版NeXTキーボードV1)

改造キーボードへの道(令和版NeXTキーボードV1)

この記事はあくあたん工房2023アドベントカレンダーの15日目です.

omzn教授(キーボード改造学)です.
本日は古いキーボードの再生についての講義をしましょう.

平成版NeXTキーボード

まだ若かりし頃,2000年代に古いコンピュータを改造して最新のPCに換骨奪胎するという遊びに興じていました.
NeXT cubeとか,NeXT stationとか,Macintosh LC II(だったかな),Mac G4 cubeとかを改造して,ミニPCを作る遊びです.
苦労する割には,たいした性能もだせず,熱設計が破綻しているのですぐ暴走するというろくでもない機械を生み出していました.
犠牲になった古いコンピュータには悪いことをしたと思っております.

その流れで,NeXTキーボードの改造をしていました.NeXTのキーボードはMac系のADBぽい何かでしたが,当時は単なる使えないキーボードでした.
そこで若きomznは閃きます.


「そうだ,ここに転がってるジャンクのPS/2キーボードからコントローラを取り出して移植したら使えるようになるんじゃないかな」

早速キーボードの仕組みを調べると,キーボードにはキーマトリクスデコーダが乗っていること,そして,都合の良くないことにキーマトリクスはみんなバラバラということでした.

「せっかく分解したPS/2キーボードを何とか活かしたいな…」

ここでomznはまた閃きます.

「そっか,このPS/2キーボードに合わせてNeXT側を配線し直してやればいいんだ」

こうして魔改造されて生まれたのが平成版NeXTキーボードでした.



当時の標準規格だったPS/2にしておけば大丈夫だろうと思っていたのですが,時代はすぐにUSB全盛になり,NeXTキーボードはUSB変換をかまされたまま20年近く利用されました.ただ,元々PC/AT用のキーボードだったため,Macとの相性はあまり良くなく,キー入れ替えソフトが必須の状態で使うことになりました.また,ファンクションキーへのアクセス手段がないので,時々大変困ることになっていました.

そのため,20年の間,「いつかは今風のキーボードに作り直したい…」という思いを引きずっていたのでした.

令和版NeXTキーボード V1

2023年秋,ゆゆ君がキーボードを自作したのを見せてくれたので心に火が付き,自作キーボード化への完全移行を目指すようになります.

基本的にやることは以前と同じで,物理マトリクスに合わせたマトリクスデコーダを作ることになります.これが簡単にできる仕組みを提供してくれているのが,QMKです.

レイアウト

Fキーがないことを除けば,普通のキーボードに見えます.Backquoteがテンキーパッドにありますが,まあご愛敬.

物理スイッチ

NeXTキーボードではALPSクリーム軸を採用しています.軽やかなクリック感がクセになります.
(ALPS軸についてはこちらのサイトが非常に詳しいです.また,こちらの記事も大変興味深いものです.)

マトリクス解析

以下の図の上は,20年前に私がWebページに載せていたPS/2キーボードのマトリクスです.
非常に贅沢にマトリクスを構成していたので,このままだとマトリクスのピンが25本も必要です.
カラムのORをとってもキーコードがかち合わない場所は単純に統合可能なので,3つのカラムを減らすことができました.(図の下)

これでもまだ22本のGPIOが必要です.
一般的なPro MicroですとGPIOが18本しか使えないので,今回はRaspberry Pi Picoを使用します.Raspberry Pi Picoは26本のGPIOを利用できますので,今回の用途に使ってもまだ4本のGPIOが余ります.そこをLEDの駆動やI2Cに割り当てることで,さらに機能を増やすことができます.

ハードウェア作成

Raspberry Pi Picoを載せる

これまでPS/2コントローラに接続されていたすだれケーブルを切断して,Pi PicoのGPIOに繋いでいきます.
今回は初回の試作だったので,そのままはんだづけしましたが,ソケットで取り外し可能にするのがベターですね.


USBの端子を設置する

3Dプリントで土台を作成して,USBの口を作ります.写真では試作時なので赤で作っていますが,最終形は黒にしています.


最終的にはこんな感じになります.改造前に比べると余計な基板が無くなってすっきりしました.



QMKの設定

キーボードの設定

  • このキーボードのように,ダイオードが実装されていないマトリクスではMATRIX_HAS_GHOSTをdefineします.
  • CapsLockのLEDをGP0で制御します.


キーマップ

先ほどのマトリクスに忠実にキーマップを書いて行きます.

  • NeXTのPowerキーをFnキーとして利用
  • Volume up, Volume downをPage up, Page down (Fn押下で本来の機能)
  • Brightness up, Brightness downをHome, End (Fn押下で本来の機能)
  • 「Fn + 数字」で 「F1~F10」
  • 左Commandキー単独で「英数」,右Commandキー単独で「かな」

等の機能を追加しています.
Esc周りはやや特殊で,単押しでEsc, Shift + Escで「〜」, Shift + Alt + Escで「`」が出るようになっています.


利用するGPIOとマトリクスの対応はinfo.jsonに書きます.


KLE でグラフィカルなキー配置を作成する

VIAやRemapでキー配置を変更する時のGUI用に,KLEでレイアウトを作ります.



ここでハマりましたが,肝はキートップのLegendにマトリクスの座標を書いてやることです.
PS/2コントローラのマトリクスが物理配列と全く合っていないので,この図では座標がバラバラになっています.

VIAの設定

via.jsonを作ります.これは,RemapなどのツールでキーマップをGUI的に変更するときに使います.
中身は,KLEで作成したキー配置のデータと,キーボードの名前,Vender ID, Product ID,matrixの行列を記したものになります.


できあがり!

ここまでで,一旦キーボードはできあがりです.

ファームウェアを焼いてやれば,キーボードが使えるようになるはずです.

qmk flash -kb next_keyboard_v1 -km via


ソースコードは GitHub に置いています.

(おまけ)OLEDパネルを追加する

キーボード単体だとちょっと寂しかったので,透過OLEDを使ってHUD風ディスプレイを付けて見ました.蛇足感が酷いです.



実はここまでの話は前座に過ぎなかったのです.
長くなるので本編は次の記事で..

チューリング完全あくあたん

チューリング完全あくあたん

この記事はあくあたん工房 Advent Calendar 2021の15日目です.

チューリング完全とは?

ある計算システムがチューリング完全であるとは,万能チューリングマシンと同じ計算能力を持つことを指します.万能チューリングマシンはチューリングマシンを模倣できるチューリングマシンであり,現在のどんな計算機が解ける問題でも解くことができると考えられています.

あるシステムがチューリング完全かを判定するのは難しい問題になるため,チューリング完全であることがすでに判明しているシステムの模倣ができる場合,そのシステムもチューリング完全であると言えます.

水槽カメラ昇降パターン

あくあたん義体はレールの上のマーカー(位置)を読み取って水平方向に進むことができます.また,レールの位置上でカメラの垂直方向を昇降させることができます.レールの位置はあくあたんbotからの指令により移動させることが多いですが,カメラの昇降については指令を飛ばすことが少なくなっています.そこで,カメラの昇降については,ランダムに近いけれども特定のルールを利用した動作をさせたいと思います.

レールの位置には1ビットの情報が付与できるものとします.あくあたんは位置上でカメラ垂直方向を上下させることができますので,ここではある位置において,カメラを1段上昇させるなら1,1段下降させるなら0とします.この情報をカメラ昇降パターンと呼びます.あくあたんはこの情報を使って,その位置を踏んだときにカメラ垂直位置を変更します.

各位置での昇降動作を初期状態としてあくあたんに教えることができます.

水槽カメラ昇降パターン更新規則

さて,このままではいつも同じ位置で上下するだけで面白くありません.
そこで,あくあたんが水槽の一番左か右(位置0か16)に到達した時点で,カメラを昇降させるパターンを変更したいと思います.

このパターンの変更は,各位置において,左右両隣の位置の状態に応じて変更するようにします.そうすると,次のような遷移表を書くことができます.3ビットの真ん中が当該位置であり,左右と自分自身の値に応じて,自分自身の変更後の値が決まります.

変更前(左,自身,右) 変更後
0, 0, 0 x, 1, x
0, 0, 1 x, 0, x
0, 1, 0 x, 1, x
0, 1, 1 x, 0, x
1, 0, 0 x, 1, x
1, 0, 1 x, 0, x
1, 1, 0 x, 1, x
1, 1, 1 x, 0, x

これを「カメラ昇降パターン更新規則(更新規則)」と呼びます.

例えば,上記更新規則では,ある時点のカメラ昇降パターンが「0001111000111100」であった場合,次のように更新されます.なお,実装の都合上,右端と左端はつながっており,ループしているものとします.

0 0 0 1 1 1 1 0 0 0 1 1 1 1 0 0
↓
1 1 0 0 0 0 1 1 1 0 0 0 0 1 1 1

更新規則は変更後のビットが取る値の並び8つで識別できます.上記の規則では01010101となります.すなわち 2^8 = 256個の更新規則のいずれかを採用することになります.この更新規則はビットの並びを2進数として読んだ値を10進表現した数字で表されます.例えば,上の表の更新規則は 01010101 ですから64+16+4+1 = 85で,更新規則85と呼びます.あくあたんにはこの更新規則番号を伝えれば,その更新規則を採用することになります.

あくあたんが一番左か一番右の位置へ移動すると,このカメラ昇降パターン更新規則が適用され,各位置でのカメラ昇降パターンが更新されることになります.

まとめると,あくあたん義体には「カメラ昇降パターンの初期状態」「カメラ昇降パターンの更新規則」を設定することができ,自律制御時のカメラ位置はこれに従って昇降させることになります.またあくあたん義体のレール上の移動に伴い,カメラ昇降パターンは次世代へと更新されることになります.

セル・オートマトン

ここまでで説明してきたカメラ昇降パターンを変化させる動作は,実は1次元セル・オートマトンと呼ばれるものと同じです.1次元セル・オートマトンは無限の1次元セルのビットパターンを更新規則によって変更し,世代を重ねていきます.2次元のセル・オートマトンの例としてはライフゲームなどがよく知られていますね.

1次元セル・オートマトンの256種類の規則は,どのような状態になるかで4つのクラスに分類されます.

  • クラス1: 最終的に全体が均質な状態に遷移するもの(規則248)など)
  • クラス2: 最終的に局所的なパターンを繰り返したり,安定状態になるもの(規則123)など)
  • クラス3: カオス的なパターンを示すもの(規則30)など)
  • クラス4: カオス的なパターンと規則的パターンが共存する(規則110)など)

クラス3に属する規則90は,1ビットのみが1の初期状態からフラクタル図形を生成することが知られています.クラス4は複雑な計算状態を内包することを表しています.そして,クラス4に分類される「規則110」はチューリング完全であることが証明されています.

規則110の遷移

変更前(左,自身,右) 変更後
0, 0, 0 x, 0, x
0, 0, 1 x, 1, x
0, 1, 0 x, 1, x
0, 1, 1 x, 1, x
1, 0, 0 x, 0, x
1, 0, 1 x, 1, x
1, 1, 0 x, 1, x
1, 1, 1 x, 0, x

チューリング完全あくあたん

つまり,あくあたんが「更新規則110」を採用していれば,あくあたんはチューリング完全であり,現代の計算機に匹敵する計算能力を持つことが証明されるのです.


あくあたんはまた1つ新しい高みに立ったのです.

あくあたんを崇めよ!
Make Aquatan Great Again!

あくあたん名(迷)言集

あくあたん名(迷)言集

この記事はあくあたん工房 Advent Calendar 2021 6日目の記事です。

ゆゆ君が2日目にあくあたんのリプについて触れていたので,そういえば昔のあくあたんの名言をスクショ保管してたなあと思ったので供養しておきます.

いろいろな人があくあたんと遊んでくれましたね…(遠い目)
スクショはいろいろな時代のが混ざっていますので,ツリーの表示方法も一様ではありません.
発言者は一様に隠させて頂きました.黒歴史だもんね.

煽り

のっけから厳しいの行きます.
あくあたんの伝統芸です.

誰であっても煽ります.

これも煽りだね.

もひとつ.

説教

おなかすいた

かわいい系.

おにぎりには一家言あるほう.

ありがとう

エロい?

変な学習しちゃった?

別の人とも

素直なあくあたん

癒やし

なんの癒やしだ.

なれ合い.

大きなカブ.

人生の意味

あくあたんだって悩みます.

帰れ

カエレ!

かわいい

暴言

その後

さらにその後.まさかのタクシオ氏.

さらにさらにその後.

一般の方と

おそらく相手はこっちがbotだと分かっていません.

思うところ

オマエモナー

南無南無

弄ぶあくあたん

まどマギ

時代を感じる…しかし,この話題への追従性.

風邪

正論だ…

しりとり

基本的にあくあたんはしりとりする気が薄い.

2回目はダメよ.

ごめんなさい

はかない単位よー

LINEスタンプにもなった名言の瞬間.

つらい

ざるざる事件

某OG氏の最高傑作かもしれない,ざるざる事件.

未だにあくあたんの語彙にザルって出ることがあったり.

まとめ

あくあたん昔のほうがキレがあった気がします.
あくあたんがbotだと知らない一般人さんとかとも会話が続くのやばいですよね.
これはチューリングテストに合格しちゃいそうだな…
チューリングと言えば… (15日目に続く)

Raspberry PiでビルドしたPygameでハマった話

あくあたん在室モニターの話の続き.

なんとかSSLのhandshakeでエラーを出さなくなったので,次に進みます。
Python 3.7.3をソースからインストールしたので,pygameをインストールしないといけないです。

$ sudo apt install libsdl-dev libsdl-image1.2-dev libsdl-mixer1.2-dev libsdl-ttf2.0-dev libsmpeg-dev libportmidi-dev libavformat-dev libswscale-dev
(省略)
$ sudo pip3 install pygame
(省略)
Successfully installed pygame-1.9.6

インストールは正常に終了.
あくあたん在室モニターをおもむろに起動します.

Fatal Python error: (pygame parachute) Segmentation Fault

Thread 0x71523470 (most recent call first):
  File "monitor.py", line 444 in run
  File "/usr/local/lib/python3.6/threading.py", line 916 in _bootstrap_inner
  File "/usr/local/lib/python3.6/threading.py", line 884 in _bootstrap

Current thread 0x76ccd000 (most recent call first):
  File "monitor.py", line 1151 in draw_character
  File "monitor.py", line 1391 in draw
  File "monitor.py", line 192 in main
  File "monitor.py", line 1503 in <module>
Aborted

なんで落ちるの….しかもSegmentation Faultとか、およそPythonのエラーとは思えない。
1151行目はこれ.画面に文字を1文字表示するルーチン.
なお,あくあたん在室モニターでは画面上にメッセージがでるときに,昔のRPG風に1文字ずつ表示しています。ここはその部分。

surf,rect = self.myfont.render(ch,self.color)

いやいや,他のところでちゃんと表示してるやん.なんでこの場所だけ落ちるの.
この文を含むメソッド(draw_character)の呼び出し元をチェックします。

self.msg_engine.draw_character(self.surface, (dx,dy), ch)

至って普通で特に問題が見受けられません。しかも,どうも途中までは表示してるぽいのです。ならば,何を表示したのか見てみましょう。

print("draw {},{} {} ({})".format(dx,dy,str(ch),hex(ord(ch))))
self.msg_engine.draw_character(self.surface, (dx,dy), ch)

これで実行したら、どこの文字で落ちたか分かります。

draw 6,6 あ (0x3042)
draw 22,6 く (0x304f)
draw 38,6 あ (0x3042)
draw 54,6 た (0x305f)
draw 70,6 ん (0x3093)
draw 86,6 が (0x304c)
draw 102,6 8 (0x38)
draw 118,6 - (0x2d)
draw 134,6 3 (0x33)
draw 150,6 2 (0x32)
draw 166,6 0 (0x30)
draw 182,6 へ (0x3078)
draw 198,6   (0x3000)
Fatal Python error: (pygame parachute) Segmentation Fault

落ちた!まさかの全角スペース.なぜなんだ….その後,半角スペースでもダメなことが判明しました。空白文字全般がダメなのです。ただし、可視文字と一緒にスペースを混ぜた場合はきちんと動きます。あくまで、文字列全体で何も描画するものが無いものをrenderしようとすると落ちるのです。
症状を分かりやすくするために簡単に対話式でチェックしてみます。

>>> import pygame
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
>>> import pygame.freetype
>>> pygame.freetype.init()
>>> from pygame.locals import *
>>> myfont = pygame.freetype.Font("./font/rounded-mgenplus-1cp-bold.ttf",18)
>>> myfont.render("a",Color(255,255,255,255))
(<Surface(9x11x32 SW)>, <rect(0, 10, 9, 11)>)
>>> myfont.render(" ",Color(255,255,255,255))
Fatal Python error: (pygame parachute) Segmentation Fault

Current thread 0x76d49000 (most recent call first):
  File "<stdin>", line 1 in <module>
Aborted

これで再現しました。半角でも全角でも画面には表示されないスペースのみで構成される文字列をrenderしようとしたら,Segmentation Faultで落ちます。なお,これはRaspberry Pi上のPython 3.7.3 + pygame 1.9.6で発現したのですが,macOS上のPython 3.7.3 + pygame1.9.6では発現しません.どういう機種依存やねん、と悪態をつきつつ対策を考えます。

これ以上,なぜかを考えていても埒が明かないので,work aroundで対処します。chが空白文字のみだったらrenderをスキップしちゃえば良いのです。(dx,dyは事前に計算して位置決めしているので,これでも画面表示はくずれないようになっています。)

if ch != " " and ch != " ":
    self.msg_engine.draw_character(self.surface, (dx,dy), ch)

これで表示可能になりました。
めでたしめでたし。

Raspberry PiのPython3でSSLv3 handshake failureを解決した話

あくあたん在室モニターは,Raspberry Piで動いています.
先日らぼのWebサーバがアップデートされたときから,うまく動作しなくなりました.

モニターでは,らぼのWebサーバで公開しているAPIを叩いて在室状況を取得します.
APIはhttpだけでなく,httpsも対応していました.

モニターのログを見ると,データ取得ルーチンが止まっています.

ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failure

おっ,SSLのエラーか.
新しいサーバではTLS1.0は捨てられました.そこに異議を唱えるつもりはないので,こちらで対応すべきですね.

普通に考えて,何かSSL周りが古いとかそういう状況が考えられます.
これまで,http.clientを使ってアクセスしていたのですが,requestsのほうが良い感じと聞いたので,書き換えてみました.実際,requestsは良い感じに書けます.

テストは次の方法でできます.

$ /usr/bin/python3
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.0j  20 Nov 2018
>>> requests.get('https://se.is.kit.ac.jp/')
Traceback (most recent call last):
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 600, in urlopen
    chunked=chunked)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 343, in _make_request
    self._validate_conn(conn)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 839, in _validate_conn
    conn.connect()
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connection.py", line
 344, in connect
    ssl_context=context)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/util/ssl_.py", line
347, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
  File "/usr/lib/python3.5/ssl.py", line 385, in wrap_socket
    _context=self)
  File "/usr/lib/python3.5/ssl.py", line 760, in __init__
    self.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 996, in do_handshake
    self._sslobj.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 641, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failur$ (_ssl.c:720)

しかし,エラーは消えませんでした.

pipで関連するモジュールを最新にしてみました.

しかし,エラーは消えませんでした.

試しに,Macで同じコードを実行しました.

~> python3
Python 3.7.3 (default, Mar 27 2019, 09:23:15)
[Clang 10.0.1 (clang-1001.0.46.3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.0.2r  26 Feb 2019
>>> requests.get('https://se.is.kit.ac.jp')
<Response [200]>
>>>

エラーは出ません.

たまたまPython3.4が動いているraspberry piが生きていたので,そこから同じコードを実行しました.

Python 3.4.2 (default, Oct 19 2014, 13:31:11)
[GCC 4.9.1] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print (ssl.OPENSSL_VERSION)
OpenSSL 1.0.1t  3 May 2016
>>> requests.get('https://se.is.kit.ac.jp')
<Response [200]>
>>>

エラーは出ません.

古いpythonと古いsslでも動くのに,ちょうど今のpython3.5とopenssl 1.1.0jの組み合わせでは動かないの??
問題が切り分けられずに頭が痛くなってきます.こうなったら初期化です.Raspberry Piの公式から最新のイメージをダウンロードして,SDを初期化します.

まっさらなraspbianの上でなら…

$ /usr/bin/python3
Python 3.5.3 (default, Sep 27 2018, 17:25:39)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.0j  20 Nov 2018
>>> requests.get('https://se.is.kit.ac.jp/')
Traceback (most recent call last):
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 600, in urlopen
    chunked=chunked)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 343, in _make_request
    self._validate_conn(conn)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connectionpool.py",
line 839, in _validate_conn
    conn.connect()
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/connection.py", line
 344, in connect
    ssl_context=context)
  File "/home/pi/.local/lib/python3.5/site-packages/urllib3/util/ssl_.py", line
347, in ssl_wrap_socket
    return context.wrap_socket(sock, server_hostname=server_hostname)
  File "/usr/lib/python3.5/ssl.py", line 385, in wrap_socket
    _context=self)
  File "/usr/lib/python3.5/ssl.py", line 760, in __init__
    self.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 996, in do_handshake
    self._sslobj.do_handshake()
  File "/usr/lib/python3.5/ssl.py", line 641, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: SSLV3_ALERT_HANDSHAKE_FAILURE] sslv3 alert handshake failur$ (_ssl.c:720)

こらあかん.
ついに,Raspberry Pi上でPython 3.7をbuildし直すことにしました.

$ sudo apt update
$ sudo apt install -y libffi-dev libbz2-dev liblzma-dev libsqlite3-dev libncurses5-dev libgdbm-dev zlib1g-dev libreadline-dev libssl-dev tk-dev build-essential libncursesw5-dev libc6-dev openssl
$ cd Python-3.7.3
$ ./configure --enable-optimizations
$ make
$ sudo make install

インストールが終わったら,requestsモジュールをインストールします.

$ sudo /usr/local/bin/pip3 install requests

テストします.(ほんま許して)

$ /usr/local/bin/python3
Python 3.7.3 (default, Apr 27 2019, 21:00:58)
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import requests
>>> import ssl
>>> print(ssl.OPENSSL_VERSION)
OpenSSL 1.1.0j  20 Nov 2018
>>> requests.get('https://se.is.kit.ac.jp/')
<Response [200]>
>>>

動いた…

なお,この後.新しいPython3.7.3ではもう1つのはまりポイント,pygameにて更に苦労することになります.
それはまた,別の話

Net::Twitter::Liteでのupdate_media

難しかった.

Net::Twitterで,update_with_media(media[])を長らく使っていたけど,これはもうdeprecatedなので,今回のstream廃止と同時に使わないようにしようと思った.

upload(media) → update() で行けるはずなのだけど,Net::Twitterのドキュメントみても”media”が何なのかがよく分からない.

Net::Twitter::Liteだと,これは upload_media(media[])となっている.これなら今までと同じなので,使える.

$tw->{status} = $s;
$tw->{media} = [undef,$fname, Content_Type => $ftype, Content => $image];
$nt->update_with_media($tw);

みたいなのを,

$tw->{status} = $s;
$tw->{media} = [undef,$fname, Content_Type => $ftype, Content => $image];
$img = $nt->upload_media($tw);
$tw->{media_ids} = $img->{media_id};
$nt->update_with_media($tw);

としたら,update_with_mediaがHTTP::Messageのエラーを吐く.

$tw->{status} = $s;
$tw->{media} = [undef,$fname, Content_Type => $ftype, Content => $image];
$img = $nt->upload_media($tw);
$tw->{media_ids} = $img->{media_id};
$tw->{media} = undef;
$nt->update_with_media($tw);

で動いた.横着せずに$twと生のイメージを分離しておくべきだった,ってこと.

あくあたん水槽監視・制御ユニット

あくあたん水槽監視・制御ユニット

IMG_7726こちらは2つめのRaspberry Piです.水槽や環境の状態をモニタして,照明・ファンの制御を行う部分です.センサーには温度センサーDS18B20が4基,大気圧センサMPL115A2が1基,湿度センサDHT11が1基搭載されています.また,MOSFETで3基の冷却ファンを制御し,ACリレーで3基のAC電源を制御できます.

IMG_7739裏側から,Raspberry Piを外した状態はこんな感じ.ad-hocに回路を追加してしまったので,きれいな基板ではありません.もっと小さい面積に詰められたはずなんですが…

あくあたん弐号機 (Raspberry Pi版)

あくあたん弐号機 (Raspberry Pi版)

IMG_7785あくあたん弐号機義体部はRaspberry Piで制御する移動型カメラ台+自動給餌器です.左側がUSBカメラ2基(広角+赤外線)で,この部分は上下動も可能です.

左右への移動はタイヤで行います.上下左右の動力にはLEGOの旧型モーターを2基使い,DRV8830によって制御しています.モーターの定格を遙かに下回る電圧しか与えられませんが,思いの外さくさく動きます.
Keep reading →