PID управление 3х клапаном

Ниже приведу пример текущей настройки для управления 3-х ходовым клапаном с помощью PID регулятора в моей конфигурации openHAB.
Из оборудования установлен 3-х ходовой актуатор ESBE на контуре теплых полов.
Управлением открытия/закрытия занимается контроллер MegaD, задействованы 2 релейных выхода. Для них созданы соответствующие Items.

1. Установлен Add-on 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

  • sw/openhab/examples/heating/3pa.txt
  • Последнее изменение: 2023/12/26 18:07
  • lazygatto