#!/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('<>', listbox_drop) listbox.bind("", input_key) listbox.bind("", dbl_click) listbox.bind("", 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()