Hello World / plɹoM ollǝH

Programmers Live in Vain

Widgetを等倍で中央寄せさせておきたい!

QScrollAreaのresizeEventを継承してゴニョゴニョしたら割と簡単にできた

f:id:dungeonneko:20170216171205g:plain

import sys
from PySide import QtCore, QtGui

class AlwaysCenterAlign(QtGui.QScrollArea):
    def __init__(self):
        super().__init__()
        self.setAlignment(QtCore.Qt.AlignCenter)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        v = self.verticalScrollBar()
        h = self.horizontalScrollBar()
        v.setValue(v.minimum() + (v.maximum() - v.minimum()) / 2)
        h.setValue(h.minimum() + (h.maximum() - h.minimum()) / 2)

    def wheelEvent(self, _):
        pass

    def keyPressEvent(self, _):
        pass

if __name__ == '__main__':
    a = QtGui.QApplication(sys.argv)
    s = AlwaysCenterAlign()
    l = QtGui.QLabel()
    l.setPixmap(QtGui.QPixmap('lena.jpg'))
    s.setWidget(l)
    s.show()
    sys.exit(a.exec_())

RaspberryPi 3 RPIOでサーボモーターを動かす

RPIOをインストール

モーターは デジタル・マイクロサーボ SG90 を使いました

下記サイトを参考に github.com

こんな感じでインストール

cd ~
git clone https://github.com/metachris/RPIO.git --branch v2 --single-branch
cd RPIO
sudo python3.4 setup.py install

スクリプトを書く

root権限で実行しないといけないっぽい

from RPIO import PWM
import time

servo = PWM.Servo()
pin = 14


def deg_to_width(deg):
    ''' 角度からパルス幅に変換

    :param: deg 角度(-90~+90)
    :return: パルス幅
    '''
    center = 127
    margin = 45
    step = (center - margin) / 90.0
    deg = max(-90.0, min(deg, 90.0))
    return int(center + deg * step) * 10

while True:
    for x in [-90, 0, 90, 0]:
        servo.set_servo(pin, deg_to_width(x))
        time.sleep(1)

ちなみにPWMとPCMは同時に使えないのでサーボ制御しながら音も出したいときはBluetoothスピーカーとかUSBサウンドカードを使ったほうがよさげ

QActionでコードもすっきり

メニューやツールバーを実装するときはQPushButtonやコールバックをガリガリ書くのではなくQActionを作る。QActionオブジェクトを作成しておけば、複数のメニューやツールバーをまたいでも、ひとつのQActionオブジェクトで挙動を定義できるし、QActionGroupを使えばラジオボタンも作れる。ショートカットキーやIconも設定できる。

f:id:dungeonneko:20170125160034g:plain

import sys
from PySide.QtCore import *
from PySide.QtGui import *

# ラジオボタン的な挙動をするメニューを作る例
if __name__ == '__main__':

    myapp = QApplication(sys.argv)
    window = QMainWindow()

    # まずはExclusiveなグループを作成
    exclusive_group = QActionGroup(window)
    exclusive_group.setExclusive(True)

    # グループに属するアクションを作る
    def createExclusiveAction(in_name, in_icon, in_shortcut):
        action = QAction(in_name, window)
        action.setActionGroup(exclusive_group)
        action.setIcon(myapp.style().standardIcon(in_icon))
        action.setShortcut(in_shortcut)
        action.setCheckable(True)
        return action

    data = [
        ('Play', QStyle.SP_MediaPlay, 'Q'),
        ('Pause', QStyle.SP_MediaPause, 'W'),
        ('Stop', QStyle.SP_MediaStop, 'E'),
        ('Forward', QStyle.SP_MediaSeekForward, 'R'),
        ('Backward', QStyle.SP_MediaSeekBackward, 'T'),
    ]

    actions = []
    for name, icon, shortcut in data:
        actions.append(createExclusiveAction(name, icon, shortcut))

    # メニューバーとツールバー両方に追加して動作テスト
    menubar = window.menuBar()
    mediamenu = menubar.addMenu('Media')
    toolbar = window.addToolBar('ToolBar')
    for x in actions:
        mediamenu.addAction(x)
        toolbar.addAction(x)

    window.show()
    sys.exit(myapp.exec_())

ラズパイ起動時にBluetooth接続してwav再生

自分の環境でちゃんと動く記事が無かったのでメモ。設定ファイルをあちこち編集するのは好きじゃないのでbash_profileで頑張った。Linuxめんどい。

環境

1. モジュールインストール

sudo apt-get install pulseaudio pavucontrol pulseaudio-module-bluetooth
reboot

2. 準備(GUIからTerminal起動してやった)

pulseaudioを起動

pulseaudio -D

ペアリング

bluetoothctl
> scan on
> (BluetoothスピーカーをペアリングモードにしてMACアドレスが表示されるまで待つ)
> scan off
> pair FF:FF:FF:FF:FF:FF  # BluetoothスピーカーのMACアドレス
> connect  FF:FF:FF:FF:FF:FF  # BluetoothスピーカーのMACアドレス
> trust  FF:FF:FF:FF:FF:FF  # BluetoothスピーカーのMACアドレス
> quit

バイスの設定

  • メニュー ⇒ Sound & Video ⇒ PulseAudio Volume Control
  • ConfigurationメニューからALSAの出力先をOffにしておく

3. 起動スクリプトを用意

.bash_profileをユーザーのホームディレクトリに作成

pulseaudio -D
sleep 5  # 適当にウェイト
bluetoothctl << EOF
power on
connect FF:FF:FF:FF:FF:FF  # BluetoothスピーカーのMACアドレス
quit
EOF
sleep 5  # 適当にウェイト
pacmd set-sink-volume 1 32767  # 音量調節(デバイス番号、音量~65565)
aplay test.wav  # wavを再生

CUIモードにしてreboot

Python lambda式の落とし穴

Pythonクロージャ内部で使われている変数は通常、実行時に値が評価されます。for文などと組み合わせてlambda式やローカル関数を使うときは、ちょっと気を付けないといけません。

例えば、0~4まで出力する関数を5つ用意したいとき

# 関数作る
functions = []
for i in range(0, 5):
    functions.append(lambda: print(i))

# 関数呼び出す
for func in functions:
    func() # この時点では i は 4 ですよ?

このように書いてしまうと、下記のような結果が出力されてしまいます。

4
4
4
4
4

こいつを回避するためには、デフォルト引数を使ってちょいと工夫してやる必要があります。

# 関数作る
functions = []
for i in range(0, 5):
    functions.append(lambda x=i: print(x)) # デフォルト引数は関数定義時に評価される

# 関数呼び出す
for func in functions:
    func()

これで意図した結果が得られます。

0
1
2
3
4

functools.partialを使う方法もあるようですね。

参照:Guide to Python - Common Gotchas

QListWidget コンテキストメニュー

f:id:dungeonneko:20160210011910g:plain

# conding: utf-8
import sys
from PySide.QtCore import *
from PySide.QtGui import *

# 右クリックでカレントの行を削除
def customMenu(widget, pos):
    if len(widget.selectedItems()) == 0:
        return
    action = QAction('Remove', widget)
    action.triggered.connect(lambda : widget.takeItem(widget.currentRow()))
    menu = QMenu()
    menu.addAction(action)
    menu.exec_(widget.mapToGlobal(pos))

# entry point
if __name__ == '__main__':
    myapp = QApplication(sys.argv)
    widget = QListWidget()
    widget.setContextMenuPolicy(Qt.CustomContextMenu) # Policyも設定しないといけない
    widget.customContextMenuRequested.connect(lambda pos : customMenu(widget, pos))
    widget.addItem('item1')
    widget.addItem('item2')
    widget.addItem('item3')
    widget.show()
    sys.exit(myapp.exec_())