dothikoのカクカクワールド2D REBOOT

プログラミングとかLinux関連(特にOSSのグラフィックツール関連)とかレトロゲームとか色々。

Radeon RX550導入 & ファン制御で黙らせてみた

玄人志向Radeon RX550搭載「RD-RX550-E2GB/OC」が7500円程度だったので、まぁRX550レベルの適正価格と判断して購入しました 今年の最初に3980円ぐらいで売ってたらしいですけどね…全然知らんかったわ(涙

Xubuntu 18.04.3マシンに載せたら、最初は何故かopenglのrendererがllvmpipeにフォールバックして全然amdgpuを使ってくれなかったので/usr/share/X11/xorg.conf.dに極普通のconfファイルを書いたら動いてくれました

最初、llvmpipeであることに気づかず、なんかちょっともっさいなぁ…RX550なんてこんなもんかと思ってしまったのは内緒。逆にいうとllvmpipeやっぱりすげぇ… *1

さて問題は冷却ファンであります。 これがねぇ…linuxの場合だからか知りませんが、デフォで全開なんですよ。 以前買ったRX460は何もせずとも極めて静音なので、やっぱ廉価版と差があるのだなぁと

いよいよとなったら付属のファンを撤去、12cmファンを塩ビアングルでの自作マウンタで設置し、atmega328かlpc1124でpwm制御して温度センサーと連動で…と思いましたが、正直面倒なので取り敢えずlinuxからファンコントロールできんかな?と思ってやってみたら、簡単に出来ました(^^) *2

当然ながら、全部sudo権限が必要ですが まず、pwm制御をenableします

echo 1 > /sys/class/drm/card0/device/hwmon/hwmon2/pwm1_enable

次にpwmレベル数値を設定します

echo 66 >  /sys/class/drm/card0/device/hwmon/hwmon2/pwm1

都会よりはずっとマシな温度のド田舎のうちでは、66ぐらいで1600rpmぐらいとなり、静かさ・温度ともにちょうどいいぐらいかな…という感じです。まぁ高負荷な処理もほぼしませんので取り敢えず。

daemonにして十分おきぐらいでアツアツになってたら全開とかやっても良いのですが、それこそもう自作マイコンでやったほうが良いんじゃないかなって気はします

なお、サスペンドから復帰した際にはファンが全開に戻りますが systemdだと/etc/pm/sleep.dではなくsystemdのスリープフックでの設定が必要です

Archwikiを参考に次のようなものを書いてmy_on_resume.serviceとして、使っています

[Unit]
Description=User resume actions
After=suspend.target

[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/my_on_resume.sh

[Install]
WantedBy=suspend.target

RX460+PentiumGの環境では、何故かgvimでのみ、ふとした瞬間に唐突にスローダウンする(てか、amdgpuだからか元々XorgのCPU占有率が高めで、しかも2コアしか無い)という意味不明の悲しい現象が起きましたが…RX460+i5-3470では平気だったし、RX550+i5-3470でもRX460+Ryzenでも平気なのですよな

まぁRyzenで平気なのは当然としても。すっかり我が家も多コア時代となってしまいました。つぅーか気づけばすっかりAMD派に…(^^;

ではでは

*1:なお、購入直後の動作チェックのためにi5-3450S搭載のサブマシンで試したときは、これは何の設定も使わずに挿しただけでamdgpuになったのですよね。実に不思議

*2:なお、llvmpipe状態でも、以下のファンコントロールは問題なく動作します。このせいでllvmpipeになってることに気づくのが遅れたともいえる

mypaint 2.0.0-alphaへの追従で四苦八苦

Mypaintの自作ブランチにupstreamのmergeを長期間怠っていたら、大変な作業量が待っていました…備忘録的に

目に付いた主な変更点

  • ビルドシステムがSConsからsetup.pyへ移行している
    • scons debug=trueに相当するのは、python setup.py build --debug
    • この結果、build/以下にlib.linux-x86_64-3.6 等のディレクトリが出来て、何かカッコいい。
  • scons cleanしておく必要がある(重要)
  • mypaintbrush 2.0が必要らしいので、gitから持ってきてmake installした。これが多分正しい。
    • エラーがでたので手当り次第に処置したけど、実はよくわかっていない…
  • python2.7でビルドするとlib/gettext.pyのfrom gettext import gettextでImportErrorが起きるように見える。
    • git cloneしたバニラなmypaint 2.0でも起きた
      • しかしpython3で一旦ビルド成功後は、python2.7でビルドし動かしても動いたりして二度と再現されない。要追跡。
    • いずれにせよ、そろそろpython3に本格的に乗り換えないとなぁ…という感じ。他の自作スクリプトも随時書き換えて行かねば。
  • update-alternativesで3をデフォにしてもいいけど、python-moinmoinが3非対応だったりして別の部分でつらい

_mypaintlib.soが見つからないエラーが出る

前述の通り、cleanしておかないとSConsで作られた古いmypaintlib.pyが lib/に残りこのエラーが起きる。削除すること。また、当然ながら以前のlib/_mypaintlib.so自体も削除しておくのが良い。

ビルドスクリプトの追加修正

独自のcppモジュールを追加するには、lib/mypaintlib.iのみならず、 setup.pyの576行目近辺

    mypaintlib = Extension(
        'lib._mypaintlib',
        [
            'lib/mypaintlib.i',
            'lib/fill.cpp',
            'lib/gdkpixbuf2numpy.cpp',
            'lib/pixops.cpp',
            'lib/fastpng.cpp',
            'lib/brushsettings.cpp',
            'lib/pyramidfill.cpp', # XXX for pyramid-fill
            'lib/opencv_util.cpp', # XXX for adjust tool
        ],

のように改変。(以前、SConstructにやっていたのと同じ)

Python3対応

  • SWIG(cpp)においてPyInt_AsLongはpython3のAPIでは存在しないので、PyLong_AsLongに変更する。(lib/pyramidfill.cpp)
  • from _future_ import division, print_functionはスクリプト先頭に置かねばならない。
    • git mergeでの自作部分とのコンフリクト検出時に不用意に済ませるとpython3ではこれがエラーとなる

importの厳密化

例えばgui/drawwindow.py でimport sizechangepopupとしてもエラーになるようになっている。 これは、

from . import sizechangepopup 
# もしくは
import gui.sizechangepopup as sizechangepopup # 若干美しくない気がする

と書かなければならない。これが物凄い多いので、普段からディレクトリを明示する等して徹底しておくべきだったorz

with_wait_cursor

このデコレータはgui.drawwindowから、gui.widgetsに移管されている。

python2のpropertyとタプルの分解で苦労した話

検索ワードが思いつかず苦労しました。アンパックというのですね。

foo, bar = (100, 200)

みたいなやつ。

例えばこんなクラスがあったとします

class _Node:
       
    def __init__(self, x=0.0, y=0.0):
        self._v = np.array( (x, y, 1) )

    @property
    def x(self):
        return self._v[0]

    @x.setter
    def x(self, v):
        self._v[0] = v

    @property
    def y(self):
        return self._v[1]

    @y.setter
    def y(self, v):
        self._v[1] = v

numpy.arrayを隠蔽して、簡単に行列とかかけられつつ 普段は感覚的に.x, .yとかで位置を変更できるという感じの。

こいつにこれをやっていました

n = Node()
n.x, n.y = (100, 101)

これをやるとダメ。

python2ではインスタンスnのプロパティにアンパックではなく、同名のxやyという属性が作成されてしまい、内部値は二重に存在している状態で、二度とプロパティでアクセスできなくなるのでございました。

なおpython3では普通に動くのでありました

ってこの話、遠い昔に見たような気がしなくもなく…(^^;

また一つpython3に乗り換える理由が増えた気がします

python-OpenCV2のfillPolyで苦労した話

python-opencv2にて、普通に [[x1, y1], [x2, y2], [x3, y3] ....] 的な配列を突っ込んだら、np.int32だろうが何だろうが以下のエラーが出続けた話。

OpenCV Error: Assertion failed (p.checkVector(2, CV_32S) >= 0) in fillPoly, file /build/opencv-L2vuMj/opencv-3.2.0+dfsg/modules/imgproc/src/drawing.cpp, line 2373

ググってたどり着いたのはやはりstackoverflow、本当に有り難い…のだけど、既に閉じてしまって今検索しても何故か見つからない。悲しい (`;ω;´)

そんなわけで書いておきますが要するにreshapeして

[ [[x1, y1] ], [ [x2, y2]], [[x3, y3]] ... ] 

のような超変な形に変えて、しかもfillPolyのptsに与えるやつをlistでくくる*1。これで動きました。

以下、動いたコードをコピペ

        pix = 255
        buf = np.zeros(bufshape, 'uint8')
        points = np.array(points).reshape((-1,1,2)).astype(np.int32)
        cv2.fillPoly(buf, pts=[points], color=pix)

このくだらねー処理に、何時間費やしたことか…

*1:これをしないと、Assertionは出なくなるがポリゴンフィルはされずに点だけ描画される

絶対に許せないmodemmanager@xubuntu 18.04.1

「絶対に許せない!」と言えばプリキュアの定番セリフですが(そうか?)

絶対に許せない現象が発生してしまいました

というのもワタクシ、実はお絵かきの左手デバイスに赤外線テレビリモコンが使えるのでは?という思いつきを胸に実際に使ってみていまして、その受信装置はarduino(の複製) + MCP2221でUSB変換してPCに接続→PC側でuinputを使うソフトがキーコードを発信というようになっているのですね。

そしてそれは、systemdでシステム起動時に自動起動するようになっております。

この状況下でubuntuっていうかxubuntuを18.04.1にアップデートして以来、

  • システム起動時にリモコンソフトが起動するが、機能しない
  • どーもオチているようである(systemdからは落ちたと認識されていないらしい)
  • systemctl restart ir_remocon.serviceすると動作するが、サスペンド→復帰するとまた動かない。
  • ソフト単体で試したら、たしかに復帰すると変換ソフトが超長いリモコンコードを受信し、アサーションを発して落ちている。
  • そこでリモコンコード異常が発生したらポート閉じて再度開く…と言う仕組みにしたら、「device reports readiness to read but returned no data」と言われるようになり、やはりまともに受信できない
  • そこでソフトを一旦落とし、再起動するとこれは動く。
  • acpidで/etc/pm/にスクリプトを置いてresumeでsystemctl restart ir_remocon.serviceするようにしてみたが、駄目

毎回愚直にターミナルからsystemctl restartしてたんですが、いい加減なんとかしなければ…と追ってみたのですが、上記のように自分には想定外の不可思議な現象が起きておりました。

んで/var/log/syslog していてようやく気づきました。

Dec  5 01:08:16 mydesktop ModemManager[649]: <info>  Creating modem with plugin 'Generic' and '1' ports
Dec  5 01:08:16 mydesktop ModemManager[649]: <warn>  Could not grab port (tty/ttyACM0): 'Cannot add port 'tty/ttyACM0', unhandled serial type'

誰だコイツ > ModemManager

いつの間に入ったのか今までは悪さをしなかったのかわかりませんが、どうもコイツが全てのシリアルポートを塞いでいるっぽいですな…そしてシステム起動・復帰時に先に塞いでいるので我がir_remocon.serviceが失敗すると

そう考えると device reports readiness〜エラーとも辻褄が合う。

そこでバッサリapt-get remove modemmanagerしました。

どうもサスペンド復帰しても大丈夫になりました、「今のところは」 あとプラセボ効果かもですが…デスクトップ描画も16.04のときのサックリサクサクに戻った気がするのですよ。気の所為かもですがね。

まぁ、本当にmodemmanagerが悪かったのかは実際の所不明w

systemdでstopした時にpythonのfinallyが呼ばれない問題について

systemdでsystemctl stop hoge.serviceすると、基本的にSIGTERM -> SIGKILLが送られてdaemonプロセスが終了します。

しかしながら、pythonのfinally節はSIGTERMで終了した場合は呼びだされません。 そのため、pythondaemonを作った時、finallyで必ず呼ばれることを期待して記述された終了処理は呼ばれずに終わるという…

これでハメられた…まぁ具体的にはRaspberry PiのGPIOをお手軽にRPi.GPIOでadd_event_detectしていたのですが、サービス化していたので何も考えずにstopしたら、テストでRuntimeErrorを吐くようになってしまいました。

finallyでcleanup()を呼んでたので、SIGTERMで終了時は呼ばれなかった結果、プロセス終了後も握り続けているのか?こうなった

dothiko@raspberrypi:~/python/usbcamcap $ ./usbcamcap.py -p 12345 -f camsconf.json 
- GPIO pin 4 as Button, 3 as LED.
./usbcamcap.py:296: RuntimeWarning: This channel is already in use, continuing anyway.  Use GPIO.setwarnings(False) to disable warnings.
  gp.setup(LED, gp.OUT)
Traceback (most recent call last):
  File "./usbcamcap.py", line 439, in <module>
    camera = Camera(args.cameras, confs)
  File "./usbcamcap.py", line 204, in __init__
    self.init_gpio()
  File "./usbcamcap.py", line 298, in init_gpio
    gp.add_event_detect(BTN, gp.BOTH, callback=self._gpio_callback)  
RuntimeError: Failed to add edge detection

ちな、このusbcamcap.pyというのは自作のUSBカメラキャプチャスクリプトで、カメラを握り続けてhttpでリクエストした時に画像を送るものです。んでもってこの中で同時にGPIOでボタンを見ていて、ボタンが押されるとシャットダウンするようにしています。 普段はラズパイを監視カメラとして使い、整備・移動する時はボタンでシャットダウンして電源断という目論見です。

最近のネットカメラはゴミばかりあまり気に入ったものがなく、スマホでアプリというものばかり。それもお手軽で良いのでしょうが外部のサーバや専用アプリを必要としたりして、しかもそれがOSのバージョンに相性があったりするなど、使い勝手が実に良くない。 もはや自分的には監視カメラはラズパイで自作する他無いという感じです。

話がそれてしまいました。

ともかく、どうすれば直るのか悩みましたがあっさり諦めてリブートしました(^^;

んで調べた結果、対策は2つあって

  • serviceファイルのservice節でKillSignal=SIGINTを記述して、syetemdのシグナルを変える。
  • python側でSIGTERMを捕捉してsys.exit()を呼び出す

後者の方はsignalモジュールでSIGTERMにハンドラを付けて、その中からsys.exit()をコールするわけです。これでsys.exit()がfinallyを呼んでくれるので問題がなくなります。

以下参考用のテストプログラム

import time
import signal
import sys

def termed(signum, frame):
    print("SIGTERM!")
    sys.exit(0)

def main():
    signal.signal(signal.SIGTERM, termed)
    try:
        while True:
            print('loop')
            time.sleep(3)
    finally:
        print("CALL FINALLY")

if __name__ == '__main__':
    main()

まぁどっちでもいいんですけど、後でうっかりパターンを考えるとSIGTERMを捕捉したほうがいいかな?

ubuntu 18.04.1でapt upgradeに失敗

正確には何をしたらおかしくなったのか分かりませんが… また、実際焦っていたのでエラーメッセージなども保存していない。スミマセン。

  • まず普通のデスクトップアプリ「ソフトウェアの更新」で更新しようとした
  • しかし、幾つかファイルがダウンロードできずに更新に失敗
  • そこでサーバを日本からメインに変更(これが悪かったか?)
  • やはり失敗
  • ターミナルからapt update & apt upgrade
  • libgs9-commonのバージョンが合わないのでインストールが出来ないという内容のメッセージが出る。
  • このバージョン番号が末尾が18.04.2なのですが、何かが間違ってないっすかねぇ?
  • apt --fix-broken installしろと出たのでやってみる
  • 無駄無駄無駄ァーッ という感じでやっぱlibgs9がアカンのでダメとなる
  • 以降、apt removeもできなくなる (apt --fix-broken installしろと出続ける)

というわけで困ったのですが、検索して得た以下の情報を元にaptのステータスをいじったところナントカ治りました(汗 そのURLを貼りたいのですがこれまた失念…stackoverflowだったと思ったのですが、ubuntuフォーラムかもしれない。 ともかく。

cp /var/lib/dpkg/status /var/lib/dpkg/status.bak # 念の為、今のステータスファイルを保存
cp /var/backups/dpkg.status.*.gz /var/lib/dpkg # すべての連番ファイル圧縮バックアップがコピーされる
gunzip -d /var/lib/dpkg/dpkg.status.*.gz # バックアップを展開する。
cp -f /var/lib/dpkg/dpkg.status.n /var/lib/dpkg/status # nは任意の番号。もっとも大きい番号のが直前のようだ。
# この状態で、以前は実行不可能だったaptが実行可能になっている。

という情報を得たのでやってみたのですが、apt update->upgradeすると、結局libgs9で同じことに。 そこでふとひらめきました。

「statusファイルを復帰させたらそこでapt remove libgs9-commonする」

という。 あとバージョン番号がなんか18.04.2という、ん〜まだリリース前じゃね?みたいな雰囲気のファイルだったので、もしかしてと思い、upgradeではなくdist-upgradeにしてみました。 結局、やったことは

statusファイル復帰
↓
apt remove libgs9-common
↓
apt update
↓
apt dist-upgrade

そしたら無事終了しました! まぁ偶然サーバのほうで何か修正が入っただけかもですが(^^;

ではでは〜