ocoge/lib/pybfm.py

224 lines
6.1 KiB
Python

#!/usr/bin/env python3
from tkinter import *
try:
from tkinterdnd2 import *
except ImportError:
dnd = False
else:
dnd = True
import os
import sys
import pyboard
import serial.tools.list_ports
from tkinter import filedialog
##### 引数 #####
args = sys.argv
try:
dev = args[1]
except IndexError as err:
dev = False
else:
dev = (dev=='-f')
##### グローバル変数 #####
gport = None
##### イベントハンドラ #####
# リストボックスにドロップ
def listbox_drop(event):
files = listbox.tk.splitlist(event.data)
putfiles(files)
# キーボード
def input_key(ev):
if ev.keysym == 'F5':
reload()
elif ev.keysym == 'Delete':
rmfiles()
# ダブルクリック
def dbl_click(ev):
run()
# 右クリックメニュー
def pop_menu(ev):
listbox.select_clear(0, END)
listbox.select_set(listbox.nearest(ev.y))
pmenu.post(ev.x_root, ev.y_root)
##### pyboard関数 #####
# 接続・repl開始
def connect():
global gport
try:
pyb = pyboard.Pyboard(gport)
except pyboard.PyboardError as err:
gport = None
listbox.delete(0, END)
set_title('Lost device : Please reload')
raise pyboard.PyboardError('Lost device')
pyb.enter_raw_repl(False)
return pyb
# repl終了・切断
def disconnect(pyb):
try:
pyb.exit_raw_repl()
except Exception as err:
pass
try:
pyb.close()
except Exception as err:
pass
# ファイル送信 (複数可)(files: list of local filepath)
def putfiles(files):
pyb = connect()
for src in files:
if os.path.exists(src):
if os.path.isfile(src):
dest = os.path.basename(src)
print('ファイル "%s" を転送' % src)
pyb.fs_put(src, dest)
else:
print('フォルダは転送できません : %s' % src)
else:
print('ファイル "%s" は見つかりません。' % f)
ls(pyb)
disconnect(pyb)
# ポートスキャン:アルファベット順で一番若いポートを返す
def find_device():
global gport
for p in sorted(serial.tools.list_ports.comports()):
if p.hwid.startswith('USB'):
gport = p.device
set_title(gport)
break
else:
gport = None
set_title('No device')
raise OSError('Device not found')
# 再読込:ここのみデバイスの再スキャンが入る
def reload():
if not gport:
find_device()
if gport:
listfiles()
# ファイルリスト取得だけを行う
def listfiles():
pyb = connect()
ls(pyb)
disconnect(pyb)
# ファイルリストを取得し、リストボックスに表示 (pyb: pyboard handle, src: target directory on device)
def ls(pyb, src='/'):
cmd = (
"import uos\nfor f in uos.listdir(%s):\n"
" print(f)"
% (("'%s'" % src) if src else "")
)
retval = pyb.exec(cmd)
files = retval.decode('utf-8').splitlines()
listbox.delete(0, END)
for f in files:
listbox.insert(END, f)
# ウィンドウタイトル (title: string)
def set_title(title):
root.title('PyBfm - ' + title)
# デバイス上のファイルを実行
def run(follow=False):
selected = listbox.curselection()
if len(selected):
src = listbox.get(selected[0])
ext = os.path.splitext(src)[1]
if ext == '.py':
cmd = 'exec(open("%s").read())' % src
pyb = connect()
try:
if follow:
print (pyb.exec(cmd).decode('utf-8'))
else:
pyb.exec_raw_no_follow(cmd)
except Exception as err:
print("Runtime error.")
print('Done.')
disconnect(pyb)
else:
print('このファイルは実行できません')
# ファイル選択ダイアログからファイル送信
def putdlg():
fpath = filedialog.askopenfilename()
if fpath:
putfiles([fpath])
# ファイル受信
def getfile():
selected = listbox.curselection()
if len(selected):
src = listbox.get(selected[0])
ext = os.path.splitext(src)[1]
dest = filedialog.asksaveasfilename(
initialfile=src,
defaultextension=ext,
filetypes=[('変更なし', ext), ('全てのファイル', '.*')]
)
if dest:
pyb = connect()
pyb.fs_get(src, dest)
disconnect(pyb)
# デバイス上のファイル削除(複数可)
def rmfiles():
selected = listbox.curselection()
if len(selected):
pyb = connect()
for i in selected:
src = listbox.get(i)
pyb.fs_rm(src)
print('Deleted %s' % src)
ls(pyb)
disconnect(pyb)
# 何もしない関数
def do_nothing():
pass
##### メイン #####
# メインウィンドウの生成
if dnd:
root = TkinterDnD.Tk()
else:
root = Tk()
root.title('PyBfm')
root.geometry('400x300')
pmenu = Menu(root, tearoff=0)
pmenu.add_command(label="実行", command=run)
if dev:
pmenu.add_command(label="実行 (追跡)", command=lambda:run(True))
pmenu.add_command(label="送る", command=putdlg)
pmenu.add_command(label="取得", command=getfile)
pmenu.add_command(label="削除", command=rmfiles)
pmenu.add_command(label="再読込", command=reload)
pmenu.add_command(label="閉じる", command=do_nothing)
# root.config(bg='#cccccc')
# Frameウィジェットの生成
frame = Frame(root)
# Listboxウィジェットの生成
listbox = Listbox(frame, selectmode=EXTENDED)
if dnd:
listbox.drop_target_register(DND_FILES)
listbox.dnd_bind('<<Drop>>', listbox_drop)
listbox.bind("<KeyPress>", input_key)
listbox.bind("<Double-Button-1>", dbl_click)
listbox.bind("<Button-3>", pop_menu)
# スクロールバーの生成
scroll = Scrollbar(frame, orient=VERTICAL)
listbox.configure(yscrollcommand=scroll.set)
scroll.config(command=listbox.yview)
# ウィジェットの配置
frame.pack(expand=True,fill=BOTH)
listbox.pack(expand=True,fill=BOTH, side=LEFT)
scroll.pack(side=RIGHT, fill=Y)
try:
find_device()
except Exception as err:
pass
else:
listfiles()
root.mainloop()