Files
primadiag-pybash/src/picowatch.py

884 lines
28 KiB
Python
Raw Normal View History

2022-12-31 01:56:48 +01:00
#!/usr/bin/env python
# Primadiag SAS - Paris
# Author: Gino D.
# Copyright (c) 2023 Primadiag
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE
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
2022-12-31 03:04:11 +01:00
import signal
2023-01-02 11:12:01 +01:00
import tempfile
2022-12-29 17:20:41 +01:00
import binascii
2022-12-30 23:33:59 +01:00
import textwrap
2023-01-05 16:56:52 +01:00
import mpy_cross
2023-01-02 11:12:01 +01:00
import subprocess
2022-12-29 17:20:41 +01:00
2022-12-31 03:36:55 +01:00
from serial import Serial
2023-01-04 18:28:31 +01:00
from dotenv import dotenv_values
2023-01-04 15:40:22 +01:00
from typing import List, Optional, Tuple, Union
2022-12-29 17:20:41 +01:00
2023-01-04 18:28:31 +01:00
BUFFER_SIZE: int = 256
2022-12-31 16:17:29 +01:00
2023-01-04 00:54:46 +01:00
class Tab():
2023-01-06 12:05:19 +01:00
bordered: bool = False
colsize: List = []
2023-01-06 15:39:28 +01:00
text_to_right: List = []
2023-01-04 00:54:46 +01:00
2023-01-06 12:05:19 +01:00
def __init__(self, *colsizes: int, nb_columns: int = 0, bordered: bool = False):
self.colsize = list(colsizes)
self.bordered = bordered
auto_size = nb_columns - len(self.colsize)
2023-01-04 00:54:46 +01:00
2023-01-06 12:05:19 +01:00
if nb_columns > 0 and auto_size > 0:
terminal_size = os.get_terminal_size()
column_size = int((terminal_size.columns - sum(self.colsize)) / auto_size)
for i in range(len(self.colsize), nb_columns):
self.colsize.append(column_size)
def head(self, *texts: str):
self.border('=')
self.line(*texts, bordered=False)
self.border('=')
def border(self, text: str = '-'):
2023-01-06 02:37:46 +01:00
sys.stdout.write(text * sum(self.colsize) + '\n')
2023-01-06 15:39:28 +01:00
def align_to_right(self, *column_num: int):
self.text_to_right = [i - 1 for i in column_num]
2023-01-06 12:05:19 +01:00
def line(self, *texts: str, bordered: bool = None):
2023-01-06 02:37:46 +01:00
lines = {}
max_lines = 0
2023-01-04 00:54:46 +01:00
2023-01-06 15:39:28 +01:00
for column_num, text in enumerate(texts[:len(self.colsize)]):
max_length = self.colsize[column_num]
2023-01-06 02:37:46 +01:00
lineno = -1
2023-01-06 15:39:28 +01:00
lines[column_num] = (max_length, [])
2023-01-06 02:37:46 +01:00
for paragraph in str(text).split('\n'):
lineno += 1
2023-01-06 15:39:28 +01:00
lines[column_num][1].append([])
2023-01-06 02:37:46 +01:00
for word in paragraph.split(' '):
word = word.strip()
2023-01-06 15:39:28 +01:00
next_sentence = ' '.join(lines[column_num][1][lineno] + [word])
2023-01-06 02:37:46 +01:00
if len(next_sentence) >= max_length:
lineno += 1
2023-01-06 15:39:28 +01:00
lines[column_num][1].append([])
2023-01-06 02:37:46 +01:00
2023-01-06 15:39:28 +01:00
lines[column_num][1][lineno].append(word)
2023-01-05 16:56:52 +01:00
2023-01-06 02:37:46 +01:00
max_lines = max(max_lines, lineno)
2023-01-04 00:54:46 +01:00
2023-01-06 02:37:46 +01:00
for i in range(0, max_lines + 1):
output = ''
2023-01-04 00:54:46 +01:00
2023-01-06 15:39:28 +01:00
for column_num, line in lines.items():
2023-01-06 02:37:46 +01:00
width, bag_of_words = line
if len(bag_of_words) > i:
sentence = ' '.join(bag_of_words[i])
2023-01-06 15:39:28 +01:00
if column_num in self.text_to_right:
output += ' ' * (width - len(sentence) - 1) + sentence + ' '
else:
output += sentence + ' ' * (width - len(sentence))
2023-01-06 02:37:46 +01:00
else:
2023-01-06 15:39:28 +01:00
output += ' ' * width
2023-01-04 00:54:46 +01:00
2023-01-06 02:37:46 +01:00
sys.stdout.write(output + '\n')
2023-01-06 12:05:19 +01:00
if bordered == False:
return
2023-01-04 00:54:46 +01:00
2023-01-06 12:05:19 +01:00
if bordered or self.bordered:
self.border('-')
2023-01-05 16:56:52 +01:00
2022-12-31 03:36:55 +01:00
class Telnet:
def __init__(self, IP: str, login: str, password: str):
import telnetlib
from collections import deque
self.tn = telnetlib.Telnet(IP, timeout=15)
self.fifo = deque()
if b'Login as:' in self.tn.read_until(b'Login as:'):
self.tn.write(bytes(login, 'ascii') + b'\r\n')
if b'Password:' in self.tn.read_until(b'Password:'):
time.sleep(0.2)
self.tn.write(bytes(password, 'ascii') + b'\r\n')
if b'for more information.' in self.tn.read_until(b'Type "help()" for more information.'):
return
raise Exception('Failed to establish a Telnet connection with the board')
def __del__(self):
self.close()
def close(self):
try:
self.tn.close()
except:
pass
def read(self, size: int = 1) -> bytes:
timeout = 0
while len(self.fifo) < size and not timeout >= 8:
timeout = 0
data = self.tn.read_eager()
if len(data):
self.fifo.extend(data)
timeout = 0
else:
timeout += 1
time.sleep(0.25)
data = b''
while len(data) < size and len(self.fifo) > 0:
data += bytes([self.fifo.popleft()])
return data
def write(self, data: bytes):
self.tn.write(data)
return len(data)
def inWaiting(self) -> int:
n_waiting = len(self.fifo)
if not n_waiting:
data = self.tn.read_eager()
self.fifo.extend(data)
n_waiting = len(data)
return n_waiting
2022-12-31 01:56:48 +01:00
class Pyboard(object):
2022-12-31 15:08:59 +01:00
i: int = 0
2022-12-31 03:36:55 +01:00
serial: Union[Serial, Telnet]
def __init__(self, device: str, baudrate: int = 115200, login: str = '', password: str = ''):
is_telnet = device and device.count('.') == 3
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
for _ in range(0, 3):
try:
2022-12-31 03:36:55 +01:00
if is_telnet:
self.serial = Telnet(device, login, password)
else:
self.serial = Serial(device, baudrate=baudrate, interCharTimeout=1)
2022-12-30 23:33:59 +01:00
break
except:
time.sleep(1)
else:
2023-01-04 18:28:31 +01:00
raise Exception(f'Failed to access device: {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
2023-01-04 00:54:46 +01:00
def transfer_status(self) -> int:
2022-12-31 15:08:59 +01:00
arrows = ['', '', '', '']
2023-01-04 00:54:46 +01:00
sys.stdout.write(f'[∘] Transfering... {arrows[self.i]}\r')
2022-12-31 15:08:59 +01:00
sys.stdout.flush()
self.i = (self.i + 1) % 4
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-31 04:10:28 +01:00
def send_ctrl_c(self) -> bytes:
2022-12-30 23:33:59 +01:00
# ctrl-C twice: interrupt any running program
2022-12-31 04:10:28 +01:00
for _ in range(0, 2):
2022-12-30 23:33:59 +01:00
self.serial.write(b'\x03')
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-31 15:25:19 +01:00
def read_until(self, delimiter: bytes, stream_output: bool = False, show_status: bool = False) -> Optional[bytes]:
2022-12-30 23:33:59 +01:00
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:]
2022-12-31 15:25:19 +01:00
elif show_status:
2023-01-04 00:54:46 +01:00
self.transfer_status()
2022-12-30 23:33:59 +01:00
else:
timeout += 1
2022-12-31 16:17:29 +01:00
time.sleep(0.0001)
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-31 15:31:00 +01:00
def __terminal(self, command: str, stream_output: bool = False) -> Optional[str]:
2022-12-30 23:33:59 +01:00
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-31 01:56:48 +01:00
for i in range(0, len(command), BUFFER_SIZE):
2022-12-31 15:25:19 +01:00
if not stream_output:
2023-01-04 00:54:46 +01:00
self.transfer_status()
2022-12-31 15:25:19 +01:00
2022-12-31 01:56:48 +01:00
self.serial.write(command[i: min(i + BUFFER_SIZE, len(command))])
2022-12-31 16:17:29 +01:00
time.sleep(0.0001)
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
# catch output
2022-12-31 15:31:00 +01:00
data = self.read_until(b'\x04', stream_output=stream_output, show_status=not 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'
2023-01-06 02:37:46 +01:00
raise Exception('' + reason.strip())
2022-12-30 23:33:59 +01:00
else:
2023-01-06 02:37:46 +01:00
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
2023-01-04 15:40:22 +01:00
class FileSystem(object):
2023-01-04 00:54:46 +01:00
pyboard: Pyboard
2022-12-29 17:20:41 +01:00
2022-12-30 23:33:59 +01:00
def __init__(self, pyboard: Pyboard):
2023-01-04 00:54:46 +01:00
self.pyboard = pyboard
2022-12-30 23:33:59 +01:00
2022-12-31 01:56:48 +01:00
def checksum(self, source: str, data: str) -> str:
2022-12-30 23:33:59 +01:00
output = self.terminal(f"""
def checksum(data):
v = 21
2023-01-05 16:56:52 +01:00
for c in data.decode('utf-8'):
2022-12-30 23:33:59 +01:00
v ^= ord(c)
return v
2023-01-05 16:56:52 +01:00
with open('{source}', 'rb') as fh:
2022-12-30 23:33:59 +01:00
print(checksum(fh.read()))
""")
if isinstance(data, bytes):
data = data.decode('utf-8')
v = 21
for c in data:
v ^= ord(c)
2023-01-04 00:54:46 +01:00
if int(v) == int(output):
return 'OK'
return f'{v} != {output}'
2022-12-30 23:33:59 +01:00
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-31 01:56:48 +01:00
result = infile.read({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)
2023-01-04 18:28:31 +01:00
2023-01-05 16:56:52 +01:00
if filename.endswith('.mpy'):
return (output, '???')
2022-12-30 23:33:59 +01:00
return (output, self.checksum(filename, output))
2022-12-29 17:20:41 +01:00
2022-12-31 01:56:48 +01:00
def download(self, source: str, destination: str) -> str:
2022-12-30 23:33:59 +01:00
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)
2022-12-31 01:56:48 +01:00
return 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
2023-01-04 00:54:46 +01:00
with self.pyboard as terminal:
2022-12-30 23:33:59 +01:00
size = len(data)
2023-01-05 16:56:52 +01:00
terminal(f"""fh = open('{filename}', 'wb')""")
2022-12-29 16:37:55 +01:00
2022-12-31 01:56:48 +01:00
for i in range(0, size, BUFFER_SIZE):
chunk_size = min(BUFFER_SIZE, size - i)
2022-12-30 23:33:59 +01:00
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
2023-01-05 16:56:52 +01:00
if filename.endswith('.mpy'):
return (filename, '???')
2022-12-30 23:33:59 +01:00
return (filename, self.checksum(filename, data))
2022-12-29 16:37:55 +01:00
2022-12-31 01:56:48 +01:00
def upload(self, source: str, destination: str) -> str:
2022-12-30 23:33:59 +01:00
with open(source, 'rb') as fh:
2022-12-31 01:56:48 +01:00
_, checksum = self.put(destination, fh.read())
return checksum
2022-12-29 20:08:50 +01:00
2022-12-31 01:56:48 +01:00
def ls(self, dirname: str = '/') -> Tuple[int, List, str]:
2023-01-04 18:28:31 +01:00
dirname = dirname.strip('./').strip('/')
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):
2022-12-31 01:56:48 +01:00
e = []
s = os.stat(dirname)
if s[0] == 0x4000:
2023-01-05 16:56:52 +01:00
if not dirname.endswith('/'):
dirname += '/'
e.append((dirname, -1))
2022-12-31 01:56:48 +01:00
for t in os.ilistdir(dirname):
2023-01-04 18:28:31 +01:00
if dirname.startswith('/'):
dirname = dirname[1:]
2022-12-31 01:56:48 +01:00
if t[1] == 0x4000:
e.extend(ls(dirname + t[0] + '/'))
else:
e.append((dirname + t[0], os.stat(dirname + t[0])[6]))
else:
e.append((dirname, s[6]))
2023-01-05 16:56:52 +01:00
return sorted(e)
2022-12-31 01:56:48 +01:00
try:
s = 1
2023-01-05 16:56:52 +01:00
r = ls('{dirname}')
2022-12-31 01:56:48 +01:00
x = ''
except Exception as e:
s = 0
2023-01-05 16:56:52 +01:00
r = [('{dirname}', -2)]
2022-12-31 01:56:48 +01:00
x = str(e)
2022-12-29 16:37:55 +01:00
2022-12-31 01:56:48 +01:00
print(json.dumps([s, r, x]))
2022-12-30 23:33:59 +01:00
""")
return json.loads(output)
2022-12-29 16:37:55 +01:00
2022-12-31 01:56:48 +01:00
def rm(self, filename: str) -> Tuple[int, List, str]:
2022-12-30 23:33:59 +01:00
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):
2022-12-31 01:56:48 +01:00
e = []
2023-01-05 16:56:52 +01:00
if not dirname.endswith('/'):
dirname += '/'
2022-12-30 23:33:59 +01:00
for t in os.ilistdir(dirname):
2023-01-04 18:28:31 +01:00
if dirname.startswith('/'):
dirname = dirname[1:]
2022-12-30 23:33:59 +01:00
if t[1] == 0x4000:
2022-12-31 01:56:48 +01:00
e.append((dirname + t[0] + '/', -1))
e.extend(ls(dirname + t[0] + '/'))
2022-12-30 23:33:59 +01:00
else:
2022-12-31 01:56:48 +01:00
e.append((dirname + t[0], os.stat(dirname + t[0])[6]))
2023-01-05 16:56:52 +01:00
return sorted(e, reverse=True)
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:
2022-12-31 01:56:48 +01:00
e = ls(filename)
2022-12-31 03:04:11 +01:00
if filename != '/':
2023-01-05 16:56:52 +01:00
e.append((filename.strip('/'), -1))
2022-12-31 01:56:48 +01:00
for f, s in e:
if not s == -1:
try:
os.remove(f)
r.append((f, 1, ''))
except Exception as e:
r.append((f, 0, str(e)))
for f, s in e:
if s == -1:
try:
os.rmdir(f)
r.append((f, 1, ''))
except Exception as e:
r.append((f, 0, str(e)))
2022-12-30 23:33:59 +01:00
else:
try:
os.remove(filename)
2022-12-31 01:56:48 +01:00
r.append((filename, 1, ''))
2022-12-30 23:33:59 +01:00
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:
2022-12-31 01:56:48 +01:00
s = 1
2023-01-05 16:56:52 +01:00
r = rm('{filename}')
2022-12-31 01:56:48 +01:00
x = ''
2022-12-30 23:33:59 +01:00
except Exception as e:
2022-12-31 01:56:48 +01:00
s = 0
2023-01-05 16:56:52 +01:00
r = [('{filename}', 0, str(e))]
2022-12-31 01:56:48 +01:00
x = str(e)
print(json.dumps([s, r, x]))
2022-12-30 23:33:59 +01:00
""")
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 = []
2023-01-05 16:56:52 +01:00
for zd in str('{dirname}').split('/'):
2022-12-30 23:33:59 +01:00
if not zd:
continue
d.append(zd)
2023-01-05 16:56:52 +01:00
zd = '/'.join(d)
2022-12-30 23:33:59 +01:00
try:
os.mkdir(zd)
2023-01-05 16:56:52 +01:00
r.append(('/' + zd, 1))
2022-12-30 23:33:59 +01:00
except Exception as e:
if str(e).find('EEXIST'):
2023-01-05 16:56:52 +01:00
r.append(('/' + zd, 1))
2022-12-30 23:33:59 +01:00
else:
2023-01-05 16:56:52 +01:00
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"""
2023-01-05 16:56:52 +01:00
with open('{filename}', 'r') as fh:
2022-12-30 23:33:59 +01:00
exec(fh.read())
""", stream_output=True)
except Exception as e:
print(str(e))
2022-12-29 16:37:55 +01:00
2022-12-31 15:31:00 +01:00
def terminal(self, command: str, stream_output: bool = False) -> str:
2023-01-04 00:54:46 +01:00
with self.pyboard as terminal:
2022-12-30 23:33:59 +01:00
try:
2022-12-31 15:31:00 +01:00
return terminal(command, stream_output=stream_output)
2022-12-30 23:33:59 +01:00
except Exception as e:
raise e
2022-12-29 16:37:55 +01:00
2022-12-31 01:56:48 +01:00
class Picowatch(object):
2023-01-05 16:56:52 +01:00
def __init__(self, pyboard: Pyboard):
2023-01-04 15:40:22 +01:00
self.filesystem = FileSystem(pyboard)
2022-12-31 04:10:28 +01:00
signal.signal(signal.SIGINT, lambda a, b: self.interupt())
def interupt(self):
2023-01-04 15:40:22 +01:00
self.filesystem.pyboard.send_ctrl_c()
2022-12-31 04:10:28 +01:00
def terminal(self, command: str):
2023-01-04 15:40:22 +01:00
self.filesystem.terminal(command, stream_output=True)
2022-12-31 01:56:48 +01:00
2023-01-05 16:56:52 +01:00
def internal_ls(self, filepath: str):
queue = []
if filepath == '/':
filepath = os.getcwd().replace(os.sep, '/')
else:
filepath = os.path.join(os.getcwd(), filepath.strip('./').strip('/')).replace(os.sep, '/')
if os.path.isfile(filepath):
queue = [(filepath, os.stat(filepath)[6])]
elif os.path.isdir(filepath):
def ls(dirname: str):
e = []
if os.path.isdir(dirname):
if not dirname.endswith('/'):
dirname += '/'
for filename in os.listdir(dirname):
if filename.startswith('.'):
continue
filename = dirname + filename
if os.path.isdir(filename):
e.extend(ls(filename))
else:
e.append((filename, os.stat(filename)[6]))
return e
queue = ls(filepath)
return queue
2023-01-04 15:40:22 +01:00
def listing(self, filepath: str = '/'):
filepath = filepath.strip('./')
2023-01-04 18:28:31 +01:00
tab = Tab(4, 30, 15, 100)
2023-01-06 12:05:19 +01:00
tab.head('[ ]', 'Filename', 'Size (kb)', 'Exception')
2023-01-04 15:40:22 +01:00
status, output, exception = self.filesystem.ls(filepath)
2022-12-31 01:56:48 +01:00
if status:
2023-01-04 15:40:22 +01:00
for filename, size in output:
2022-12-31 01:56:48 +01:00
if size == -1:
2023-01-06 02:37:46 +01:00
tab.line('[*]', filename, '-')
2022-12-31 01:56:48 +01:00
else:
2023-01-06 02:37:46 +01:00
tab.line('[*]', filename, f'{round(size / 1024, 2)} kb')
2022-12-31 01:56:48 +01:00
else:
2023-01-06 02:37:46 +01:00
tab.line('[?]', filepath, '', str(exception))
2022-12-31 01:56:48 +01:00
2023-01-04 15:40:22 +01:00
def contents(self, filename: str):
filename = filename.strip('./').strip('/')
content, _ = self.filesystem.get(filename)
print('-' * 50)
2022-12-31 03:04:11 +01:00
2023-01-04 15:40:22 +01:00
for ln in content.decode('utf-8').split('\n'):
print(ln)
2022-12-31 03:04:11 +01:00
def upload(self, filepath: str):
2023-01-05 16:56:52 +01:00
tab = Tab(4, 30, 15, 15, 100)
2023-01-06 12:05:19 +01:00
tab.head('[ ]', 'Filename', 'Size (kb)', 'Checksum', 'Exception')
2022-12-31 03:04:11 +01:00
2023-01-05 16:56:52 +01:00
for source, size in self.internal_ls(filepath):
destination = source.replace(os.getcwd().replace(os.sep, '/'), '').strip('/')
2023-01-04 15:40:22 +01:00
2022-12-31 03:04:11 +01:00
try:
2023-01-06 02:37:46 +01:00
tab.line('[↑]', destination, f'{round(size / 1024, 2)} kb', self.filesystem.upload(source, destination))
2022-12-31 03:04:11 +01:00
except Exception as e:
2023-01-06 02:37:46 +01:00
tab.line('[?]', destination, '', '', str(e))
2022-12-31 01:56:48 +01:00
def download(self, filepath: str):
2023-01-04 18:28:31 +01:00
tab = Tab(4, 30, 15, 100)
2023-01-06 12:05:19 +01:00
tab.head('[ ]', 'Filename', 'Checksum', 'Exception')
2023-01-05 16:56:52 +01:00
status, output, exception = self.filesystem.ls(filepath.strip('./').strip('/'))
2022-12-29 16:37:55 +01:00
2022-12-31 01:56:48 +01:00
if status:
for remote, size in output:
if size == -1:
2023-01-05 16:56:52 +01:00
os.makedirs(os.path.join(os.getcwd(), remote), 777, exist_ok=True)
2022-12-29 17:20:41 +01:00
2022-12-31 01:56:48 +01:00
for remote, size in output:
2023-01-05 16:56:52 +01:00
destination = os.path.join(os.getcwd(), remote).replace(os.sep, '/')
2023-01-04 18:28:31 +01:00
2022-12-31 01:56:48 +01:00
if not size == -1:
try:
2023-01-06 02:37:46 +01:00
tab.line('[↓]', remote, self.filesystem.download(remote, destination))
2022-12-31 01:56:48 +01:00
except Exception as e:
2023-01-06 02:37:46 +01:00
tab.line('[?]', remote, '', str(e))
2022-12-31 01:56:48 +01:00
else:
2023-01-06 02:37:46 +01:00
tab.line('[?]', filepath, '', exception)
2022-12-31 01:56:48 +01:00
def delete(self, filepath: str):
2023-01-04 18:28:31 +01:00
tab = Tab(4, 30, 100)
2023-01-06 12:05:19 +01:00
tab.head('[ ]', 'Filename', 'Exception')
2023-01-05 16:56:52 +01:00
status, output, exception = self.filesystem.rm(filepath.strip('./'))
2022-12-31 01:56:48 +01:00
if status:
2023-01-04 15:40:22 +01:00
for filename, checked, exception in output:
2022-12-31 01:56:48 +01:00
if checked:
2023-01-06 02:37:46 +01:00
tab.line('[-]', filename)
2022-12-31 01:56:48 +01:00
else:
2023-01-06 02:37:46 +01:00
tab.line('[?]', filename, exception)
2022-12-31 01:56:48 +01:00
else:
2023-01-06 02:37:46 +01:00
tab.line('[?]', filepath, exception)
2023-01-02 11:12:01 +01:00
2023-01-04 15:40:22 +01:00
def compare(self, filepath: str):
2023-01-05 16:56:52 +01:00
content, _ = self.filesystem.get(filepath.strip('./').strip('/'))
2023-01-04 15:40:22 +01:00
fh, tempname = tempfile.mkstemp()
2023-01-02 11:12:01 +01:00
try:
with os.fdopen(fh, 'wb') as tmp:
tmp.write(content)
2023-01-05 16:56:52 +01:00
subprocess.Popen(f'code --diff "{tempname}" "{os.path.join(os.getcwd(), filepath)}"', stdout=subprocess.PIPE, shell=True).communicate()
2023-01-02 11:12:01 +01:00
finally:
2023-01-04 00:54:46 +01:00
input('Press Enter to delete temp file.')
2023-01-04 15:40:22 +01:00
os.remove(tempname)
2023-01-02 11:12:01 +01:00
2023-01-04 15:40:22 +01:00
def status(self, return_output: bool = False):
2023-01-04 00:54:46 +01:00
changes = []
try:
2023-01-04 15:40:22 +01:00
output = subprocess.check_output(['git', 'status', '-s'], stderr=subprocess.STDOUT)
2023-01-04 00:54:46 +01:00
for filename in [f.strip() for f in output.decode('utf-8').split('\n')]:
if not filename:
continue
status, filename = filename.split(' ')
2023-01-05 16:56:52 +01:00
if status in ['A', 'M', '??']:
changes.append((1, filename))
elif status == 'D':
changes.append((-1, filename))
2023-01-04 15:40:22 +01:00
except Exception as e:
2023-01-05 16:56:52 +01:00
try:
print(e.output.decode('utf-8').strip())
except:
print(e.decode('utf-8').strip())
2023-01-04 15:40:22 +01:00
finally:
if return_output:
return changes
2023-01-04 00:54:46 +01:00
2023-01-04 15:40:22 +01:00
tab = Tab(4, 40)
2023-01-06 12:05:19 +01:00
tab.head('[ ]', 'Filename')
2023-01-02 11:12:01 +01:00
2023-01-04 15:40:22 +01:00
for status, filename in changes:
2023-01-06 02:37:46 +01:00
tab.line('[+]' if status == 1 else '[-]', filename)
2023-01-04 15:40:22 +01:00
def push(self):
2023-01-04 18:28:31 +01:00
tab = Tab(4, 30, 15, 100)
2023-01-06 12:05:19 +01:00
tab.head('[ ]', 'Filename', 'Size (kb)', 'Checksum', 'Exception')
2023-01-04 15:40:22 +01:00
changes = self.status(return_output=True)
for filepath in [filename for status, filename in changes if status == -1]:
filepath = filepath.strip('/')
status, output, exception = self.filesystem.rm(filepath)
if status:
for filename, checked, exception in output:
if checked:
2023-01-06 02:37:46 +01:00
tab.line('[-]', filename, '', 'DELETED')
2023-01-04 15:40:22 +01:00
else:
2023-01-06 02:37:46 +01:00
tab.line('[?]', filename, '', '', exception)
2023-01-04 15:40:22 +01:00
else:
2023-01-06 02:37:46 +01:00
tab.line('[?]', filepath, '', exception)
2023-01-04 15:40:22 +01:00
queue = []
2022-12-31 01:56:48 +01:00
2023-01-04 15:40:22 +01:00
for filepath in [filename for status, filename in changes if status == 1]:
2023-01-05 16:56:52 +01:00
queue.extend(self.internal_ls(filepath))
2022-12-31 04:10:28 +01:00
2023-01-05 16:56:52 +01:00
for source, size in queue:
destination = source.replace(os.getcwd().replace(os.sep, '/'), '').strip('/')
2022-12-31 04:10:28 +01:00
2023-01-04 15:40:22 +01:00
try:
2023-01-06 02:37:46 +01:00
tab.line('[↑]', destination, f'{round(size / 1024, 2)} kb', self.filesystem.upload(source, destination))
2023-01-04 15:40:22 +01:00
except Exception as e:
2023-01-06 02:37:46 +01:00
tab.line('[?]', destination, '', '', str(e))
2023-01-04 15:40:22 +01:00
print('Pico board up to date.')
2023-01-05 16:56:52 +01:00
def compile(self, filename: str):
_, error = mpy_cross.run(filename, stderr=subprocess.PIPE, stdout=subprocess.PIPE, shell=True).communicate()
if error:
print(error.decode('utf-8'))
else:
print(f'MicroPython File from "{filename}" created!')
2023-01-04 15:40:22 +01:00
def test(self, filename: str):
2023-01-05 16:56:52 +01:00
with open(os.path.join(os.getcwd(), filename), 'r') as fh:
2023-01-04 15:40:22 +01:00
self.filesystem.terminal(fh.read(), stream_output=True)
2023-01-05 16:56:52 +01:00
def launch(self, filename: str):
self.filesystem.launch(filename)
2022-12-31 04:10:28 +01:00
2022-12-31 15:08:59 +01:00
print('Welcome to Picowatch Terminal')
2023-01-05 16:56:52 +01:00
print(f'Listening to project: {os.getcwd().replace(os.sep, "/")}/')
2023-01-04 15:40:22 +01:00
picowatch = False
2023-01-04 18:28:31 +01:00
try:
env = dotenv_values('.picowatch')
2023-01-05 16:56:52 +01:00
picowatch = Picowatch(Pyboard(env["DEVICE"], env["BAUDRATE"]))
2023-01-04 18:36:15 +01:00
print(f'Connected automatically to device: {env["DEVICE"]} at a baudrate of: {env["BAUDRATE"]}')
2023-01-04 18:28:31 +01:00
except:
while not picowatch:
print('-' * 50)
device = input('Port: ').strip()
baudrate = input('Baudrate (115200): ').strip() or 115200
try:
2023-01-05 16:56:52 +01:00
picowatch = Picowatch(Pyboard(device, baudrate))
2023-01-04 18:28:31 +01:00
print('-' * 50)
2023-01-05 16:56:52 +01:00
print(f'Connected to device: {device} and baudrate: {baudrate}')
with open(os.path.join(os.getcwd(), '.picowatch'), 'w+') as fh:
fh.write(f'DEVICE = "{device}"\n')
fh.write(f'BAUDRATE = {baudrate}\n')
2023-01-04 18:28:31 +01:00
except Exception as e:
print(str(e))
2023-01-04 15:40:22 +01:00
2023-01-04 18:36:15 +01:00
print('-' * 50)
2022-12-31 04:10:28 +01:00
picowatch.interupt()
while True:
try:
2022-12-31 16:17:29 +01:00
unmessage = input('>>> ').strip()
for message in unmessage.split('&'):
match message.strip().split(' '):
2023-01-02 11:12:01 +01:00
case ['exit']:
2022-12-31 16:17:29 +01:00
sys.exit()
2023-01-04 18:28:31 +01:00
case ['help']:
print('TODO')
2023-01-02 11:12:01 +01:00
case ['reboot']:
2022-12-31 16:17:29 +01:00
picowatch.terminal('help()')
2023-01-04 15:40:22 +01:00
case ['ls' | 'list', *source]:
2022-12-31 16:17:29 +01:00
picowatch.listing(source[0] if source else '/')
2023-01-04 15:40:22 +01:00
case ['cat' | 'code', source]:
2022-12-31 16:17:29 +01:00
picowatch.contents(source)
2023-01-04 15:40:22 +01:00
case ['rm' | 'delete', source]:
2022-12-31 16:17:29 +01:00
picowatch.delete(source)
2023-01-04 15:40:22 +01:00
case ['put' | 'upload', source]:
2022-12-31 16:17:29 +01:00
picowatch.upload(source)
2023-01-04 15:40:22 +01:00
case ['get' | 'download', source]:
2022-12-31 16:17:29 +01:00
picowatch.download(source)
2023-01-04 15:40:22 +01:00
case ['diff' | 'compare', filename]:
2023-01-02 11:12:01 +01:00
picowatch.compare(filename)
case ['status']:
2023-01-04 15:40:22 +01:00
picowatch.status(return_output=False)
2023-01-02 11:12:01 +01:00
case ['push']:
2023-01-04 15:40:22 +01:00
picowatch.push()
2023-01-05 16:56:52 +01:00
case ['mpy' | 'compile', filename]:
picowatch.compile(filename)
2023-01-04 15:40:22 +01:00
case ['install', package_name]:
2023-01-02 11:12:01 +01:00
pass
case ['test', filename]:
2023-01-04 15:40:22 +01:00
picowatch.test(filename)
2022-12-31 16:17:29 +01:00
case ['!']:
2023-01-04 15:40:22 +01:00
picowatch.test('main.py')
2022-12-31 16:17:29 +01:00
case ['!!']:
picowatch.launch('main.py')
case _:
if message.startswith('./'):
picowatch.launch(message[2:])
elif message:
print(f'"{message}" is not recognized.')
2022-12-31 04:10:28 +01:00
except Exception as e:
print(str(e))