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 →

PMAN 3.2.3

PMAN 3.2.3

PMAN3.2.3をリリースしました.

  • MathJax (http://www.mathjax.org/)に対応させました.その結果,不要となったmimetex, imgtexのサポートを打ち切り,設定画面からも消しました.
  • 画面遷移のバグを1件修正しています.