Amazonで売っている中華ガジェット、ぶっちゃけハズレも多いのですが、なんかそそられるものがありますよね。
今回は中華ガジェットを買ったらハズレだったので、自分で中身を入れ替えて使えるようにした、そんな話です。
と、話を書いても良いのですが、動画にまとめてニコニコ動画にアップしたので、それを見てやってください。
解説は動画を見ていただいた前提で書かせていただきます。まだ見ていない方はご視聴頂ますようお願いします。
まずはソースコード全体です。
ソースコードのダウンロードはこちらから→code.py
import board import rotaryio import busio import digitalio import time import usb_hid from adafruit_hid.keyboard import Keyboard from adafruit_hid.keycode import Keycode import adafruit_debouncer import adafruit_ssd1306 BUTTON_IO = [board.GP26, board.GP27, board.GP28] BUTTON_KEYCODE = [Keycode.KEYPAD_ZERO, Keycode.KEYPAD_PERIOD, Keycode.KEYPAD_ENTER] FF_KEY = Keycode.PERIOD RW_KEY = Keycode.COMMA SCL = board.GP7 SDA = board.GP6 ENC_A = board.GP2 ENC_B = board.GP3 class logic_key: """Logic Pro専用キーボードのクラス """ def __init__(self) -> None: """コンストラクタ """ self.keyboard = Keyboard(usb_hid.devices) self.encoder = rotaryio.IncrementalEncoder(ENC_A, ENC_B) self.prepos = self.encoder.position try: self.i2c = busio.I2C(SCL, SDA) self.display = adafruit_ssd1306.SSD1306_I2C(128, 64, self.i2c, addr=0x3C) except RuntimeError as e: # I2Cがつながっていないとプルアップされていない例外が出るので # それを拾ってself.displayを使わないようにする。 print(e) print("ディスプレイが見つからないので使わないことにします。") self.display = None self.shows = [self.show_stop, self.show_pause, self.show_play] self.buttons = [] self.init_buttons() print("初期化完了") def init_buttons(self) -> None: """ボタンの初期化メソッド """ for b in BUTTON_IO: tmp_pin = digitalio.DigitalInOut(b) tmp_pin.direction = digitalio.Direction.INPUT tmp_pin.pull = digitalio.Pull.UP self.buttons.append( adafruit_debouncer.Debouncer(tmp_pin) ) def main_loop(self) -> None: """メインループメソッド """ while True: self.encoder_detect() self.button_detect() def encoder_detect(self) -> None: """ロータリーエンコーダーを検知するメソッド """ pos = self.encoder.position # if pos != self.prepos: # print(pos) if pos > self.prepos: self.keyboard.send(FF_KEY) if pos < self.prepos: self.keyboard.send(RW_KEY) self.prepos = pos def button_detect(self) -> None: """ボタンを検知するメソッド """ for i, btn in enumerate(self.buttons): btn.update() if btn.fell: self.send_key(i, True) if btn.rose: self.send_key(i, False) def send_key(self, btn_num: int, is_pressed: bool): """ボタンに応じたキーボード情報を送るメソッド Args: btn_num (int): ボタンの番号 is_pressed (bool): 押したとき->True / 離したとき->False """ if is_pressed: # print("btn", btn_num, "pressed.") self.keyboard.press(BUTTON_KEYCODE[btn_num]) self.shows[btn_num]() else: # print("btn", btn_num, "released.") self.keyboard.release(BUTTON_KEYCODE[btn_num]) def show_play(self) -> None: """再生ボタン時のディスプレイ表示 """ if self.display is None: return self.display.fill(0) self.display.text("PLAY", 20, 0, 1, size=3) for i in range(16): self.display.hline(40, 32 + i, i * 2, 1) self.display.hline(40, 63 - i, i * 2, 1) self.display.show() def show_stop(self) -> None: """停止ボタン時のディスプレイ表示 """ if self.display is None: return self.display.fill(0) self.display.text("STOP", 20, 0, 1, size=3) self.display.fill_rect(40, 32, 32, 32, 1) self.display.show() def show_pause(self) -> None: """一時停止ボタン時のディスプレイ表示 """ if self.display is None: return self.display.fill(0) self.display.text("PAUSE", 20, 0, 1, size=3) self.display.fill_rect(40, 32, 11, 32, 1) self.display.fill_rect(61, 32, 11, 32, 1) self.display.show() def main() -> None: """メイン関数 """ lk = logic_key() lk.main_loop() if __name__ == "__main__": main()
やっていることは単純です。USBキーボードとして動かして、
この3つになります。
BUTTON_IO = [board.GP26, board.GP27, board.GP28] BUTTON_KEYCODE = [Keycode.KEYPAD_ZERO, Keycode.KEYPAD_PERIOD, Keycode.KEYPAD_ENTER] FF_KEY = Keycode.PERIOD RW_KEY = Keycode.COMMA SCL = board.GP7 SDA = board.GP6 ENC_A = board.GP2 ENC_B = board.GP3
定数のキモは、ボタンのIO配列とキーコードのリストです。この両方のリストのインデックスでボタンと送るキーコードを紐づけています。
他の定数は、まぁ、定数名そのままって感じです。
def __init__(self) -> None: """コンストラクタ """ self.keyboard = Keyboard(usb_hid.devices) self.encoder = rotaryio.IncrementalEncoder(ENC_A, ENC_B) self.prepos = self.encoder.position try: self.i2c = busio.I2C(SCL, SDA) self.display = adafruit_ssd1306.SSD1306_I2C(128, 64, self.i2c, addr=0x3C) except RuntimeError as e: # I2Cがつながっていないとプルアップされていない例外が出るので # それを拾ってself.displayを使わないようにする。 print(e) print("ディスプレイが見つからないので使わないことにします。") self.display = None self.shows = [self.show_stop, self.show_pause, self.show_play] self.buttons = [] self.init_buttons() print("初期化完了")
動画中でも言ってますが、今回の作例では、ネット上にある作例ではあまり使われていないクラスを作ってます。
クラスのコンストラクタでクラスの初期化とハードウェアの初期化もしています。
ロータリーエンコーダーを見る部分を自分でつくろうと思ったら、エンコーダーのA相とB相の関係を色々見てあげないといけないのですが、CircuitPythonではrotaryio.IncrementalEncoderと宣言するだけで、ロータリーエンコーダーの現在位置をカウントしてくれます。非常に便利です。
例外処理が入っているのはI2Cの部分で、ディスプレイが接続されてI2CのI/Oがプルアップされていないと、RuntimeErrorがraiseしてくるので、それを捕まえてself.displayをNoneにしています。ディスプレイを使うメソッドなどでself.displayをチェックをしていてNoneならば処理をしないようにしています。
もう一つのポイントは、self.showのリストです。これはメソッドの配列で、ボタンの番号とメソッド配列の番号が紐付けられるようにしています。
def init_buttons(self) -> None: """ボタンの初期化メソッド """ for b in BUTTON_IO: tmp_pin = digitalio.DigitalInOut(b) tmp_pin.direction = digitalio.Direction.INPUT tmp_pin.pull = digitalio.Pull.UP self.buttons.append( adafruit_debouncer.Debouncer(tmp_pin) )
定数の配列で宣言されていたボタンのGPIOに対して初期化を行いつつ、self.buttonsというリストにadafruit_debouncer.Debouncerを割り当ててます。
Debouncerはいわゆるチャタリング対策をして、その上で立ち上がり/立ち下がりを検出してくれるものです。
実はadafruit_debouncer.Buttonというのもあるのですが、今回はDebouncerのほうが使いやすいです。
詳細はadafruitのドキュメントを見てください。
def main_loop(self) -> None: """メインループメソッド """ while True: self.encoder_detect() self.button_detect()
2つのメソッドを無限に回すメインループです。
情報系の人が見たらこんな無限ループはビビっちゃいますけど、組み込み系だと当たり前に使うやつですね。
encoder_detect()でロータリーエンコーダーの動きを見て、button_detect()でボタンの動きを見てます。
def encoder_detect(self) -> None: """ロータリーエンコーダーを検知するメソッド """ pos = self.encoder.position # if pos != self.prepos: # print(pos) if pos > self.prepos: self.keyboard.send(FF_KEY) if pos < self.prepos: self.keyboard.send(RW_KEY) self.prepos = pos
self.encoder.positionで現在のロータリーエンコーダーの位置がint型で得られます。楽ですねぇ。
前回の位置と比較して、大きければ早送りのキーを、小さければ巻き戻しのキーを送ってます。
同じならば何もしないで、最後に前回の位置self.preposに現在位置を入れてこのメソッドは終了です。
def button_detect(self) -> None: """ボタンを検知するメソッド """ for i, btn in enumerate(self.buttons): btn.update() if btn.fell: self.send_key(i, True) if btn.rose: self.send_key(i, False) def send_key(self, btn_num: int, is_pressed: bool): """ボタンに応じたキーボード情報を送るメソッド Args: btn_num (int): ボタンの番号 is_pressed (bool): 押したとき->True / 離したとき->False """ if is_pressed: # print("btn", btn_num, "pressed.") self.keyboard.press(BUTTON_KEYCODE[btn_num]) self.shows[btn_num]() else: # print("btn", btn_num, "released.") self.keyboard.release(BUTTON_KEYCODE[btn_num])
button_detect()では、ボタンリストのenumerateでforループで回します。btnをupdate()すると現在の状況と前回との差が得られます。ボタンは押されると立ち下がり離されると立ち上がります。それぞれbtn.fellとbtn.roseで検出できます。そうして、ボタンの番号と押されたのか離されたのかをis_pressedとしてsend_key()メソッドに送っています。
send_key()ではis_pressedに応じてボタン番号で指定されたキーコードをキーボードに送っています。
また、ボタン番号に応じてメソッドのリストshowsを実行しています。
これらはまぁ、見たまんまなので解説することもないでしょうか。
三角形を描くメソッドがないのでいろいろな長さの線をいっぱい描く力技ぐらいでしょうねw
解説は以上です。
ざっくり解説なのでわかりにくいところもあるかとは思いますが、あしからず。
Twitterでシェア Facebookでシェア