Having an issue with smoke alarm announcements

I’m sure that this is something extremely obvious that I’m missing here but I’m getting this error.

2018-11-01 10:28:30.471407 WARNING AppDaemon: ------------------------------------------------------------
2018-11-01 10:28:30.474303 WARNING AppDaemon: Unexpected error in worker for App smoke_alarms:
2018-11-01 10:28:30.475601 WARNING AppDaemon: Worker Ags: {'name': 'smoke_alarms', 'id': UUID('74489e3a-f189-4b22-a51d-b7e75d235873'), 'type': 'attr', 'function': <bound method SensorNotification.state_change of <smoke_detector.SensorNotification object at 0x7fa178041be0>>, 'attribute': 'state', 'entity': 'sensor.upstairs_smoke', 'new_state': 'Testing', 'old_state': 'Idle', 'kwargs': {'handle': UUID('8ad8af65-7a03-4e7e-a3fd-b1fb451af7df')}}
2018-11-01 10:28:30.476900 WARNING AppDaemon: ------------------------------------------------------------
2018-11-01 10:28:30.477919 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 595, in worker
    self.sanitize_state_kwargs(app, args["kwargs"]))
  File "/conf/apps/smoke_detector.py", line 50, in state_change
    self.announcement_cb(self, message_text)
  File "/conf/apps/smoke_detector.py", line 58, in announcement_cb
    self.call_service("tts/google_say", entity_id=self.media_player, message=kwargs.message_text)
AttributeError: 'str' object has no attribute 'message_text'

2018-11-01 10:28:30.478345 WARNING AppDaemon: ------------------------------------------------------------

Here is my code

import appdaemon.plugins.hass.hassapi as hass

#
# App to send notification when a sensor changes state
#
# Args:
#
# sensor: sensor to monitor e.g. sensor.upstairs_smoke
# idle_state - normal state of sensor e.g. Idle
# turn_on - scene or device to activate when sensor changes e.g. scene.house_bright
# Release Notes
#
# Version 1.0:
#   Initial Version

class SensorNotification(hass.Hass):

  def initialize(self):
    if "sensor" in self.args:
      for sensor in self.split_device_list(self.args["sensor"]):
        self.listen_state(self.state_change, sensor)
    self.media_player = 'group.speakers'

  def state_change(self, entity, attribute, old, new, kwargs):
    self.log("{} is in state {}".format(self.friendly_name(entity), new))
    #self.notify("{} is in state {}".format(self.friendly_name(entity), new), name="ios")
    if self.get_state(entity) == "Fire":
        self.log("Announcing there is a fire")
        message_text = "The {} has detected smoke.  Please exit the house immediately through the safest path." .format(self.friendly_name(entity))
        self.announcement(self, message=message_text)
        for light in self.args["turn_on"]:
            self.turn_on(light)
        self.turn_on(self.args["turn_on"])
        while self.get_state(entity) == "Fire":
            self.fire_handle = self.run_in(self.announcement, 15, message=message_text)
        else:
            self.cancel_timer(self.fire_handle)
    if new == "CO":
        self.log("Announcing there is CO")
        self.turn_on(self.args["turn_on"])
        self.announcement(self, message=message_text)
        message_text = "The {} has detected carbon monoxide.  Please exit the house immediately through the safest path." .format(self.friendly_name(entity))
        while new == "CO":
            self.co_handle = self.run_in(self.announcement, 15, message=message_text)
        else:
            self.cancel_timer(self.co_handle)
    if new == "Testing":
        self.log("Announcing that we're testing")
        message_text = "The {} has detected test mode.  There is no need for you to panic this is only a test." .format(self.friendly_name(entity))
        self.announcement(self, message=message_text)
        while new == "Testing":
            self.testing_handle = self.run_in(self.announcement, 15, message=message_text)
        else:
            self.cancel_timer(self.testing_handle)

    def announcement(self,kwargs):
        self.call_service("media_player/volume_set", entity_id=self.media_player, volume_level = 100)
        self.call_service("tts/google_say", entity_id=self.media_player, message=(kwargs['message']))

I just figured it’d be cleaner if I had just a stand alone callback for handling all of the announcements but I don’t think I"m passing the message text in properly. Thank you all for your help.

kwargs[“message_text”]

Cool now I’m getting

2018-11-01 12:58:47.936103 WARNING AppDaemon: ------------------------------------------------------------
2018-11-01 12:58:47.936966 WARNING AppDaemon: Unexpected error in worker for App smoke_alarms:
2018-11-01 12:58:47.937657 WARNING AppDaemon: Worker Ags: {'name': 'smoke_alarms', 'id': UUID('e14340f6-b1be-4044-867c-21a05b7f95a7'), 'type': 'attr', 'function': <bound method SensorNotification.state_change of <smoke_detector.SensorNotification object at 0x7fa1707a1860>>, 'attribute': 'state', 'entity': 'sensor.upstairs_smoke', 'new_state': 'Testing', 'old_state': 'Idle', 'kwargs': {'handle': UUID('0545bee7-4dd4-4b5d-8d1b-f6a974102b1b')}}
2018-11-01 12:58:47.938179 WARNING AppDaemon: ------------------------------------------------------------
2018-11-01 12:58:47.939695 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 595, in worker
    self.sanitize_state_kwargs(app, args["kwargs"]))
  File "/conf/apps/smoke_detector.py", line 50, in state_change
    self.announcement_cb(self, message_text)
TypeError: announcement_cb() takes 2 positional arguments but 3 were given

2018-11-01 12:58:47.940314 WARNING AppDaemon: ------------------------------------------------------------

I don’t understand what 3 args it is passing into it. As I only define 2 args to pass.

self is always there. dont ask me why.
so this works

self.announcement(message=message_text)

Excellent that did the trick thank you. Now onto my next issue :slight_smile: . Reading one of your posts about the while loop I see what you mean about appdaemon hanging itself. It gets stuck in the loop and threads out. So what is the best means to have this re-announce every 15 seconds? I figured it was ok to just be like while this is true do this but it just spun up so many threads and my speakers were announcing non-stop. Do I just need to build a handle in here to check the state once that function is triggered?

EDIT: and I know that putting a sleep in is also a bad idea because of the fact that the sleep hogs the thread.

to repeat something every 15 seconds you use

starttime = datetime.datetime.now() + datetime.timedelta(seconds=1)
self.timer = self.run_every(self.announcement,starttime,15)

now you can cancel that with

self.cancel_timer(self.timer)

for example i use an input_boolean and a listen_state for that and if that is set to on, the announcements stop.

  self.listen_state(self.stop_announcement,"input_boolean.stop_announcements")
def stop_announcement(self, entity, attribute, old, new, kwargs):
  if new == "on":
    self.cancel_timer(self.timer)
    self.turn_off(entity)

Here is the almost final product

import appdaemon.plugins.hass.hassapi as hass
import time
import datetime

#
# App to send notification when a sensor changes state
#
# Args:
#
# sensor: sensor to monitor e.g. sensor.upstairs_smoke
# idle_state - normal state of sensor e.g. Idle
# turn_on - scene or device to activate when sensor changes e.g. scene.house_bright
# Release Notes
#
# Version 1.0:
#   Initial Version

class SensorNotification(hass.Hass):

  def initialize(self):
    if "sensor" in self.args:
      for sensor in self.split_device_list(self.args["sensor"]):
        self.listen_state(self.state_change, sensor)
    self.media_player = 'group.speakers'

  def state_change(self, entity, attribute, old, new, kwargs):
    self.log("{} is in state {}".format(self.friendly_name(entity), new))
    #self.notify("{} is in state {}".format(self.friendly_name(entity), new), name="ios")
    if self.get_state(entity) == "Fire":
        self.log("Announcing there is a fire")
        message_text = "The {} has detected smoke.  Please exit the house immediately through the safest path." .format(self.friendly_name(entity))
        self.announcement_cb(message=message_text)
        self.call_service("notify/hangouts", message=message_text)
        for light in self.args["turn_on"]:
            self.turn_on(light)
        self.turn_on(self.args["turn_on"])
        starttime = datetime.datetime.now() + datetime.timedelta(seconds=1)
        self.timer = self.run_every(self.announcement_cb,starttime,15)

    if new == "CO":
        self.log("Announcing there is CO")
        self.turn_on(self.args["turn_on"])
        message_text = "The {} has detected carbon monoxide.  Please exit the house immediately through the safest path." .format(self.friendly_name(entity))
        self.announcement_cb(self.message_text)
        self.call_service("notify/hangouts", message=message_text)
        for light in self.args["turn_on"]:
            self.turn_on(light)
        self.turn_on(self.args["turn_on"])
        starttime = datetime.datetime.now() + datetime.timedelta(seconds=1)
        self.timer = self.run_every(self.announcement_cb,starttime,15)

    if new == "Testing":
        self.log("Announcing that we're testing")
        self.message_text = "The {} has detected test mode.  There is no need for you to panic this is only a test." .format(self.friendly_name(entity))
        self.announcement_cb(self.message_text)
        self.call_service("notify/hangouts", message=self.message_text)
        starttime = datetime.datetime.now() + datetime.timedelta(seconds=1)
        self.timer = self.run_every(self.announcement_cb,starttime,15)

    if new == "Idle" and old != "Idle":
       self.log("The {} has gone back to Idle".format(self.friendly_name(entity), new))
       self.cancel_timer(self.timer)

  def announcement_cb(self,kwargs):
      self.call_service("media_player/volume_set", entity_id=self.media_player, volume_level = 100)
      self.call_service("tts/google_say", entity_id=self.media_player, message=self.message_text)

Now if I can just get the friendly_name bit to work. Right now its just putting in the sensor name and not the actual friendly name. So trying to figure that bit out.

to what sensors are you listening and what states do they have?

what is in your yaml?

I figured you’d be in bed by now :slight_smile:

smoke_alarms:
  module: smoke_detector
  class: SensorNotification
  sensor: sensor.jason_office_smoke,sensor.garage_smoke,sensor.kitchen_smoke,sensor.attic_smoke,sensor.crawlspace_smoke,sensor.upstairs_smoke
  turn_on:
    - group.all_lights

BTW I’m basing this whole thing off of something that @aimc wrote intially :slight_smile:
Here is the template for the sensor

- platform: template
  sensors:
    crawlspace_smoke:
      value_template: '{%- if is_state("sensor.crawlspace_smoke_alarm_alarm_type", "0") %}
                        Idle
                        {%else%}
                          {%- if is_state("sensor.crawlspace_smoke_alarm_alarm_type", "1") %}
                          Fire
                          {%- elif is_state("sensor.crawlspace_smoke_alarm_alarm_type", "2") %}
                          C02
                          {%- elif is_state("sensor.crawlspace_smoke_alarm_alarm_type", "12") %}
                          Testing
                          {%- elif is_state("sensor.crawlspace_smoke_alarm_alarm_type", "13") %}
                          Idle
                          {% else %}
                          Unknown
                          {%- endif %}
                        {%endif%}'
      friendly_name: 'Crawlspace Smoke Alarm'

- platform: template
  sensors:
    upstairs_smoke:
      value_template: '{%- if is_state("sensor.upstairs_smoke_detector_alarm_type", "0") %}
                        Idle
                        {%else%}
                          {%- if is_state("sensor.upstairs_smoke_detector_alarm_type", "1") %}
                          Fire
                          {%- elif is_state("sensor.upstairs_smoke_detector_alarm_type", "2") %}
                          C02
                          {%- elif is_state("sensor.upstairs_smoke_detector_alarm_type", "12") %}
                          Testing
                          {%- elif is_state("sensor.upstairs_smoke_detector_alarm_type", "13") %}
                          Idle
                          {% else %}
                          Unknown
                          {%- endif %}
                        {%endif%}'
      friendly_name: 'Upstairs Smoke Alarm'

i normally dont go to bed before 3:00 :wink:
i recognized it :wink:
because you use split_device_list which is something that is from AD 2 :wink:
since AD 3 is based on yaml you just can create a list like:

sensor:
  - sensor.jason_office_smoke
  - sensor.garage_smoke
  - sensor.kitchen_smoke
  - sensor.attic_smoke
  - sensor.crawlspace_smoke
  - sensor.upstairs_smoke

and then use

      for sensor in self.args["sensor"]:

but i have no clue why friendly_name isnt working. it should!
but you can try:

friendlyname = self.get_state(entity, attribute="friendly_name")

I feel like I"m so close

2018-11-01 16:35:23.865634 WARNING AppDaemon: ------------------------------------------------------------
2018-11-01 16:36:56.291004 WARNING AppDaemon: ------------------------------------------------------------
2018-11-01 16:36:56.294009 WARNING AppDaemon: Unexpected error in worker for App smoke_alarms:
2018-11-01 16:36:56.295951 WARNING AppDaemon: Worker Ags: {'name': 'smoke_alarms', 'id': UUID('2a104307-d843-4f33-8ded-0bf3a52e0d84'), 'type': 'attr', 'function': <bound method SensorNotification.state_change of <smoke_detector.SensorNotification object at 0x7f54b46537f0>>, 'attribute': 'state', 'entity': 'sensor.upstairs_smoke', 'new_state': 'Idle', 'old_state': 'Testing', 'kwargs': {'handle': UUID('d9e91c4c-59f4-4360-9d3b-92510cc62076')}}
2018-11-01 16:36:56.297595 WARNING AppDaemon: ------------------------------------------------------------
2018-11-01 16:36:56.300459 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 595, in worker
    self.sanitize_state_kwargs(app, args["kwargs"]))
  File "/conf/apps/smoke_detector.py", line 28, in state_change
    self.log("{} is in state {}".format(self.friendlyname, new))
AttributeError: 'SensorNotification' object has no attribute 'friendlyname'

2018-11-01 16:36:56.300821 WARNING AppDaemon: ------------------------------------------------------------

I took the self off of friendlyname and no more error but now it says the None lol

by the way you can simplefy a whole lot!

  def state_change(self, entity, attribute, old, new, kwargs):
    self.log("{} is in state {}".format(self.friendly_name(entity), new))
    testing = False
    if new == "Fire":
        self.log("Announcing there is a fire")
        message_text = "The {} has detected smoke.  Please exit the house immediately through the safest path." .format(self.friendly_name(entity))

    elif new == "CO":
        self.log("Announcing there is CO")
        message_text = "The {} has detected carbon monoxide.  Please exit the house immediately through the safest path." .format(self.friendly_name(entity))

    elif new == "Testing":
        testing = True
        self.log("Announcing that we're testing")
        message_text = "The {} has detected test mode.  There is no need for you to panic this is only a test." .format(self.friendly_name(entity))

    elif new == "Idle" and old != "Idle":
        self.log("The {} has gone back to Idle".format(self.friendly_name(entity), new))
        self.cancel_timer(self.timer)
    starttime = datetime.datetime.now() + datetime.timedelta(seconds=1)
    self.timer = self.run_every(self.announcement_cb,starttime,15, message_text=message_text,testing=testing)

    else:
        self.log("i got an unknown state")
        return

  def announcement_cb(self,kwargs):
      self.call_service("media_player/volume_set", entity_id=self.media_player, volume_level = 100)
      self.call_service("tts/google_say", entity_id=self.media_player, message=kwargs["message_text"])
      if not kwargs["testing"]:
          self.call_service("notify/hangouts", message=kwargs["message_text"])
          for light in self.args["turn_on"]:
              self.turn_on(light)
1 Like

Always for cleaning up code

1 Like

then always be on the lookout for identical lines in an if structure.
most of the time you can move it a step downwards

1 Like

Its having an issue with the else in there as an invalid syntax. Trying to figure out what its issue is.

EDIT: Figured it out had to put the timer stuff after the else :slight_smile: . Makes sense no need for them to be in the middle of the conditions I reckon

1 Like

sorry wrong place for the else

    elif new == "Idle" and old != "Idle":
        self.log("The {} has gone back to Idle".format(self.friendly_name(entity), new))
        self.cancel_timer(self.timer)
    else:
        self.log("i got an unknown state")
        return
    starttime = datetime.datetime.now() + datetime.timedelta(seconds=1)
    self.timer = self.run_every(self.announcement_cb,starttime,15, message_text=message_text,testing=testing)

Now back to figuring out my friendly_name conundrum :slight_smile:

should be self.friendly_name :wink:

But if I declare the variable like you did here

friendlyname = self.get_state(entity, attribute="friendly_name")

then wouldn’t it be the self.friendlyname?
i.e.

def state_change(self, entity, attribute, old, new, kwargs):
    friendlyname = self.get_state(entity, attribute="friendly_name")
    self.log("{} is in state {}".format(self.friendlyname, new))

no you save the state in a var called friendlyname
and in the log you ask for self.friendlyname
which are 2 different vars.

friendlyname = a local var
self.friendlyname = a global var

and i thought you meant the function self.friendly_name() :wink:

def state_change(self, entity, attribute, old, new, kwargs):
    friendlyname = self.get_state(entity, attribute="friendly_name")
    self.log("{} is in state {}".format(friendlyname, new))