Skip to content

Commit

Permalink
Manual merge of main's PID / SlowPWM
Browse files Browse the repository at this point in the history
  • Loading branch information
schwabix-1311 committed Nov 24, 2024
1 parent b5992d9 commit 1661ece
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 78 deletions.
29 changes: 18 additions & 11 deletions aquaPi/machineroom/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .msg_bus import MsgBus
from .ctrl_nodes import MinimumCtrl, MaximumCtrl, PidCtrl, SunCtrl, FadeCtrl
from .in_nodes import AnalogInput, ScheduleInput
from .out_nodes import SwitchDevice, AnalogDevice
from .out_nodes import SwitchDevice, SlowPwmDevice, AnalogDevice
from .aux_nodes import ScaleAux, MinAux, MaxAux, AvgAux
from .hist_nodes import History
from .alert_nodes import Alert, AlertAbove, AlertBelow
Expand Down Expand Up @@ -127,10 +127,14 @@ def create_default_nodes(self) -> None:

# single water temp sensor, switched relay
wasser_i = AnalogInput('Wasser', 'DS1820 xA2E9C', 25.0, '°C',
avg=1, interval=30)
wasser = MinimumCtrl('Temperatur', wasser_i.id, 25.0)
wasser_o = SwitchDevice('Heizstab', wasser.id,
'GPIO 12 out', inverted=True)
avg=1, interval=180)
#wasser = MinimumCtrl('Temperatur', wasser_i.id, 25.0)
#wasser_o = SwitchDevice('Heizstab', wasser.id,
# 'GPIO 12 out', inverted=True)
wasser = PidCtrl('PID Temperatur', wasser_i.id, 25.0,
p_fact=1.5, i_fact=0.1, d_fact=0.)
wasser_o = SlowPwmDevice('Heizstab', wasser.id,
'GPIO 12 out', inverted=True, cycle=90)
wasser_i.plugin(self.bus)
wasser.plugin(self.bus)
wasser_o.plugin(self.bus)
Expand Down Expand Up @@ -241,16 +245,19 @@ def create_default_nodes(self) -> None:
if SIM_TEMP:
if not COMPLEX_TEMP:
# single temp sensor -> temp ctrl -> relay
wasser_i = AnalogInput('Wasser', 'DS1820 xA2E9C', 25.0, '°C')
#wasser = MinimumCtrl('Temperatur', wasser_i.id, 25.0)
wasser_pid = PidCtrl('Temperatur', wasser_i.id, 25.0)
wasser_o = SwitchDevice('Heizstab', wasser_pid.id, 'GPIO 12 out')
wasser_pid.plugin(self.bus)
wasser_i = AnalogInput('Wasser', 'DS1820 xA2E9C', 25.0, '°C', interval=61)
#wasser = MinimumCtrl('Temperaturregler', wasser_i.id, 25.0)
#wasser_o = SwitchDevice('Heizstab', wasser_pid.id, 'GPIO 12 out')
wasser = PidCtrl('Temperaturregler', wasser_i.id, 25.0,
p_fact=1.5, i_fact=0.1, d_fact=0.)
wasser_o = SlowPwmDevice('Heizstab', wasser.id,
'GPIO 12 out', inverted=True, cycle=20)
wasser.plugin(self.bus)
wasser_o.plugin(self.bus)
wasser_i.plugin(self.bus)

t_history = History('Temperaturen',
[wasser_i.id, wasser_pid.id, wasser_o.id])
[wasser_i.id, wasser.id, wasser_o.id])
t_history.plugin(self.bus)

else:
Expand Down
2 changes: 1 addition & 1 deletion aquaPi/machineroom/alert_nodes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python3

from abc import ABC, abstractmethod
import logging
from typing import Any
from abc import ABC, abstractmethod

from .msg_types import (Msg, MsgPayload, MsgData)
from .msg_bus import (BusListener, BusRole, DataRange)
Expand Down
2 changes: 1 addition & 1 deletion aquaPi/machineroom/aux_nodes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python3

from abc import ABC
import logging
from typing import (Iterable, Any)
from abc import ABC

from .msg_types import (Msg, MsgData)
from .msg_bus import (BusListener, BusRole, DataRange)
Expand Down
85 changes: 29 additions & 56 deletions aquaPi/machineroom/ctrl_nodes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python3

from abc import ABC
import logging
from typing import Any
from abc import ABC
import time
import math
import random
Expand Down Expand Up @@ -218,102 +218,75 @@ class PidCtrl(ControllerNode):
receives - id of a single (!) input to receive measurements from
setpoint - the target value
p_fact/i_fact,d_fact - the PID factors
sample - the ratio of sensor reads to 1 PID output
Output:
posts a series of PWM pulses
"""
data_range = DataRange.PERCENT
#data_range = DataRange.BINARY

def __init__(self, name: str, receives: str, setpoint: float,
p_fact: float = 1.0, i_fact: float = .1, d_fact: float = 0.1,
sample: int = 10,
p_fact: float = 1.0, i_fact: float = 0.05, d_fact: float = 0.,
_cont: bool = False):
super().__init__(name, receives, _cont=_cont)
self.setpoint: float = setpoint
self.p_fact: float = p_fact
self.i_fact: float = i_fact
self.d_fact: float = d_fact
self.sample: int = sample
self._err_sum: float = 0
self._err_pre: float = 0
self._ta_pre: float = 0
self._t_pre: float = 0
self._cnt: int = 0
self._err_old: float = 0
self._tm_old: float = 0
self.data: float = 0.

def __getstate__(self) -> dict[str, Any]:
state = super().__getstate__()
state.update(setpoint=self.setpoint)
state.update(p_fact=self.p_fact)
state.update(i_fact=self.i_fact)
state.update(d_fact=self.d_fact)
state.update(sample=self.sample)
return state

def __setstate__(self, state: dict[str, Any]) -> None:
log.debug('__SETstate__ %r', state)
self.data = state['data']
PidCtrl.__init__(self, state['name'], state['receives'], state['setpoint'],
p_fact=state['p_fact'], i_fact=state['i_fact'], d_fact=state['d_fact'],
sample=state['sample'],
_cont=True)

def _pulse(self, dur: float, perc: float):
log.info(' PID ON: %d %% -> %f s', perc, round(dur * perc / 100, 1))
if perc:
self.post(MsgData(self.id, 100))
time.sleep(dur * perc / 100)
log.info(' PID off')
if perc < 100:
self.post(MsgData(self.id, 0))
return

def listen(self, msg: Msg) -> bool:
def listen(self, msg):
if isinstance(msg, MsgData):
log.debug('PID got %s', msg)
now = time.time()
ta = now - self._t_pre
ta = now - self._tm_old
err = float(msg.data) - self.setpoint
if self._ta_pre:
err_sum = self._err_sum + err
val = self.p_fact * err \
+ self.i_fact * ta * err_sum \
+ self.d_fact / ta * (err - self._err_pre)

log.debug('PID err %f, e-sum %f, p %f / i %f / d %f, ',
if self._tm_old >= 1.:
self._err_sum = self._err_sum / 1 + err
p_dev = self.p_fact * err
i_dev = self.i_fact * ta * self._err_sum / 100 #??
d_dev = self.d_fact / ta * (err - self._err_old)
val = p_dev + i_dev + d_dev

log.warning('PID err %f, e-sum %f | P %+.1f%% / I %+.1f%% / D %+.1f %%, ',
err, self._err_sum,
self.p_fact * err,
self.i_fact * ta * err_sum,
self.d_fact / ta * (err - self._err_pre))
self.data = min(max(0., 50. - val*10), 100.)
if self.data > 0.0 and self.data < 100.:
self._err_sum = err_sum
else:
log.debug('clipped')

self._cnt += 1
if self._cnt % self.sample == 0:
log.info('PID -> %f (%f)', self.data, val)
if self.data_range == DataRange.PERCENT:
self.post(MsgData(self.id, round(self.data, 4)))
#self.post(MsgData(self.id, 100 if self.data >= 50 else 0))
else:
Thread(name='PIDpulse', target=self._pulse, args=[9 * ta, self.data], daemon=True).start()
self._err_pre = err
self._ta_pre = ta
self._t_pre = now
100 * p_dev, 100 * i_dev, 100 * d_dev)
self.data = min(max(0., 50. - val*100.), 100.)
log.brief('PID -> %f (%+.1f)', self.data, -val * 100)
self.post(MsgData(self.id, round(self.data, 4)))

if self.data <= 0. or self.data >= 100.:
self._err_sum /= 2
self._err_old = err
self._ta_old = ta
self._tm_old = now

return super().listen(msg)

def get_settings(self) -> list[tuple]:
settings = super().get_settings()
settings.append(('setpoint', 'Sollwert [%s]' % self.unit,
self.setpoint, 'type="number"'))
settings.append(('p_fact', 'P Faktor', self.p_fact, 'type="number" min="0" max="10" step="0.1"'))
settings.append(('i_fact', 'I Faktor', self.i_fact, 'type="number" min="0" max="10" step="0.1"'))
settings.append(('d_fact', 'D Faktor', self.d_fact, 'type="number" min="0" max="10" step="0.1"'))
settings.append(('sample', 'Samples', self.sample, 'type="number" min="1" max="25" step="1"'))
self.setpoint, 'type="number" step="0.1"'))
settings.append(('p_fact', 'P Faktor', self.p_fact, 'type="number" min="-10" max="10" step="0.1"'))
settings.append(('i_fact', 'I Faktor', self.i_fact, 'type="number" min="-10" max="10" step="0.01"'))
settings.append(('d_fact', 'D Faktor', self.d_fact, 'type="number" min="-10" max="10" step="0.1"'))
return settings


Expand Down
5 changes: 3 additions & 2 deletions aquaPi/machineroom/hist_nodes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python3

from abc import (ABC, abstractmethod)
import logging
from typing import Any
from abc import (ABC, abstractmethod)
import os
import sys
import platform
Expand Down Expand Up @@ -404,7 +404,8 @@ def __setstate__(self, state: dict[str, Any]) -> None:

def listen(self, msg) -> bool:
if isinstance(msg, MsgData):
self.db.feed(msg.sender, msg.data)
if self.db:
self.db.feed(msg.sender, msg.data)
if time() >= self._nextrefresh:
self.post(MsgData(self.id, 0))
self._nextrefresh = int(time()) + 10
Expand Down
4 changes: 2 additions & 2 deletions aquaPi/machineroom/in_nodes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env python3

from abc import ABC
import logging
from typing import Any
from abc import ABC
import time
from datetime import datetime
from croniter import croniter
Expand Down Expand Up @@ -107,7 +107,7 @@ def get_settings(self) -> list[tuple]:
settings.append(('port', 'Input',
self.port, 'type="text"'))
settings.append(('interval', 'Leseintervall [s]',
self.interval, 'type="number" min="0.1" max="60" step="0.1"'))
self.interval, 'type="number" min="1" max="600" step="1"'))
return settings


Expand Down
4 changes: 2 additions & 2 deletions aquaPi/machineroom/msg_bus.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3

import logging
from abc import (ABC, abstractmethod)
import logging
import time
from queue import Queue
from enum import (Enum, Flag, auto)
Expand Down Expand Up @@ -73,7 +73,7 @@ def __getstate__(self) -> dict[str, Any]:
state = {'name': self.name}
state.update(id=self.id)
state.update(identifier=self.identifier)
state.update(receives=self.receives)
state.update(receives=self.receives) #FIXME: "No overload var. of "update" of "MutableMapping" matches list[str]"
state.update(data=self.data)
state.update(unit=self.unit)
state.update(data_range=self.data_range.name)
Expand Down
2 changes: 1 addition & 1 deletion aquaPi/machineroom/msg_types.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env python3

import logging
from abc import ABC
import logging
from typing import Any


Expand Down
Loading

0 comments on commit 1661ece

Please sign in to comment.