Skip to content

Commit 38da5ed

Browse files
authored
Merge Carglglz/develop for upydevice 0.3.3 release
Merge Develop branch for upydevice 0.3.3 release
2 parents 0bd0686 + 69dea91 commit 38da5ed

File tree

15 files changed

+443
-37
lines changed

15 files changed

+443
-37
lines changed

changelog.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
55
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
66

7-
## [0.3.3] Unreleased Github Repo [develop]
7+
## [0.3.4] Unreleased Github Repo [develop]
8+
9+
## [0.3.3] 2021-12-16
10+
### Added
11+
- `Host Name` in `__repr__` command device info
812
## [0.3.2] 2021-10-24
913
### Added
1014
- mDNS `.local`/`dhcp_hostname` compatibility, so device configuration works across networks
Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
import sys
2+
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel, QVBoxLayout,
3+
QMainWindow)
4+
from PyQt5.QtGui import QPixmap
5+
import os
6+
from upydevice import Device
7+
import traceback
8+
from PyQt5.QtCore import (QObject, QRunnable, QThreadPool, pyqtSignal,
9+
pyqtSlot, Qt, QMargins, QTimer)
10+
from PyQt5.QtMultimedia import QSound
11+
import time
12+
from datetime import timedelta
13+
# ICON_RED_LED_OFF = os.path.join(os.getcwd(), "icons/xsled-red-off.png")
14+
# ICON_RED_LED_ON = os.path.join(os.getcwd(), "icons/xsled-red-on.png")
15+
TIMER_FX = os.path.join(os.getcwd(), "sounds/beep-07a.wav")
16+
TIMER_FX3 = os.path.join(os.getcwd(), "sounds/beep-3.wav")
17+
18+
# THREAD WORKERS
19+
20+
21+
class WorkerSignals(QObject):
22+
'''
23+
Defines the signals available from a running worker thread.
24+
25+
Supported signals are:
26+
27+
finished
28+
No data
29+
30+
error
31+
`tuple` (exctype, value, traceback.format_exc() )
32+
33+
result
34+
`object` data returned from processing, anything
35+
36+
progress
37+
`int` indicating % progress
38+
39+
'''
40+
finished = pyqtSignal()
41+
error = pyqtSignal(tuple)
42+
result = pyqtSignal(object)
43+
progress = pyqtSignal(object)
44+
45+
46+
class Worker(QRunnable):
47+
'''
48+
Worker thread
49+
50+
Inherits from QRunnable to handler worker thread setup, signals and wrap-up.
51+
52+
:param callback: The function callback to run on this worker thread. Supplied args and
53+
kwargs will be passed through to the runner.
54+
:type callback: function
55+
:param args: Arguments to pass to the callback function
56+
:param kwargs: Keywords to pass to the callback function
57+
58+
'''
59+
60+
def __init__(self, fn, *args, **kwargs):
61+
super(Worker, self).__init__()
62+
63+
# Store constructor arguments (re-used for processing)
64+
self.fn = fn
65+
self.args = args
66+
self.kwargs = kwargs
67+
self.signals = WorkerSignals()
68+
self.kwargs['progress_callback'] = self.signals.progress
69+
70+
@pyqtSlot()
71+
def run(self):
72+
'''
73+
Initialise the runner function with passed args, kwargs.
74+
'''
75+
76+
# Retrieve args/kwargs here; and fire processing using them
77+
try:
78+
self.fn(*self.args, **self.kwargs)
79+
except:
80+
traceback.print_exc()
81+
exctype, value = sys.exc_info()[:2]
82+
self.signals.error.emit((exctype, value, traceback.format_exc()))
83+
# Return the result of the processing
84+
finally:
85+
self.signals.finished.emit()
86+
87+
88+
class MainWindow(QMainWindow):
89+
def __init__(self, widgetlist):
90+
super().__init__()
91+
# self.setStyleSheet("background-color: black;")
92+
self.widgets = widgetlist
93+
self.setWindowTitle("Test App")
94+
self.setGeometry(50, 50, 400, 600)
95+
layout = QVBoxLayout()
96+
for w in widgetlist:
97+
layout.addWidget(w)
98+
99+
widget = QWidget()
100+
widget.setLayout(layout)
101+
102+
# Set the central widget of the Window. Widget will expand
103+
# to take up all the space in the window by default.
104+
self.setCentralWidget(widget)
105+
106+
# Keypressevents
107+
108+
def closeEvent(self, event):
109+
self.widgets[0].closeEvent(event)
110+
111+
112+
class ActionLabel(QLabel):
113+
def __init__(self, parent=None):
114+
super(ActionLabel, self).__init__(parent)
115+
self._value = 0
116+
self.rest_bg = "background-color: rgb(0,255,0); color: white;"
117+
self.work_bg = "background-color: red; color: white;"
118+
self.finished_bg = "background-color: blue; color: white;"
119+
self._type_action_style = {'P': self.rest_bg,
120+
'R': self.rest_bg, 'W': self.work_bg}
121+
self._type_action = {'P': "PREPARE", 'R': "REST", 'W': "WORK"}
122+
self.setStyleSheet(self.rest_bg)
123+
font = self.font()
124+
font.setPointSize(60)
125+
self.setFont(font)
126+
self.setText("PREPARE")
127+
self.resize(400, 100)
128+
self.setAlignment(Qt.AlignCenter)
129+
# self.setMargin(10)
130+
# self.setScaledContents(True)
131+
132+
@property
133+
def value(self):
134+
return self._value
135+
136+
@value.setter
137+
def value(self, val):
138+
self._value = val
139+
if val:
140+
# self.setStyleSheet(self.work_bg)
141+
# self.setText("WORK")
142+
print('on')
143+
else:
144+
# self.setStyleSheet(self.rest_bg)
145+
# self.setText("REST")
146+
print('off')
147+
self.repaint()
148+
149+
def toggle(self):
150+
self.value = not self._value
151+
152+
153+
class TimerLabel(QLabel):
154+
def __init__(self, parent=None):
155+
super(TimerLabel, self).__init__(parent)
156+
self._value = 0
157+
self._intervals = [('P', 5), ('W', 5), ('R', 5), ('W', 5), ('R', 5)]
158+
self._int_gen = (interval for interval in self._intervals)
159+
self.color = "color: white;"
160+
self.setStyleSheet(self.color)
161+
font = self.font()
162+
font.setPointSize(120)
163+
self.setFont(font)
164+
self.setText("00:00")
165+
self.resize(400, 200)
166+
self.setAlignment(Qt.AlignCenter)
167+
# self.setMargin(10)
168+
# self.setScaledContents(True)
169+
170+
def reset_intervals(self):
171+
self._int_gen = (interval for interval in self._intervals)
172+
173+
174+
class ActionWidget(QWidget):
175+
def __init__(self, parent=None, dev=None):
176+
super(ActionWidget, self).__init__(parent)
177+
self.setStyleSheet("background-color: black;")
178+
vbox = QVBoxLayout()
179+
self.dev = dev
180+
self.action = ActionLabel(self)
181+
self.timerlabel = TimerLabel(self)
182+
vbox.addWidget(self.action)
183+
vbox.addWidget(self.timerlabel)
184+
vbox.setContentsMargins(QMargins(0, 0, 0, 0))
185+
self.setLayout(vbox)
186+
# Workers
187+
self.threadpool = QThreadPool()
188+
self.quit_thread = False
189+
self._active_listen = True
190+
# Timer
191+
self._interval_done = True
192+
self._interval_time = 0
193+
self._timer = QTimer()
194+
self._timer.timeout.connect(self.iterate)
195+
# Sound
196+
self.timer_sound = QSound(TIMER_FX)
197+
self.interval_sound = QSound(TIMER_FX3)
198+
# Button
199+
self._button = None
200+
# ON EXIT
201+
self.thread_done = False
202+
203+
self.start_action_signal()
204+
205+
def toggle_led(self):
206+
self._active_listen = False
207+
self.dev.wr_cmd("pyb.LED(1).toggle()")
208+
self.action.toggle()
209+
self._active_listen = True
210+
if self.action.value:
211+
self._timer.start(1000)
212+
else:
213+
self._timer.stop()
214+
215+
def update_state(self, state):
216+
self.action.toggle()
217+
self._button.pushbutton(True)
218+
if state == "ON":
219+
self._timer.start(1000)
220+
else:
221+
self._timer.stop()
222+
223+
def listen_action_state(self, progress_callback):
224+
while not self.quit_thread:
225+
if self._active_listen:
226+
if self.dev.serial.readable() and self.dev.serial.in_waiting:
227+
state = self.dev.serial.readline().decode().replace('\r\n', '')
228+
progress_callback.emit(state)
229+
print(state)
230+
time.sleep(0.1)
231+
232+
print('Thread Done!')
233+
self.thread_done = True
234+
235+
def start_action_signal(self):
236+
# Pass the function to execute
237+
# Any other args, kwargs are passed to the run function
238+
worker_led = Worker(self.listen_action_state)
239+
# worker.signals.result.connect(self.print_output)
240+
# worker.signals.finished.connect(self.thread_complete)
241+
# worker.signals.progress.connect(self.progress_fn)
242+
worker_led.signals.progress.connect(self.update_state)
243+
244+
# Execute
245+
self.threadpool.start(worker_led)
246+
247+
def iterate(self):
248+
try:
249+
if self._interval_done:
250+
interval_type, self._interval_time = next(self.timerlabel._int_gen)
251+
self.action.setStyleSheet(self.action._type_action_style[interval_type])
252+
self.action.setText(self.action._type_action[interval_type])
253+
self.timerlabel.setText(
254+
str(timedelta(seconds=self._interval_time)).split('.')[0][2:])
255+
self._interval_done = False
256+
else:
257+
self._interval_time -= 1
258+
if self._interval_time > 0 and self._interval_time <= 3:
259+
self.timer_sound.play()
260+
elif self._interval_time == 0:
261+
self._interval_done = True
262+
self.interval_sound.play()
263+
self.timerlabel.setText(
264+
str(timedelta(seconds=self._interval_time)).split('.')[0][2:])
265+
266+
except StopIteration:
267+
self.toggle_led()
268+
self.finish_state()
269+
self.timerlabel.reset_intervals()
270+
self._button.pushbutton(True)
271+
272+
def finish_state(self):
273+
self.action.setStyleSheet(self.action.finished_bg)
274+
self.action.setText("Finished")
275+
self.timerlabel.setText("00:00")
276+
277+
def closeEvent(self, event):
278+
self._timer.stop()
279+
self.quit_thread = True
280+
try:
281+
while not self.thread_done:
282+
time.sleep(0.5)
283+
print("shutdown...")
284+
except Exception as e:
285+
print(e)
286+
print("SHUTDOWN COMPLETE")
287+
sys.exit()
288+
289+
290+
class ControlsWidget(QWidget):
291+
def __init__(self, parent=None, dev=None, action_label=None):
292+
super(ControlsWidget, self).__init__(parent)
293+
vbox = QVBoxLayout()
294+
self.button = QPushButton(self)
295+
self.button.setText("Start")
296+
self._value = False
297+
# self.button.setStyleSheet("background-color: white;")
298+
# self.button.move(100, 100)
299+
self.dev = dev
300+
# self.action.setScaledContents(True)
301+
# self.action.move(140, 50)
302+
self.action = action_label
303+
self.button.clicked.connect(self.pushbutton)
304+
vbox.addWidget(self.button)
305+
vbox.setContentsMargins(QMargins(0, 0, 0, 0))
306+
self.setLayout(vbox)
307+
308+
def pushbutton(self, soft=False):
309+
if not self._value:
310+
self.button.setText("Stop")
311+
else:
312+
self.button.setText("Start")
313+
314+
self._value = not self._value
315+
if not soft:
316+
self.action.toggle_led()
317+
self.action.timer_sound.play()
318+
319+
320+
def main():
321+
322+
# SerialDevice
323+
pyboard_led_callback = """
324+
def toggle_led():
325+
pyb.LED(1).toggle()
326+
if not not pyb.LED(1).intensity():
327+
print('ON')
328+
else:
329+
print('OFF')"""
330+
print("Connecting to device...")
331+
mydev = Device("/dev/tty.usbmodem3370377430372", init=True)
332+
mydev.paste_buff(pyboard_led_callback)
333+
mydev.wr_cmd('\x04')
334+
mydev.wr_cmd("sw = pyb.Switch();sw.callback(toggle_led)")
335+
# # WebSocketDevice
336+
# mydev = Device('192.168.1.73', 'keyespw', init=True)
337+
338+
# # BleDevice
339+
# mydev = Device('9998175F-9A91-4CA2-B5EA-482AFC3453B9', init=True)
340+
print("Connected")
341+
342+
app = QApplication(sys.argv)
343+
action_widget = ActionWidget(dev=mydev)
344+
# action_widget.setGeometry(50, 50, 400, 600)
345+
# action_widget.setWindowTitle("Upydevice Button Led Toggle")
346+
controls_widget = ControlsWidget(dev=mydev, action_label=action_widget)
347+
action_widget._button = controls_widget
348+
main_w = MainWindow([action_widget, controls_widget])
349+
# action_widget.show()
350+
main_w.show()
351+
352+
app.exec_()
353+
354+
355+
if __name__ == "__main__":
356+
main()
9.47 KB
Binary file not shown.
136 KB
Binary file not shown.

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def readme():
99

1010

1111
setup(name='upydevice',
12-
version='0.3.2',
12+
version='0.3.3',
1313
description='Python library to interface with wireless/serial MicroPython devices',
1414
long_description=readme(),
1515
long_description_content_type='text/markdown',

test/PYBS.config

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ def pytest_addoption(parser):
66
help="indicate the device with which to run the test")
77
parser.addoption("--wss", action="store_true", default=False,
88
help="to indicate use of ssl if WebSecureREPL enabled in WebSocketDevice")
9+
parser.addoption("--devp", action="store", default="default",
10+
help="indicate the device with which to run the test")
911

1012

1113
@pytest.fixture

test/pytest.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
[pytest]
22
log_cli = 1
33
log_cli_level = INFO
4-
log_cli_format = %(asctime)s [%(name)s] [%(dev)s] : %(message)s
4+
log_cli_format = %(asctime)s [%(name)s] [%(dev)s] [%(devp)s] : %(message)s

0 commit comments

Comments
 (0)