Skip to content

Commit

Permalink
v2.10.2
Browse files Browse the repository at this point in the history
  • Loading branch information
MAKOMO committed Feb 28, 2024
1 parent ed837cf commit e25aed8
Show file tree
Hide file tree
Showing 71 changed files with 177,322 additions and 172,281 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Version History

| Version | Date | Comment |
|---------|------:|---------|
| [v2.10.2](https://github.com/artisan-roaster-scope/artisan/releases/tag/v2.10.2) | Feb&nbsp;29,&nbsp;2024 | Adds support for machines from [Mill City Roasters](https://millcityroasters.com/), the [IKAWA PRO X](https://www.ikawacoffee.com/), the standard WinUSB driver for the [Aillio R1](https://aillio.com/) on Windows, the [Digi-Sense 20250-07 IR](https://www.coleparmer.com/i/digi-sense-ir-thermometer-thermocouple-probe-input-and-nist-traceable-calibration-30-1/2025007), and the [Extech 42570 IR](https://www.extech-online.com/index.php?main_page=product_info&cPath=78_21_35&products_id=99).<br>|
| [v2.10.0](https://github.com/artisan-roaster-scope/artisan/releases/tag/v2.10.0) | Nov&nbsp;28,&nbsp;2023 | Adds support for [Bühler Roastmaster](https://www.buhlergroup.com/global/de/products/roastmaster_coffeeroaster.html), [Joper](https://joper-roasters.com/), and [Cogen](https://cogen-company.com/) roasting machines, the Phidget [DAQ1000](https://phidgets.com/?prodid=622), [DAQ1200](https://phidgets.com/?prodid=623), [DAQ1300](https://phidgets.com/?prodid=624), [DAQ1301](https://phidgets.com/?prodid=625), AppleSilicon support to macOS build, a Raspbian Bookworm 64bit build, extra devices to [Roast Comparator](https://artisan-roasterscope.blogspot.com/2020/05/roast-comparator.html) and many performance and stability improvements.<br><b>Release Sponsor: [Paolo Scimone Coffee Consulting](https://www.paoloscimone.com/)</b><br/>|
| [v2.8.4](https://github.com/artisan-roaster-scope/artisan/releases/tag/v2.8.4) | Jun&nbsp;21,&nbsp;2023 | Adds official integration with [Kaleido](https://www.kaleido-roaster.com/) roasters as well as dark mode support on Windows and Linux (last version supporting macOS 11, but newer legacy builds still support macOS 10.13 and newer)<br><b>Release Sponsor: [BC Roasters](https://bcroasters.com/)</b><br/>|
| [v2.8.2](https://github.com/artisan-roaster-scope/artisan/releases/tag/v2.8.2) | Dec&nbsp;21,&nbsp;2022 | Adds support for [Sivetz fluid bed roasting machines](https://artisan-scope.org/machines/sivetz/), [Santoker Q Series and R Series roasters](https://artisan-scope.org/machines/santoker/), the [Yocto Watt module](https://artisan-scope.org/devices/yoctopuce/#Yocto-Watt), the [Phidget DAQ1500](https://artisan-scope.org/devices/phidgets/#DAQ1500), and speeds up the [Designer](https://artisan-roasterscope.blogspot.com/2019/05/using-artisan-designer.html) (last version supporting macOS 10.15, but legacy builds of v2.8 still supports macOS 10.13+) |
Expand Down
4 changes: 2 additions & 2 deletions src/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.10.1</string>
<string>2.10.2</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
Expand All @@ -131,7 +131,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>Artisan 2.10.1</string>
<string>Artisan 2.10.2</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>LSArchitecturePriority</key>
Expand Down
2 changes: 1 addition & 1 deletion src/artisanlib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = '2.10.1'
__version__ = '2.10.2'
__revision__ = '0'
__build__ = '0'

Expand Down
2 changes: 1 addition & 1 deletion src/artisanlib/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def _process(self, x:float) -> float:
y_live_lfilter = [live_lfilter(y) for y in yraw]


# define lowpass filter with 2.5 Hz cutoff frequency if order 2
# define lowpass filter with 2.5 Hz cutoff frequency of order 2
sos = scipy.signal.iirfilter(2, Wn=2.5, fs=fs, btype='low',
ftype='butter', output='sos')
y_scipy_sosfilt = scipy.signal.sosfilt(sos, yraw)
Expand Down
29 changes: 17 additions & 12 deletions src/artisanlib/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16046,8 +16046,8 @@ def addEventSlot(self, value:int, etype:int, record:bool, fire_slider_action:boo
if fire_slider_action:
self.fireslideractionSignal.emit(etype)
# create a new event
nv:float = self.qmc.eventsExternal2InternalValue(new_value)
if record and self.qmc.flagstart:
nv:float = self.qmc.eventsExternal2InternalValue(new_value)
self.qmc.eventRecordActionSignal.emit(etype,nv,'',True)

# kaleidoSendMessageAwait() sends out the message to the machine, awaits the reply and creates a corresponding event entry
Expand Down Expand Up @@ -16754,16 +16754,15 @@ def settingsLoad(self, filename:Optional[str] = None, theme:bool = False, machin
self.pidcontrol.svValue = toInt(settings.value('svValue',self.pidcontrol.svValue))
self.pidcontrol.loadRampSoakFromBackground = bool(toBool(settings.value('loadRampSoakFromBackground',self.pidcontrol.loadRampSoakFromBackground)))
self.pidcontrol.svLabel = toString(settings.value('svLabel',self.pidcontrol.svLabel))

self.sliderSV.blockSignals(True)
try:
if settings.contains('dutyMin'):
self.pidcontrol.dutyMin = toInt(settings.value('dutyMin',self.pidcontrol.dutyMin))
if settings.contains('dutyMax'):
self.pidcontrol.dutyMax = toInt(settings.value('dutyMax',self.pidcontrol.dutyMax))
finally:
self.sliderSV.blockSignals(False)

self.pidcontrol.dutyMin = toInt(settings.value('dutyMin',self.pidcontrol.dutyMin))
self.pidcontrol.dutyMax = toInt(settings.value('dutyMax',self.pidcontrol.dutyMax))
self.pidcontrol.positiveTargetRangeLimit = bool(toBool(settings.value('positiveTargetRangeLimit',self.pidcontrol.positiveTargetRangeLimit)))
self.pidcontrol.positiveTargetMin = toInt(settings.value('positiveTargetMin',self.pidcontrol.positiveTargetMin))
self.pidcontrol.positiveTargetMax = toInt(settings.value('positiveTargetMax',self.pidcontrol.positiveTargetMax))
self.pidcontrol.negativeTargetRangeLimit = bool(toBool(settings.value('negativeTargetRangeLimit',self.pidcontrol.negativeTargetRangeLimit)))
self.pidcontrol.negativeTargetMin = toInt(settings.value('negativeTargetMin',self.pidcontrol.negativeTargetMin))
self.pidcontrol.negativeTargetMax = toInt(settings.value('negativeTargetMax',self.pidcontrol.negativeTargetMax))
self.pidcontrol.derivative_filter = toInt(settings.value('derivative_filter',self.pidcontrol.derivative_filter))
self.pidcontrol.activateSVSlider(self.pidcontrol.svSlider)
self.pidcontrol.pidKp = toFloat(settings.value('pidKp',self.pidcontrol.pidKp))
self.pidcontrol.pidKi = toFloat(settings.value('pidKi',self.pidcontrol.pidKi))
Expand Down Expand Up @@ -18392,6 +18391,13 @@ def saveAllSettings(self, settings:QSettings, default_settings:Optional[Dict[str
self.settingsSetValue(settings, default_settings, 'svValue',self.pidcontrol.svValue, read_defaults)
self.settingsSetValue(settings, default_settings, 'dutyMin',self.pidcontrol.dutyMin, read_defaults)
self.settingsSetValue(settings, default_settings, 'dutyMax',self.pidcontrol.dutyMax, read_defaults)
self.settingsSetValue(settings, default_settings, 'positiveTargetRangeLimit',self.pidcontrol.positiveTargetRangeLimit, read_defaults)
self.settingsSetValue(settings, default_settings, 'positiveTargetMin',self.pidcontrol.positiveTargetMin, read_defaults)
self.settingsSetValue(settings, default_settings, 'positiveTargetMax',self.pidcontrol.positiveTargetMax, read_defaults)
self.settingsSetValue(settings, default_settings, 'negativeTargetRangeLimit',self.pidcontrol.negativeTargetRangeLimit, read_defaults)
self.settingsSetValue(settings, default_settings, 'negativeTargetMin',self.pidcontrol.negativeTargetMin, read_defaults)
self.settingsSetValue(settings, default_settings, 'negativeTargetMax',self.pidcontrol.negativeTargetMax, read_defaults)
self.settingsSetValue(settings, default_settings, 'derivative_filter',self.pidcontrol.derivative_filter, read_defaults)
self.settingsSetValue(settings, default_settings, 'pidKp',self.pidcontrol.pidKp, read_defaults)
self.settingsSetValue(settings, default_settings, 'pidKi',self.pidcontrol.pidKi, read_defaults)
self.settingsSetValue(settings, default_settings, 'pidKd',self.pidcontrol.pidKd, read_defaults)
Expand Down Expand Up @@ -22511,7 +22517,6 @@ def PIDcontrol(self, _:bool = False) -> None:
dialog.setModal(False)
dialog.show()
dialog.setFixedSize(dialog.size())
# QApplication.processEvents()
# Hottop
elif self.qmc.device == 53:
modifiers = QApplication.keyboardModifiers()
Expand Down
31 changes: 29 additions & 2 deletions src/artisanlib/pid.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@

import time
import numpy
import scipy.signal # type:ignore[import-untyped]
import logging
from typing import Final, List, Optional, Callable

from artisanlib.filters import LiveSosFilter

try:
from PyQt6.QtCore import QSemaphore # @Reimport @UnresolvedImport @UnusedImport
except ImportError:
Expand All @@ -35,7 +38,8 @@ class PID:
__slots__ = [ 'pidSemaphore', 'outMin', 'outMax', 'dutySteps', 'dutyMin', 'dutyMax', 'control', 'Kp',
'Ki', 'Kd', 'pOnE', 'Pterm', 'errSum', 'Iterm', 'lastError', 'lastInput', 'lastOutput', 'lastTime',
'lastDerr', 'target', 'active', 'derivative_on_error', 'output_smoothing_factor', 'output_decay_weights',
'previous_outputs', 'input_smoothing_factor', 'input_decay_weights', 'previous_inputs', 'force_duty', 'iterations_since_duty' ]
'previous_outputs', 'input_smoothing_factor', 'input_decay_weights', 'previous_inputs', 'force_duty', 'iterations_since_duty',
'derivative_filter_level', 'derivative_filter' ]

def __init__(self, control:Callable[[float], None]=lambda _: None, p:float=2.0, i:float=0.03, d:float=0.0) -> None:
self.pidSemaphore:QSemaphore = QSemaphore(1)
Expand Down Expand Up @@ -72,6 +76,9 @@ def __init__(self, control:Callable[[float], None]=lambda _: None, p:float=2.0,
self.previous_inputs:List[float] = []
self.force_duty:int = 3 # at least every n update cycles a new duty value is send, even if its duplicating a previous duty (within the duty step)
self.iterations_since_duty:int = 0 # reset once a duty is send; incremented on every update cycle
# PID derivative smoothing
self.derivative_filter_level: int = 0 # 0: off, 1: on
self.derivative_filter:LiveSosFilter = self.derivativeFilter()

def _smooth_output(self,output:float) -> float:
# create or update smoothing decay weights
Expand Down Expand Up @@ -179,6 +186,8 @@ def update(self, i:Optional[float]) -> None:
else:
D = - self.Kd * dtinput

if self.derivative_filter_level > 0:
D = self.derivative_filter(D)
output:float = self.Pterm + self.Iterm + D

output = self._smooth_output(output)
Expand All @@ -189,7 +198,7 @@ def update(self, i:Optional[float]) -> None:
elif output < self.outMin:
output = self.outMin

int_output = min(self.dutyMax,max(self.dutyMin,int(round(output))))
int_output = int(round(min(self.dutyMax,max(self.dutyMin,output))))
if self.lastOutput is None or self.iterations_since_duty >= self.force_duty or int_output >= self.lastOutput + self.dutySteps or int_output <= self.lastOutput - self.dutySteps:
if self.active:
self.control(int_output)
Expand Down Expand Up @@ -316,3 +325,21 @@ def getDuty(self) -> Optional[float]:
finally:
if self.pidSemaphore.available() < 1:
self.pidSemaphore.release(1)

def derivativeFilter(self) -> LiveSosFilter:
return LiveSosFilter(
scipy.signal.iirfilter(1, # order
Wn=0.2, # 0 < Wn < fs/2 (fs=1 -> fs/2=0.5)
fs=1, # sampling rate, Hz
btype='low',
ftype='butter', output='sos'))

def setDerivativeFilterLevel(self, v:int) -> None:
try:
self.pidSemaphore.acquire(1)
self.derivative_filter_level = v
# reset the derivative filter on each filter level change (also on PID ON)
self.derivative_filter = self.derivativeFilter()
finally:
if self.pidSemaphore.available() < 1:
self.pidSemaphore.release(1)
Loading

0 comments on commit e25aed8

Please sign in to comment.