Datetime.time -15 minutes

i think you better find out why it isnt working, because it should
what is in the logs? and what do you have in appdaemon.yaml?
and did you restart appdaemon, to make sure everything is correctly initialised?

and why do you call the heating service 3 times?

Why call the service 3 times? Yeah its kind of a hack right now and the only way to get the dh to handle events correctly is to spam it with requests.

The thermostat is hooked up via the rheem EcoNet web service which is setup as a device on my hubitat hub using a device handler that I (very quickly) ported from SmartThings. This is then exposed to HA via MQTT. I’m surprised it works at all, but I seem to be able to at least change modes by spamming it with commands.

I wrote a good bit of the original EcoNet device handler for smart things. There looks to be some rudimentary EcoNet support water heater support in HA natively, so I’m planning digging into it and extending it to support thermostats as well as water heaters over the holidays.

I concluded that the run_hvac function wasn’t firing not based on the thermostat, but based on the fact that the other switch I’m switching in that function(switch.hotwater_tank) didn’t turn on either.

Where can I look for further appDaemon logging? I’m running on hass.io and have been watching the logs that come out of the container in the add-on page, but don’t know where the logs actually live. If I miss something in real-time I don’t know how to get back to it in the logs.

you can set the logs in appdaemon.yaml
else if not catched they should be in systemlogs.

if the service trows an error the switch will never turn.
only the logs can show if the callback did run.

So, I’ve been peering into this thread every now and then and I gotta ask… Why are you tethering brightness to alarm time?

i also asked that

Yeah it’s basically a hack to leverage arbitrary alexa phrases (aka routines) into something I can use. A virtual dimmer is nice because you get 100 different binary states with one device. You just have to keep your mappings straight.

So I’m confused with your if statements. By my calculations 82% shouldn’t map to 8:30, it should map to 8:15.

If you say 830 to alexa, that’s 830/1000 in alexa speak which would be 0.83*255 = 211… Is that possibly your problem?

That’s also what’s confusing me about your if statements in general. You could build an equation that handles that translation.

def getWakeup(potval):
    pct = round(potval / 255., 3)
    base = 5
    value = int(base * round((pct*1000)/5)) # turns our potval back into a number based on 1000, in increments of 5
    hour = value // 100 # returns floor of value / 100
    minute = value % 100 # returns remainder of value / 100
    if hour == 1:
       hour *= 10
    return hour, minute

That code uses 2 functions you may not know of. Integer division and Mod.

// # this is int division
% # this is mod.

int division is essentially flooring a number and getting back an integer. So 945 // 100 would return 9.
mod returns the remainder of division. So 945 % 100 returns 45.

Then in your code to handle those special cases:

def alarm_set_event(self, entity, attrubite, old, new, kwargs):
    hour, minute = getWakeup(new)
    self.wakeup = datetime.time(hour, minute, 0)
    self.set_timers()

Then this can handle 5 minute increments instead of 15 and your original time frames will work.

FYI, the output vrs what you say in alexa:

Saying pot value (hour, minute)
5 128.0 (5, 0)
515 131.0 (5, 15)
530 135.0 (5, 30)
545 139.0 (5, 45)
6 153.0 (6, 0)
615 157.0 (6, 15)
630 161.0 (6, 30)
645 164.0 (6, 45)
7 179.0 (7, 0)
715 182.0 (7, 15)
730 186.0 (7, 30)
745 190.0 (7, 45)
8 204.0 (8, 0)
815 208.0 (8, 15)
830 212.0 (8, 30)
845 215.0 (8, 45)
9 230.0 (9, 0)
915 233.0 (9, 15)
930 237.0 (9, 30)
945 241.0 (9, 45)
1 26.0 (10, 0)
115 29.0 (10, 15)
130 33.0 (10, 30)
145 37.0 (10, 45)

hmm… I don’t have a problem with this part of the code.

AFAIK alexa “routines” don’t return a text to speech int or time value. If they did I wouldn’t bother with the dimmer.

I’m sure a custom skill can be crafted to understand a number or time value, but “routines” just match the phrase and perform the specified “action”. In my case it sets a virtual dimmer to a %. My original scheme was something like 7%=7am 8%=8am etc. I then modified that so that 7% (or maybe it’s 70%) = 7am, 71%= 7:15am,72%=7:30am, 73%=7:45am, etc. It’s just an arbitrary mapping. There’s no calculation happening here, its just a quick and dirty way to leverage an alexa phrase into something I can use without writing a custom skill.

Well, even with that method, you still don’t need a map if it’s a dimmer in alexa and you are using percentages.

def getWakeup(potval):
    value = int(round(potval / 255., 2)*100)
    hour = value // 10
    minute = value % 10
    minute_map = {0:0, 1:15, 2:30, 3:45}
    minute = minute_map.get(minute)
    if not minute:
        minute = 0
    if hour == 1:
        hour = 10
    return hour, minute
saying (percent) pot value (hour, minute)
0.1 26.0 (10, 0)
0.11 28.0 (10, 15)
0.12 31.0 (10, 30)
0.13 33.0 (10, 45)
0.2 51.0 (2, 0)
0.21 54.0 (2, 15)
0.22 56.0 (2, 30)
0.23 59.0 (2, 45)
0.3 77.0 (3, 0)
0.31 79.0 (3, 15)
0.32 82.0 (3, 30)
0.33 84.0 (3, 45)
0.4 102.0 (4, 0)
0.41 105.0 (4, 15)
0.42 107.0 (4, 30)
0.43 110.0 (4, 45)
0.5 128.0 (5, 0)
0.51 130.0 (5, 15)
0.52 133.0 (5, 30)
0.53 135.0 (5, 45)
0.6 153.0 (6, 0)
0.61 156.0 (6, 15)
0.62 158.0 (6, 30)
0.63 161.0 (6, 45)
0.7 179.0 (7, 0)
0.71 181.0 (7, 15)
0.72 184.0 (7, 30)
0.73 186.0 (7, 45)
0.8 204.0 (8, 0)
0.81 207.0 (8, 15)
0.82 209.0 (8, 30)
0.83 212.0 (8, 45)
0.9 230.0 (9, 0)
0.91 232.0 (9, 15)
0.92 235.0 (9, 30)
0.93 237.0 (9, 45)

So I took the liberty of correct the issues I found:

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

def getWakeup(potval):
  value = int(round(potval / 255., 2)*100)
  hour = value // 10
  minute = value % 10
  minute_map = {0:0, 1:15, 2:30, 3:45}
  minute = minute_map.get(minute)
  if not minute:
    return 8, 15
  if hour == 1:
    hour = 10
  return hour, minute

def offsetTime(datetime_time, minutes):
  # offsets the time
  dt = datetime.datetime.combine(datetime.date(1, 1, 1), datetime_time)
  offset = datetime.timedelta(minutes=minutes)
  return (dt + offset).time()

# Declare Class
class WakeUp(hass.Hass):
  #initialize() function which will be called at startup and reload
  def initialize(self):
    self.handle_espresso = None
    self.handle_hvac = None
    self.handle_lights = None
    self.handle_wakeup = None

    self.listen_state(self.alarm_set_event, self.args["alarm_switch"], attribute="brightness")
    #self.log("listening: {}".format(self.args["alarm_switch"]))

  def alarm_set_event(self, entity, attrubite, old, new, kwargs):
    self.cancel()
    
    hour, minute = getWakeup(new)
    wakeup = datetime.time(hour, minute, 0)

    espresso_on = offsetTime(wakeup, -45)
    lights_on = offsetTime(wakeup, -15)
    hvac_on = offsetTime(wakeup, -7)

    self.handle_espresso = self.run_daily(self.run_espresso, espresso_on)
    self.handle_hvac = self.run_daily(self.run_hvac, hvac_on)
    self.handle_lights = self.run_daily(self.run_lights, lights_on)
    self.handle_wakeup = self.run_daily(self.run_wakeup, wakeup)

    self.log("Alarm Set for: {}".format(wakeup))
    self.log("Espresso Machine On: {}".format(espresso_on))
    self.log("Light Fade Start: {}".format(lights_on))
    self.log("HVAC On: {}".format(hvac_on))

  def run_lights(self, kwargs):
    self.log("Starting lights")
    self.fire_event("MODE_CHANGE", mode = "Morning")
    self._fade(1)

  def _fade(self, brightness):
    if brightness < 15:
      self.turn_on("group.bedroom_lights", brightness_pct=brightness)
      brightness += 1
      self.run_in(self.fade, 58, brightness=brightness)

  def fade(self, kwargs):
    brightness = kwargs.get('brightness')
    if brightness != None:
      self._fade(brightness)

  def run_espresso(self, kwargs):
    self.log("Starting Espresso Machine")
    self.turn_on("switch.espresso_machine")

  def run_hvac(self, kwargs):
    self.log("Starting HVAC")
    for i in range(0,3):
      self.call_service("climate/set_operation_mode", entity_id = "climate.Heating", operation_mode = "Heating")
    self.turn_on("switch.hotwater_tank")

  def run_wakeup(self, kwargs):
    self.log("Starting Wakeup")
    self.fire_event("MODE_CHANGE", mode = "Present")
    self.turn_on("group.bedroom_lights", brightness_pct=60)
    self.turn_on("switch.hotwater_loop")

  def cancel(self):               
    self.log("Cancel existing timers")
    self.cancel_timer(self.handle_espresso)
    self.cancel_timer(self.handle_lights)
    self.cancel_timer(self.handle_hvac)
    self.cancel_timer(self.handle_wakeup)

Pointers / Issues:

  • You seem to not understand when to use self. This is a complicated subject. All I can say is that you should brush up on object oriented languages. There is no fault in not knowing what self is, as I said before its complicated. Took me a while to understand when to use it and when not to. My suggestion, learn python outside of AppDeamon. Take some tutorials that cover creating classes. Then you’ll understand the significance of self and when to use it.
  • You incorrectly pushed self to self.fade. You really needed to pass a dictionary. That’s what kwargs are, a dictionary of keyword arguments. This can get complicated, so instead I made a function with the intention of it being called by self.fade. The function requires a brightness. So instead of calling fade in run_lights, you call _fade. fade is still called by the run_in() func. Also, added brightness to run_in as a pass through.
  • wakeup, brightness, espresso_on, lights_on, and hvac_on did not need to be ‘global’ objects. This is personal preference, I made the change.
  • your handle for wakeup wasn’t using self.wakeup, it was just using wakeup. I think that was a typo. but that doesn’t matter now because we made it a local variable instead of global.
  • I made a function to calculate the time. This is personal preference, but now it has the proper math behind it and it’s a short function call. If you want to offset before, use a negative number. If you want to offset after, use positive. That way, if you ever add things to this, you won’t have to deal with making a whole new line and possibly screwing that up.

EDIT: I just noticed some more refining that could be done and I made a mistake so I fixed them and removed bullets.

he got that part from me and i use it all the time without any problems. why create 2 functions for it?

  def fade(self, kwargs):
    if self.brightness < 15:
      self.turn_on("group.bedroom_lights", brightness_pct=self.brightness)
      self.brightness = self.brightness +1
      self.run_in(self.fade,58)

this works like it should. and if it would be a problem to run self.fade(self) then he could use self.run_in(self.fade,1) or just self.fade({}) to get to the CB the first time.
i also did chose to use a global var brightness instead of sending it through kwargs. its just a choice and 1 isnt better then the other.
i prefer it as a global var because then it could be used in another function as well to see how var the brightness is at that point.

i also dont see why you would want to move functions outside of an app. i rather keep everything inside the app. and for sure such functions that could be reused.

you still dont get the dimmer part.
he got all the work done (the most of the work is creating the routines.)
if he would want to have a function that calculates he would need to delete all the existing routines and create new ones.

Thanks for the tips. You’re right, my grasp of python concepts are rudimentary at this point, especially the use of self. Christ, I had a narcissistic girlfriend once years ago. She couldn’t say 2 words in a row without referring to her “self” either. Heh, there’s a joke there somewhere but it needs a bit more workshopping…

Anyhoo, I was figuring that any var with self.varname was global to the app object. This, while perhaps not stylishly optimal, seemed acceptable for my purposes in most cases.

The first iteration of my project at this point is mostly done, and that was to port my webcore programming to appDaemon. The house actually now works better on AppDaemon than it ever did on WebCoRE/SmartThings or WebCore/Hubitat. I was able to accomplish this with about 5 days of Python under my belt. I only mention that as encouragement to others who may want to make the leap to appDaemon but don’t have much familiarity with python. It’s not that hard, though I still prefer ruby.

I also didn’t need to migrate any of my z-wave or zigbee devices off of hubitat. They’re all shared via MQTT to a hass.io and appDaemon instance running on a vm. This allows a migration path wherein you can move one rule at a time from your rule engine of choice on hubitat, to appDaemon, without breaking the whole house.

As for calculating the time from the dimmer percentage, I can see how this would be valuable in some cases but I tend to think of the virtual dimmer/ alexa interaction as little more than a patch panel. Perhaps I want to set a more specific wakeup alarm for 8:21 or something. I can simply overload my mapping scheme and map level 84% (or any other frankly) to that phrase. Perhaps I want to map a phrase that has nothing at all to do with the wakeup timer. I can do this too…

Also thanks for the example of how to get.kwargs. I was wondering how you accessed those.

As far as your choice of language goes, maybe you could work on that a bit? Code can be sub-optimal in a number of ways but if it largely works as designed it’s a stretch to call it “incorrect”. These sorts of pronouncements can easily be interpreted as spilling into douche-nerd territory. But again, I do appreciate the tips.

2 Likes

I’m sorry that you feel offended that I changed that function. Brightness in that context does not need to be attached to the hass.HASS object, it just adds unnecessary memory. As I stated in the line before this is personal preference. But if this were a larger project and memory was an issue, this would be bad practice.

So to reiterate my point, you don’t have to change that function. I decided to.

They aren’t outside the app, they are outside hass.HASS object. Unless when you refer to app you are referring to the hass.HASS object. They don’t need to be inside the hass.HASS object. They don’t need to be put inside the object because they don’t access things from hass.HASS. They can be reused over and over again where they are. If you place them outside the hass.HASS, other modules can also import them without issue so that you can reuse these methods everywhere.

I perfectly get the dimmer part. I’ve been down his path. From personal experience I can tell you that managing if-elif statements is a lot more overhead work than making a single equation. I’m trying to steer him towards good coding practice. He is more than welcome to replace that portion of the code.

Yes in essence it is global. Like I said, this is preference. You’re welcome to change it back, just have to change your initial call from self.fade(self) to self.fade({}) or self.fade(None).

Yes, I understand this. As you can tell, it’s my opinion that the overhead isn’t worth the time. Also, the most recent method I posted still give you the ability to map 8:21, you’d just do it in the function in 1 place and be done with it. No extra code if else statements, just adding a new map to minute_map = {0:0, 1:15, 2:30, 3:45}. Anyway, its clear you don’t want this, so I’ll drop it.

There are many ways to get kwargs. Kwargs is a dictionary. You have the following access methods:

dict.get(‘key’)

x = kwargs.get('x')

This method gets x if it exists. If it doesn’t it will set x = None. That means you should check for None if you want to be safe.

If you want to add default to fall back on, It’s pretty simple:

x = kwargs.get('x') or 0

This will return 0 if get('x') returned None.


dict[‘key’]

x = kwargs['x']

This method just grabs it. It will raise a KeyError if it doesn’t exist.

Typically with this method, i’ll do something like this:

if 'x' in kwargs:
    x = kwargs['x']
else:
    x = 0

Which can be shortened to (if you want):

x = kwargs['x'] if 'x' in kwargs else 0

I understand your sentiment here, but I only used the word incorrect in the spot that you used self incorrectly. It’s not a personal preference at that position. You were sending the whole entire hass.HASS (your app) object into the the fade function. While you may not like me using the word incorrect, it was not correct. Sorry if I offended you.

i didnt feel offended :wink:

what is an app? a class that you declare in the yaml.
if it isnt a part from the class you cant use get_app to call the function, because they are not part from the app.

if you did follow the topic he stated before that he wanted to use as less time as possible to make it working.
then it doesnt make sense to stear him to a lot of work to change something that might be a bit simpler code, but he would need to take a lot of time to replace all routines in the alexa app.

if you want to help people its important to look at what they want, not what you think would be better.
off course you could say: he, i would do it that wat, because of … reason.
but then also mention and realise the amount of time it would take.

i love the amount of time and energy you put in helping people. and a lot of times i am also learning from it.
but sometimes i get the feeling you are trying to teach people to swimm in deep water at the moment that they show they are still trying to dip their toe in on the side of the pool. :wink:
real pythonic programming isnt logical for noobs :wink:

I wasn’t aware of a functions to access other ‘apps’ methods. Without that call, you’d do what I added and then use the following code.

from x import func
func()

That’s why I moved it.

I made the function to remove the overhead of dealing with else if’s. The way his current else if’s are set up is linking pot values to functionality. I was trying to make a function for him to use so that he can use all times in the future and then also add 1 line of code to handle a larger amount of cases. With if and else if, the overhead can be large.

Example. He wants to map x4% (54%, 64%, 74%, 84%, 94%, 14% - 10am) to the 21st minute. To do this with the current mapping method he would need to do the following:

  • calculate the 6 pot values.
  • Add 6 elif statements, essentially 12 new lines of code with the chance of typos.

With the calculation, the line would change from

minute_map = {0:0, 1:15, 2:30, 3:45}

to

minute_map = {0:0, 1:15, 2:30, 3:45, 4:21}

Also, this would cover ranges 24% to 44%.
So what’s faster and easier to manage?

Sometimes, helping can pave the way for easy adjustments in the future. That’s all I was trying to do with that function. Nothing more. I wasn’t calling him stupid for using it. Just trying to save time in the future. I know I always curse myself when I create more work later down the road.

Just try changing the self.fade(self) to self.fade({}) or self.fade(None). It shouldn’t matter which one you choose because you aren’t using kwargs. This seems in line with the issue at hand being the 3rd run_daily. Other than that your app looks good. I’m guessing the typo (wakeup to self.wakeup) I reference in a previous post was my own doing not yours.

thats probably because you are more into python then into appdaemon. :wink:
there are probably way more things in appdaemon then you are aware off.
and off course i am aware of the option to import functions. but i rather keep them inside an app, because when i want to change a function later on so that it uses some functionality from appdaemon i would need to move it and change all code that depends on it.

i guess it depends on how you look at things.
appdaemon is a framework that is based on python, but a lot of python users use at as python with some extra functions.

i know. but if someone says: “i know this can be done better, but i dont have the time or mind to change this at this moment”, i think there is no point going on about that at that moment.
you see the advantage for the future, but in this case the point made was: “i dont want to spend time on that”

in this case it would be better to find something that makes his code better but that wouldnt cost him a lot of time changing routines.
that can be done by a function like:

def wakeup(self,brightness):
  timedict = self.args["times"]
  if brightness in timedict:
    return self.args["times"][brightness]
  else:
    return datetime.time(8, 15, 0)

and in yaml:

times:
  127: 05:00:10 
  130: 05:15:00 
  132: 05:30:00 

  237: 9:45:00   
  25: 10:00:00 
  28: 10:15:10 

that too would allow him just to add new times without making typos, but it would not involve the need to delete and recreate routines.

by the way i need to thank you because i didnt realise the mem aspect from using self to avoid errors.

Yes yes, I tend to go overboard.

I think I’ve derailed this enough and at this point we need to get @bmcgair app running.

1 Like

i am still waiting till he shows the log :wink:
@bmcgair had the feeling that 1 of the tun_dailys wasnt working, but only the log can show if thats true or if there is a problem with his service call.