Skip to content

Commit 1740d96

Browse files
committed
- adds support for Orbiter smart roaster
- adds popup on missing bean information on START if plus is connected
1 parent 88ccf63 commit 1740d96

78 files changed

Lines changed: 64889 additions & 64110 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
105 Bytes
Binary file not shown.
89 Bytes
Binary file not shown.

doc/help_dialogs/Output_html/eventbuttons_help.html

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,11 @@
497497
<td>kaleido(&lt;target&gt;,&lt;value&gt;)</td>
498498
<td>sends &lt;value&gt; to &lt;target&gt; via the Kaleido Serial or Network protocol</td>
499499
</tr>
500+
<tr>
501+
<td>&#160;</td>
502+
<td>orbiter(&lt;cmd&gt;[,&lt;value&gt;[,&lt;param&gt;]])</td>
503+
<td>sends &lt;cmd&gt; (1byte in HEX) and optional &lt;value&gt; (0-65535) and &lt;param&gt; (0-255) to Orbiter</td>
504+
</tr>
500505
<tr>
501506
<td>&#160;</td>
502507
<td>shellyrelay(n,b)</td>

doc/help_dialogs/Output_html/eventsliders_help.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,12 @@
441441
<td>sends &lt;value&gt; to &lt;target&gt; via the Kaleido Serial or Network protocol</td>
442442
<td>&#160;</td>
443443
</tr>
444+
<tr>
445+
<td>&#160;</td>
446+
<td>orbiter(&lt;cmd&gt;[,&lt;value&gt;[,&lt;param&gt;]])</td>
447+
<td>sends &lt;cmd&gt; (1byte in HEX) and optional &lt;value&gt; (0-65535) and &lt;param&gt; (0-255) to Orbiter</td>
448+
<td>&#160;</td>
449+
</tr>
444450
<tr>
445451
<td>&#160;</td>
446452
<td>shellyrelay(n,b)</td>

src/artisanlib/async_comm.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ async def readuntil(self, separator:bytes = b'\n') -> bytes:
147147
class AsyncComm:
148148

149149
__slots__ = [ '_asyncLoopThread', '_write_queue', '_running', '_host', '_port', '_serial', '_connected_handler', '_disconnected_handler',
150-
'_verify_crc', '_logging' ]
150+
'_verify_crc', '_logging', '_send_timeout' ]
151151

152152
def __init__(self, host:str = '127.0.0.1', port:int = 8080, serial:'SerialSettings|None' = None,
153153
connected_handler:Callable[[], None]|None = None,
@@ -169,6 +169,7 @@ def __init__(self, host:str = '127.0.0.1', port:int = 8080, serial:'SerialSettin
169169
# configuration
170170
self._verify_crc:bool = True # if True the CRC of incoming messages is verified
171171
self._logging = False # if True device communication is logged
172+
self._send_timeout:Final[float] = 0.6 # in seconds
172173

173174

174175
# external API
@@ -328,6 +329,38 @@ def send(self, message:bytes) -> None:
328329
asyncio.run_coroutine_threadsafe(self._write_queue.put(message), self.async_loop_thread.loop)
329330

330331

332+
# adds message to write queue and awaits new data
333+
async def write_await(self, message:bytes, event:asyncio.Event, send_timeout:float) -> None:
334+
if self._write_queue is None:
335+
return
336+
await self._write_queue.put(message)
337+
# await a response containing a new value for var with timeout
338+
try:
339+
await asyncio.wait_for(event.wait(), send_timeout)
340+
except TimeoutError:
341+
if self._logging:
342+
_log.info('write_await (msg=%s, send_timeout:%s)', message.strip(), send_timeout)
343+
344+
def send_await(self, message:bytes, event:asyncio.Event, timeout:float|None = None) -> None:
345+
if self.async_loop_thread is not None and self._write_queue is not None:
346+
send_timeout:float = self._send_timeout
347+
if timeout is not None:
348+
send_timeout = timeout
349+
task = self.write_await(message, event, send_timeout)
350+
if self._asyncLoopThread is not None:
351+
future = asyncio.run_coroutine_threadsafe(task, self._asyncLoopThread.loop)
352+
try:
353+
future.result()
354+
except TimeoutError:
355+
# the coroutine took too long, cancelling the task...
356+
if self._logging:
357+
_log.info('send_request timeout (msg=%s, timeout:%s, send_timeout:%s)',message,timeout,send_timeout)
358+
future.cancel()
359+
except Exception as ex: # pylint: disable=broad-except
360+
_log.error(ex)
361+
362+
363+
331364
# start/stop sample thread
332365

333366
def start(self, connect_timeout:float=5) -> None:

src/artisanlib/canvas.py

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,12 @@ def __init__(self, parent:QWidget, dpi:int, locale:str, aw:'ApplicationWindow')
992992
'+Phidget HUM1000 Hum/Temp', #192
993993
'+Phidget PRE1000', #193
994994
'+Yocto Meteo Hum/Temp', #194
995-
'+Yocto Meteo Pressure' #195
995+
'+Yocto Meteo Pressure', #195
996+
'Orbiter BT/ET', #196
997+
'+Orbiter IT/DT', #197
998+
'+Orbiter Sound/Drum', #198
999+
'+Orbiter Damper/Heater', #199
1000+
'+Orbiter Air/RoR' #200
9961001
]
9971002

9981003
# ADD DEVICE:
@@ -1156,7 +1161,10 @@ def __init__(self, parent:QWidget, dpi:int, locale:str, aw:'ApplicationWindow')
11561161
192, # +Phidget HUM1000 Hum/Temp
11571162
193, # +Phidget PRE1000
11581163
194, # +Yocto Meteo Hum/Temp
1159-
195 # +Yocto Meteo Pressure
1164+
195, # +Yocto Meteo Pressure
1165+
198, # +Orbiter Sound/Drum
1166+
199, # +Orbiter Damper/Heater
1167+
200 # +Orbiter Air/RoR
11601168
]
11611169

11621170
# ADD DEVICE:
@@ -4455,7 +4463,9 @@ def intChannel(self, n:int, c:int) -> bool:
44554463
(self.aw.s7.div[idx*2 + c] == 0 or self.aw.s7.type[idx*2 + c] == 2) and
44564464
no_math_formula_defined)
44574465
# others
4458-
if self.extradevices[n] in {54, 90, 91, 135, 136, 140, 141, 165}: # Hottop Heater/Fan, Slider 12, Slider 34, Santoker Power / Fan, Kaleido Fan/Drum, Kaleido Heater/AH, Mugma Heater/Fan
4466+
if self.extradevices[n] in {54, 90, 91, 135, 136, 140, 141, 165,
4467+
198, 199
4468+
}: # Hottop Heater/Fan, Slider 12, Slider 34, Santoker Power / Fan, Kaleido Fan/Drum, Kaleido Heater/AH, Mugma Heater/Fan, Orbiter Sound/Drum, Orbiter Damper/Heater
44594469
return True
44604470
if self.extradevices[n] == 136 and c == 0: # Santoker Drum
44614471
return True
@@ -13376,6 +13386,21 @@ def OnMonitor(self) -> None:
1337613386
self.aw.mugma.setLogging(self.device_logging)
1337713387
self.aw.mugma.start()
1337813388

13389+
elif self.device == 196:
13390+
# connect Orbiter
13391+
from artisanlib.orbiter import Orbiter
13392+
orbiter_serial = SerialSettings(
13393+
port = self.aw.ser.comport,
13394+
baudrate = self.aw.ser.baudrate,
13395+
bytesize = self.aw.ser.bytesize,
13396+
stopbits = self.aw.ser.stopbits,
13397+
parity = self.aw.ser.parity,
13398+
timeout = self.aw.ser.timeout)
13399+
self.aw.orbiter = Orbiter(orbiter_serial,
13400+
connected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} connected').format('Orbiter'),True,None),
13401+
disconnected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} disconnected').format('Orbiter'),True,None))
13402+
self.aw.orbiter.setLogging(self.device_logging)
13403+
self.aw.orbiter.start()
1337913404

1338013405
self.aw.initializedMonitoringExtraDeviceStructures()
1338113406

@@ -13526,6 +13551,11 @@ def OffMonitorCloseDown(self, respectAlwaysON:bool) -> None:
1352613551
self.aw.mugma.stop()
1352713552
self.aw.mugma = None
1352813553

13554+
# disconnect Orbiter
13555+
if not bool(self.aw.simulator) and self.device == 196 and self.aw.orbiter is not None:
13556+
self.aw.orbiter.stop()
13557+
self.aw.orbiter = None
13558+
1352913559
# at OFF we stop the follow-background on FujiPIDs and set the SV to 0
1353013560
if self.device == 0 and self.aw.fujipid.followBackground and self.aw.fujipid.sv and self.aw.fujipid.sv > 0:
1353113561
try:

src/artisanlib/comm.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -570,7 +570,12 @@ def __init__(self, aw:'ApplicationWindow') -> None:
570570
self.Phidget_HUM1000_HumTemp, #192
571571
self.Phidget_PRE1000, #193
572572
self.Yocto_Meteo_HumTemp, #194
573-
self.Yocto_Meteo_Pressure #195
573+
self.Yocto_Meteo_Pressure, #195
574+
self.Orbiter_BTET, #196
575+
self.Orbiter_ITDT, #197
576+
self.Orbiter_Sound_Drum, #198
577+
self.Orbiter_Damper_Heater, #199
578+
self.Orbiter_Air_RoR #200
574579
]
575580
#string with the name of the program for device #27
576581
self.externalprogram:str = 'test.py'
@@ -2211,7 +2216,56 @@ def Kaleido_HeaterFan(self) -> tuple[float,float,float]:
22112216
t1,t2 = self.aw.kaleido.getHeaterFan()
22122217
else:
22132218
t1 = t2 = -1
2214-
return tx,t2,t1 # time Fan (chan2), Heater (chan1)
2219+
return tx,t2,t1 # time, Fan (chan2), Heater (chan1)
2220+
2221+
# Orbiter
2222+
2223+
def Orbiter_BTET(self) -> tuple[float,float,float]:
2224+
tx = self.aw.qmc.timeclock.elapsedMilli()
2225+
t1:float = -1
2226+
t2:float = -1
2227+
if self.aw.orbiter is not None:
2228+
t1 = self.aw.orbiter.getBT()
2229+
t2 = self.aw.orbiter.getET()
2230+
return tx,t2,t1
2231+
2232+
def Orbiter_ITDT(self) -> tuple[float,float,float]:
2233+
tx = self.aw.qmc.timeclock.elapsedMilli()
2234+
t1:float = -1
2235+
t2:float = -1
2236+
if self.aw.orbiter is not None:
2237+
t1 = self.aw.orbiter.getIT()
2238+
t2 = self.aw.orbiter.getDT()
2239+
return tx,t2,t1 # time, DT (chan2), IT (chan1)
2240+
2241+
def Orbiter_Sound_Drum(self) -> tuple[float,float,float]:
2242+
tx = self.aw.qmc.timeclock.elapsedMilli()
2243+
t1:float = -1
2244+
t2:float = -1
2245+
if self.aw.orbiter is not None:
2246+
t1 = self.aw.orbiter.getSound()
2247+
t2 = self.aw.orbiter.getDrum()
2248+
return tx,t2,t1 # time, Drum (chan2), Sound (chan1)
2249+
2250+
def Orbiter_Damper_Heater(self) -> tuple[float,float,float]:
2251+
tx = self.aw.qmc.timeclock.elapsedMilli()
2252+
t1:float = -1
2253+
t2:float = -1
2254+
if self.aw.orbiter is not None:
2255+
t1 = self.aw.orbiter.getDamper()
2256+
t2 = self.aw.orbiter.getHeater()
2257+
return tx,t2,t1 # time, Heater (chan2), Damper (chan1)
2258+
2259+
def Orbiter_Air_RoR(self) -> tuple[float,float,float]:
2260+
tx = self.aw.qmc.timeclock.elapsedMilli()
2261+
t1:float = -1
2262+
t2:float = -1
2263+
if self.aw.orbiter is not None:
2264+
t1 = self.aw.orbiter.getAir()
2265+
t2 = self.aw.orbiter.getRoR()
2266+
return tx,t2,t1 # time, RoR (chan2), Air (chan1)
2267+
2268+
# IKAWA
22152269

22162270
def Ikawa(self) -> tuple[float,float,float]:
22172271
tx = self.aw.qmc.timeclock.elapsedMilli()

src/artisanlib/devices.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4472,6 +4472,30 @@ def okEvent(self) -> None: # pyright: ignore [reportGeneralTypeIssues] # Code is
44724472
##########################
44734473
#### DEVICE 195 is +Yocto Meteo Pressure
44744474
##########################
4475+
##########################
4476+
#### DEVICE 196 is Kaleido BT/ET
4477+
elif meter == 'Orbiter BT/ET':
4478+
self.aw.qmc.device = 196
4479+
#self.aw.ser.comport = "COM4"
4480+
self.aw.ser.baudrate = 115200
4481+
self.aw.ser.bytesize = 8
4482+
self.aw.ser.parity= 'N'
4483+
self.aw.ser.stopbits = 1
4484+
self.aw.ser.timeout = 1.0
4485+
message = QApplication.translate('Message','Device set to {0}').format(meter)
4486+
##########################
4487+
##########################
4488+
#### DEVICE 197 is +Orbiter IT/DT but +DEVICE cannot be set as main device
4489+
##########################
4490+
##########################
4491+
#### DEVICE 198 is +Orbiter Sound/Drum but +DEVICE cannot be set as main device
4492+
##########################
4493+
##########################
4494+
#### DEVICE 199 is +Orbiter Damper/Heater but +DEVICE cannot be set as main device
4495+
##########################
4496+
##########################
4497+
#### DEVICE 200 is +Orbiter Air/RoR but +DEVICE cannot be set as main device
4498+
##########################
44754499

44764500

44774501
# ADD DEVICE:
@@ -4687,7 +4711,12 @@ def okEvent(self) -> None: # pyright: ignore [reportGeneralTypeIssues] # Code is
46874711
3, # 192
46884712
3, # 193
46894713
3, # 194
4690-
3 # 195
4714+
3, # 195
4715+
8, # 196
4716+
8, # 197
4717+
8, # 198
4718+
8, # 199
4719+
8 # 200
46914720
]
46924721
#init serial settings of extra devices
46934722
for i, _ in enumerate(self.aw.qmc.extradevices):

src/artisanlib/main.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@
177177
from artisanlib.bluedot import BlueDOT # pylint: disable=unused-import
178178
from artisanlib.mugma import Mugma # pylint: disable=unused-import
179179
from artisanlib.kaleido import KaleidoPort # pylint: disable=unused-import
180+
from artisanlib.orbiter import Orbiter # pylint: disable=unused-import
180181
from artisanlib.phases_canvas import tphasescanvas # pylint: disable=unused-import
181182
try:
182183
from artisanlib.ikawa import IKAWA_BLE # pylint: disable=unused-import
@@ -1428,6 +1429,7 @@ class ApplicationWindow(QMainWindow):
14281429
santokerSendMessageSignal = pyqtSignal(bytes,int)
14291430
kaleidoSendMessageSignal = pyqtSignal(str,str)
14301431
kaleidoSendMessageAwaitSignal = pyqtSignal(str,str,int,int)
1432+
orbiterSendMessageSignal = pyqtSignal(bytes,bytes,bytes)
14311433
addEventSignal = pyqtSignal(int,int,bool,bool,bool)
14321434
addRawEventSignal = pyqtSignal(int,float,int,bool,bool,bool)
14331435
updateMessageLogSignal = pyqtSignal()
@@ -1840,6 +1842,9 @@ def __init__(self, parent:QWidget|None = None, *, locale:str, WebEngineSupport:b
18401842
self.kaleido:KaleidoPort|None = None # holds the Kaleido instance created on connect; reset to None on disconnect
18411843
self.kaleidoEventFlags:list[bool] = [False, False, False, False, False, False, False ] # CHARGE, DRY, FCs, FCe, SCs, SCe, DROP
18421844

1845+
# Orbiter
1846+
self.orbiter:Orbiter|None = None # holds the Orbiter instance created on connect; reset to None on disconnect
1847+
18431848
# Ikawa BLE
18441849
self.ikawa:'IKAWA_BLE|None' = None # noqa: UP037
18451850

@@ -4277,6 +4282,7 @@ def __init__(self, parent:QWidget|None = None, *, locale:str, WebEngineSupport:b
42774282
self.santokerSendMessageSignal.connect(self.santokerSendMessage)
42784283
self.kaleidoSendMessageSignal.connect(self.kaleidoSendMessage)
42794284
self.kaleidoSendMessageAwaitSignal.connect(self.kaleidoSendMessageAwait)
4285+
self.orbiterSendMessageSignal.connect(self.orbiterSendMessage)
42804286
self.addEventSignal.connect(self.addEventSlot, type=Qt.ConnectionType.QueuedConnection) # type: ignore[call-arg]
42814287
self.addRawEventSignal.connect(self.addRawEventSlot, type=Qt.ConnectionType.QueuedConnection) # type: ignore[call-arg]
42824288
# by default the connection type is AutoConnection (If the emitter & receiver are in the same thread, a DirectConnection is used. Otherwise, a QueuedConnection is used.)
@@ -5945,7 +5951,7 @@ def openMachineSettings(self, _checked:bool = False) -> None:
59455951
self.mugmaHost = host
59465952
else:
59475953
res = False
5948-
elif (self.qmc.device in {0, 9, 19, 53, 101, 115, 126} or ((self.qmc.device == 29 or 29 in self.qmc.extradevices) and self.modbus.type in {0, 1, 2}) or
5954+
elif (self.qmc.device in {0, 9, 19, 53, 101, 115, 126, 196} or ((self.qmc.device == 29 or 29 in self.qmc.extradevices) and self.modbus.type in {0, 1, 2}) or
59495955
(self.qmc.device == 134 and self.santokerSerial and not self.santokerBLE) or
59505956
(self.qmc.device == 138 and self.kaleidoSerial)): # Fuji, Center301, TC4, Hottop, Behmor or MODBUS serial, HB/ARC
59515957
select_device_name = None
@@ -9551,6 +9557,30 @@ def eventaction_internal(self, action:int, cmd:str, eventtype:int|None) -> None:
95519557
# send message, await new value and create an event with the new value
95529558
self.kaleidoSendMessageAwaitSignal.emit(target, vs, eventtype, lastbuttonpressed)
95539559

9560+
9561+
## orbiter(<cmd>[,<value>[,<param>]]) : <cmd>: command (1 byte in hex syntax), optional: <value> a 16bit positive number, <param> a number 0-255
9562+
# ex: orbiter(0D,7) => set heater power to 7
9563+
elif c.startswith('orbiter'):
9564+
if self.orbiter is not None:
9565+
args = c[len('orbiter'):]
9566+
if args.startswith('(') and args.endswith(')'):
9567+
parts = args[1:-1].split(',')
9568+
try:
9569+
if len(parts) > 0:
9570+
orbiter_cmd:bytes = bytes.fromhex(parts[0])
9571+
orbiter_data:bytes
9572+
if len(parts) > 2:
9573+
orbiter_param = min(255, max(0, int(round(float(parts[2]))))).to_bytes(1, 'little')
9574+
else:
9575+
orbiter_param = b'\x00'
9576+
if len(parts) > 1:
9577+
orbiter_data = min(65535, max(0, int(round(float(parts[1]))))).to_bytes(2, 'little')
9578+
else:
9579+
orbiter_data = b'\x00\x00'
9580+
self.orbiterSendMessageSignal.emit(orbiter_cmd, orbiter_data, orbiter_param)
9581+
except Exception as e: # pylint: disable=broad-except
9582+
_log.error(e)
9583+
95549584
## shellyrelay(n,b) : switches Shelly plug number <n> ON if b is true or 1, and OFF otherwise
95559585
elif c.startswith('shellyrelay'):
95569586
cs_a = re.findall(r'[0-9a-zA-Z-.:]+', c)
@@ -17798,6 +17828,12 @@ def kaleidoSendMessage(self, target:str, value:str) -> None:
1779817828
if self.kaleido is not None:
1779917829
self.kaleido.send_msg(target, value)
1780017830

17831+
# orbiterSendMessage() just sends out the message to the machine without waiting for a response
17832+
@pyqtSlot(bytes,bytes,bytes)
17833+
def orbiterSendMessage(self, cmd:bytes, data:bytes, param:bytes) -> None:
17834+
if self.orbiter is not None:
17835+
self.orbiter.send_msg(cmd, data, param)
17836+
1780117837
# if record is True, an event is added during recording, otherwise only the slider is moved
1780217838
# if fire_slider_action is True, the slider action is fired
1780317839
# if force is True, process even if value is equal to the events lastvalue resp. the current slider value

0 commit comments

Comments
 (0)