Die Idee für die Logik kam aus meinem Alarmmodul. Dort sollten im Alarm-/Feuer-/Panikfall die Lichter an der Hauswand und die Gartenbeleuchtung im Wechsel blinken. Aber auch für die Signalisieren auf Taster LEDs könnte die Logik ihren Einsatz finden. Beispielsweise um im Schlafzimmer einen Status per LED auszugeben, ohne dass die LED die ganze Nacht leuchtet.

Somit war der Grundstein für die Logik gelegt, die möglichst flexibel, für unterschiedliche Anwendungen, konfigurierbar sein soll.

Funktion:

Gesteuert wird die Logik über ein control-item, welches zugleich in der Logik Konfiguration als watch-item dient. Wir das control-item auf True gesetzt, so läuft die Logik. False stoppt die Blinkroutine.

blink_cycles setzt Initial den Countdown, der mit jedem Aufruf um 1 nach unten zählt. Bei 0 stoppt die Logik und setzt zudem das control-item auf False. 

blink_interval bestimmt die Blinkabstand in Sekunden. 

Mit items_cyclic und items_anticyclic werden die Items übergeben, die blinken sollen. Antizyklische Items werden immer dann aus/an wenn die zyklischen Items an/aus sind. 

Bevor die items zum blinken gebracht werden, sichert die Logik die ursprünglichen Werte, die auch am Ende wieder hergestellt werden.

Um die Routine ohne aktives schalten der Items testen zu können, wurde noch dry_run aufgenommen.

Zusammengefasst ergibt sich folgende Beispiel-Konfiguration für ../etc/logic.yaml:

visual_alarm:
    filename: blinker.py
    watch_item: HOME.alarm.action.blinkinglight
    set_items_cyclic:
        - AUSSEN.south.light.outer
        - AUSSEN.west.light.outer
    set_items_anticyclic:
        - AUSSEN.south.light.inner
        - AUSSEN.east.light.door
        - AUSSEN.west.light.nested
    set_blink_cycles: 4 # -1 for endless loop 
    set_blink_interval: 4
    dry_run: 1

Alle items müssen vom Typ boolean sein, da nur einfache Schaltoperationen durchgeführt werden.

Die Logik lässt sich relativ einfach nutzen, indem das im watch_item verbundene Item, also im Beispiel HOME.alarm.action.blinkinglight, auf True gesetzt wird. Die Logik ruft sich selbst solange auf, bis set_blink_cycles erreicht ist oder vorher das watch_item auf False gesetzt wurde. Am Ende wird das watch_item auf von der Logik auf False gesetzt.

Die Logik selbst ist in ../logics/blinker.py:

#!/usr/bin/env python3
# vim: tabstop=2 softtabstop=2 shiftwidth=2 expandtab

# LOGIC config
# -------------------------------------------------------
# visual_alarm:
#   filename: blinker.py
#   watch_item: HOME.alarm.action.blinkinglight
#   set_items_cyclic:
#     - AUSSEN.south.light.outer
#     - AUSSEN.west.light.outer
#   set_items_anticyclic: 
#     - AUSSEN.south.light.inner
#     - AUSSEN.east.light.door 
#     - AUSSEN.west.light.nested
#   set_blink_cycles: -1 " -1: endless 
#   set_blink_interval: 2
#   dry_run: 0 

control_item_name = trigger['source'] 
control_item = sh.return_item(control_item_name)
triggered_by = trigger['by'] 


if control_item is not None:
  logger.info("LOGIC blinker.py: {0} triggered from source {1} by {2}".format(logic.name, control_item_name, triggered_by))

  # control_item_active = trigger['value']
  control_item_active = control_item()

  # set parameters from logic config
  if hasattr(logic, 'set_items_cyclic'):
    blink_items_cyclic = logic.set_items_cyclic

  if hasattr(logic, 'set_items_anticyclic'):
    blink_items_anticyclic = logic.set_items_anticyclic

  if hasattr(logic, 'set_blink_cycles'):
    blink_cycles = int(logic.set_blink_cycles)
  else:
    blink_cycles = 2
    logger.info("LOGIC blinker.py: no blink cycles set, setting to default: {2}".format(blink_cycles))

  if hasattr(logic, 'set_blink_interval'):
    blink_interval = int(logic.set_blink_interval)
  else:
    blink_interval = 2
    logger.info("LOGIC blinker.py: no blink interval set, setting to default: {2}".format(blink_interval))

  if hasattr(logic, 'dry_run'):
    dry_run = (int(logic.dry_run) > 0)
  else:
    dry_run = False

  if control_item_active == True:

    # first call?
    if not hasattr(logic, 'blink_init'):
      logger.info("LOGIC blinker.py: initiating {0} blinks for {1} cyclic and {2} anti-cyclic items".format(blink_cycles, len(blink_items_cyclic), len(blink_items_anticyclic)))
      logic.blink_init = True

      blink_items = blink_items_cyclic + blink_items_anticyclic
      if len(blink_items) > 0:
        # SAVE item states
        logic.saved_items = {}
        for item_name in blink_items:
          item = sh.return_item(item_name)
          logic.saved_items[item_name] = item()
          logger.info("LOGIC blinker.py: save {0} = {1}".format(item_name, item()))

      if dry_run == True:
        logger.warn("LOGIC blinker.py: test mode, dry run active")

      else:
        logger.warn("LOGIC blinker.py: no blink items set")
 

    # prepare blink
    if hasattr(logic, 'blink_value'):
      logic.blink_value = not logic.blink_value
    else:
      logic.blink_value = True

    if hasattr(logic, 'blink_countdown'):
      logic.blink_countdown -= 1
    else:
      logic.blink_countdown = blink_cycles
     
    # any blinks left?
    if logic.blink_countdown > 0 or blink_cycles == -1:
      if blink_cycles == -1:
        logger.info("LOGIC blinker.py BLINK (endless)")
      else:
        logger.info("LOGIC blinker.py BLINK ({0} cycles remaining)".format(logic.blink_countdown))

      # set all cyclic items
      for item_name in blink_items_cyclic:
        item = sh.return_item(item_name)
        logger.info("LOGIC blinker.py SET {item} to {value})".format(item=item_name, value=logic.blink_value))
        if dry_run == False:
          item(logic.blink_value)

      # set all anti-cyclic items
      for item_name in blink_items_anticyclic:
        item = sh.return_item(item_name)
        logger.info("LOGIC blinker.py SET {item} to {value})".format(item=item_name, value=(not logic.blink_value)))
        if dry_run == False:
          item(not logic.blink_value)

      sh.trigger("logics.{0}".format(logic.name), by=trigger['by'], source=trigger['source'], value=trigger['value'], dt=sh.now() + datetime.timedelta(seconds=blink_interval))

    else:
      logger.info("LOGIC blinker.py countdown finished, set control item to False")
      control_item(False) # this triggers logic again!

  else:
    logger.info("LOGIC blinker.py control item set to false: stop blink logic, restore saved item values")

    # RESTORE item states
    if hasattr(logic, 'saved_items'):
      for item_name in logic.saved_items:
        item = sh.return_item(item_name)
        value = logic.saved_items[item_name]
        logger.info("LOGIC blinker.py: restore {0} = {1}".format(item_name, item()))
        if dry_run == False:
          item(value)
      
    # reset persistent values
    if hasattr(logic, 'saved_items'):
      delattr(logic, 'saved_items')  
    if hasattr(logic, 'blink_countdown'):
      delattr(logic, 'blink_countdown')  
    if hasattr(logic, 'blink_value'):
      delattr(logic, 'blink_value')  
    if hasattr(logic, 'blink_init'):
      delattr(logic, 'blink_init')  
    
else:
  logger.info("LOGIC blinker.py triggered without source... ignoring...")

 

 


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.