Skip to content

Commit

Permalink
- replaces zh_CN translations by zh_TW translations translated to zh_…
Browse files Browse the repository at this point in the history
…CN via GoogleTranslate (Discussion #1785)

- adds IMF machine setup with control
- update volume not density if weight changes and volume is set in Roast Properties (Discussion #1786)
- removes the non-standard MODBUS little-endian byte order
- removes the experimental PID P-on-Measurement/Input mode (Issue #1744)
  • Loading branch information
MAKOMO committed Jan 25, 2025
1 parent dfebca6 commit b112b49
Show file tree
Hide file tree
Showing 80 changed files with 176,900 additions and 176,717 deletions.
166 changes: 83 additions & 83 deletions src/README.txt
Original file line number Diff line number Diff line change
@@ -1,84 +1,84 @@
#### Artisan helps coffee roasters record, analyze, and control roast profiles. With the help of a thermocouple data logger, or a proportional–integral–derivative controller (PID controller), this software offers roasting metrics to help make decisions that influence the final coffee flavor.
Artisan is free for personal and commercial use, but asks for a [donation](https://www.paypal.me/MarkoLuther).
## HOME
<https://artisan-scope.org>
The home of its development is on GitHub were all source and binary files are available as well as an issue tracker.
<https://github.com/artisan-roaster-scope/artisan>
## DISCUSSION FORUM
<https://github.com/artisan-roaster-scope/artisan/discussions>
## ARTISAN BLOG
https://artisan-roasterscope.blogspot.de
## FEATURES
Runs on 64bit x64 Windows 10, macOS 11 Big Sur (legacy builds support Windows 8 x64 and macOS 10.13 High Sierra), Redhat/Debian Linux (incl. 32bit Raspberry Pi) and supports a large number of devices and roasting machines. See <https://artisan-scope.org/devices/index> for supported devices and <https://artisan-scope.org/machines/index> for supported machines.
**Artisan offers**
- Unlimited number of curves.
- Rate-of-rise (RoR), area-under-the-curve (AUC), development-time-ratio (DTR) calculations.
- Projection lines and head-up-display (HUD).
- Roast profile evaluation and statistics.
#### Artisan helps coffee roasters record, analyze, and control roast profiles. With the help of a thermocouple data logger, or a proportional–integral–derivative controller (PID controller), this software offers roasting metrics to help make decisions that influence the final coffee flavor.

Artisan is free for personal and commercial use, but asks for a [donation](https://www.paypal.me/MarkoLuther).

## HOME

<https://artisan-scope.org>

The home of its development is on GitHub were all source and binary files are available as well as an issue tracker.

<https://github.com/artisan-roaster-scope/artisan>

## DISCUSSION FORUM

<https://github.com/artisan-roaster-scope/artisan/discussions>

## ARTISAN BLOG

https://artisan-roasterscope.blogspot.de

## FEATURES

Runs on 64bit x64 Windows 10, macOS 11 Big Sur (legacy builds support Windows 8 x64 and macOS 10.13 High Sierra), Redhat/Debian Linux (incl. 32bit Raspberry Pi) and supports a large number of devices and roasting machines. See <https://artisan-scope.org/devices/index> for supported devices and <https://artisan-scope.org/machines/index> for supported machines.

**Artisan offers**
- Unlimited number of curves.
- Rate-of-rise (RoR), area-under-the-curve (AUC), development-time-ratio (DTR) calculations.
- Projection lines and head-up-display (HUD).
- Roast profile evaluation and statistics.
- Roast-, production- and ranking reports.
- Automated reproduction of roasts via alarm programs, replay of events or PID control.
- Batch counter.
- Profile designer, cupping editor, spider- and wheel graph.
- Roast Analyzer, Comparator, Simulator and Transformator tools
- User defined buttons and sliders with programmable actions.
- Many import and export formats.
## INSTALLATION
See <https://github.com/artisan-roaster-scope/artisan/blob/master/wiki/Installation.md>
## LICENCE
Artisan is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Artisan is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Copies of the GNU General Public License has been included with this
distribution in the file `LICENSE.txt`. An online version is available at
<http://www.gnu.org/licenses/>.
## LIBRARIES
Artisan uses the following libraries in unmodified forms:
- Python released under the PSF licence http://www.python.org/psf/
http://www.python.org/
- QT under the Qt GNU Lesser General Public License version 2.1 (LGPL)
http://qt-project.org/products/licensing
- Numpy and Scipy, Copyright (c) 2005-2023, NumPy Developers; All Rights Reserved
http://www.scipy.org/
- PyQt under the Qt GNU GPL v. 3.0 licence; Copyright (c) 2010-2023 Riverbank Computing Limited
http://www.riverbankcomputing.co.uk/software/pyqt/
- matplotlib, Copyright (c) 2002-2015 John D. Hunter; All Rights Reserved. Distributed under a licence based on PSF.
http://matplotlib.sourceforge.net
- py2app under the PSF open source licence; Copyright (c) 2004-2006 Bob Ippolito <bob at redivi.com>
Copyright (c) 2010-2023 Ronald Oussoren <ronaldoussoren at mac.com>.
http://packages.python.org/py2app/
- pyinstaller under the GPL license
http://www.pyinstaller.org
- pymodbus under the BSD License by Galen Collins
https://github.com/bashwork/pymodbus
- python-snap7 under MIT license
https://github.com/gijzelaerr/python-snap7
- arabic_reshaper.py under GPL by Abd Allah Diab (Mpcabd)
## VERSION HISTORY
See <https://github.com/artisan-roaster-scope/artisan>
- Automated reproduction of roasts via alarm programs, replay of events or PID control.
- Batch counter.
- Profile designer, cupping editor, spider- and wheel graph.
- Roast Analyzer, Comparator, Simulator and Transformator tools
- User defined buttons and sliders with programmable actions.
- Many import and export formats.

## INSTALLATION

See <https://github.com/artisan-roaster-scope/artisan/blob/master/wiki/Installation.md>

## LICENCE

Artisan is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Artisan is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Copies of the GNU General Public License has been included with this
distribution in the file `LICENSE.txt`. An online version is available at
<http://www.gnu.org/licenses/>.

## LIBRARIES

Artisan uses the following libraries in unmodified forms:
- Python released under the PSF licence http://www.python.org/psf/
http://www.python.org/
- QT under the Qt GNU Lesser General Public License version 2.1 (LGPL)
http://qt-project.org/products/licensing
- Numpy and Scipy, Copyright (c) 2005-2023, NumPy Developers; All Rights Reserved
http://www.scipy.org/
- PyQt under the Qt GNU GPL v. 3.0 licence; Copyright (c) 2010-2023 Riverbank Computing Limited
http://www.riverbankcomputing.co.uk/software/pyqt/
- matplotlib, Copyright (c) 2002-2015 John D. Hunter; All Rights Reserved. Distributed under a licence based on PSF.
http://matplotlib.sourceforge.net
- py2app under the PSF open source licence; Copyright (c) 2004-2006 Bob Ippolito <bob at redivi.com>
Copyright (c) 2010-2023 Ronald Oussoren <ronaldoussoren at mac.com>.
http://packages.python.org/py2app/
- pyinstaller under the GPL license
http://www.pyinstaller.org
- pymodbus under the BSD License by Galen Collins
https://github.com/bashwork/pymodbus
- python-snap7 under MIT license
https://github.com/gijzelaerr/python-snap7
- arabic_reshaper.py under GPL by Abd Allah Diab (Mpcabd)

Check failure on line 79 in src/README.txt

View workflow job for this annotation

GitHub Actions / codespell

Abd ==> And, Bad


## VERSION HISTORY

See <https://github.com/artisan-roaster-scope/artisan>
8 changes: 4 additions & 4 deletions src/artisanlib/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -3218,7 +3218,7 @@ def event_popup_action(self, action:QAction) -> None:
self.aw.onMarkMoveToNext(self.aw.buttonCHARGE)

self.xaxistosm(redraw=False) # need to fix uneven x-axis labels like -0:13
self.updateProjection() # we update the data here to have the projections drawn by the redraw() triggered by belows timealign
self.updateProjection() # we update the data here to have the projections drawn by the redraw() triggered by the call to timealign below
else:
# we keep xaxis limit the same but adjust to updated timeindex[0] mark
if timeindex_before > -1:
Expand Down Expand Up @@ -3557,7 +3557,7 @@ def checkTPalarmtime(self) -> bool:
# if v[-1] is the current temperature then check if
# we are 20sec after CHARGE
# len(BT) > 4
# BT[-5] <= BT[-4] abd BT[-5] <= BT[-3] and BT[-5] <= BT[-2] and BT[-5] <= BT[-1] and BT[-5] < BT[-1]
# BT[-5] <= BT[-4] and BT[-5] <= BT[-3] and BT[-5] <= BT[-2] and BT[-5] <= BT[-1] and BT[-5] < BT[-1]
if seconds_since_CHARGE > 20 and not self.afterTP and len(self.temp2) > 3 and (self.temp2[-5] <= self.temp2[-4]) and (self.temp2[-5] <= self.temp2[-3]) and (self.temp2[-5] <= self.temp2[-2]) and (self.temp2[-5] <= self.temp2[-1]) and (self.temp2[-5] < self.temp2[-1]):
self.afterTP = True
return self.afterTP
Expand Down Expand Up @@ -6617,7 +6617,7 @@ def reset(self,redraw:bool = True, soundOn:bool = True, keepProperties:bool = Fa
self.roastbatchpos = 1 # initialized to 1, set to increased batchsequence on DROP
self.roastbatchprefix = self.batchprefix

# reset scheduleID/Date (prevents re-using a previous loaded profiles scheduleID to be 'reused')
# reset scheduleID/Date (prevents reusing a previous loaded profiles scheduleID to be 'reused')
self.scheduleID = None
self.scheduleDate = None

Expand Down Expand Up @@ -12979,7 +12979,7 @@ def markCharge(self, noaction:bool = False) -> None:
if time_temp_annos is not None:
self.l_annotations += time_temp_annos

self.updateProjection() # we update the data here to have the projections drawn by the redraw() triggered by belows timealign
self.updateProjection() # we update the data here to have the projections drawn by the redraw() triggered by the call to timealign below

# mark active slider values that are not zero
for slidernr in range(4):
Expand Down
2 changes: 1 addition & 1 deletion src/artisanlib/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -2497,7 +2497,7 @@ def createEventbuttonTable(self) -> None:
columns = 9
if self.eventbuttontable is not None and self.eventbuttontable.columnCount() == columns:
# rows have been already established
# save the current columnWidth to reset them afte table creation
# save the current columnWidth to reset them after table creation
self.aw.eventbuttontablecolumnwidths = [self.eventbuttontable.columnWidth(c) for c in range(self.eventbuttontable.columnCount())]

self.nbuttonsSpinBox.setValue(int(self.aw.buttonlistmaxlen))
Expand Down
19 changes: 8 additions & 11 deletions src/artisanlib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,16 +673,21 @@ def permissionUpdated(permission:'QPermission') -> None:
pass

# returns False if message is duplicate and should be suppressed from log output
# only directly repeated messages will be filtered (maximal 10 times)
# DEBUG messages will never be filtered
class DuplicateFilter(logging.Filter):
def __init__(self) -> None:
super().__init__()
self._message_lockup: Dict[int,int] = {}

def filter(self, record:logging.LogRecord) -> bool:
try:
if logging.getLogger(record.name).isEnabledFor(logging.DEBUG): # don't filter anything in DEBUG mode
return True
log_interval:int = 10
message_Id = zlib.crc32(str(record.getMessage()).encode('utf-8'))
if message_Id not in self._message_lockup:
self._message_lockup = {} # clear all other "remembered" messages thus remove only directly repeated messages
self._message_lockup[message_Id] = 0
return True
self._message_lockup[message_Id] += 1
Expand Down Expand Up @@ -9084,7 +9089,7 @@ def eventaction_internal(self, action:int, cmd:str, eventtype:Optional[int]) ->
# 2. call setpid(self,k) with k that active pid
self.fujipid.setpidPXF(N,kp,ki,kd)
else:
self.pidcontrol.confPID(kp,ki,kd,pOnE=self.pidcontrol.pOnE)
self.pidcontrol.confPID(kp,ki,kd)
#self.pidcontrol.setPID(kp,ki,kd) # we don't set the new values in the dialog
elif action == 12: # Fuji Command (currently only "write(<unitId>,<register>,<value>)" is supported
if cmd_str:
Expand Down Expand Up @@ -9816,7 +9821,7 @@ def eventaction_internal(self, action:int, cmd:str, eventtype:Optional[int]) ->
self.fujipid.setpidPXF(N,kp,ki,kd)
self.sendmessage(f'Artisan Command: {cs}')
else:
self.pidcontrol.confPID(kp,ki,kd,pOnE=self.pidcontrol.pOnE)
self.pidcontrol.confPID(kp,ki,kd)
self.sendmessage(f'Artisan Command: {cs}')
except Exception as e: # pylint: disable=broad-except
_log.exception(e)
Expand Down Expand Up @@ -9872,7 +9877,7 @@ def eventaction_internal(self, action:int, cmd:str, eventtype:Optional[int]) ->
kp = self.pidcontrol.pidKp
ki = self.pidcontrol.pidKi
kd = self.pidcontrol.pidKd
self.pidcontrol.confPID(kp,ki,kd,pOnE=self.pidcontrol.pOnE,source=source)
self.pidcontrol.confPID(kp,ki,kd,source=source)
self.sendmessage(f'Artisan Command: {cs}')
except Exception as e: # pylint: disable=broad-except
_log.exception(e)
Expand Down Expand Up @@ -12727,8 +12732,6 @@ def loadPIDFromProfile(self, profile:'ProfileData') -> None:
self.pidcontrol.pidKd = float(profile['pidKd'])
if 'pidSource' in profile:
self.pidcontrol.pidSource = int(profile['pidSource'])
if 'pOnE' in profile:
self.pidcontrol.pOnE = bool(profile['pOnE'])
if 'svLookahead' in profile:
self.pidcontrol.svLookahead = int(profile['svLookahead'])
except Exception as e: # pylint: disable=broad-except
Expand Down Expand Up @@ -16124,7 +16127,6 @@ def getProfile(self) -> 'ProfileData':
profile['pidKi'] = self.pidcontrol.pidKi
profile['pidKd'] = self.pidcontrol.pidKd
profile['pidSource'] = self.pidcontrol.pidSource
profile['pOnE'] = self.pidcontrol.pOnE
profile['svLookahead'] = self.pidcontrol.svLookahead
try:
ds = list(self.qmc.extradevices)
Expand Down Expand Up @@ -17291,7 +17293,6 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
self.modbus.inputFloatsAsInt[i] = toBool(settings.value(f'input{i+1}FloatsAsInt',self.modbus.inputFloatsAsInt[i]))
self.modbus.inputBCDsAsInt[i] = toBool(settings.value(f'input{i+1}BCDsAsInt',self.modbus.inputBCDsAsInt[i]))
self.modbus.inputSigned[i] = toBool(settings.value(f'input{i+1}Signed',self.modbus.inputSigned[i]))
self.modbus.byteorderLittle = toBool(settings.value('littleEndianFloats',self.modbus.byteorderLittle))
self.modbus.wordorderLittle = toBool(settings.value('wordorderLittle',self.modbus.wordorderLittle))
self.modbus.optimizer = toBool(settings.value('optimizer',self.modbus.optimizer))
self.modbus.fetch_max_blocks = toBool(settings.value('fetch_max_blocks',self.modbus.fetch_max_blocks))
Expand Down Expand Up @@ -17444,7 +17445,6 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
self.pidcontrol.pidPositiveTarget = toInt(settings.value('pidPositiveTarget',self.pidcontrol.pidPositiveTarget))
self.pidcontrol.pidNegativeTarget = toInt(settings.value('pidNegativeTarget',self.pidcontrol.pidNegativeTarget))
self.pidcontrol.invertControl = toBool(settings.value('invertControl',self.pidcontrol.invertControl))
self.pidcontrol.pOnE = toBool(settings.value('pOnE',self.pidcontrol.pOnE))

for n in range(self.pidcontrol.RSLen):
svLabelLabel = 'RS_svLabel'+str(n)
Expand Down Expand Up @@ -19033,7 +19033,6 @@ def saveAllSettings(self, settings:QSettings, default_settings:Optional[Dict[str
self.settingsSetValue(settings, default_settings, 'PIDmultiplier',self.modbus.PIDmultiplier, read_defaults)
self.settingsSetValue(settings, default_settings, 'SVmultiplier',self.modbus.SVmultiplier, read_defaults)
self.settingsSetValue(settings, default_settings, 'SVwriteLong',self.modbus.SVwriteLong, read_defaults)
self.settingsSetValue(settings, default_settings, 'littleEndianFloats',self.modbus.byteorderLittle, read_defaults)
self.settingsSetValue(settings, default_settings, 'wordorderLittle',self.modbus.wordorderLittle, read_defaults)
self.settingsSetValue(settings, default_settings, 'optimizer',self.modbus.optimizer, read_defaults)
self.settingsSetValue(settings, default_settings, 'fetch_max_blocks',self.modbus.fetch_max_blocks, read_defaults)
Expand Down Expand Up @@ -19112,7 +19111,6 @@ def saveAllSettings(self, settings:QSettings, default_settings:Optional[Dict[str
self.settingsSetValue(settings, default_settings, 'pidPositiveTarget',self.pidcontrol.pidPositiveTarget, read_defaults)
self.settingsSetValue(settings, default_settings, 'pidNegativeTarget',self.pidcontrol.pidNegativeTarget, read_defaults)
self.settingsSetValue(settings, default_settings, 'invertControl',self.pidcontrol.invertControl, read_defaults)
self.settingsSetValue(settings, default_settings, 'pOnE',self.pidcontrol.pOnE, read_defaults)
for n in range(self.pidcontrol.RSLen):
self.settingsSetValue(settings, default_settings, 'RS_svLabel'+str(n),self.pidcontrol.RS_svLabels[n], read_defaults)
self.settingsSetValue(settings, default_settings, 'RS_svValues'+str(n),self.pidcontrol.RS_svValues[n], read_defaults)
Expand Down Expand Up @@ -23095,7 +23093,6 @@ def setcommport(self, _:bool = False) -> None:
self.modbus.SVmultiplier = dialog.modbus_SVmultiplier.currentIndex()
self.modbus.SVwriteLong = bool(dialog.modbus_SVwriteLong.isChecked())
self.modbus.PIDmultiplier = dialog.modbus_PIDmultiplier.currentIndex()
self.modbus.byteorderLittle = bool(dialog.modbus_littleEndianBytes.isChecked())
self.modbus.wordorderLittle = bool(dialog.modbus_littleEndianWords.isChecked())
self.modbus.optimizer = bool(dialog.modbus_optimize.isChecked())
self.modbus.fetch_max_blocks = bool(dialog.modbus_full_block.isChecked())
Expand Down
Loading

0 comments on commit b112b49

Please sign in to comment.