ぽけこん on ラズベリーパイ導入手順
はじめにPoke-Controller on Raspberry Piを導入にあたって
Poke-Controllerをラズベリーパイで動作させることのメリットとデメリットについて
メリット
動作電力の削減
機器接続環境の簡略化
SDカードの交換による使用用途によってのOSの切り替えが簡単
デメリット
実際に動かす自動化pythonプログラムはWindows環境で作成されている場合が多いため、
動作にあたって入手した自動化ファイルの修正が必要になる可能性がある。
導入にあたってlinuxの操作、OSの知識などが必要になる。
本手順ではlinuxについての知識があるものとして説明は省くものとする。
本環境で使用する機器について
RasberryPiOS導入するためのWindowsコンピュータ(teratermのscpにて各種ファイルの転送などを実施)
HDMI付きのテレビ
Nintendo Switch
Raspberry Pi 4B 4GBメモリー版 ¥15,000位 ※現在半導体不足で価格高騰中の為、RaspberryPi400を推奨
ラズベリーパイにつなぐUSBマウス、USBキーボード ¥2,000位 ※RaspberryPi400ならばキーボード不要
MicroSDHCカード 16GB ¥500位
ジャンパーワイヤ ♀♂2本 ¥400位(束で購入可♂♂があるとマイコンのショートに便利、♀♀もあると便利)
ブレッドボード ¥400位
中華キャプチャーボード ¥800位
マイコン(pro micro Atmega32U4)¥1800位
raspiOSのダウンロード
OPENCVが64bit対応なため、64bit版のraspiOSをダウンロードします。
2024/09/04 更新
raspios 6.6.31-1+rpt1 (2024-05-29) aarch64 版で更新
2024/09/04時点での手順になりますので、最新のOSのアップデートにより手順が古くなり同じコマンドなどが実行できなくなっている可能性がありますがご了承ください
raspiOSのインストールツールのダウンロード
Raspberry Pi Imagerを使用し、WindowsOSでraspiOSをインストールします。
https://www.raspberrypi.com/software/
WindowsのスタートメニューからRaspberry Pi Imagerを起動します。
CHOOSE OSボタンを押し、Use customからダウンロードした64bit版の
Raspberry Pi Imagerの64bit版のraspiOSを選択します。
(ダウンロードしたzipファイルのまま選択可能)
CHOOSE STORAGE ボタンを押し、インストール対象のmicroSDHCカードを選択します。
※sdカードは初期化されますので、書き込む場合は必ずsdカードが正しいことを確認してください。
※間違えて大事なファイル等が削除された場合、一切責任を取りませんのでご了承ください。
WRITEボタンを押し、microSDHCカードにRaspiOSをインストールします。
容量にもよりますが、インストールには30分程度かかります。
※完了後、フォーマットしますかとのポップアップが表示されますが、いいえを選択してください。
WindowsからSDカードを取り外し、raspberry piにSDカードを差し込みます。
RasberryPiのHDMIをテレビに接続
RasberryPiのUSBにキーボード、マウス、キャプチャーボードの接続
RasberryPiOSの起動とネットワークの設定の実施
※自分の家の環境に設定してください。
raspiコンフィグにて初期設定
sudo raspi-config
1.3 パスワードの設定 piユーザーのパスワードの設定
1.4 ホスト名の変更 ご希望のホスト名
3.2 sshの有効化 yes
3.6 over serial no
serial port hardware enable yes
Finishにて再起動の実施
config.txtの編集
sudo nano /boot/config.txt
末尾に下記内容を追加してシリアルポートの有効化とBuleToothの停止
[all]
enable_uart=1
dtoverlay=pi3-disable-bt
パッケージリストの更新
sudo apt-get update
ポケコン用Python環境の導入
RasberryPiOSにはインストール後にPythonが導入されていますのでpipコマンドにてパッケージを追加していきます。
2024/09/04更新
OSのPythonからpipで導入できなくなったため、venv環境にて導入します
venvの作成
mkdir ~/pokecon
python3 -m venv pokecon
source ~/pokecon/bin/activate
deactivate
コマンド
source ~/pokecon/bin/activate
pip install numpy
pip install opencv-python
pip install pynput
pip install pipenv
deactivate
コンソールの再起動
source ~/pokecon/bin/activate
pipenv install pyserial
pip install Pillow
deactivate
sudo apt install python3-pil.imagetk
下記パッケージは用途によって導入します。
ポケコンModified verの場合
source ~/pokecon/bin/activate
pip install pygubu
pip install pandas
pip install scipy
deactivate
OCRを使用する場合tesseract-OCR
sudo apt install tesseract-ocr
source ~/pokecon/bin/activate
pip install tesseract
pip install pyocr
deactivate
LINE Notifyを使用する場合
source ~/pokecon/bin/activate
pip install requests
deactivate
ポケコンの起動
source ~/pokecon/bin/activate
cd ~/Poke-Controller-Modified/SerialController/
python Window.py
コマンド
pip install numpy
pip install opencv-python
pip install pynputl
pip install pipenv
exit
コンソールの再起動
pipenv install pyserial
pip install Pillow
sudo apt install python3-pil.imagetk
下記パッケージは用途によって導入します。
ポケコンModified verの場合
pip install pygubu
pip install pandas
pip install scipy
OCRを使用する場合
tesseract-OCR
sudo apt install tesseract-ocr
pip install tesseract
pip install pyocr
pip install pyocr.builder
LINE Notifyを使用する場合
pip install requests
Poke-Controllerのダウンロード ホームディレクトリに解凍
unzip Poke-Controller.zip
本家
https://github.com/KawaSwitch/Poke-Controller
Modified版
https://github.com/Moi-poke/Poke-Controller-Modified
lufaのダウンロード
https://github.com/abcminiuser/lufa
解凍するとlufa-masterフォルダができるので
lufaに名前変更してPoke-Controllerフォルダ配下のlufaフォルダに上書き保存。
ArduinoIDEのダウンロード ホームディレクトリに解凍
https://www.arduino.cc/en/software
tar xvf arduino-1.8.19-linuxaarch64.tar.xz
マイコンハードウェア情報の編集
nano ~/arduino-1.8.19/hardware/arduino/avr/boards.txt
285 leonardo.vid.1=0x2341
286 leonardo.pid.1=0x8036
↓
285行目:leonardo.vid.1=0x0f0d
286行目:leonardo.pid.1=0x0092
311 leonardo.build.vid=0x2341
312 leonardo.build.pid=0x8036
↓
311行目:leonardo.build.vid=0x0f0d
312行目:leonardo.build.pid=0x0092
LeonardoのMCUに変更
nano ~/Poke-Controller-Modified/makefile
14 MCU = atmega16u2
↓
14 MCU = atmega32u4
Leonardo用hexファイルの作成とマイコンへの書き込み
export PATH=$PATH:/home/pi/arduino-1.8.19/hardware/tools/avr/bin
cd ~/Poke-Controller
make clean
make
makeしたhexファイルをArduino Leonardoに書き込み
ArduinoLeonardoをリセット(RSTとGNDをショートさせる)
以下ここから、以上までをコピー&ペーストして実行------
ARDUINO_UPLOAD_PORT="$(find /dev/ttyACM* | head -n 1)"
stty -f "${ARDUINO_UPLOAD_PORT}" 1200
while :; do
sleep 0.5
[ -c "${ARDUINO_UPLOAD_PORT}" ] && break
done
avrdude -C /home/pi/arduino-1.8.19/hardware/tools/avr/etc/avrdude.conf -v -patmega32u4 -cavr109 -P"${ARDUINO_UPLOAD_PORT}" -b57600 -D -V -Uflash:w:/home/pi/Poke-Controller/Joystick.hex:i
以上---------------------------------------
ラズベリーパイGPIOとマイコンの接続
ジャンパーワイヤーにて接続します。
GND⇔GND
TXD⇒RXD
※今環境はGPIOと直接マイコンを接続するためシリアルアダプタは使用しません
※GPIOの接続は間違えた場合最悪基盤が壊れる可能性がありますので正しい場所を結線してください。
※必ず電源を停止した状態で、自己責任の下で行ってください。
ラズベリーパイとマイコンとスイッチの接続図
ラズベリーパイに中華キャプチャーボードを接続したときは特にドライバは必要ありませんでした。(USB2.0のポートに接続しないと認識しませんでした。)
Poke-Controllerのシリアル接続情報の書き換え
USB→AMAに変更
nano ~/Poke-Controller-Modified/SerialController/Commands/Sender.py
print('connecting to ' + "/dev/ttyUSB" + str(portNum))
↓
print('connecting to ' + os.name + "/dev/ttyAMA" + str(portNum))
self.ser = serial.Serial("/dev/ttyUSB" + str(portNum), 9600)
↓
self.ser = serial.Serial("/dev/ttyAMA" + str(portNum), 9600)
Poke-Controllerの起動
source ~/pokecon/bin/activate
cd ~/Poke-Controller-Modified/SerialController/
python Window.py
OPENCVのバージョンの問題か、画像認識時に読み込み失敗エラーが発生するため下記のとおり変更する。
PythonCommandBase.pyの編集
nano ~/Poke-Controller-Modified/SerialController/Commands/PythonCommandBase.py
# Read a current image
src = self.camera.readFrame()
src = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) if use_gray else src
src = src[area[2]:area[3], area[0]:area[1]] if not area else src # trim
# Read a template image
template = cv2.imread(TEMPLATE_PATH+template_path, cv2.IMREAD_GRAYSCALE if use_gray else cv2.IMREAD_COLOR)
template = template[tmp_area[2]:tmp_area[3], tmp_area[0]:tmp_area[1]] # trim
w, h = template.shape[1], template.shape[0]
↓
# Read a current image
while True:
src = self.camera.readFrame()
if src is not None:
break
else:
continue
src = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) if use_gray else src
src = src[area[2]:area[3], area[0]:area[1]] if area else src # trim
# Read a template image
template = cv2.imread(TEMPLATE_PATH + template_path, cv2.IMREAD_GRAYSCALE if use_gray else cv2.IMREAD_COLOR)
template = template[tmp_area[2]:tmp_area[3], tmp_area[0]:tmp_area[1]] # trim
w, h = template.shape[1], template.shape[0]
↓
# Read a current image
while True:
src = self.camera.readFrame()
if src is not None:
break
else:
continue
src = cv2.cvtColor(src, cv2.COLOR_BGR2GRAY) if use_gray else src
src = src[area[2]:area[3], area[0]:area[1]] if area else src # trim
# Read a template image
template = cv2.imread(TEMPLATE_PATH + template_path, cv2.IMREAD_GRAYSCALE if use_gray else cv2.IMREAD_COLOR)
template = template[tmp_area[2]:tmp_area[3], tmp_area[0]:tmp_area[1]] # trim
w, h = template.shape[1], template.shape[0]
2024/9/4 更新 ゆう様よりいただいたパッチ内容の引用
Modified3.0.2.6.2版のみCamera_thread.patchファイルでの提供
sudo apt install git
cp -p ~/Camera_thread.patch ~/Poke-Controller-Modified/
#現行ファイルのバックアップ
cp -p ~/Poke-Controller-Modified/SerialController/Camera.py ~/Poke-Controller-Modified/SerialController/Camera.py.org
cp -p ~/Poke-Controller-Modified/SerialController/GuiAssets.py ~/Poke-Controller-Modified/SerialController/GuiAssets.py.org
#パッチファイルの移動とファイル確認
cd ~/Poke-Controller-Modified/
ls -l *.patch #実行結果
-rw-r--r-- 1 pi pi 5712 Sep 3 18:11 Camera_thread.patch
#パッチファイルの適用
patch -p0 --binary < ./Camera_thread.patch
#実行結果
.patchpatching file SerialController/Camera.py
Hunk #2 succeeded at 44 (offset -19 lines).
Hunk #3 succeeded at 77 (offset -19 lines).
Hunk #4 succeeded at 96 (offset -19 lines).
Hunk #5 succeeded at 114 with fuzz 2 (offset -19 lines).
Hunk #6 succeeded at 148 (offset -20 lines).
patching file SerialController/GuiAssets.py
下記修正後のCamera.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2
import datetime
from time import sleep
import time
import os
import numpy as np
from logging import getLogger, DEBUG, NullHandler
from threading import Thread, Lock
def imwrite(filename, img, params=None):
#self ._logger = getLogger(__name__)
self._logger.addHandler(NullHandler())
self._logger.setLevel(DEBUG)
self._logger.propagate = True
try:
ext = os.path.splitext(filename)[1]
result, n = cv2.imencode(ext, img, params)
if result:
with open(filename, mode='w+b') as f:
n.tofile(f)
return True
else:
return False
except Exception as e:
print(e)
_logger.error(f"Image Write Error: {e}")
return False
class Camera:
def __init__(self, fps=45):
self.camera = None
self.capture_size = (1280, 720)
# self.capture_size = (1920, 1080)
self.capture_dir = "Captures"
self.fps = int(fps)
self._logger = getLogger(__name__)
self._logger.addHandler(NullHandler())
self._logger.setLevel(DEBUG)
self._logger.propagate = True
self.buffersize = 4
self.cycle = 0.00
self.started = False
self.read_lock = Lock()
def openCamera(self, cameraId):
if self.camera is not None and self.camera.isOpened():
self._logger.debug("Camera is already opened")
self.destroy()
isV4L2 = False
if os.name == 'nt':
self._logger.debug("NT OS")
self.camera = cv2.VideoCapture(cameraId, cv2.CAP_DSHOW)
# self.camera = cv2.VideoCapture(cameraId)
else:
self._logger.debug("Not NT OS")
##self.camera = cv2.VideoCapture(cameraId)
try:
self.camera = cv2.VideoCapture(cameraId, cv2.CAP_V4L2)
isV4L2 = True
except TypeError:
self.camera = cv2.VideoCapture(cameraId)
if not self.camera.isOpened():
print("Camera ID " + str(cameraId) + " can't open.")
self._logger.error(f"Camera ID {cameraId} cannot open.")
return
print("Camera ID " + str(cameraId) + " opened successfully")
self._logger.debug(f"Camera ID {cameraId} opened successfully.")
# print(self.camera.get(cv2.CAP_PROP_FRAME_WIDTH))
# self.camera.set(cv2.CAP_PROP_FPS, 60)
self.camera.set(cv2.CAP_PROP_FRAME_WIDTH, self.capture_size[0])
self.camera.set(cv2.CAP_PROP_FRAME_HEIGHT, self.capture_size[1])
if isV4L2:
self.camera.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'));
self.camera.set(cv2.CAP_PROP_FPS, 60)
self.camera.set(cv2.CAP_PROP_BUFFERSIZE, self.buffersize)
print("Camera WH:" + str(self.camera.get(cv2.CAP_PROP_FRAME_WIDTH))
+ "x" + str(self.camera.get(cv2.CAP_PROP_FRAME_HEIGHT)))
fourcc_str = int(self.camera.get(cv2.CAP_PROP_FOURCC)).to_bytes(4, 'little').decode('utf-8')
print("Camera Format(After):" + fourcc_str)
print("Camera FPS(After):" + str(self.camera.get(cv2.CAP_PROP_FPS)))
_, self._image_bgr = self.camera.read()
self.camera_read_start()
# self.camera.set(cv2.CAP_PROP_SETTINGS, 0)
def isOpened(self):
self._logger.debug("Camera is opened")
return self.camera.isOpened()
def readFrame(self):
##_, self.image_bgr = self.camera.read()
##return self.image_bgr
self.read_lock.acquire()
frame = self._image_bgr.copy()
self.read_lock.release()
return frame
def saveCapture(self, filename=None, crop=None, crop_ax=None, img=None):
if crop_ax is None:
crop_ax = [0, 0, 1280, 720]
else:
pass
# print(crop_ax)
dt_now = datetime.datetime.now()
if filename is None or filename == "":
filename = dt_now.strftime('%Y-%m-%d_%H-%M-%S') + ".png"
else:
filename = filename + ".png"
image_bgr = self.readFrame()
if crop is None:
##image = self.image_bgr
image = image_bgr
elif crop is 1 or crop is "1":
##image = self.image_bgr[
image = image_bgr[
crop_ax[1]:crop_ax[3],
crop_ax[0]:crop_ax[2]
]
elif crop is 2 or crop is "2":
##image = self.image_bgr[
image = image_bgr[
crop_ax[1]:crop_ax[1] + crop_ax[3],
crop_ax[0]:crop_ax[0] + crop_ax[2]
]
elif img is not None:
image = img
else:
##image = self.image_bgr
image = image_bgr
if not os.path.exists(self.capture_dir):
os.makedirs(self.capture_dir)
self._logger.debug("Created Capture folder")
save_path = os.path.join(self.capture_dir, filename)
try:
imwrite(save_path, image)
self._logger.debug(f"Capture succeeded: {save_path}")
print('capture succeeded: ' + save_path)
except cv2.error as e:
print("Capture Failed")
self._logger.error(f"Capture Failed :{e}")
def destroy(self):
if self.camera is not None and self.camera.isOpened():
self.camera_read_stop()
self.camera.release()
self.camera = None
self._logger.debug("Camera destroyed")
def camera_read_start(self):
if self.started:
print("already started!!")
self.started = True
self.thread = Thread(target=self.camera_update, args=())
self.thread.start()
def camera_read_stop(self) :
self.started = False
self.thread.join()
def camera_update(self) :
while self.started :
_, frame = self.camera.read()
self.read_lock.acquire()
self._image_bgr = frame
self.read_lock.release()
time.sleep(self.cycle)
下記修正後のGuiAsset.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2
import os
import time
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
import numpy as np
import datetime
from collections import deque
from PIL import Image, ImageTk
from Commands import UnitCommand
from Commands import StickCommand
from Commands.Keys import Direction, Stick, Button, Direction, KeyPress
import logging
from logging import INFO, StreamHandler, getLogger, DEBUG, NullHandler
from Commands.PythonCommandBase import PythonCommand, StopThread
try:
os.makedirs('log')
except FileExistsError:
pass
isTakeLog = False
# logger_stick = getLogger(__name__)
nowtime = datetime.datetime.fromtimestamp(time.time()).strftime('%Y%m%d_%H%M%S')
# press button at duration times(s)
class MouseStick(PythonCommand):
NAME = 'MOUSEスティック'
def __init__(self):
super().__init__()
self._logger = getLogger(__name__)
self._logger.addHandler(NullHandler())
self._logger.setLevel(DEBUG)
self._logger.propagate = True
def do(self):
pass
def stick(self, buttons, duration=0.1, wait=0.1):
self.keys.input(buttons, ifPrint=False)
self.wait(duration)
self.wait(wait)
# press button at duration times(s)
def stickEnd(self, buttons):
self.keys.inputEnd(buttons)
class CaptureArea(tk.Canvas):
def __init__(self, camera, fps, is_show, ser, master=None, show_width=640, show_height=360):
super().__init__(master, borderwidth=0, cursor='tcross', width=show_width, height=show_height)
self._logger = getLogger(__name__)
self._logger.addHandler(NullHandler())
self._logger.setLevel(DEBUG)
self._logger.propagate = True
self.master = master
self.radius = 60 # 描画する円の半径
self.camera = camera
# self.show_size = (640, 360)
self.show_width = int(show_width)
self.show_height = int(show_height)
self.show_size = (self.show_width, self.show_height)
self.is_show_var = is_show
self.lx_init, self.ly_init = 0, 0
self.rx_init, self.ry_init = 0, 0
self.min_x, self.min_y = 0, 0
self.max_x, self.max_y = 0, 0
self.keys = None
self.ser = ser
self.lcircle = None
self.lcircle2 = None
self.rcircle = None
self.rcircle2 = None
self.LStick = None
self.RStick = None
self.calc_time = None
self.ss = None
self.dq = None
self._langle = None
self._lmag = None
self._rangle = None
self._rmag = None
self.stick_handler = StreamHandler()
self.stick_logging_level = DEBUG
self.stick_handler.setLevel(self.stick_logging_level)
# self._logger.setLevel(self.stick_logging_level)
# self._logger.addHandler(self.stick_handler)
# self._logger.propagate = False
if isTakeLog:
filename_base = os.path.join("log", f"{nowtime}")
self.LS = logging.FileHandler(filename=f"{filename_base}_LStick.log", encoding='utf-8')
self.LS.setLevel(logging.DEBUG)
self.LSTICK_logger = logging.getLogger("L_STICK")
self.LSTICK_logger.setLevel(logging.DEBUG)
self.LSTICK_logger.addHandler(self.LS)
self.RS = logging.FileHandler(filename=f"{filename_base}_RStick.log", encoding='utf-8')
self.RS.setLevel(logging.DEBUG)
self.RSTICK_logger = logging.getLogger("R_STICK")
self.RSTICK_logger.setLevel(logging.DEBUG)
self.RSTICK_logger.addHandler(self.RS)
# self.circle =
self.setFps(fps)
self.bind("<Control-ButtonPress-1>", self.mouseCtrlLeftPress)
self.bind("<Control-ButtonRelease-1>", self.mouseCtrlLeftRelease)
self.bind("<Control-Shift-ButtonPress-1>", self.StartRangeSS)
self.bind("<Control-Shift-Button1-Motion>", self.MotionRangeSS)
self.bind("<Control-Shift-ButtonRelease-1>", self.ReleaseRangeSS)
# Set disabled image first
disabled_img = cv2.imread("../Images/disabled.png", cv2.IMREAD_GRAYSCALE)
disabled_pil = Image.fromarray(disabled_img)
self.disabled_tk = ImageTk.PhotoImage(disabled_pil)
self.im = self.disabled_tk
# self.configure(image=self.disabled_tk) # labelからキャンバスに変更したので微修正
self.im_ = self.create_image(0, 0, image=self.disabled_tk, anchor=tk.NW)
def ApplyLStickMouse(self):
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
else:
self.UnbindLeftClick()
def ApplyRStickMouse(self):
if self.master.is_use_right_stick_mouse.get():
self.BindRightClick()
else:
self.UnbindRightClick()
def StartRangeSS(self, event):
##self.ss = self.camera.image_bgr
image_bgr = self.camera.readFrame()
self.ss = image_bgr
if self.master.is_use_left_stick_mouse.get():
self.UnbindLeftClick()
if self.master.is_use_right_stick_mouse.get():
self.UnbindRightClick()
self.min_x, self.min_y = event.x, event.y
self.delete('SelectArea')
self.create_rectangle(self.min_x,
self.min_y,
self.min_x + 1,
self.min_y + 1,
outline='red',
tag='SelectArea')
ratio_x = float(self.camera.capture_size[0] / self.show_size[0])
ratio_y = float(self.camera.capture_size[1] / self.show_size[1])
print('Mouse down: Show ({}, {}) / Capture ({}, {})'.format(self.min_x, self.min_y,
int(self.min_x * ratio_x),
int(self.min_y * ratio_y)))
self._logger.info('Mouse down: Show ({}, {}) / Capture ({}, {})'.format(self.min_x, self.min_y,
int(self.min_x * ratio_x),
int(self.min_y * ratio_y)))
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
if self.master.is_use_right_stick_mouse.get():
self.BindRightClick()
def MotionRangeSS(self, event):
if event.x < 0:
self.max_x = 0
else:
self.max_x = min(self.show_width, event.x)
if event.y < 0:
self.max_y = 0
else:
self.max_y = min(self.show_height, event.y)
self.coords('SelectArea', self.min_x, self.min_y, self.max_x + 1, self.max_y + 1)
self.coords('SelectAreaFilled', self.min_x, self.min_y, self.max_x + 1, self.max_y + 1)
def ReleaseRangeSS(self, event):
# self.max_x, self.max_y = event.x, event.y
ratio_x = float(self.camera.capture_size[0] / self.show_size[0])
ratio_y = float(self.camera.capture_size[1] / self.show_size[1])
print('Mouse up: Show ({}, {}) / Capture ({}, {})'.format(self.max_x, self.max_y,
int(self.max_x * ratio_x),
int(self.max_y * ratio_y)))
self._logger.info('Mouse up: Show ({}, {}) / Capture ({}, {})'.format(self.max_x, self.max_y,
int(self.max_x * ratio_x),
int(self.max_y * ratio_y)))
if self.min_x > self.max_x:
self.min_x, self.max_x = self.max_x, self.min_x
if self.min_y > self.max_y:
self.min_y, self.max_y = self.max_y, self.min_y
self.camera.saveCapture(crop=1,
crop_ax=[
int(self.min_x * ratio_x), int(self.min_y * ratio_x),
int(self.max_x * ratio_x), int(self.max_y * ratio_x)])
t = 0
self.after(250, self.delete('SelectArea'))
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
if self.master.is_use_right_stick_mouse.get():
self.BindRightClick()
def setFps(self, fps):
# self.next_frames = int(16 * (60 / int(fps)))
self.next_frames = int(1000 / int(fps))
self._logger.info(f"FPS set to {fps}")
def setShowsize(self, show_height, show_width):
self.show_width = int(show_width)
self.show_height = int(show_height)
self.show_size = (self.show_width, self.show_height)
self.config(width=self.show_width, height=self.show_height)
print("Show size set to {0} x {1}".format(self.show_width, self.show_height))
self._logger.info("Show size set to {0} x {1}".format(self.show_width, self.show_height))
def mouseCtrlLeftPress(self, event):
##_img = cv2.cvtColor(self.camera.image_bgr, cv2.COLOR_BGR2RGB)
image_bgr = self.camera.readFrame()
_img = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
if self.master.is_use_left_stick_mouse.get():
self.UnbindLeftClick()
x, y = event.x, event.y
ratio_x = float(self.camera.capture_size[0] / self.show_size[0])
ratio_y = float(self.camera.capture_size[1] / self.show_size[1])
print('Mouse down: Show ({}, {}) / Capture ({}, {})'.format(x, y, int(x * ratio_x), int(y * ratio_y)))
print(f"Color [R: {_img[int(y * ratio_y), int(x * ratio_x)][0]}, "
f"G: {_img[int(y * ratio_y), int(x * ratio_x)][1]}, "
f"B: {_img[int(y * ratio_y), int(x * ratio_x)][2]}]")
self._logger.info(
'Mouse down: Show ({}, {}) / Capture ({}, {})'.format(x, y, int(x * ratio_x), int(y * ratio_y)))
def mouseCtrlLeftRelease(self, event):
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
def mouseLeftPress(self, event, ser):
if self.master.is_use_right_stick_mouse.get():
self.UnbindRightClick()
self.config(cursor='dot')
self.lx_init, self.ly_init = event.x, event.y
self.lcircle = self.create_oval(self.lx_init - self.radius, self.ly_init - self.radius,
self.lx_init + self.radius, self.ly_init + self.radius,
outline='cyan', tag="lcircle")
self.lcircle2 = self.create_oval(self.lx_init - self.radius // 10, self.ly_init - self.radius // 10,
self.lx_init + self.radius // 10, self.ly_init + self.radius // 10,
fill="cyan", tag="lcircle2")
# self.LStick = StickCommand.StickLeft()
# self.LStick.start(ser)
if isTakeLog:
if self.dq is None:
self.dq = deque()
else:
self.dq.clear()
if self.calc_time is None:
self.calc_time = time.perf_counter()
else:
# LSTICK_logger.debug(f"{0},{0},{time.perf_counter() - self.calc_time}")
self.dq.append([0, 0, time.perf_counter() - self.calc_time])
self._langle = None
self._lmag = None
def mouseLeftPressing(self, event, ser, angle=0):
# _time = self.calc_time
langle = np.rad2deg(np.arctan2(self.ly_init - event.y, event.x - self.lx_init))
mag = np.sqrt((self.ly_init - event.y) ** 2 + (event.x - self.lx_init) ** 2) / self.radius
if mag <= 0:
mag = 0
elif mag >= 1:
mag = 1
if (self._langle and self._lmag) is not None and isTakeLog:
_time = time.perf_counter()
if _time - self.calc_time > 0.05:
# thread_1 = threading.Thread(target=self.LStick.LStick,
# args=(langle,),
# kwargs={'r': mag, 'duration': _time - self.calc_time})
# thread_1.start()
self.ser.writeRow(
f'3 8 '
f'{hex(int(128 + mag * 127.5 * np.cos(np.deg2rad(langle))))} '
f'{hex(int(128 - mag * 127.5 * np.sin(np.deg2rad(langle))))} '
f'80 80',
is_show=False
)
self.dq.append([langle,
mag,
_time - self.calc_time])
self.calc_time = _time
elif not isTakeLog:
self.ser.writeRow(
f'3 8 '
f'{hex(int(128 + mag * 127.5 * np.cos(np.deg2rad(langle))))} '
f'{hex(int(128 - mag * 127.5 * np.sin(np.deg2rad(langle))))}'
f' 80 80',
is_show=False
)
if mag >= 1:
center_x = (self.radius + self.radius // 11) * np.cos(np.deg2rad(langle))
center_y = (self.radius + self.radius // 11) * np.sin(np.deg2rad(langle))
circ_x_1 = self.lx_init + center_x - self.radius // 10
circ_x_2 = self.lx_init + center_x + self.radius // 10
circ_y_1 = self.ly_init - center_y - self.radius // 10
circ_y_2 = self.ly_init - center_y + self.radius // 10
else:
circ_x_1 = event.x - self.radius // 10
circ_x_2 = event.x + self.radius // 10
circ_y_1 = event.y - self.radius // 10
circ_y_2 = event.y + self.radius // 10
self.coords('lcircle2', circ_x_1, circ_y_1, circ_x_2, circ_y_2, )
self._langle = langle
self._lmag = mag
def mouseLeftRelease(self, ser):
self.config(cursor='tcross')
self.ser.writeRow(
f'3 8 80 80',
is_show=False
)
self.delete("lcircle")
self.delete("lcircle2")
if self.master.is_use_right_stick_mouse.get():
self.BindRightClick()
# self.event_generate('<Motion>', warp=True, x=self.lx_init, y=self.ly_init)
if isTakeLog:
self.dq.append([self._langle,
self._lmag,
time.perf_counter() - self.calc_time])
for _ in self.dq:
self.LSTICK_logger.debug(",".join(list(map(str, _))))
def mouseRightPress(self, event, ser):
if self.master.is_use_left_stick_mouse.get():
self.UnbindLeftClick()
self.config(cursor='dot')
self.rx_init, self.ry_init = event.x, event.y
self.rcircle = self.create_oval(self.rx_init - self.radius, self.ry_init - self.radius,
self.rx_init + self.radius, self.ry_init + self.radius,
outline='red', tag="rcircle")
self.rcircle2 = self.create_oval(self.rx_init - self.radius // 10, self.ry_init - self.radius // 10,
self.rx_init + self.radius // 10, self.ry_init + self.radius // 10,
fill="red", tag="rcircle2")
# self.RStick = StickCommand.StickRight()
# self.RStick.start(ser)
if isTakeLog:
if self.dq is None:
self.dq = deque()
else:
self.dq.clear()
if self.calc_time is None:
self.calc_time = time.perf_counter()
else:
# LSTICK_logger.debug(f"{0},{0},{time.perf_counter() - self.calc_time}")
self.dq.append([0, 0, time.perf_counter() - self.calc_time])
self._rangle = None
self._rmag = None
def mouseRightPressing(self, event, ser, angle=0):
rangle = np.rad2deg(np.arctan2(self.ry_init - event.y, event.x - self.rx_init))
mag = np.sqrt((self.ry_init - event.y) ** 2 + (event.x - self.rx_init) ** 2) / self.radius
if mag <= 0:
mag = 0
elif mag >= 1:
mag = 1
if (self._langle and self._lmag) is not None and isTakeLog:
_time = time.perf_counter()
if _time - self.calc_time > 0.05:
# thread_1 = threading.Thread(target=self.RStick.RStick,
# args=(rangle,),
# kwargs={'r': mag, 'duration': _time - self.calc_time})
# thread_1.start()
# self.RStick.RStick(rangle, r=mag)
self.ser.writeRow(
f'3 8 80 80 '
f'{hex(int(128 + mag * 127.5 * np.cos(np.deg2rad(rangle))))} '
f'{hex(int(128 - mag * 127.5 * np.sin(np.deg2rad(rangle))))}',
is_show=False
)
self.dq.append([rangle, mag, _time - self.calc_time])
self.calc_time = _time
elif not isTakeLog:
self.ser.writeRow(
f'3 8 80 80 '
f'{hex(int(128 + mag * 127.5 * np.cos(np.deg2rad(rangle))))} '
f'{hex(int(128 - mag * 127.5 * np.sin(np.deg2rad(rangle))))}',
is_show=False
)
if mag >= 1:
center_x = (self.radius + self.radius // 11) * np.cos(np.deg2rad(rangle))
center_y = (self.radius + self.radius // 11) * np.sin(np.deg2rad(rangle))
circ_x_1 = self.rx_init + center_x - self.radius // 10
circ_x_2 = self.rx_init + center_x + self.radius // 10
circ_y_1 = self.ry_init - center_y - self.radius // 10
circ_y_2 = self.ry_init - center_y + self.radius // 10
else:
circ_x_1 = event.x - self.radius // 10
circ_x_2 = event.x + self.radius // 10
circ_y_1 = event.y - self.radius // 10
circ_y_2 = event.y + self.radius // 10
self.coords('rcircle2', circ_x_1, circ_y_1, circ_x_2, circ_y_2, )
self._rangle = rangle
self._rmag = mag
def mouseRightRelease(self, ser):
self.config(cursor='tcross')
self.ser.writeRow(
f'3 8 80 80 80 80',
is_show=False
)
self.delete("rcircle")
self.delete("rcircle2")
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
# self.event_generate('<Motion>', warp=True, x=self.rx_init, y=self.ry_init)
if isTakeLog:
self.dq.append([self._rangle,
self._rmag,
time.perf_counter() - self.calc_time])
for _ in self.dq:
self.RSTICK_logger.debug(",".join(list(map(str, _))))
def startCapture(self):
self.capture()
def capture(self):
if self.is_show_var.get():
image_bgr = self.camera.readFrame()
else:
self.after(self.next_frames, self.capture)
return
if image_bgr is not None:
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
image_pil = Image.fromarray(image_rgb).resize(self.show_size)
image_tk = ImageTk.PhotoImage(image_pil)
self.im = image_tk
# self.configure( image=image_tk)
self.itemconfig(self.im_, image=image_tk)
else:
self.im = self.disabled_tk
# self.configure(image=self.disabled_tk)
self.itemconfig(self.im_, image=self.disabled_tk)
self.after(self.next_frames, self.capture)
def saveCapture(self):
self.camera.saveCapture()
def ImgRect(self, x1, y1, x2, y2, outline, tag, ms):
ratio_x = float(self.show_size[0] / self.camera.capture_size[0])
ratio_y = float(self.show_size[1] / self.camera.capture_size[1])
self.create_rectangle((x1 - 1.0) * ratio_x, (y1 - 1.0) * ratio_y, (x2 + 1.0) * ratio_x, (y2 + 1.0) * ratio_y,
width=4.5,
outline="white", tag=tag)
self.create_rectangle(x1 * ratio_x, y1 * ratio_y, x2 * ratio_x, y2 * ratio_y, width=2.5,
outline=outline, tag=tag)
self.after(ms, self.deleteImageRect, tag)
def deleteImageRect(self, tag):
self.delete(tag)
def BindLeftClick(self):
self.bind("<ButtonPress-1>", lambda ev: self.mouseLeftPress(ev, self.ser))
self.bind("<Button1-Motion>", lambda ev: self.mouseLeftPressing(ev, self.ser))
self.bind("<ButtonRelease-1>", lambda ev: self.mouseLeftRelease(self.ser))
self._logger.debug("Bind <ButtonPress-1>")
self._logger.debug("Bind <Button1-Motion>")
self._logger.debug("Bind <ButtonRelease-1>")
def BindRightClick(self):
self.bind("<ButtonPress-3>", lambda ev: self.mouseRightPress(ev, self.ser))
self.bind("<Button3-Motion>", lambda ev: self.mouseRightPressing(ev, self.ser))
self.bind("<ButtonRelease-3>", lambda ev: self.mouseRightRelease(self.ser))
self._logger.debug("Bind <ButtonPress-3>")
self._logger.debug("Bind <Button3-Motion>")
self._logger.debug("Bind <ButtonRelease-3>")
def UnbindLeftClick(self):
self.unbind("<ButtonPress-1>")
self.unbind("<Button1-Motion>")
self.unbind("<ButtonRelease-1>")
self._logger.debug("Unbind <ButtonPress-1>")
self._logger.debug("Unbind <Button1-Motion>")
self._logger.debug("Unbind <ButtonRelease-1>")
def UnbindRightClick(self):
self.unbind("<ButtonPress-3>")
self.unbind("<Button3-Motion>")
self.unbind("<ButtonRelease-3>")
self._logger.debug("Unbind <ButtonPress-3>")
self._logger.debug("Unbind <Button3-Motion>")
self._logger.debug("Unbind <ButtonRelease-3>")
# GUI of switch controller simulator
class ControllerGUI:
def __init__(self, root, ser):
self._logger = getLogger(__name__)
self._logger.addHandler(NullHandler())
self._logger.setLevel(DEBUG)
self._logger.propagate = True
self.window = tk.Toplevel(root)
self.window.title('Switch Controller Simulator')
self.window.geometry("%dx%d%+d%+d" % (600, 300, 250, 125))
self.window.resizable(0, 0)
joycon_L_color = '#95f1ff'
joycon_R_color = '#ff6b6b'
joycon_L_frame = tk.Frame(self.window, width=300, height=300, relief='flat', bg=joycon_L_color)
joycon_R_frame = tk.Frame(self.window, width=300, height=300, relief='flat', bg=joycon_R_color)
hat_frame = tk.Frame(joycon_L_frame, relief='flat', bg=joycon_L_color)
abxy_frame = tk.Frame(joycon_R_frame, relief='flat', bg=joycon_R_color)
# ABXY
tk.Button(abxy_frame, text='A', command=lambda: UnitCommand.A().start(ser)).grid(row=1, column=2)
tk.Button(abxy_frame, text='B', command=lambda: UnitCommand.B().start(ser)).grid(row=2, column=1)
tk.Button(abxy_frame, text='X', command=lambda: UnitCommand.X().start(ser)).grid(row=0, column=1)
tk.Button(abxy_frame, text='Y', command=lambda: UnitCommand.Y().start(ser)).grid(row=1, column=0)
abxy_frame.place(relx=0.2, rely=0.3)
# HAT
tk.Button(hat_frame, text='UP', command=lambda: UnitCommand.UP().start(ser)).grid(row=0, column=1)
tk.Button(hat_frame, text='', command=lambda: UnitCommand.UP_RIGHT().start(ser)).grid(row=0, column=2)
tk.Button(hat_frame, text='RIGHT', command=lambda: UnitCommand.RIGHT().start(ser)).grid(row=1, column=2)
tk.Button(hat_frame, text='', command=lambda: UnitCommand.DOWN_RIGHT().start(ser)).grid(row=2, column=2)
tk.Button(hat_frame, text='DOWN', command=lambda: UnitCommand.DOWN().start(ser)).grid(row=2, column=1)
tk.Button(hat_frame, text='', command=lambda: UnitCommand.DOWN_LEFT().start(ser)).grid(row=2, column=0)
tk.Button(hat_frame, text='LEFT', command=lambda: UnitCommand.LEFT().start(ser)).grid(row=1, column=0)
tk.Button(hat_frame, text='', command=lambda: UnitCommand.UP_LEFT().start(ser)).grid(row=0, column=0)
hat_frame.place(relx=0.2, rely=0.6)
# L side
tk.Button(joycon_L_frame, text='L', width=20, command=lambda: UnitCommand.L().start(ser)).place(x=30, y=30)
tk.Button(joycon_L_frame, text='ZL', width=20, command=lambda: UnitCommand.ZL().start(ser)).place(x=30, y=0)
tk.Button(joycon_L_frame, text='LCLICK', width=7, command=lambda: UnitCommand.LCLICK().start(ser)).place(x=120,
y=120)
tk.Button(joycon_L_frame, text='MINUS', width=5, command=lambda: UnitCommand.MINUS().start(ser)).place(x=220,
y=70)
tk.Button(joycon_L_frame, text='CAP', width=5, command=lambda: UnitCommand.CAPTURE().start(ser)).place(x=200,
y=270)
# R side
tk.Button(joycon_R_frame, text='R', width=20, command=lambda: UnitCommand.R().start(ser)).place(x=120, y=30)
tk.Button(joycon_R_frame, text='ZR', width=20, command=lambda: UnitCommand.ZR().start(ser)).place(x=120, y=0)
tk.Button(joycon_R_frame, text='RCLICK', width=7, command=lambda: UnitCommand.RCLICK().start(ser)).place(x=120,
y=205)
tk.Button(joycon_R_frame, text='PLUS', width=5, command=lambda: UnitCommand.PLUS().start(ser)).place(x=35, y=70)
tk.Button(joycon_R_frame, text='HOME', width=5, command=lambda: UnitCommand.HOME().start(ser)).place(x=50,
y=270)
joycon_L_frame.grid(row=0, column=0)
joycon_R_frame.grid(row=0, column=1)
# button style settings
for button in abxy_frame.winfo_children():
self.applyButtonSetting(button)
for button in hat_frame.winfo_children():
self.applyButtonSetting(button)
for button in [b for b in joycon_L_frame.winfo_children() if type(b) is tk.Button]:
self.applyButtonColor(button)
for button in [b for b in joycon_R_frame.winfo_children() if type(b) is tk.Button]:
self.applyButtonColor(button)
self._logger.debug("Create GUI controller")
def applyButtonSetting(self, button):
button['width'] = 7
self.applyButtonColor(button)
def applyButtonColor(self, button):
button['bg'] = '#343434'
button['fg'] = '#fff'
def bind(self, event, func):
self.window.bind(event, func)
def protocol(self, event, func):
self.window.protocol(event, func)
def focus_force(self):
self.window.focus_force()
def destroy(self):
self.window.destroy()
self._logger.debug("GUI controller destroyed")
# To avoid the error says 'ScrolledText' object has no attribute 'flush'
class MyScrolledText(ScrolledText):
def flush(self):
pass
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import cv2
import os
import time
import tkinter as tk
from tkinter.scrolledtext import ScrolledText
import numpy as np
import datetime
from collections import deque
from PIL import Image, ImageTk
from Commands import UnitCommand
from Commands import StickCommand
from Commands.Keys import Direction, Stick, Button, Direction, KeyPress
import logging
from logging import INFO, StreamHandler, getLogger, DEBUG, NullHandler
from Commands.PythonCommandBase import PythonCommand, StopThread
try:
os.makedirs('log')
except FileExistsError:
pass
isTakeLog = False
# logger_stick = getLogger(__name__)
nowtime = datetime.datetime.fromtimestamp(time.time()).strftime('%Y%m%d_%H%M%S')
# press button at duration times(s)
class MouseStick(PythonCommand):
NAME = 'MOUSEスティック'
def __init__(self):
super().__init__()
self._logger = getLogger(__name__)
self._logger.addHandler(NullHandler())
self._logger.setLevel(DEBUG)
self._logger.propagate = True
def do(self):
pass
def stick(self, buttons, duration=0.1, wait=0.1):
self.keys.input(buttons, ifPrint=False)
self.wait(duration)
self.wait(wait)
# press button at duration times(s)
def stickEnd(self, buttons):
self.keys.inputEnd(buttons)
class CaptureArea(tk.Canvas):
def __init__(self, camera, fps, is_show, ser, master=None, show_width=640, show_height=360):
super().__init__(master, borderwidth=0, cursor='tcross', width=show_width, height=show_height)
self._logger = getLogger(__name__)
self._logger.addHandler(NullHandler())
self._logger.setLevel(DEBUG)
self._logger.propagate = True
self.master = master
self.radius = 60 # 描画する円の半径
self.camera = camera
# self.show_size = (640, 360)
self.show_width = int(show_width)
self.show_height = int(show_height)
self.show_size = (self.show_width, self.show_height)
self.is_show_var = is_show
self.lx_init, self.ly_init = 0, 0
self.rx_init, self.ry_init = 0, 0
self.min_x, self.min_y = 0, 0
self.max_x, self.max_y = 0, 0
self.keys = None
self.ser = ser
self.lcircle = None
self.lcircle2 = None
self.rcircle = None
self.rcircle2 = None
self.LStick = None
self.RStick = None
self.calc_time = None
self.ss = None
self.dq = None
self._langle = None
self._lmag = None
self._rangle = None
self._rmag = None
self.stick_handler = StreamHandler()
self.stick_logging_level = DEBUG
self.stick_handler.setLevel(self.stick_logging_level)
# self._logger.setLevel(self.stick_logging_level)
# self._logger.addHandler(self.stick_handler)
# self._logger.propagate = False
if isTakeLog:
filename_base = os.path.join("log", f"{nowtime}")
self.LS = logging.FileHandler(filename=f"{filename_base}_LStick.log", encoding='utf-8')
self.LS.setLevel(logging.DEBUG)
self.LSTICK_logger = logging.getLogger("L_STICK")
self.LSTICK_logger.setLevel(logging.DEBUG)
self.LSTICK_logger.addHandler(self.LS)
self.RS = logging.FileHandler(filename=f"{filename_base}_RStick.log", encoding='utf-8')
self.RS.setLevel(logging.DEBUG)
self.RSTICK_logger = logging.getLogger("R_STICK")
self.RSTICK_logger.setLevel(logging.DEBUG)
self.RSTICK_logger.addHandler(self.RS)
# self.circle =
self.setFps(fps)
self.bind("<Control-ButtonPress-1>", self.mouseCtrlLeftPress)
self.bind("<Control-ButtonRelease-1>", self.mouseCtrlLeftRelease)
self.bind("<Control-Shift-ButtonPress-1>", self.StartRangeSS)
self.bind("<Control-Shift-Button1-Motion>", self.MotionRangeSS)
self.bind("<Control-Shift-ButtonRelease-1>", self.ReleaseRangeSS)
# Set disabled image first
disabled_img = cv2.imread("../Images/disabled.png", cv2.IMREAD_GRAYSCALE)
disabled_pil = Image.fromarray(disabled_img)
self.disabled_tk = ImageTk.PhotoImage(disabled_pil)
self.im = self.disabled_tk
# self.configure(image=self.disabled_tk) # labelからキャンバスに変更したので微修正
self.im_ = self.create_image(0, 0, image=self.disabled_tk, anchor=tk.NW)
def ApplyLStickMouse(self):
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
else:
self.UnbindLeftClick()
def ApplyRStickMouse(self):
if self.master.is_use_right_stick_mouse.get():
self.BindRightClick()
else:
self.UnbindRightClick()
def StartRangeSS(self, event):
##self.ss = self.camera.image_bgr
image_bgr = self.camera.readFrame()
self.ss = image_bgr
if self.master.is_use_left_stick_mouse.get():
self.UnbindLeftClick()
if self.master.is_use_right_stick_mouse.get():
self.UnbindRightClick()
self.min_x, self.min_y = event.x, event.y
self.delete('SelectArea')
self.create_rectangle(self.min_x,
self.min_y,
self.min_x + 1,
self.min_y + 1,
outline='red',
tag='SelectArea')
ratio_x = float(self.camera.capture_size[0] / self.show_size[0])
ratio_y = float(self.camera.capture_size[1] / self.show_size[1])
print('Mouse down: Show ({}, {}) / Capture ({}, {})'.format(self.min_x, self.min_y,
int(self.min_x * ratio_x),
int(self.min_y * ratio_y)))
self._logger.info('Mouse down: Show ({}, {}) / Capture ({}, {})'.format(self.min_x, self.min_y,
int(self.min_x * ratio_x),
int(self.min_y * ratio_y)))
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
if self.master.is_use_right_stick_mouse.get():
self.BindRightClick()
def MotionRangeSS(self, event):
if event.x < 0:
self.max_x = 0
else:
self.max_x = min(self.show_width, event.x)
if event.y < 0:
self.max_y = 0
else:
self.max_y = min(self.show_height, event.y)
self.coords('SelectArea', self.min_x, self.min_y, self.max_x + 1, self.max_y + 1)
self.coords('SelectAreaFilled', self.min_x, self.min_y, self.max_x + 1, self.max_y + 1)
def ReleaseRangeSS(self, event):
# self.max_x, self.max_y = event.x, event.y
ratio_x = float(self.camera.capture_size[0] / self.show_size[0])
ratio_y = float(self.camera.capture_size[1] / self.show_size[1])
print('Mouse up: Show ({}, {}) / Capture ({}, {})'.format(self.max_x, self.max_y,
int(self.max_x * ratio_x),
int(self.max_y * ratio_y)))
self._logger.info('Mouse up: Show ({}, {}) / Capture ({}, {})'.format(self.max_x, self.max_y,
int(self.max_x * ratio_x),
int(self.max_y * ratio_y)))
if self.min_x > self.max_x:
self.min_x, self.max_x = self.max_x, self.min_x
if self.min_y > self.max_y:
self.min_y, self.max_y = self.max_y, self.min_y
self.camera.saveCapture(crop=1,
crop_ax=[
int(self.min_x * ratio_x), int(self.min_y * ratio_x),
int(self.max_x * ratio_x), int(self.max_y * ratio_x)])
t = 0
self.after(250, self.delete('SelectArea'))
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
if self.master.is_use_right_stick_mouse.get():
self.BindRightClick()
def setFps(self, fps):
# self.next_frames = int(16 * (60 / int(fps)))
self.next_frames = int(1000 / int(fps))
self._logger.info(f"FPS set to {fps}")
def setShowsize(self, show_height, show_width):
self.show_width = int(show_width)
self.show_height = int(show_height)
self.show_size = (self.show_width, self.show_height)
self.config(width=self.show_width, height=self.show_height)
print("Show size set to {0} x {1}".format(self.show_width, self.show_height))
self._logger.info("Show size set to {0} x {1}".format(self.show_width, self.show_height))
def mouseCtrlLeftPress(self, event):
##_img = cv2.cvtColor(self.camera.image_bgr, cv2.COLOR_BGR2RGB)
image_bgr = self.camera.readFrame()
_img = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
if self.master.is_use_left_stick_mouse.get():
self.UnbindLeftClick()
x, y = event.x, event.y
ratio_x = float(self.camera.capture_size[0] / self.show_size[0])
ratio_y = float(self.camera.capture_size[1] / self.show_size[1])
print('Mouse down: Show ({}, {}) / Capture ({}, {})'.format(x, y, int(x * ratio_x), int(y * ratio_y)))
print(f"Color [R: {_img[int(y * ratio_y), int(x * ratio_x)][0]}, "
f"G: {_img[int(y * ratio_y), int(x * ratio_x)][1]}, "
f"B: {_img[int(y * ratio_y), int(x * ratio_x)][2]}]")
self._logger.info(
'Mouse down: Show ({}, {}) / Capture ({}, {})'.format(x, y, int(x * ratio_x), int(y * ratio_y)))
def mouseCtrlLeftRelease(self, event):
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
def mouseLeftPress(self, event, ser):
if self.master.is_use_right_stick_mouse.get():
self.UnbindRightClick()
self.config(cursor='dot')
self.lx_init, self.ly_init = event.x, event.y
self.lcircle = self.create_oval(self.lx_init - self.radius, self.ly_init - self.radius,
self.lx_init + self.radius, self.ly_init + self.radius,
outline='cyan', tag="lcircle")
self.lcircle2 = self.create_oval(self.lx_init - self.radius // 10, self.ly_init - self.radius // 10,
self.lx_init + self.radius // 10, self.ly_init + self.radius // 10,
fill="cyan", tag="lcircle2")
# self.LStick = StickCommand.StickLeft()
# self.LStick.start(ser)
if isTakeLog:
if self.dq is None:
self.dq = deque()
else:
self.dq.clear()
if self.calc_time is None:
self.calc_time = time.perf_counter()
else:
# LSTICK_logger.debug(f"{0},{0},{time.perf_counter() - self.calc_time}")
self.dq.append([0, 0, time.perf_counter() - self.calc_time])
self._langle = None
self._lmag = None
def mouseLeftPressing(self, event, ser, angle=0):
# _time = self.calc_time
langle = np.rad2deg(np.arctan2(self.ly_init - event.y, event.x - self.lx_init))
mag = np.sqrt((self.ly_init - event.y) ** 2 + (event.x - self.lx_init) ** 2) / self.radius
if mag <= 0:
mag = 0
elif mag >= 1:
mag = 1
if (self._langle and self._lmag) is not None and isTakeLog:
_time = time.perf_counter()
if _time - self.calc_time > 0.05:
# thread_1 = threading.Thread(target=self.LStick.LStick,
# args=(langle,),
# kwargs={'r': mag, 'duration': _time - self.calc_time})
# thread_1.start()
self.ser.writeRow(
f'3 8 '
f'{hex(int(128 + mag * 127.5 * np.cos(np.deg2rad(langle))))} '
f'{hex(int(128 - mag * 127.5 * np.sin(np.deg2rad(langle))))} '
f'80 80',
is_show=False
)
self.dq.append([langle,
mag,
_time - self.calc_time])
self.calc_time = _time
elif not isTakeLog:
self.ser.writeRow(
f'3 8 '
f'{hex(int(128 + mag * 127.5 * np.cos(np.deg2rad(langle))))} '
f'{hex(int(128 - mag * 127.5 * np.sin(np.deg2rad(langle))))}'
f' 80 80',
is_show=False
)
if mag >= 1:
center_x = (self.radius + self.radius // 11) * np.cos(np.deg2rad(langle))
center_y = (self.radius + self.radius // 11) * np.sin(np.deg2rad(langle))
circ_x_1 = self.lx_init + center_x - self.radius // 10
circ_x_2 = self.lx_init + center_x + self.radius // 10
circ_y_1 = self.ly_init - center_y - self.radius // 10
circ_y_2 = self.ly_init - center_y + self.radius // 10
else:
circ_x_1 = event.x - self.radius // 10
circ_x_2 = event.x + self.radius // 10
circ_y_1 = event.y - self.radius // 10
circ_y_2 = event.y + self.radius // 10
self.coords('lcircle2', circ_x_1, circ_y_1, circ_x_2, circ_y_2, )
self._langle = langle
self._lmag = mag
def mouseLeftRelease(self, ser):
self.config(cursor='tcross')
self.ser.writeRow(
f'3 8 80 80',
is_show=False
)
self.delete("lcircle")
self.delete("lcircle2")
if self.master.is_use_right_stick_mouse.get():
self.BindRightClick()
# self.event_generate('<Motion>', warp=True, x=self.lx_init, y=self.ly_init)
if isTakeLog:
self.dq.append([self._langle,
self._lmag,
time.perf_counter() - self.calc_time])
for _ in self.dq:
self.LSTICK_logger.debug(",".join(list(map(str, _))))
def mouseRightPress(self, event, ser):
if self.master.is_use_left_stick_mouse.get():
self.UnbindLeftClick()
self.config(cursor='dot')
self.rx_init, self.ry_init = event.x, event.y
self.rcircle = self.create_oval(self.rx_init - self.radius, self.ry_init - self.radius,
self.rx_init + self.radius, self.ry_init + self.radius,
outline='red', tag="rcircle")
self.rcircle2 = self.create_oval(self.rx_init - self.radius // 10, self.ry_init - self.radius // 10,
self.rx_init + self.radius // 10, self.ry_init + self.radius // 10,
fill="red", tag="rcircle2")
# self.RStick = StickCommand.StickRight()
# self.RStick.start(ser)
if isTakeLog:
if self.dq is None:
self.dq = deque()
else:
self.dq.clear()
if self.calc_time is None:
self.calc_time = time.perf_counter()
else:
# LSTICK_logger.debug(f"{0},{0},{time.perf_counter() - self.calc_time}")
self.dq.append([0, 0, time.perf_counter() - self.calc_time])
self._rangle = None
self._rmag = None
def mouseRightPressing(self, event, ser, angle=0):
rangle = np.rad2deg(np.arctan2(self.ry_init - event.y, event.x - self.rx_init))
mag = np.sqrt((self.ry_init - event.y) ** 2 + (event.x - self.rx_init) ** 2) / self.radius
if mag <= 0:
mag = 0
elif mag >= 1:
mag = 1
if (self._langle and self._lmag) is not None and isTakeLog:
_time = time.perf_counter()
if _time - self.calc_time > 0.05:
# thread_1 = threading.Thread(target=self.RStick.RStick,
# args=(rangle,),
# kwargs={'r': mag, 'duration': _time - self.calc_time})
# thread_1.start()
# self.RStick.RStick(rangle, r=mag)
self.ser.writeRow(
f'3 8 80 80 '
f'{hex(int(128 + mag * 127.5 * np.cos(np.deg2rad(rangle))))} '
f'{hex(int(128 - mag * 127.5 * np.sin(np.deg2rad(rangle))))}',
is_show=False
)
self.dq.append([rangle, mag, _time - self.calc_time])
self.calc_time = _time
elif not isTakeLog:
self.ser.writeRow(
f'3 8 80 80 '
f'{hex(int(128 + mag * 127.5 * np.cos(np.deg2rad(rangle))))} '
f'{hex(int(128 - mag * 127.5 * np.sin(np.deg2rad(rangle))))}',
is_show=False
)
if mag >= 1:
center_x = (self.radius + self.radius // 11) * np.cos(np.deg2rad(rangle))
center_y = (self.radius + self.radius // 11) * np.sin(np.deg2rad(rangle))
circ_x_1 = self.rx_init + center_x - self.radius // 10
circ_x_2 = self.rx_init + center_x + self.radius // 10
circ_y_1 = self.ry_init - center_y - self.radius // 10
circ_y_2 = self.ry_init - center_y + self.radius // 10
else:
circ_x_1 = event.x - self.radius // 10
circ_x_2 = event.x + self.radius // 10
circ_y_1 = event.y - self.radius // 10
circ_y_2 = event.y + self.radius // 10
self.coords('rcircle2', circ_x_1, circ_y_1, circ_x_2, circ_y_2, )
self._rangle = rangle
self._rmag = mag
def mouseRightRelease(self, ser):
self.config(cursor='tcross')
self.ser.writeRow(
f'3 8 80 80 80 80',
is_show=False
)
self.delete("rcircle")
self.delete("rcircle2")
if self.master.is_use_left_stick_mouse.get():
self.BindLeftClick()
# self.event_generate('<Motion>', warp=True, x=self.rx_init, y=self.ry_init)
if isTakeLog:
self.dq.append([self._rangle,
self._rmag,
time.perf_counter() - self.calc_time])
for _ in self.dq:
self.RSTICK_logger.debug(",".join(list(map(str, _))))
def startCapture(self):
self.capture()
def capture(self):
if self.is_show_var.get():
image_bgr = self.camera.readFrame()
else:
self.after(self.next_frames, self.capture)
return
if image_bgr is not None:
image_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
image_pil = Image.fromarray(image_rgb).resize(self.show_size)
image_tk = ImageTk.PhotoImage(image_pil)
self.im = image_tk
# self.configure( image=image_tk)
self.itemconfig(self.im_, image=image_tk)
else:
self.im = self.disabled_tk
# self.configure(image=self.disabled_tk)
self.itemconfig(self.im_, image=self.disabled_tk)
self.after(self.next_frames, self.capture)
def saveCapture(self):
self.camera.saveCapture()
def ImgRect(self, x1, y1, x2, y2, outline, tag, ms):
ratio_x = float(self.show_size[0] / self.camera.capture_size[0])
ratio_y = float(self.show_size[1] / self.camera.capture_size[1])
self.create_rectangle((x1 - 1.0) * ratio_x, (y1 - 1.0) * ratio_y, (x2 + 1.0) * ratio_x, (y2 + 1.0) * ratio_y,
width=4.5,
outline="white", tag=tag)
self.create_rectangle(x1 * ratio_x, y1 * ratio_y, x2 * ratio_x, y2 * ratio_y, width=2.5,
outline=outline, tag=tag)
self.after(ms, self.deleteImageRect, tag)
def deleteImageRect(self, tag):
self.delete(tag)
def BindLeftClick(self):
self.bind("<ButtonPress-1>", lambda ev: self.mouseLeftPress(ev, self.ser))
self.bind("<Button1-Motion>", lambda ev: self.mouseLeftPressing(ev, self.ser))
self.bind("<ButtonRelease-1>", lambda ev: self.mouseLeftRelease(self.ser))
self._logger.debug("Bind <ButtonPress-1>")
self._logger.debug("Bind <Button1-Motion>")
self._logger.debug("Bind <ButtonRelease-1>")
def BindRightClick(self):
self.bind("<ButtonPress-3>", lambda ev: self.mouseRightPress(ev, self.ser))
self.bind("<Button3-Motion>", lambda ev: self.mouseRightPressing(ev, self.ser))
self.bind("<ButtonRelease-3>", lambda ev: self.mouseRightRelease(self.ser))
self._logger.debug("Bind <ButtonPress-3>")
self._logger.debug("Bind <Button3-Motion>")
self._logger.debug("Bind <ButtonRelease-3>")
def UnbindLeftClick(self):
self.unbind("<ButtonPress-1>")
self.unbind("<Button1-Motion>")
self.unbind("<ButtonRelease-1>")
self._logger.debug("Unbind <ButtonPress-1>")
self._logger.debug("Unbind <Button1-Motion>")
self._logger.debug("Unbind <ButtonRelease-1>")
def UnbindRightClick(self):
self.unbind("<ButtonPress-3>")
self.unbind("<Button3-Motion>")
self.unbind("<ButtonRelease-3>")
self._logger.debug("Unbind <ButtonPress-3>")
self._logger.debug("Unbind <Button3-Motion>")
self._logger.debug("Unbind <ButtonRelease-3>")
# GUI of switch controller simulator
class ControllerGUI:
def __init__(self, root, ser):
self._logger = getLogger(__name__)
self._logger.addHandler(NullHandler())
self._logger.setLevel(DEBUG)
self._logger.propagate = True
self.window = tk.Toplevel(root)
self.window.title('Switch Controller Simulator')
self.window.geometry("%dx%d%+d%+d" % (600, 300, 250, 125))
self.window.resizable(0, 0)
joycon_L_color = '#95f1ff'
joycon_R_color = '#ff6b6b'
joycon_L_frame = tk.Frame(self.window, width=300, height=300, relief='flat', bg=joycon_L_color)
joycon_R_frame = tk.Frame(self.window, width=300, height=300, relief='flat', bg=joycon_R_color)
hat_frame = tk.Frame(joycon_L_frame, relief='flat', bg=joycon_L_color)
abxy_frame = tk.Frame(joycon_R_frame, relief='flat', bg=joycon_R_color)
# ABXY
tk.Button(abxy_frame, text='A', command=lambda: UnitCommand.A().start(ser)).grid(row=1, column=2)
tk.Button(abxy_frame, text='B', command=lambda: UnitCommand.B().start(ser)).grid(row=2, column=1)
tk.Button(abxy_frame, text='X', command=lambda: UnitCommand.X().start(ser)).grid(row=0, column=1)
tk.Button(abxy_frame, text='Y', command=lambda: UnitCommand.Y().start(ser)).grid(row=1, column=0)
abxy_frame.place(relx=0.2, rely=0.3)
# HAT
tk.Button(hat_frame, text='UP', command=lambda: UnitCommand.UP().start(ser)).grid(row=0, column=1)
tk.Button(hat_frame, text='', command=lambda: UnitCommand.UP_RIGHT().start(ser)).grid(row=0, column=2)
tk.Button(hat_frame, text='RIGHT', command=lambda: UnitCommand.RIGHT().start(ser)).grid(row=1, column=2)
tk.Button(hat_frame, text='', command=lambda: UnitCommand.DOWN_RIGHT().start(ser)).grid(row=2, column=2)
tk.Button(hat_frame, text='DOWN', command=lambda: UnitCommand.DOWN().start(ser)).grid(row=2, column=1)
tk.Button(hat_frame, text='', command=lambda: UnitCommand.DOWN_LEFT().start(ser)).grid(row=2, column=0)
tk.Button(hat_frame, text='LEFT', command=lambda: UnitCommand.LEFT().start(ser)).grid(row=1, column=0)
tk.Button(hat_frame, text='', command=lambda: UnitCommand.UP_LEFT().start(ser)).grid(row=0, column=0)
hat_frame.place(relx=0.2, rely=0.6)
# L side
tk.Button(joycon_L_frame, text='L', width=20, command=lambda: UnitCommand.L().start(ser)).place(x=30, y=30)
tk.Button(joycon_L_frame, text='ZL', width=20, command=lambda: UnitCommand.ZL().start(ser)).place(x=30, y=0)
tk.Button(joycon_L_frame, text='LCLICK', width=7, command=lambda: UnitCommand.LCLICK().start(ser)).place(x=120,
y=120)
tk.Button(joycon_L_frame, text='MINUS', width=5, command=lambda: UnitCommand.MINUS().start(ser)).place(x=220,
y=70)
tk.Button(joycon_L_frame, text='CAP', width=5, command=lambda: UnitCommand.CAPTURE().start(ser)).place(x=200,
y=270)
# R side
tk.Button(joycon_R_frame, text='R', width=20, command=lambda: UnitCommand.R().start(ser)).place(x=120, y=30)
tk.Button(joycon_R_frame, text='ZR', width=20, command=lambda: UnitCommand.ZR().start(ser)).place(x=120, y=0)
tk.Button(joycon_R_frame, text='RCLICK', width=7, command=lambda: UnitCommand.RCLICK().start(ser)).place(x=120,
y=205)
tk.Button(joycon_R_frame, text='PLUS', width=5, command=lambda: UnitCommand.PLUS().start(ser)).place(x=35, y=70)
tk.Button(joycon_R_frame, text='HOME', width=5, command=lambda: UnitCommand.HOME().start(ser)).place(x=50,
y=270)
joycon_L_frame.grid(row=0, column=0)
joycon_R_frame.grid(row=0, column=1)
# button style settings
for button in abxy_frame.winfo_children():
self.applyButtonSetting(button)
for button in hat_frame.winfo_children():
self.applyButtonSetting(button)
for button in [b for b in joycon_L_frame.winfo_children() if type(b) is tk.Button]:
self.applyButtonColor(button)
for button in [b for b in joycon_R_frame.winfo_children() if type(b) is tk.Button]:
self.applyButtonColor(button)
self._logger.debug("Create GUI controller")
def applyButtonSetting(self, button):
button['width'] = 7
self.applyButtonColor(button)
def applyButtonColor(self, button):
button['bg'] = '#343434'
button['fg'] = '#fff'
def bind(self, event, func):
self.window.bind(event, func)
def protocol(self, event, func):
self.window.protocol(event, func)
def focus_force(self):
self.window.focus_force()
def destroy(self):
self.window.destroy()
self._logger.debug("GUI controller destroyed")
# To avoid the error says 'ScrolledText' object has no attribute 'flush'
class MyScrolledText(ScrolledText):
def flush(self):
pass
変更後、Poke-Controllerの再起動を実行してください。
以上でラズベリーパイにPoke-Controllerを導入する手順は終了です。
Poke-Controller上で動作させるpythonプログラムについては各自修正してください。
必要に応じてラズベリーパイ版Visual Studio Codeと画像編集のためにGIMPをインストールしておくと便利です。
sudo apt install code
sudo apt install gimp
pokecon poke-controller ぽけこん ポケコン ポケコントローラー NX NX2 NX Macro Controller 2 ラズパイ ラズベリーパイ raspberry pi
この記事が気に入ったらサポートをしてみませんか?