Zustand-Templates

Struktur Templates

Neben der vom Plugin bereitgestellten Möglichkeit, Templates zu definieren (siehe weiter unten), bietet sich ab smarthomeNG 1.6 das struct Attribut an. Zum einen können in der Datei etc/struct.yaml eigene Vorlagen definiert werden, zum anderen stellt das Plugin einige Vorlagen fix fertig bereit. Sie können wie folgt eingebunden werden:

#items/item.yaml
beispiel:
    trigger:
        type: bool
        enforce_updates: True

    raffstore1:
        automatik:
            struct:
              - stateengine.general
              - stateengine.state_lock
              - stateengine.state_suspend
              - stateengine_default_raffstore #beispielsweise eine in etc/struct.yaml angelegte Vorlage

            manuell:
               # Weitere Attribute werden bereits über das Template stateengine.state_suspend geladen
                eval_trigger:
                   - beispiel.raffstore1.aufab
                   - beispiel.raffstore1.step
                   - beispiel.raffstore1.hoehe
                   - beispiel.raffstore1.lamelle
                se_manual_exclude:
                   - KNX:y.y.y:1/2/3 # konkrete Gruppenadresse eines konkreten KNX Geräts
                   - Init:*

            rules:
              eval_trigger:
                  - ..lock
                  - ..supsend
                  - .. release
                  - beispiel.trigger

              additional_state1:
                  type: foo

Unter SmarthomeNG Version 1.7.0 werden die eval_trigger Angaben aus den einzelnen Struct-Vorgaben nicht kumuliert. Es ist daher wichtig, die eval_trigger Liste nochmals manuell im endgültigen Item anzulegen. Bei neueren Versionen kann man sich das erneute Listen der Trigger wie lock, suspend und release sparen, da diese bereits im struct des Plugins gelistet sind.

Die Vorlagen beinhalten folgende Strukturen:

general

Die general Vorlage enthält die Items, die generell für einen Zustandsautomaten angelegt werden sollten. Das „rules“ Item ist das Regelwerk-Item mit aktiviertem se_plugin. Dieser Codeblock wird zwingend von jedem Zustandsautomaten benötigt.

#stateengine.general
state_id:
    # The id/path of the actual state is assigned to this item by the stateengine
    type: str
    visu_acl: r
    cache: True

state_name:
    # The name of the actual state is assigned to this item by the stateengine
    type: str
    visu_acl: r
    cache: True

conditionset_id:
    remark: The id/path of the actual condition set is assigned to this item by the stateengine
    type: str
    visu_acl: r
    cache: True

conditionset_name:
    remark: The name of the actual condition set is assigned to this item by the stateengine
    type: str
    visu_acl: r
    cache: True

rules:
    name: Regeln und Item Verweise für den Zustandsautomaten
    type: bool
    se_plugin: active
    eval: True

    # se_startup_delay: 30
    # se_repeat_actions: true
    # se_suspend_time: 7200

    se_laststate_item_id: ..state_id
    se_laststate_item_name: ..state_name
    se_lastconditionset_item_id: ..conditionset_id
    se_lastconditionset_item_name: ..conditionset_name

lock

Die state_lock Vorlage beinhaltet zum einen den Lock Zustand mit dem Namen „gesperrt“, zum anderen ein Item mit dem Namen lock. Wird dieses auf „1/True“ gesetzt, wird der Zustand eingenommen. Der Zustand sollte immer als erster Zustand eingebunden werden.

#stateengine.state_lock
lock:
    type: bool
    knx_dpt: 1
    visu_acl: rw
    cache: 'on'

rules:
    se_item_lock: ..lock
    eval_trigger:
        - ..lock

    lock:
        name: gesperrt

        on_leave:
            se_action_lock:
              - 'function: set'
              - 'to: False'

        enter:
            se_value_lock: True

suspend

Die state_suspend Vorlage dient dem Abfragen von manuellen Tätigkeiten, wie z.B. Schalten eines Lichts oder Fahren einer Jalousie mittels Taster oder Visu. In diesem Fall soll die automatiche Evaluierung für eine gewisse Zeit pausieren.

Beim manuell Item muss unter Umständen der Eintrag se_manual_exclude in der eigenen Baumstruktur überschrieben und durch einen Eintrag (z.B. beim Einsatz von KNX Aktoren) - KNX:physikalische Adresse:Gruppenadresse ergänzt werden. Außerdem muss ein eval_trigger manuell deklariert werden. Hier sollten alle Items gelistet sein, die für ein vorübergehendes Aussetzen der Automatisierung sorgen sollen (z.B. Schalt- und Dimm-Items)

Das Item settings.suspendduration ermöglicht es, die Dauer der Pausierung bequem über eine Visu oder das Backend zu ändern. Das darunter angesiedelte duration_format konvertiert die angegebene Dauer in Minuten in das „Duration Format“ mit Tage-, Stunden- und Minutenangabe, für 5 Minuten also z.B. 0d 0h 5i. Dies ist genau wie das suspend_start.unix_timestamp Item für das smartvisu Widget clock.countdown notwendig.

Setzt man das Item settings.suspend_active auf False, wird der Pause-Zustand deaktiviert und manuelle Betätigungen werden beim nächsten Durchlauf eventuell durch andere Zustände überschrieben.

#stateengine.state_suspend
state_suspend:
    name: Zustandsvorlage für manuelles Aussetzen

    suspend:
        type: bool
        knx_dpt: 1
        visu_acl: rw
        cache: True

        visu:
            type: bool
            knx_dpt: 1
            visu_acl: rw
            cache: True

    suspend_end:
        type: str
        visu_acl: ro
        eval: "'' if not any(char.isdigit() for char in sh..self.date_time()) else sh..self.date_time().split(' ')[1].split('.')[0]"
        eval_trigger: .date_time
        crontab: init

        date_time:
            type: str
            visu_acl: ro
            cache: True

        unix_timestamp:
            type: num
            visu_acl: ro
            eval: "0 if not any(char.isdigit() for char in sh...date_time()) else  sh.tools.dt2ts(shtime.datetime_transform(sh...date_time())) * 1000"
            eval_trigger: ..date_time
            crontab: init

    suspend_start:
        type: str
        visu_acl: ro
        eval: "'' if not any(char.isdigit() for char in sh..self.date_time()) else sh..self.date_time().split(' ')[1].split('.')[0]"
        eval_trigger: .date_time
        crontab: init

        date_time:
            type: str
            visu_acl: ro
            cache: True

        unix_timestamp:
            remark: Can be used for the clock.countdown widget
            type: num
            visu_acl: ro
            eval: "0 if not any(char.isdigit() for char in sh...date_time()) else  sh.tools.dt2ts(shtime.datetime_transform(sh...date_time())) * 1000"
            eval_trigger: ..date_time
            crontab: init

    manuell:
        type: bool
        name: manuell
        se_manual_invert: True
        remark: Adapt the se_manual_exclude the way you need it
        #se_manual_include: KNX:* Force manual mode based on source
        se_manual_exclude:
          - database:*
          - init:*

    retrigger:
        remark: Item to retrigger the rule set evaluation
        type: bool
        visu_acl: rw
        enforce_updates: True
        on_update: ..rules = True

    settings:
        remark: Use these settings for your condition values
        type: foo
        eval: (sh..suspendduration(sh..suspendduration(), "Init", "Start"), sh..suspendvariant.suspendduration0(sh..suspendduration(), "Init", "Start"), sh..suspendvariant.suspendduration1(sh..suspendvariant.suspendduration1(), "Init", "Start"), sh..suspendvariant.suspendduration2(sh..suspendvariant.suspendduration2(), "Init", "Start"))
        crontab: init = True

        suspendduration:
            remark: duration of suspend mode in minutes (gets converted automatically)
            type: num
            visu_acl: rw
            cache: True
            initial_value: 60
            on_change: .seconds = value * 60 if not sh..self.property.last_change_by == "On_Change:{}".format(sh..seconds.property.path) else None
            on_update: .seconds = value * 60 if "Init" in sh..self.property.last_update_by else None

            duration_format:
                remark: Can be used for the clock.countdown widget
                type: str
                cache: True
                visu_acl: ro
                eval: "'{}d {}h {}i {}s'.format(int(sh...seconds()//86400), int((sh...seconds()%86400)//3600), int((sh...seconds()%3600)//60), round((sh...seconds()%3600)%60))"
                eval_trigger:
                  - ..seconds
                  - ..

            seconds:
                remark: duration of suspend mode in seconds (gets converted automatically)
                type: num
                visu_acl: rw
                cache: True
                on_change: .. = value / 60 if not sh..self.property.last_change_by in [ "On_Change:{}".format(sh....property.path), "On_Update:{}".format(sh....property.path)] else None

        suspend_active:
            remark: Use this to (de)activate suspend mode in general
            type: bool
            visu_acl: rw
            cache: True
            initial_value: True

        settings_edited:
            type: bool
            name: settings editiert
            eval_trigger: ...settings.*
            eval: not sh..self()
            on_update: ...retrigger = True if sh..self.property.prev_update_age > 0.1 else None

    rules:
        se_item_suspend: ..suspend
        se_item_suspend_visu: ..suspend.visu
        se_item_suspend_end: ..suspend_end.date_time
        se_item_suspend_start: ..suspend_start.date_time
        se_item_suspend_active: ..settings.suspend_active
        se_suspend_time: ..settings.suspendduration

        eval_trigger:
            - ..manuell

        suspend:
            name: ausgesetzt

            on_enter:
                se_action_suspend_visu:
                  - 'function: set'
                  - 'to: True'
                  - 'order: 2'

            on_enter_or_stay:
                se_action_suspend:
                  - 'function: special'
                  - 'value: suspend:..suspend, ..manuell'
                  - 'repeat: True'
                  - 'order: 1'
                se_action_suspend_end:
                  - 'function: set'
                  - "to: eval:se_eval.insert_suspend_time('..suspend', suspend_text='%Y-%m-%d %H:%M:%S.%f%z')"
                  - 'repeat: True'
                  - 'order: 3'
                se_action_suspend_start:
                  - 'function: set'
                  - "to: eval:str(shtime.now())"
                  - 'repeat: True'
                  - 'conditionset: enter_manuell'
                  - 'order: 4'
                se_action_retrigger:
                  - 'function: special'
                  - 'value: retrigger:..retrigger'
                  - 'delay: var:item.suspend_remaining'
                  - 'repeat: True'
                  - 'order: 5'

            on_leave:
                se_action_suspend:
                  - 'function: set'
                  - 'to: False'
                  - 'order: 2'
                se_action_suspend_visu:
                  - 'function: set'
                  - 'to: False'
                  - 'order: 3'
                se_action_suspend_end:
                  - 'function: set'
                  - 'to:  '
                  - 'order: 4'
                se_action_suspend_start:
                  - 'function: set'
                  - 'to:  '
                  - 'order: 5'
                  - 'delay: 1'

            enter_manuell:
                se_value_trigger_source: eval:se_eval.get_relative_itemproperty('..manuell', 'path')
                se_value_suspend_active: True

            enter_stay:
                se_value_laststate: var:current.state_id
                se_agemax_suspend: var:item.suspend_time
                se_value_suspend: True
                se_value_suspend_active: True

release

Die state_release Vorlage ist nicht unbedingt nötig, kann aber dazu genutzt werden, schnell den Sperr- oder Pause-Zustand zu verlassen und die erneute Evaluierung der Zustände anzuleiern.

#stateengine.state_release
release: #triggers the release
    type: bool
    knx_dpt: 1
    visu_acl: rw
    enforce_updates: True

rules:
    se_item_lock: ..lock
    se_item_suspend: ..suspend
    se_item_retrigger: ..rules
    se_item_release: ..release
    se_item_suspend_end: ..suspend_end
    eval_trigger:
        - ..release

    release:
        name: release

        on_enter_or_stay:
            se_action_suspend:
              - 'function: set'
              - 'to: False'
              - 'order: 1'
            se_action_lock:
              - 'function: set'
              - 'to: False'
              - 'order: 2'
            se_action_release:
              - 'function: set'
              - 'to: False'
              - 'order: 3'
            se_action_suspend_end:
              - 'function: set'
              - 'to: '
              - 'order: 4'
            se_action_retrigger:
              - 'function: set'
              - 'to: True'
              - 'order: 5'
              - 'repeat: True'
              - 'delay: 1'

        enter:
            se_value_release: True

Pluginspezifische Templates

Es ist neben der oben beschriebene Variante möglich, Vorgabezustände in der Item-Konfiguration über se_use zu definieren und diese später für konkrete Regelwerke durch Plugin-interne Attribute zu nutzen. Dabei können im konkreten Zustand auch Einstellungen des Vorgabezustands überschrieben werden. Alternativ ist es möglich, die struct Vorlagen aus SmarthomeNG >= 1.6 zu nutzen bzw. selbst welche zu erstellen.

Vorgabezustände werden als Item an beliebiger Stelle innerhalb der Item-Struktur definiert. Es ist sinnvoll, die Vorgabezustände unter einem gemeinsamen Item namens default zusammenzufassen. Innerhalb der Vorgabezustand-Items stehen die gleichen Möglichkeiten wie in normalen Zustands-Items zur Verfügung. Das dem Vorgabezustands-Item übergeordnete Item darf nicht das Attribut se_plugin: active haben, da diese Items nur Vorlagen und keine tatsächlichen State Machines darstellen. Im Item über dem Vorgabezustands-Item können jedoch Items über se_item_<Bedingungsname|Aktionsname> angegeben werden. Diese stehen in den Vorgabezuständen und in den von den Vorgabezuständen abgeleiteten Zuständen zur Verfügung und müssen so nicht jedes Mal neu definiert werden.

Im konkreten Zustands-Item kann das Vorgabezustand-Item über das Attribut

se_use:
  - struct:stateengine.state_suspend.rules.suspend
  - item:<string item mit Verweis auf Vorgabezustand>
  - eval:<Ausdruck zum dynamischen Einbinden von Vorgabezuständen>
  - <(relative) Itemangabe zum Vorgabezustand> #z.B. .suspend

eingebunden werden. Die Vorgabezustand-Items können als Liste angegben und geschachtelt werden; das heißt ein Vorgabezustand kann also selbst wiederum über se_use von einem weiteren Vorgabezustand abgeleitet werden. Um unnötige Komplexität und Zirkelbezüge zu vermeiden, ist die maximale Tiefe jedoch auf 5 Ebenen begrenzt. Jede in einem se_use angegebene Definition kann durch eine höher geordnete Angabe mit dem gleichen Namen überschrieben werden.

Beispiel

Die Konfiguration…

wetter:
  helligkeit:
    type: num
    initial_value: 400
  temperatur:
    type: num
    initial_value: 15

beispiel:
   define_use:
       type: str
       initial_value: 'beispiel.default.Mittags'

   default:
       se_eval_height: se_eval.get_relative_item('...hoehe')
       se_item_helligkeit: wetter.helligkeit
       se_item_temperatur: wetter.temperatur
       Nacht:
           enter:
               se_min_helligkeit: 300
               se_min_temperatur: 0
               se_max_helligkeit: 4000
           se_set_height: value:100
           se_set_lamella: 0
       Morgens:
           enter:
               se_min_helligkeit: 900
               se_max_temperatur: 12
           se_set_height: value:90
           se_set_lamella: 25
       Mittags:
           se_set_lamella: 55
           enter:
               se_min_helligkeit: 5900
               se_min_temperatur: 18

   raffstore1:
       lamelle:
          type: num
       hoehe:
          type: num

       automatik:
           struct: stateengine.general
           lock:
              type: bool

           rules:
               se_item_lamella: ...lamelle
               se_item_helligkeit: wetter.helligkeit
               Nacht:
                   se_use: beispiel.default.Nacht
                   se_set_lamella: 10
                   enter_additional:
                       se_min_helligkeit: 20
                   enter:
                       se_min_helligkeit: 500
                       se_min_temperatur: 0
               Morgens:
                   name: morgens
                   se_use:
                     - beispiel.default.Morgens
                     - .Nacht
                     - struct:stateengine.state_lock.rules.lock
                     - item:beispiel.define_use

führt zu folgendem Ergebnis:

State beispiel.raffstore1.automatik.rules.Nacht:
    State Name: Nacht
    Updating Web Interface...
    Finished Web Interface Update
    State configuration extended by se_use: beispiel.default.Nacht
    Condition sets to enter state:
            Condition Set 'enter':
                    Condition 'helligkeit':
                     item: helligkeit (wetter.helligkeit)
                     min: 500
                     max: 4000
                     negate: False
                    Condition 'temperatur':
                     item: temperatur (wetter.temperatur)
                     min: 0
                     negate: False
            Condition Set 'enter_additional':
                    Condition 'helligkeit':
                     item: helligkeit (wetter.helligkeit)
                     max: 200
                     negate: False
    Actions to perform on enter or stay:
            Action 'height':
                    name: height
                    item from eval: se_eval.get_relative_item('...hoehe')
                    value: 100
            Action 'lamella':
                    name: lamella
                    item from eval: beispiel.raffstore1.lamelle
                    value: 10
State beispiel.raffstore1.automatik.rules.Morgens:
    State Name: morgens
    Updating Web Interface...
    Finished Web Interface Update
    State configuration extended by se_use: [Item: beispiel.default.Morgens, Item: beispiel.raffstore1.automatik.rules.Nacht, Item: beispiel.default.Nacht, SeStructMain stateengine.state_lock.rules.lock, Item: beispiel.default.Mittags]
    Condition sets to enter state:
            Condition Set 'enter':
                    Condition 'lock':
                     item: lock (beispiel.raffstore1.automatik.lock)
                     value: True
                     negate: False
                    Condition 'temperatur':
                     item: temperatur (wetter.temperatur)
                     min: 18
                     max: 12
                     negate: False
                    Condition 'helligkeit':
                     item: helligkeit (wetter.helligkeit)
                     min: 5900
                     max: 12000
                     negate: False
            Condition Set 'enter_additional':
                    Condition 'helligkeit':
                     item: helligkeit (wetter.helligkeit)
                     max: 200
                     negate: False
    Actions to perform on enter or stay:
            Action 'height':
                    name: height
                    item from eval: se_eval.get_relative_item('...hoehe')
                    value: 100
            Action 'lamella':
                    name: lamella
                    item from eval: beispiel.raffstore1.lamelle
                    value: 55

Bemerkung

Folgende Besonderheiten sind bei der Konfiguration zu beachten:

  • Beim Laden via se_use werden relative Itemdeklarationen NICHT relativ zum StateEngine Item (rules) gesucht, sondern relativ zu dem Item, wo das Attribut se_item_.. steht. Daher muss hier unbedingt se_eval_.. wie z.B. se_eval_height: se_eval.get_relative_item('...hoehe') genutzt werden.

  • Auf gleicher Ebene zum StateEngine Item (rules) müssen mittels struct: stateengine.general die Standarditems und Attribute des Plugins eingebunden werden, damit eine korrekte Funktion garantiert werden kann.

  • Über se_use eingebundene Zustandsvorlagen suchen zwar im darüber gelegenen Item nach den entsprechenden se_item/eval Deklarationen, binden aber keine darüber liegenden Items ein. Daher muss z.B. beim Referenzieren des Lock-Zustands aus dem Plugin Struct das lock Item manuell angelegt werden. Beim Nutzen von struct: stateengine.state_lock wäre das nicht notwendig, weshalb sich eine Referenzierung auf die Plugin Templates via se_use nur bedingt eignet.