Files
primadiag-pybash/picowatch_d/picowatch.py

415 lines
12 KiB
Python
Raw Normal View History

2022-12-30 23:33:59 +01:00
import os
2022-12-29 16:37:55 +01:00
import sys
import time
2022-12-30 23:33:59 +01:00
import json
import serial
2022-12-29 17:20:41 +01:00
import binascii
2022-12-30 23:33:59 +01:00
import textwrap
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
from typing import Dict, List, Optional, Tuple
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
class Pyboard:
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
PYBOARD_BUFFER_SIZE: int = 256
serial: serial.Serial
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def __init__(self, device: str, baudrate: int = 115200):
for _ in range(0, 3):
try:
self.serial = serial.Serial(device, baudrate=baudrate, interCharTimeout=1)
break
except:
time.sleep(1)
else:
raise Exception(f'Failed to access {device}')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def close(self):
self.serial.close()
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def stdout_write_bytes(self, data: str):
sys.stdout.buffer.write(data.replace(b'\x04', b''))
sys.stdout.buffer.flush()
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def send_ok(self) -> bytes:
self.serial.write(b'\x04')
return b'OK'
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def send_ctrl_a(self) -> bytes:
# ctrl-A: enter raw REPL
self.serial.write(b'\x01')
return b'raw REPL; CTRL-B to exit\r\n>'
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def send_ctrl_b(self):
# ctrl-B: exit raw REPL
self.serial.write(b'\x02')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def send_ctrl_c(self, max_times: int = 2) -> bytes:
# ctrl-C twice: interrupt any running program
for _ in range(0, max_times):
self.serial.write(b'\x03')
time.sleep(0.1)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
return b'raw REPL; CTRL-B to exit\r\n'
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def send_ctrl_d(self) -> bytes:
# ctrl-D: soft reset
self.serial.write(b'\x04')
return b'soft reboot\r\n'
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def read_until(self, delimiter: bytes, stream_output: bool = False) -> Optional[bytes]:
data = self.serial.read(1)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if stream_output:
self.stdout_write_bytes(data)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
timeout = 0
max_len = len(delimiter)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
while not timeout >= 1000:
if data.endswith(delimiter):
return data
elif self.serial.inWaiting() > 0:
timeout = 0
stream_data = self.serial.read(1)
data += stream_data
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if stream_output:
self.stdout_write_bytes(stream_data)
data = data[-max_len:]
else:
timeout += 1
time.sleep(0.001)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def __enter__(self):
self.send_ctrl_c()
2022-12-29 17:20:41 +01:00
n = self.serial.inWaiting()
2022-12-30 23:33:59 +01:00
2022-12-29 17:20:41 +01:00
while n > 0:
self.serial.read(n)
n = self.serial.inWaiting()
2022-12-30 23:33:59 +01:00
for _ in range(0, 5):
if self.read_until(self.send_ctrl_a()):
2022-12-29 17:20:41 +01:00
break
2022-12-30 23:33:59 +01:00
time.sleep(0.01)
2022-12-29 17:20:41 +01:00
else:
2022-12-30 23:33:59 +01:00
raise Exception('REPL: could not enter')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if not self.read_until(self.send_ctrl_d()):
raise Exception('REPL: could not soft reboot')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if not self.read_until(self.send_ctrl_c()):
raise Exception('REPL: could not interrupt after soft reboot')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
return self.__terminal
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def __exit__(self, a, b, c):
self.send_ctrl_b()
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def __terminal(self, command: str, stream_output: bool = False, catch_output: bool = True) -> Optional[str]:
command = textwrap.dedent(command)
# send input
if not isinstance(command, bytes):
command = bytes(command, encoding='utf8')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if not self.read_until(b'>'):
raise Exception('Terminal: prompt has been lost')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
for i in range(0, len(command), Pyboard.PYBOARD_BUFFER_SIZE):
self.serial.write(command[i: min(i + Pyboard.PYBOARD_BUFFER_SIZE, len(command))])
time.sleep(0.001)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if not self.read_until(self.send_ok()):
raise Exception('Terminal: could not execute command')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if not catch_output:
return
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
# catch output
data = self.read_until(b'\x04', stream_output=stream_output)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if not data:
raise Exception('Terminal: timeout waiting for first EOF reception')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
exception = self.read_until(b'\x04')
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if not exception:
raise Exception('Terminal: timeout waiting for second EOF reception')
2022-12-29 20:08:50 +01:00
2022-12-30 23:33:59 +01:00
data, exception = (data[:-1].decode('utf-8'), exception[:-1].decode('utf-8'))
2022-12-29 20:08:50 +01:00
2022-12-30 23:33:59 +01:00
if exception:
2022-12-29 20:08:50 +01:00
reason = 'Traceback (most recent call last):'
2022-12-30 23:33:59 +01:00
traceback = exception.split(reason)
2022-12-29 20:08:50 +01:00
if len(traceback) == 2:
for call in traceback[1][:-2].split('\\r\\n'):
reason += f'{call}\n'
2022-12-30 23:33:59 +01:00
raise Exception(reason.strip())
else:
raise Exception(exception)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
return data.strip()
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
class Picowatch(object):
_pyboard: Pyboard
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
@property
def pyboard(self) -> Optional[Pyboard]:
return self._pyboard
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def __init__(self, pyboard: Pyboard):
self._pyboard = pyboard
def checksum(self, source: str, data: str = '') -> bool:
output = self.terminal(f"""
def checksum(data):
v = 21
for c in data.decode("utf-8"):
v ^= ord(c)
return v
with open("{source}", "rb") as fh:
print(checksum(fh.read()))
""")
if isinstance(data, bytes):
data = data.decode('utf-8')
v = 21
for c in data:
v ^= ord(c)
return str(v) == output
def get(self, filename: str) -> Tuple[bytes, bool]:
if not filename.startswith('/'):
filename = '/' + filename
output = self.terminal(f"""
2022-12-29 17:20:41 +01:00
import sys
import ubinascii
with open('{filename}', 'rb') as infile:
while True:
2022-12-30 23:33:59 +01:00
result = infile.read({Pyboard.PYBOARD_BUFFER_SIZE})
2022-12-29 17:20:41 +01:00
if result == b'':
break
2022-12-30 23:33:59 +01:00
sys.stdout.write(ubinascii.hexlify(result))
""")
output = binascii.unhexlify(output)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
return (output, self.checksum(filename, output))
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def save(self, source: str, destination: str) -> Tuple[str, bool]:
output, checksum = self.get(source)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
with open(destination, 'wb') as fh:
fh.write(output)
return (destination, checksum)
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def put(self, filename: str, data: bytes) -> Tuple[str, bool]:
if not filename.startswith('/'):
filename = '/' + filename
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
if not isinstance(data, bytes):
data = bytes(data, encoding='utf8')
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
try:
2022-12-29 16:37:55 +01:00
if os.path.dirname(filename):
2022-12-30 23:33:59 +01:00
self.mkdir(os.path.dirname(filename))
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
with self._pyboard as terminal:
size = len(data)
terminal(f"""fh = open("{filename}", "wb")""")
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
for i in range(0, size, Pyboard.PYBOARD_BUFFER_SIZE):
chunk_size = min(Pyboard.PYBOARD_BUFFER_SIZE, size - i)
chunk = repr(data[i : i + chunk_size])
terminal(f"""fh.write({chunk})""")
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
terminal("""fh.close()""")
except Exception as e:
raise e
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
return (filename, self.checksum(filename, data))
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
def copy(self, source: str, destination: str) -> Tuple[str, int, bool]:
with open(source, 'rb') as fh:
return self.put(destination, fh.read())
2022-12-29 20:08:50 +01:00
2022-12-30 23:33:59 +01:00
def ls(self, dirname: str = '/') -> List[Dict[str, int]]:
if not dirname.startswith('/'):
dirname = '/' + dirname
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
if not dirname.endswith('/'):
dirname += '/'
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
output = self.terminal(f"""
try:
import os
import json
except ImportError:
import uos as os
import ujson as json
def ls(dirname):
d = []
f = []
if not dirname.endswith("/"):
dirname += "/"
for t in os.ilistdir(dirname):
if t[1] == 0x4000:
d.append(dirname + t[0] + '/')
zd, zf = ls(dirname + t[0] + '/')
d.extend(zd)
f.extend(zf)
else:
f.append((dirname + t[0], os.stat(dirname + t[0])[6]))
return d, f
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
print(json.dumps(ls("{dirname}")))
""")
return json.loads(output)
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
def rm(self, filename: str) -> List:
if not filename.startswith('/'):
filename = '/' + filename
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
output = self.terminal(f"""
try:
import os
import json
except ImportError:
import uos as os
import ujson as json
def ls(dirname):
d = []
f = []
if not dirname.endswith("/"):
dirname += "/"
for t in os.ilistdir(dirname):
if t[1] == 0x4000:
d.append(dirname + t[0] + '/')
zd, zf = ls(dirname + t[0] + '/')
d.extend(zd)
f.extend(zf)
else:
f.append(dirname + t[0])
return d, f
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
def rm(filename):
r = []
if os.stat(filename)[0] == 0x4000:
d, f = ls(filename)
for zf in f:
try:
os.remove(zf)
r.append((zf, 1))
except Exception as e:
r.append((zf, 0, str(e)))
for zd in d:
try:
os.rmdir(zd)
r.append((zd, 1))
except Exception as e:
r.append((zd, 0, str(e)))
try:
os.rmdir(filename)
r.append((filename, 1))
except Exception as e:
r.append((filename, 0, str(e)))
else:
try:
os.remove(filename)
r.append((filename, 1))
except Exception as e:
r.append((filename, 0, str(e)))
return r
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
try:
print(json.dumps(rm("{filename}")))
except Exception as e:
print(json.dumps([("{filename}", 0, str(e))]))
""")
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
return json.loads(output)
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
def mkdir(self, dirname: str) -> List:
if not dirname.startswith('/'):
dirname = '/' + dirname
if dirname.endswith('/'):
dirname = dirname[:-1]
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
output = self.terminal(f"""
try:
import os
import json
except ImportError:
import uos as os
import ujson as json
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
r = []
d = []
for zd in str("{dirname}").split("/"):
if not zd:
continue
d.append(zd)
zd = "/".join(d)
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
try:
os.mkdir(zd)
r.append(("/" + zd, 1))
except Exception as e:
if str(e).find('EEXIST'):
r.append(("/" + zd, 1))
else:
r.append(("/" + zd, 0, str(e)))
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
print(json.dumps(r))
""")
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
return json.loads(output)
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
def launch(self, filename: str):
try:
self.terminal(f"""
with open("{filename}", "r") as fh:
exec(fh.read())
""", stream_output=True)
except Exception as e:
print(str(e))
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
def devmode(self, filename: str):
with self._pyboard as terminal:
try:
with open(filename, 'r') as fh:
terminal(fh.read(), stream_output=True)
except Exception as e:
print(str(e))
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
def terminal(self, command: str, stream_output: bool = False) -> str:
with self._pyboard as terminal:
try:
return terminal(command, stream_output=stream_output)
except Exception as e:
raise e
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
# picowatch = Picowatch(Pyboard('COM5'))
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
# with open('./setenv.py', 'rb') as fh:
# print(picowatch.put('setenv.py', fh.read()))
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
# print(picowatch.get('setenv.py'))
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
# print(picowatch.save('main.py', 'cpmain.py'))
# print(picowatch.copy('cpmain.py', 'main.py'))
2022-12-29 16:37:55 +01:00
2022-12-30 23:33:59 +01:00
# picowatch.devmode('main.py')