-
Notifications
You must be signed in to change notification settings - Fork 4
/
thumby.py
executable file
·158 lines (127 loc) · 5.39 KB
/
thumby.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python3
# This sends everything in ./Games/ to your Thumby, and launches the program. It
# only sends files modified since the last send to your Tumby to save time. If
# you want to, it can also compile some specific files to `.mpy`, and send those
# instead of the `.py` files.
# Tested on Mac, depends on Ampy and mpy_cross being installed.
# See https://github.com/scientifichackers/ampy#installation for instructions
# for Ampy and do something like this for mpy_cross:
# pip3 install mpy-cross-v5
# Available commands:
# `./thumby.py` --> Compiles your project, sends it to Thumby and runs it
# `./thumby.py build` --> Only compiles your project
# `./thumby.py send` --> Only sends the files in your project to the Thumby
# `./thumby.py run` --> Only runs the project on the Thumby
# Start of config:
# This should be your main file to run on the Thumby:
initFile = '/Games/GrayscaleTest/GrayscaleTest.py'
# This file keeps track of the last time files were sent to the Thumby.
# Add it to your .gitignore file if you use Git.
timeFile = 'send.time'
# These are the files you want to have compiled to `.mpy` files.
# Use an empty list to disable compilation.
filesToCompileToMPY = []
# End of config, start of script
from os import listdir, system
from os.path import isfile, isdir, join, getmtime, splitext, exists
from glob import glob
from textwrap import dedent
from inspect import getsource
from ampy import pyboard, files
from sys import argv, platform
def build(files):
import mpy_cross
for file in files:
name, ext = splitext(file)
mpyfile = name + '.mpy'
if not exists(mpyfile) or (exists(mpyfile) and getmtime(mpyfile) < getmtime(file)):
print("Compiling file", file[1:])
mpy_cross.run(file)
# Functions to run on the Thumby
def present(file):
from os import stat
try:
stat(file)
print(True)
except OSError:
print(False)
def startProgram(file):
__import__(file)
# Thumby abstraction to use from the PC side
class Thumby:
def _thumby(self):
if not hasattr(self, 'thumby'):
if platform == 'darwin':
devices = glob('/dev/tty.usbmodem*')
elif platform == 'linux' or platform == 'linux2':
devices = glob('/dev/ttyACM*')
else:
raise Exception(f"Don't know how to find Thumby on {platform}")
port = devices and devices[0]
if not port:
print('Could not find your Thumby! Is it plugged in and turned on..?')
exit()
try:
self.thumby = pyboard.Pyboard(port)
except pyboard.PyboardError as err:
print('Ampy gave an error opening the device. Is code.thumby.us interfering..?')
exit()
return self.thumby
def _files(self):
if not hasattr(self, 'files'):
self.files = files.Files(self._thumby())
return self.files
def _hasBeenUpdated(self, file):
return not isfile(timeFile) or getmtime(file) > getmtime(timeFile)
def _thumbyCall(self, command, streaming=False):
self._thumby().enter_raw_repl()
result = self._thumby().exec(dedent(command), streaming)
self._thumby().exit_raw_repl()
return result.decode('utf-8')
def execute(self, function, args=[], verbose=False):
code = dedent(getsource(function))
args = map(lambda v: f'"{v}"' if isinstance(v, str) else f'{v}', args)
args = ','.join(args)
code += f'\n{function.__name__}({args})\n'
if verbose: print("Running on Thumby:\n\n", code)
return self._thumbyCall(code, verbose)
def exists(self, file):
return self.execute(present, [file]).strip() == "True"
def put(self, localfile, remotefile):
with open(localfile, "rb") as infile:
data = infile.read()
self._files().put(remotefile, data)
def send(self, path):
for f in listdir(path):
file = join(path, f)
remotefile = file[1:]
# Skip hidden files, including .DS_Store
if f[0] == '.':
continue
# Send newly updated files to Thumby
if isfile(file) and self._hasBeenUpdated(file):
name, ext = splitext(file)
if ext == '.py' and isfile(name + '.mpy'):
if self.exists(remotefile):
print('Removing file', remotefile, '(because .mpy version exists now)')
self._files().rm(remotefile)
else:
print('Skipping file', remotefile, '(because there is a .mpy version)')
else:
print("Sending file", remotefile)
self.put(file, remotefile)
# Create directories that don't exist yet
if isdir(file):
self._files().mkdir(remotefile, True)
self.send(file)
# When ran from the command line, execution starts here:
if __name__ == "__main__":
arg = None if len(argv) < 2 else argv[1]
thumby = Thumby()
if (arg == None or arg == 'build') and len(filesToCompileToMPY) > 0:
build(filesToCompileToMPY)
if arg == None or arg == 'send':
thumby.send('./Games/')
system('touch send.time')
if arg == None or arg == 'run':
thumby.execute(startProgram, [initFile], verbose=True)