====== PID управление 3х клапаном ======
Ниже приведу пример текущей настройки для управления 3-х ходовым клапаном с помощью PID регулятора в моей конфигурации openHAB. \\
Из оборудования установлен 3-х ходовой актуатор ESBE на контуре теплых полов. \\
Управлением открытия/закрытия занимается контроллер MegaD, задействованы 2 релейных выхода. Для них созданы соответствующие Items.
1. Установлен Add-on [[https://www.openhab.org/addons/automation/pidcontroller/|PID Controller]], который соответственно рассчитывает выходное значение в зависимости от температуры уставки (''tp_setpoint'') и текущей температуры подачи (''tp_flowpipe'') и записывает его в Item ''tp_pid''. Настройки по коэффициентами пока выбраны следующие: P=10, I=0.5, D=80, D-T1=300
Код правила для расчета PID:
configuration: {}
triggers:
- id: 09519cc5-516c-42a4-857f-e3ed9eacec19
label: PID Controller Trigger
configuration:
input: tp_flowpipe
setpoint: tp_setpoint
kp: 10
kd: 80
kdTimeConstant: 300
ki: 0.5
loopTime: 120000
type: pidcontroller.trigger
conditions:
- inputs: {}
id: "2"
configuration:
itemName: boiler_flame
state: ON
operator: =
type: core.ItemStateCondition
- inputs: {}
id: "3"
configuration:
itemName: boiler_status
state: ON
operator: =
type: core.ItemStateCondition
actions:
- inputs: {}
id: "1"
configuration:
itemName: tp_pid
type: core.ItemCommandAction
Поскольку котел у меня 1-контурный, и в момент переключения котла в режим подогрева ГВС температура в контуре подачи теплого пола неминуемо падает - стоит исключение на запуск этого правила, когда ''boiler_flame'' и ''boiler_status'' активны. Это позволяет несколько снизить лишнее "кручение" клапаном.
2. Следующее правило запускается по cron каждые 3 минуты, и в зависимости от текущего значения PID управляет 3-х ходовым клапаном.
Код правила управления 3-х ходовым клапаном:
configuration: {}
triggers:
- id: "1"
configuration:
cronExpression: 0 0/3 * * * ? *
type: timer.GenericCronTrigger
conditions: []
actions:
- inputs: {}
id: "2"
configuration:
type: application/javascript
script: >-
/*
Items to control 3p Actuator
three_way_close - close relay
three_way_open - open relay
*/
function rotateActuator (pidValue) {
var actionTimerCorrection = 2 // Maybe 1 to any reasonable value, decreasing rotation time
var actionTimerMax = 15000 // milliseconds, maximum 15 sec
var actionTimerMin = 1000 // milliseconds, minimum 1 sec
console.log('PID: ' + pidValue)
var actionItem3pActuator = null
var action = null
var actionTimer = 0
action = (pidValue > 0) ? 'open' : 'close';
action3pActuator = items.getItem('three_way_' + action)
actionTimer = Math.round( Math.abs( pidValue ) / actionTimerCorrection ) * 500
// Limit to actionTimerMax if it more
actionTimer = (actionTimer > actionTimerMax) ? actionTimerMax : actionTimer;
if (actionTimer >= actionTimerMin)
{
console.log ('Actuator action: ' + action3pActuator.name + ' for ' + actionTimer + ' (' + actionTimer/1000 + ' sec)')
console.info(action3pActuator.name + ' send command ON')
action3pActuator.sendCommand('ON')
setTimeout(() => {
console.info(action3pActuator.name + ' send command OFF')
action3pActuator.sendCommand('OFF')
}, actionTimer);
}
else
{
console.info('actionTimer: ' + actionTimer/1000 + ' < ' + actionTimerMin/1000 + ' sec - nothing to do with 3p actuator')
}
}
rotateActuator(items.tp_pid.state)
type: script.ScriptAction
Знать текущее положение клапана, насколько он открыт или закрыт в реальности абсолютно не нужно. В крайних положениях он в любом случае отключается по внутри установленным реле.\\
Но интересно же! :)
Поэтому попробовал реализовать и такую вещь, как отображение текущего уровня открытия 3-х ходового клапана.
Логика следующая. Есть отдельный Item (''tp_3pA_status''), в который будет писаться уровень открытия клапана. \\
И отдельное правило, которое отслеживает работу реле открытия/закрытия клапана, считает время, на которое соответствующее реле было открыто или закрыто, и обновляет собственно статус в нужный нам Item.
Экспериментальным путем было установлено, что полное время открытия/закрытия клапана составляет в моем случае 127 сек. (при заявленных 120 сек. производителем). Именно это значение используется в дальнейшем.
Код правила для обновления статуса клапана:
configuration: {}
triggers:
- id: "1"
configuration:
itemName: three_way_close
type: core.ItemStateChangeTrigger
- id: "2"
configuration:
itemName: three_way_open
type: core.ItemStateChangeTrigger
conditions: []
actions:
- inputs: {}
id: "3"
configuration:
type: application/javascript
script: >-
var maxRotationTime = 127000
var current3pAState = items.tp_3pA_status.state
var new3pAState = null
var cacheId = 'tp_3pAStatus_Id'
var processDuration = 0
var stor = cache.private.get(cacheId, () => ({
'cacheId': cacheId,
'openStarts': null,
'closeStarts': null,
}));
function resetStorage(stor) {
stor.openStarts = null;
stor.closeStarts = null;
cache.private.put(stor.cacheId, stor);
}
function startMoving(stor, direction) {
stor[direction + 'Starts'] = Date.now();
cache.private.put(stor.cacheId, stor);
console.log('3pA ' + direction + ' starts at: ' + stor[direction + 'Starts'])
}
function stopMoving(stor, direction) {
processDuration = Date.now() - stor[direction + 'Starts'];
console.log('3pA ' + direction + ' stopped at ' + Date.now() + ', start was at: ' + stor[direction + 'Starts'] + ', Dur:' + processDuration)
rotationPercent = processDuration / maxRotationTime;
console.log('Rotation was at: ' + rotationPercent * 100 + '%')
new3pAState = Number.parseFloat(current3pAState) + ((direction == 'open') ? + Number.parseFloat(rotationPercent) : - Number.parseFloat(rotationPercent))
console.log('New status is set to: ' + (new3pAState) * 100 + '%')
items.tp_3pA_status.postUpdate(new3pAState)
resetStorage(stor)
}
var direction = null
switch(event.itemName) {
case 'three_way_open':
direction = 'open'
if (event.itemState.toString() == 'ON') {
startMoving(stor, direction)
} else {
stopMoving(stor, direction)
}
break
case 'three_way_close':
direction = 'close'
if (event.itemState.toString() == 'ON') {
startMoving(stor, direction)
} else {
stopMoving(stor, direction)
}
break
default:
console.error('We not supposed to be here. Something wrong')
break
}
type: script.ScriptAction
4. Ну и в завершение есть еще небольшое правило, для сброса статуса уровня открытия клапана. Оно вращает клапан до крайнего положения закрытия, и обновляет статус в ноль. Ну а на время своей работы отключает другие правила, которые могут попытаться как то с клапаном работать.
configuration: {}
triggers: []
conditions: []
actions:
- inputs: {}
id: "1"
configuration:
type: application/javascript
script: >+
console.log("=== Resetting 3 point actuator Status ===")
var maxRotationTime = 127000 // 127 sec
var rulesToDisable = [
'pidTest',
'update3pAStatus',
'rotate_3p_actuator_with_pid',
]
// Disable rules
console.log('Disabling needed rules, to prevent their runnig:')
rulesToDisable.forEach((rule) => {
rules.setEnabled(rule, false);
console.log('Rule ' + rule + ' disabled.')
});
items.three_way_close.sendCommand('ON')
console.log('Starting closing 3pA for ' + Math.round(maxRotationTime/1000) + ' sec.')
setTimeout(() => {
items.three_way_close.sendCommand('OFF')
console.log('Closing 3pA done')
items.tp_3pA_status.postUpdate(0)
console.log('Status of 3pA updated to 0')
// Enable rules
console.log('Resetting complete. Enabling rules:')
rulesToDisable.forEach((rule) => {
rules.setEnabled(rule, true);
console.log('Rule ' + rule + ' enabled.')
});
}, maxRotationTime);
type: script.ScriptAction