Skip to content

Commit

Permalink
- optimized JSON processing
Browse files Browse the repository at this point in the history
- add MUGMA machine setup
- improved numpy 2 compatibility
- added pip update back
- fixes transparent color support
- scheduler fixes
- updates libs
  • Loading branch information
MAKOMO committed Jul 4, 2024
1 parent 00c0163 commit a64126a
Show file tree
Hide file tree
Showing 25 changed files with 255 additions and 87 deletions.
2 changes: 1 addition & 1 deletion .ci/install-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ sudo apt-get install -y -q libdbus-1-3 libxkbcommon-x11-0 libxcb-icccm4 libxcb-i

sudo gem install dotenv -v ${DOTENV_VER}
sudo gem install fpm # Linux build fails using 1.13.0
# pip install --upgrade pip # pip update to 24.1 breaks CI
pip install --upgrade pip # pip update to 24.1 breaks CI
pip install -r src/requirements.txt | sed '/^Ignoring/d'

.ci/install-libusb.sh
Expand Down
2 changes: 1 addition & 1 deletion .ci/install-win.bat
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ if NOT "%PYUPGRADE_WIN_V%" == "" (
::
:: get pip up to date
::
#python -m pip install --upgrade pip # pip update to 24.1 breaks CI
python -m pip install --upgrade pip # pip update to 24.1 breaks CI
python -m pip install wheel

::
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/mypy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ jobs:
cache: 'pip' # caching pip dependencies
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r src/requirements-dev.txt
pip install -r src/requirements.txt
- uses: tsuyoshicho/action-mypy@v4
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/pylint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ jobs:
cache: 'pip' # caching pip dependencies
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r src/requirements-dev.txt
pip install -r src/requirements.txt
- name: Set up reviewdog
Expand Down
2 changes: 1 addition & 1 deletion src/artisanlib/alarms.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,7 @@ def exportalarmsJSON(self, filename:str) -> bool:
alarms['alarmstrings'] = list(self.aw.qmc.alarmstrings)
from json import dump as json_dump
with open(filename, 'w', encoding='utf-8') as outfile:
json_dump(alarms, outfile, ensure_ascii=True)
json_dump(alarms, outfile, indent=None, separators=(',', ':'), ensure_ascii=False)
outfile.write('\n')
return True
except Exception as ex: # pylint: disable=broad-except
Expand Down
31 changes: 16 additions & 15 deletions src/artisanlib/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
from artisanlib.util import (uchr, fill_gaps, deltaLabelPrefix, deltaLabelUTF8, deltaLabelMathPrefix, stringfromseconds,
fromFtoC, fromFtoCstrict, fromCtoF, fromCtoFstrict, RoRfromFtoC, RoRfromFtoCstrict, RoRfromCtoF, toInt, toString,
toFloat, application_name, getResourcePath, getDirectory, convertWeight,
abbrevString, scaleFloat2String, is_proper_temp, weight_units, volume_units, float2float)
abbrevString, scaleFloat2String, is_proper_temp, weight_units, render_weight, volume_units, float2float)
from artisanlib import pid
from artisanlib.time import ArtisanTime
from artisanlib.filters import LiveMedian
Expand Down Expand Up @@ -874,7 +874,7 @@ def __init__(self, parent:QWidget, dpi:int, locale:str, aw:'ApplicationWindow')
'Extech 42570', #163
'Mugma BT/ET', #164
'+Mugma Heater/Fan', #165
'+Mugma Catalyzer', #166
'+Mugma Heater/Catalyzer', #166
'+Mugma SV' #167
]

Expand Down Expand Up @@ -1009,7 +1009,7 @@ def __init__(self, parent:QWidget, dpi:int, locale:str, aw:'ApplicationWindow')
159, # +Phidget DAQ1301 67
160, # IKAWA \Delta Humidity / \Delat Humidity direction
165, # +Mugma Heater/Fan
166 # +Mugma Catalyzer
166 # +Mugma Heater/Catalyzer
]

# ADD DEVICE:
Expand Down Expand Up @@ -3232,15 +3232,9 @@ def updateWebLCDs(self, bt:Optional[str] = None, et:Optional[str] = None, time:O
payload['alert']['title'] = alertTitle
if alertTimeout:
payload['alert']['timeout'] = alertTimeout
# send update via http:
# import requests
# url = f'http://127.0.0.1:{self.aw.WebLCDsPort}/send'
# headers = {'content-type': 'application/json'}
# requests.post(url, data=json_dumps(payload), headers=headers, timeout=0.3)
# send update directly:
if self.aw.weblcds_server is not None:
from json import dumps as json_dumps
self.aw.weblcds_server.send_msg(json_dumps(payload))
self.aw.weblcds_server.send_msg(json_dumps(payload, indent=None, separators=(',', ':')))
except Exception as e: # pylint: disable=broad-except
_log.exception(e)

Expand Down Expand Up @@ -3392,7 +3386,9 @@ def intChannel(self, n:int, c:int) -> bool:
return True
if self.extradevices[n] in {140, 141}: # Kaleido drum/AH, heater/fan
return True
if self.extradevices[n] == 144: # IKAWA heater/fan, state/humidity
if self.extradevices[n] == 144 or (self.extradevices[n] == 145 and c==0): # IKAWA heater/fan, state
return True
if self.extradevices[n] == 165: # MUGMA heater/fan
return True
return False
return False
Expand Down Expand Up @@ -4372,6 +4368,7 @@ def updateLCDs(self, time:Optional[float], temp1:List[float], temp2:List[float],
ndev = min(len(XTs1),len(XTs2))
extra1_values:List[Optional[str]] = []
extra2_values:List[Optional[str]] = []

for i in range(ndev):
if i < self.aw.nLCDS:
try:
Expand Down Expand Up @@ -7080,7 +7077,7 @@ def getAnnoPositions(self) -> List[List[float]]:
time_anno = v[1].xyann
if all(not numpy.isnan(e) for e in temp_anno + time_anno):
# we add the entry only if all of the coordinates are proper numpers and not nan
res.append([k,temp_anno[0],temp_anno[1],time_anno[0],time_anno[1]])
res.append([k,float(temp_anno[0]),float(temp_anno[1]),float(time_anno[0]),float(time_anno[1])])
return res

def setAnnoPositions(self, anno_positions:List[List[Union[int,float]]]) -> None:
Expand All @@ -7101,7 +7098,7 @@ def getFlagPositions(self) -> List[List[float]]:
for k,v in self.l_event_flags_dict.items():
flag_anno = v.xyann
if all(not numpy.isnan(e) for e in flag_anno):
res.append([k,flag_anno[0],flag_anno[1]])
res.append([k,float(flag_anno[0]),float(flag_anno[1])])
return res

def setFlagPositions(self, flag_positions:List[List[float]]) -> None:
Expand Down Expand Up @@ -8150,7 +8147,11 @@ def redraw(self, recomputeAllDeltas:bool = True, re_smooth_foreground:bool = Tru
if self.flagstart or self.xgrid == 0:
self.set_xlabel('')
else:
self.set_xlabel(f'{self.__dijstra_to_ascii(self.roastertype_setup)} {self.roastersize_setup:g}kg')
self.set_xlabel(f'{self.__dijstra_to_ascii(self.roastertype_setup)} {render_weight(self.roastersize_setup, 1, weight_units.index(self.weight[2]))}')





try:
y_label.set_in_layout(False) # remove y-axis labels from tight_layout calculation
Expand Down Expand Up @@ -11688,7 +11689,7 @@ def OnMonitor(self) -> None:
elif self.device == 164:
# connect Mugma
from artisanlib.mugma import Mugma
self.aw.mugma = Mugma(self.aw.mugmaHost, self.aw.mugmaPort,
self.aw.mugma = Mugma(self.aw.mugmaHost, self.aw.mugmaPort, self.device_logging,
connected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} connected').format('Mugma'),True,None),
disconnected_handler=lambda : self.aw.sendmessageSignal.emit(QApplication.translate('Message', '{} disconnected').format('Mugma'),True,None))
self.aw.mugma.setLogging(self.device_logging)
Expand Down
13 changes: 7 additions & 6 deletions src/artisanlib/comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ def __init__(self, aw:'ApplicationWindow') -> None:
self.Extech42570, #163
self.Mugma_BTET, #164
self.Mugma_HeaterFan, #165
self.Mugma_Catalyzer, #166
self.Mugma_HeaterCatalyzer, #166
self.Mugma_SV #167
]
#string with the name of the program for device #27
Expand Down Expand Up @@ -1635,19 +1635,20 @@ def Mugma_BTET(self) -> Tuple[float,float,float]:
def Mugma_HeaterFan(self) -> Tuple[float,float,float]:
tx = self.aw.qmc.timeclock.elapsedMilli()
if self.aw.mugma is not None:
t1 = self.aw.mugma.getHeater()
t2 = self.aw.mugma.getFan()
t1 = self.aw.mugma.getFan()
t2 = self.aw.mugma.getHeater()
else:
t1 = t2 = -1
return tx,t1,t2 # time, Fan (chan2), Heater (chan1)

def Mugma_Catalyzer(self) -> Tuple[float,float,float]:
def Mugma_HeaterCatalyzer(self) -> Tuple[float,float,float]:
tx = self.aw.qmc.timeclock.elapsedMilli()
if self.aw.mugma is not None:
t1 = t2 = self.aw.mugma.getCatalyzer()
t1 = self.aw.mugma.getCatalyzer()
t2 = self.aw.mugma.getHeater()
else:
t1 = t2 = -1
return tx,t1,t2 # time, Catalyzer (chan2), Catalyzer (chan1)
return tx,t1,t2 # time, Catalyzer (chan2), Heater (chan1)

def Mugma_SV(self) -> Tuple[float,float,float]:
tx = self.aw.qmc.timeclock.elapsedMilli()
Expand Down
4 changes: 3 additions & 1 deletion src/artisanlib/devices.py
Original file line number Diff line number Diff line change
Expand Up @@ -3212,7 +3212,7 @@ def okEvent(self) -> None: # pyright: ignore [reportGeneralTypeIssues] # Code is
#### DEVICE 165 is +Mugma Heater/Fan but +DEVICE cannot be set as main device
##########################
##########################
#### DEVICE 166 is +Mugma Catalyzer but +DEVICE cannot be set as main device
#### DEVICE 166 is +Mugma Heater/Catalyzer but +DEVICE cannot be set as main device
##########################
##########################
#### DEVICE 167 is +Mugma SV but +DEVICE cannot be set as main device
Expand Down Expand Up @@ -3558,6 +3558,8 @@ def okEvent(self) -> None: # pyright: ignore [reportGeneralTypeIssues] # Code is
except Exception as e: # pylint: disable=broad-except
_log.exception(e)

self.aw.qmc.intChannel.cache_clear() # device type and thus int channels might have been changed
self.aw.qmc.clearLCDs()
self.aw.qmc.redraw(recomputeAllDeltas=False)
self.aw.sendmessage(message)
#open serial conf Dialog
Expand Down
10 changes: 6 additions & 4 deletions src/artisanlib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1739,7 +1739,7 @@ def __init__(self, parent:Optional[QWidget] = None, *, locale:str, WebEngineSupp
# Mugma Network
self.mugma_default_host:Final[str] = '127.0.0.1'
self.mugmaHost:str = '127.0.0.1'
self.mugmaPort:int = 1503
self.mugmaPort:int = 1504
self.mugma:Optional[Mugma] = None # holds the Mugma instance created on connect; reset to None on disconnect

# Kaleido Network
Expand Down Expand Up @@ -10806,6 +10806,8 @@ def showEventsMinieditor(self) -> None:
self.EventsGroupLayout.setVisible(True)

def updateLCDproperties(self) -> None:
# clear intChannel cache
self.qmc.intChannel.cache_clear()
# set LCDframe visibilities and labels
ndev = len(self.qmc.extradevices)
for i in range(ndev):
Expand Down Expand Up @@ -13197,7 +13199,7 @@ def exportJSON(self, filename:str) -> bool:
try:
with open(filename, 'w', encoding='utf-8') as outfile:
from json import dump as json_dump
json_dump(self.getProfile(), outfile, ensure_ascii=True)
json_dump(self.getProfile(), outfile, indent=None, separators=(',', ':'), ensure_ascii=False)
outfile.write('\n')
return True
except Exception as ex: # pylint: disable=broad-except
Expand Down Expand Up @@ -15471,9 +15473,9 @@ def computedProfileInformation(self) -> 'ComputedProfileInformation':
try:
det,dbt = self.curveSimilarity()
if det is not None and not math.isnan(det):
computedProfile['det'] = det
computedProfile['det'] = float(det)
if dbt is not None and not math.isnan(dbt):
computedProfile['dbt'] = dbt
computedProfile['dbt'] = float(dbt)
except Exception as e: # pylint: disable=broad-except
_log.exception(e)
######### Energy Use #########
Expand Down
39 changes: 18 additions & 21 deletions src/artisanlib/mugma.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,23 @@

class Mugma(AsyncComm):

__slots__ = [ '_bt', '_et', '_heater', '_fan', '_catalyzer', '_sv' ]
__slots__ = [ 'device_logging', '_bt', '_et', '_heater', '_fan', '_catalyzer', '_sv' ]

def __init__(self, host:str = '127.0.0.1', port:int = 1503,
def __init__(self, host:str = '127.0.0.1', port:int = 1504, device_logging:bool = False,
connected_handler:Optional[Callable[[], None]] = None,
disconnected_handler:Optional[Callable[[], None]] = None) -> None:

self.device_logging = device_logging

super().__init__(host, port, None, connected_handler, disconnected_handler)

# current readings
self._et:float = -1 # environmental temperature in °C
self._bt:float = -1 # bean temperature in °C
self._fan:int = -1 # fan speed in % [0-100]
self._heater:int = -1 # heater power in % [0-100]
self._fan:float = -1 # fan speed in % [0-100]
self._heater:float = -1 # heater power in % [0-100]
self._catalyzer:float = -1 # catalyzer heating power in %
self._sv:float = -1 # target temperature in °C
self._sv:float = -1 # target temperature (SV) in °C


# external API to access machine state
Expand All @@ -48,9 +50,9 @@ def getET(self) -> float:
return self._et
def getBT(self) -> float:
return self._bt
def getFan(self) -> int:
def getFan(self) -> float:
return self._fan
def getHeater(self) -> int:
def getHeater(self) -> float:
return self._heater
def getCatalyzer(self) -> float:
return self._catalyzer
Expand All @@ -66,27 +68,22 @@ def resetReadings(self) -> None:
self._sv = -1


@staticmethod
def average(current_value:float, new_value:float) -> float:
if current_value == -1 or new_value == -1:
return new_value
return (current_value + new_value) / 2


# https://www.oreilly.com/library/view/using-asyncio-in/9781492075325/ch04.html
async def read_msg(self, stream: asyncio.StreamReader) -> None:
# read line
try:
data:bytes = await stream.readline()
if self.device_logging:
_log.info('data: %s', data)
reading = data.decode('utf-8').strip().split(',')
if len(reading) > 9 and reading[0] == '1':
# system status received
self._et = self.average(self._et, float(reading[4]))
self._bt = self.average(self._bt, float(reading[5]))
self._fan = int(reading[6])
self._heater = int(reading[7])
self._catalyzer = self.average(self._catalyzer, float(reading[8]))
self._sv = self.average(self._sv, float(reading[9]))
self._et = float(reading[4]) / 10
self._bt = float(reading[5]) / 10
self._fan = int(reading[6]) # investigate: should this also be divided by 10!?
self._heater = float(reading[7]) / 10
self._catalyzer = float(reading[8]) / 10
self._sv = float(reading[9]) / 10
except ValueError:
# buffer overrun
pass
Expand All @@ -95,7 +92,7 @@ async def read_msg(self, stream: asyncio.StreamReader) -> None:

def main() -> None:
import time
mugma = Mugma(host = '127.0.0.1', port = 1503)
mugma = Mugma(host = '127.0.0.1', port = 1504)
mugma.start()
for _ in range(4):
print('>>> hallo')
Expand Down
4 changes: 2 additions & 2 deletions src/artisanlib/pid_dialogs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1039,7 +1039,7 @@ def exportrampsoaksJSON(self, filename:str) -> bool:
rampsoaks['mode'] = self.aw.qmc.mode
from json import dump as json_dump
with open(filename, 'w', encoding='utf-8') as outfile:
json_dump(rampsoaks, outfile, ensure_ascii=True)
json_dump(rampsoaks, outfile, indent=None, separators=(',', ':'), ensure_ascii=False)
outfile.write('\n')
self.aw.qmc.rsfile = filename
self.rsfile.setText(self.aw.qmc.rsfile)
Expand Down Expand Up @@ -3015,7 +3015,7 @@ def savePIDJSON(self, filename:str) -> bool:
pids['segments'] = segments
from json import dump as json_dump
with open(filename, 'w', encoding='utf-8') as outfile:
json_dump(pids, outfile, ensure_ascii=True)
json_dump(pids, outfile, indent=None, separators=(',', ':'), ensure_ascii=False)
outfile.write('\n')
return True
except Exception as ex: # pylint: disable=broad-exception-caught
Expand Down
4 changes: 2 additions & 2 deletions src/artisanlib/roast_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@

#from artisanlib.suppress_errors import suppress_stdout_stderr
from artisanlib.util import (deltaLabelUTF8, stringfromseconds,stringtoseconds, toInt, toFloat, abbrevString,
scaleFloat2String, comma2dot, weight_units, weight_units_lower, volume_units, float2floatWeightVolume, float2float,
scaleFloat2String, comma2dot, weight_units, render_weight, weight_units_lower, volume_units, float2floatWeightVolume, float2float,
convertWeight, convertVolume)
from artisanlib.dialogs import ArtisanDialog, ArtisanResizeablDialog
from artisanlib.widgets import MyQComboBox, ClickableQLabel, ClickableTextEdit, MyTableWidgetItemNumber
Expand Down Expand Up @@ -3684,7 +3684,7 @@ def populateSetupDefaults(self) -> None:
if self.setup_ui is not None:
self.setup_ui.labelOrganizationDefault.setText(self.aw.qmc.organization_setup)
self.setup_ui.labelOperatorDefault.setText(self.aw.qmc.operator_setup)
self.setup_ui.labelMachineSizeDefault.setText(f'{self.aw.qmc.roastertype_setup} {self.aw.qmc.roastersize_setup}kg')
self.setup_ui.labelMachineSizeDefault.setText(f'{self.aw.qmc.roastertype_setup} {render_weight(self.aw.qmc.roastersize_setup, 1, weight_units.index(self.aw.qmc.weight[2]))}')
self.setup_ui.labelHeatingDefault.setText(self.aw.qmc.heating_types[self.aw.qmc.roasterheating_setup])
self.setup_ui.labelDrumSpeedDefault.setText(self.aw.qmc.drumspeed_setup)

Expand Down
4 changes: 2 additions & 2 deletions src/artisanlib/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -432,13 +432,13 @@ def getDirectory(filename: str, ext: Optional[str] = None, share: bool = False)

# converts QColor ARGB names to a standard/MPL hex color strings with alpha values at the end
def argb_colorname2rgba_colorname(c:str) -> str:
if len(c) == 9 and c[0] == '#' and c[1:].isdigit():
if len(c) == 9 and c[0] == '#':
return f'#{c[3:9]}{c[1:3]}'
return c

# converts standard/MPL hex color strings to QColor ARGB names with alpha at the begin
def rgba_colorname2argb_colorname(c:str) -> str:
if len(c) == 9 and c[0] == '#' and c[1:].isdigit():
if len(c) == 9 and c[0] == '#':
return f'#{c[7:9]}{c[1:7]}'
return c

Expand Down
2 changes: 1 addition & 1 deletion src/artisanlib/wsport.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ def send(self, request:Dict[str,Any], block:bool = True) -> Optional[Dict[str,An
request[self.id_node] = message_id
if self.machine_node:
request[self.machine_node] = self.machineID
json_req = json.dumps(request)
json_req = json.dumps(request, indent=None, separators=(',', ':'), ensure_ascii=True) # we conservatively use escaping for accent characters here dispite the utf-8 encoding as some clients might not be able to process non-ascii data

Check failure on line 350 in src/artisanlib/wsport.py

View workflow job for this annotation

GitHub Actions / codespell

dispite ==> despite
if self._write_queue is not None:
if block:
future = asyncio.run_coroutine_threadsafe(self.registerRequest(message_id), self._loop)
Expand Down
Loading

0 comments on commit a64126a

Please sign in to comment.