@kylerw This is great! I’m excited. I want to encourage you to keep improving, so I’m going to give some feedback.
Adding in a reference to utils
… love it! Others who are following along and want to learn more about what this means should check out lesson #3.
entities_to_monitor = []
if "entities" in self.args:
for entity in self.split_device_list(self.args["entities"]):
## If Group - initialize all entities from the group
if "group" in entity:
groupitem = self.get_state(entity,"all")
entities_to_monitor.extend(groupitem['attributes']['entity_id'])
else:
entities_to_monitor.extend([entity])
else:
# This will monitory ALL of the entities in your house
self.log("No entity provided in cfg, not doing anything.")
This is explicit, and I love it. It makes it simpler for your users to specify entities to monitor. It also reduces the chances of identifying the group-state in your tracker. My only gripe is your comment is technically incorrect. Upon reaching self._add_tracker()
, your list entities_to_monitor
is still empty.
To take this one step further, I would suggest you have your app provide some feedback (other than the log) to the user if you reach your else
conditional. Feedback is important to users who aren’t going to think about checking the logs.
def tracker(self, entity, attribute, old, new, kwargs):
try:
self.cancel_timer(self.timer_library[entity])
except KeyError:
self.log('Tried to cancel a timer for {}, but none existed!'.format(entity),
level='DEBUG')
You have some incorrect indentation here, but I assume that’s a copy/paste error. I did want to take notice of it as this is still a continuation of lesson 1 for some of our audience. This will cause a SyntaxError
.
improperStates = self.utils.getImproperStates()
if new in improperStates:
I love that you are centralizing all your improper states and making reference to that, this is exactly my intention for the utils app. It helps to keep your code nice and tidy. Your background in other languages are taking over, with the camelcase convention. In the grand scheme of things, this is a minor, flavorful adjustment. My goal is to keep to the PEP 8 style guide as close as possible so that the newbies aren’t thrown off when looking through others’ apps. Again, I should stress that this is minor and not necessarily anything “wrong”.
Additionally, you can make these two lines of code into a single one. This is how I would rewrite them.
if new in self.utils.get_improper_states():
I would be interested to see what alertNotifier
and cancelNotifier
are doing. I’m not entirely entirely convinced replacing the handle in the timer_library
is necessary for your else
conditional. You should just be able to do something like…
if new in self.utils.get_improper_states():
# do stuff here
else:
# could add a call to the logger here
self.cancel_timer(self.timer_library[entity])
I’m not entirely sure of what you’re aiming to achieve with the friendlyState
. It seems to me that when you’re calling notifier
and reminder
… couldn’t you simply just pull the state directly, be it improper or proper, with self.get_state(entity_name)
?
This might be due to my lack of understanding of how the state is presented in Home Assistant. I’ve rewritten your utility function getFriendlyState
taking into consideration the style guide and what I think you’re trying to do. Again, this is assuming your domains lock
, garage
, cover
all report their state as open
/closed
… or …on
/off
. If they all report “pretty names” like Locked/Unlocked for lock, Open/Closed for cover and garage, then you should be fine pulling the state.
def get_friendly_state(self, state_type, entity_id):
"""
Return a beautified string of entity_id's current state
args::
state_type - "proper" or "improper"
entity_id - the entity to validate state of
eg: improper, binary_sensor.door -> Open
proper, garage.door -> Closed
proper, lock.front_door -> Locked
improper, cover.garage_door -> Open
"""
domain_state_mapping = {
"garage": {
"proper": "Closed",
"improper": "Open"
},
"cover": {
"proper": "Closed",
"improper": "Open"
},
"door": {
"proper": "Closed",
"improper": "Open"
},
"lock": {
"proper": "Locked",
"improper": "Unlocked"
}
}
domain, entity = self.split_entity(entity_id)
if domain == 'binary_sensor':
try:
return domain_state_mapping[entity][state_type]
except KeyError:
for key in domain_state_mapping:
if key in entity:
return domain_state_mapping[key][state_type]
else:
try:
return domain_state_mapping[domain][state_type]
except KeyError:
self.error('Domain {} not implemented.'.format(domain))
raise NotImplementedError
Give the assumption above, I believe this would do what you’re looking for and take care to see examples in the docstring. The goal of this function is to return a string that is human-readable and looks pretty, for use in a whatever front-end application is preferred. For binary sensors, it is assumed that you’ve already figured out whether or not “on” == “improper” and passed in the semantically correct argument. We try to access the pretty proper/improper strings as if the entity
were the exact name of the domain, and if that fails (it most likely will), then we will iterate through domain_state_mapping
's keys to find a match.
If we’re not looking at a binary_sensor
then try to access the domain’s pretty string directly again, and raise a NotImplementedError
if the domain hasn’t been cared for.
Maybe this works for you, maybe it doesn’t.
Great post @kylerw, I love seeing how you guys use and expand on these ideas!