Ocusim: 'OccuSim' object has no attribute 'turn_on'

@Tyfoon
Hello there,
yes I managed to get it running fine.
I reinstalled appdaemon latest version using docker.

here is my config:

Occupancy Simulator:
  module: occusim
  class: OccuSim
  log: 1
  #notify: 1
  enable: input_boolean.occusim_switch,on
  #test: 1
  dump_times: 1
  reset_time: 00:00:01


# Treppenhaus Tagsüber Random an
  random_treppenhaus_end: Evening
  random_treppenhaus_maxduration: 00:02:00
  random_treppenhaus_minduration: 00:01:00
  random_treppenhaus_name: Treppenhaus Tagsüber
  random_treppenhaus_number: '50'
  random_treppenhaus_on_1: scene.occusim_treppenhaus_og_on
  random_treppenhaus_off_1: scene.occusim_treppenhaus_og_off
  random_treppenhaus_on_2: scene.occusim_treppenhaus_ug_on
  random_treppenhaus_off_2: scene.occusim_treppenhaus_ug_off
  random_treppenhaus_on_3: scene.occusim_treppenhaus_eg_on
  random_treppenhaus_off_3: scene.occusim_treppenhaus_eg_off
  random_treppenhaus_start: Morning

# Treppenhaus Nachts Random an
  random_treppenhausnachts_end: Evening
  random_treppenhausnachts_maxduration: 00:02:00
  random_treppenhausnachts_minduration: 00:01:00
  random_treppenhausnachts_name: Treppenhaus Nachts
  random_treppenhausnachts_number: '10'
  random_treppenhausnachts_on_1: scene.occusim_treppenhaus_og_on
  random_treppenhausnachts_off_1: scene.occusim_treppenhaus_og_off
  random_treppenhausnachts_on_2: scene.occusim_treppenhaus_ug_on
  random_treppenhausnachts_off_2: scene.occusim_treppenhaus_ug_off
  random_treppenhausnachts_on_3: scene.occusim_treppenhaus_eg_on
  random_treppenhausnachts_off_3: scene.occusim_treppenhaus_eg_off
  random_treppenhausnachts_start: Morning


## Wohnzimmer Nachts Random an
  random_kuchenachts_end: Morning
  random_kuchenachts_maxduration: 00:10:00
  random_kuchenachts_minduration: 00:01:00
  random_kuchenachts_name: KucheNachts
  random_kuchenachts_number: '2'
  random_kuchenachts_on_1: scene.occusim_nachts_kuche_on
  random_kuchenachts_off_1: scene.occusim_nachts_kuche_off
  random_kuchenachts_start: Night

## Wohnzimmer Abends Random an
  random_wzAbends_end: Eveningend
  random_wzAbends_maxduration: 03:10:00
  random_wzAbends_minduration: 00:20:00
  random_wzAbends_name: wzAbends
  random_wzAbends_number: '2'
  random_wzAbends_on_1: scene.occusim_nachts_kuche_on
  random_wzAbends_off_1: scene.occusim_nachts_kuche_off
  random_wzAbends_start: Evening

### All Days:
  step_evening_name: Evening
  step_evening_start: sunset - 00:30:00

  step_morning_name: Morning
  step_morning_start: sunrise - 00:30:00

  step_night_name: Night
  step_night_start: "00:00:02"

  step_eveningend_name: Eveningend
  step_eveningend_start: '23:59:59'

Hope that helps somebody!

2 Likes

@VolkerRacho Thanks! Do you also have the actual latest ocusim app. for me? I only have the original when it was launched

tyfoon, if you would have taken the time to read the breakin changes as i suggested to you, youi would find out that you only need to edit 2 lines! in the app, and you would have had it running 3 days ago.

1 Like

You are probably right but I’m not in a hurry and most important; Everything (ok, most) around appdeamon is beyond my skill set. I have no clue how to make or interpret AppDeamon apps. I just copy with pride. Next to that I’m on Appdeamon 1.x and checking & finding all breaking changes between then and now (and understanding them) is not that easy. I’m not a coder and -unfortunately- do not have the time to deepdive. I hope you understand and don’t translate this in laziness and understand I truly appreciate the work you and Andrew have out in this.

p.s. I remember when AppDeamon was originally launched; ‘We have Apps’ and one of the key benefits was to share and re-use apps people build so still don’t see this as a very strange question

i get you there. and maybe you have noticed that i try to help even the biggest noobs here to get where they want.
if i am pointing you in a certain direction, then i am sure! you are able to make it work (when needed with help and guidance)

i also understand that you have the feeling that starting to work with python is a big deal (allthought it really isnt) and dont have the time to start with that.
you might want to read my My-AppDaemon/AppDaemon_for_Beginner at master · ReneTode/My-AppDaemon · GitHub to find out that it is actually quite easy to start :wink:

but to help you with the occusim ( and you might want to do the same for the other apps)

find these 2 lines in the apps:

import appdaemon.appapi as appapi

class MyClass(appapi.AppDaemon):

and change them to:

import appdaemon.plugins.hass.hassapi as hass

class MyClass(hass.Hass):

Myclass is something that is different in every app so only the part between () changes.

like i pointed out before, make absolutely sure you have AD 3 up and running problemfree before! you start adding apps.

i really want you to have it working, so you get all my time and attention to help you with that.
if neccecary you can find me on discord and i can help you 1 on 1 in the chat.

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

#
# App to simulate occupancy in an empty house
#

__version__ = "1.1.5"


class OccuSim(hass.Hass):

    def initialize(self):

        if "test" in self.args and self.args["test"] == "1":
            self.test = True
        else:
            self.test = False

        self.timers = ()

        # Set a timer to recreate the day's events at 3am
        if "reset_time" in self.args:
            time = self.parse_time(self.args["reset_time"])
        else:
            time = datetime.time(3, 0, 0)
        self.run_daily(self.create_events, time)

        # Create today's random events
        self.create_events({})

    def create_events(self, kwargs):
        # self.log_notify("Running Create Events")

        events = {}
        steps = ()
        randoms = ()

        for arg in self.args:
            m = re.search("step_(.+)_name", arg)
            if m:
                steps = steps + (m.group(1),)
            m = re.search("random_(.+)_name", arg)
            if m:
                randoms = randoms + (m.group(1),)

        # First pick up absolute events
        for step in steps:
            event = None
            step = "step_" + step + "_"
            if (step + "start") in self.args:
                stepname = self.args[step + "name"]
                cbargs = {}
                cbargs["step"] = stepname
                if (step + "days") in self.args:
                    cbargs["days"] = self.args[step + "days"]
                span = 0
                for arg in self.args:
                    if re.match(step + "on", arg) or re.match(step + "off", arg):
                        cbargs[arg] = self.args[arg]

                start_p = self.args[step + "start"]
                start = self.parse_time(start_p)
                end_p = self.args.get(step + "end")
                if end_p != None:
                    end = self.parse_time(end_p)
                    start_ts = datetime.datetime.combine(self.date(), start).timestamp()
                    end_ts = datetime.datetime.combine(self.date(), end).timestamp()
                    span = int(end_ts - start_ts)
                if span > 0:
                    event = datetime.datetime.combine(self.date(), start) + datetime.timedelta(
                        seconds=random.randrange(span))
                elif span == 0:
                    event = datetime.datetime.combine(self.date(), start)
                elif span < 0:
                    self.log("step_{} end < start - ignoring end".format(i))
                    event = datetime.datetime.combine(self.date(), start)

                events[stepname] = {}
                events[stepname]["event"] = event
                events[stepname]["args"] = cbargs.copy()

        # Now relative events - multiple passes required as the order could be arbitrary

        changes = 1

        while changes > 0:
            changes = 0
            for step in steps:
                event = None
                step = "step_" + step + "_"
                if (step + "relative") in self.args:
                    stepname = self.args[step + "name"]
                    if stepname not in events:
                        cbargs = {}
                        cbargs["step"] = stepname
                        if (step + "days") in self.args:
                            cbargs["days"] = self.args[step + "days"]
                        span = 0
                        for arg in self.args:
                            if re.match(step + "on", arg) or re.match(step + "off", arg):
                                cbargs[arg] = self.args[arg]

                        steprelative = self.args[step + "relative"]
                        if steprelative in events:
                            start_offset_p = self.args[step + "start_offset"]
                            start_offset = self.parse_time(start_offset_p)
                            start = events[steprelative]["event"] + datetime.timedelta(hours=start_offset.hour,
                                                                                       minutes=start_offset.minute,
                                                                                       seconds=start_offset.second)
                            end_offset_p = self.args.get(step + "end_offset")
                            if end_offset_p != None:
                                end_offset = self.parse_time(end_offset_p)
                                end = events[steprelative]["event"] + datetime.timedelta(hours=end_offset.hour,
                                                                                         minutes=end_offset.minute,
                                                                                         seconds=end_offset.second)
                                span = int(end.timestamp() - start.timestamp())
                            if span > 0:
                                event = start + datetime.timedelta(seconds=random.randrange(span))
                            elif span == 0:
                                event = start
                            elif span < 0:
                                self.log("step_{} end < start - ignoring end".format(i))
                                event = start

                            events[stepname] = {}
                            events[stepname]["event"] = event
                            events[stepname]["args"] = cbargs.copy()
                            changes += 1

        list = ""
        for step in steps:
            stepname = self.args["step_" + step + "_name"]
            if stepname not in events:
                list += stepname + " "

        if list != "":
            self.log("unable to schedule the following steps due to missing prereq step: {}".format(list), "WARNING")

        # Schedule random events

        for step in randoms:
            event = None
            step = "random_" + step + "_"
            stepname = self.args[step + "name"]
            cbonargs = {}
            cboffargs = {}
            if (step + "days") in self.args:
                cbonargs["days"] = self.args[step + "days"]
                cboffargs["days"] = self.args[step + "days"]

            span = 0
            for arg in self.args:
                if re.match(step + "on", arg):
                    cbonargs[arg] = self.args[arg]
                if re.match(step + "off", arg):
                    cboffargs[arg] = self.args[arg]

            startstep = self.args[step + "start"]
            endstep = self.args[step + "end"]
            starttime = events[startstep]["event"]
            endtime = events[endstep]["event"]
            tspan = int(endtime.timestamp() - starttime.timestamp())

            mind_p = self.args[step + "minduration"]
            mind = self.parse_time(mind_p)
            mindts = datetime.datetime.combine(self.date(), mind).timestamp()
            mindtsdur = mindts - datetime.datetime.combine(self.date(), mind).replace(hour=0, minute=0, second=0).timestamp()
            maxd_p = self.args[step + "maxduration"]
            maxd = self.parse_time(maxd_p)
            maxdts = datetime.datetime.combine(self.date(), maxd).timestamp()
            dspan = int(maxdts - mindts)

            for i in range(int(self.args[step + "number"])):
                start = starttime + datetime.timedelta(seconds=random.randrange(tspan))
                end = start + datetime.timedelta(seconds=random.randrange(dspan)) + datetime.timedelta(seconds=mindtsdur)
                if end > endtime:
                    end = endtime

                eventname = stepname + "_on_" + str(i)
                events[eventname] = {}
                events[eventname]["event"] = start
                cbonargs["step"] = eventname
                events[eventname]["args"] = cbonargs.copy()

                eventname = stepname + "_off_" + str(i)
                events[eventname] = {}
                events[eventname]["event"] = end
                cboffargs["step"] = eventname
                events[eventname]["args"] = cboffargs.copy()

        # Take all the events we found and schedule them

        for event in sorted(events.keys(), key=lambda event: events[event]["event"]):
            stepname = events[event]["args"]["step"]
            start = events[event]["event"]
            args = events[event]["args"]
            if start > self.datetime():
                # schedule it
                if "enable" in self.args:
                    args["constrain_input_boolean"] = self.args["enable"]
                if "select" in self.args:
                    args["constrain_input_select"] = self.args["select"]
                if "days" in events[event]["args"]:
                    args["constrain_days"] = events[event]["args"]["days"]
                self.run_at(self.execute_step, start, **args)
                if "dump_times" in self.args:
                    self.log("{}: @ {}".format(stepname, start))
            else:
                self.log("{} in the past - ignoring".format(stepname))

    def execute_step(self, kwargs):
        # Set the house up for the specific step
        self.step = kwargs["step"]
        self.log_notify("Executing step {}".format(self.step))
        for arg in kwargs:
            if re.match(".+_on_.+", arg):
                self.activate(kwargs[arg], "on")
            elif re.match(".+_off_.+", arg):
                self.activate(kwargs[arg], "off")

    def activate(self, entity, action):
        type = action
        m = re.match('event\.(.+)\,(.+)', entity)
        if m:
            if not self.test: self.fire_event(m.group(1), **{m.group(2): self.step})
            if "log" in self.args:
                self.log("fired event {} with {} = {}".format(m.group(1), m.group(2), self.step))
            return
        m = re.match('input_select\.', entity)
        if m:
            if not self.test: self.select_option(entity, self.step)
            self.log("set {} to value {}".format(entity, self.step))
            return
        if action == "on":
            if not self.test: self.turn_on(entity)
        else:
            if re.match("scene\.", entity) or re.match("script\.", entity):
                type = "on"
                if not self.test: self.turn_on(entity)
            else:
                if not self.test: self.turn_off(entity)

        if "log" in self.args:
            self.log("turned {} {}".format(type, entity))

    def log_notify(self, message):
        if "log" in self.args:
            self.log(message)
        if "notify" in self.args:
            self.notify(message)

@Tyfoon Here you go.
I really appreciate @aimc 's and your (@ReneTode) help.

Thats true! You helped me out several times now. Thank your for that.

1 Like

Thanks Rene!

I have updated the apps (starting with switch_reset and motion lights) based on the instructions above and validated v3 is working on hass.io (the dashboard app is running so assuming all is fine).

Now breaking up my existing appdeamon.cfg. I assume the first part of the .cfg file is now in appdeamon.yaml and as such I understand there is no real need to change/modify that; Is that assumption correct?

Then for the each app I need to create (next to the actual .py file / app a yaml file with the same name (correct?).

I know have in my old appdeamon.cfg file for switch_reset the following:

[Switch Reset]
module = switch_reset
class = SwitchReset
file = /home/hass/.homeassistant/switches
delay = 30
log = 1

Which should become (?):

[Switch Reset]
  module : switch_reset
  class : SwitchReset
  file = /home/hass/.homeassistant/switches *** What should I do with this line?? *****
  delay : 30
  log : 1

And for motion lights now (old):

[MotionLights]
module = motionlights
class = MotionLights

constrain_start_time = sunset - 00:45:00
constrain_end_time = sunrise + 00:45:00 
sensor: binary_sensor.fibaro_system_fgms001_motion_sensor_sensor_10_0
entity_on : scene.kitchen_timer
entity_off : scene.kitchen_timer_off
delay: 600

Should become in a dedicates motion_lights.yaml with;

[MotionLights]
    module : motionlights
    class : MotionLights
    constrain_start_time : sunset - 00:45:00
    constrain_end_time :  sunrise + 00:45:00 
    sensor: binary_sensor.fibaro_system_fgms001_motion_sensor_sensor_10_0
    entity_on : scene.kitchen_timer
    entity_off : scene.kitchen_timer_off
    delay: 600

Is this correct?

almost.

where you use “[MotionLights]” it should say “MotionLights:”

and to make sure (when you get errors you always can put old things between “” because yaml can translate where you dont like it.
so 30 should be “30” in some cases

you see it isnt as hard s you expected :wink: most of what you have done is right :wink:

Ok, thx. Will try that later this weekend. What about the file location in the first app? Just delete the directory reference (but leave the filename) as I use hassio?

no just change it to yaml style like:

file: /home/hass/.homeassistant/switches

if that is still the right place.

Small update; Got Switch reset and motion_lights working. So great start and this allows me to switch off my ‘old’ pi and switch to hass.io. Thanks for your help so far!

p.s. on the file location for switch_reset: I had to take away any directory reference to get that working.

i didnt know you where moving from 1 device with 1 setup to another device with another setup :wink:
i thought you were just upgrading.

I had to upgrade Python in my Venv so used that opportunity to switch to hass.io instead (mentioned here) which seems better for amateurs like me. Migration is pretty smooth (also thanks to this forum) and all seems to be working out fine up till now.

1 Like

i didnt get the all in one :wink:
you probably mean hasbian with that. but no problem, now i know

Hi Rene, The all in one was this. Not much to find about it anymore but was used a lot pre-hassbian.

ah thats why i didnt get it :wink:
thx for that info

**** Update solved: All times now between " " and no errors. *******

Hi Rene, now working on final app (Occusim) but I get some errors I can’t grab.

Error:

2018-03-26 20:09:45.675113 INFO AppDaemon: Initializing app Occupancy Simulator using class OccuSim from module occusim
2018-03-26 20:09:45.698730 WARNING AppDaemon: ------------------------------------------------------------
2018-03-26 20:09:45.699312 WARNING AppDaemon: Unexpected error running initialize() for Occupancy Simulator
2018-03-26 20:09:45.699771 WARNING AppDaemon: ------------------------------------------------------------
2018-03-26 20:09:45.704447 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 1513, in init_object
    init()
  File "/config/appdaemon/apps/occusim.py", line 32, in initialize
    self.create_events({})
  File "/config/appdaemon/apps/occusim.py", line 65, in create_events
    start = self.parse_time(start_p)
  File "/usr/lib/python3.6/site-packages/appdaemon/appapi.py", line 277, in parse_time
    return self.AD.parse_time(time_str, name)
  File "/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 1015, in parse_time
    parts = re.search('^(\d+):(\d+):(\d+)', time_str)
  File "/usr/lib/python3.6/re.py", line 182, in search
    return _compile(pattern, flags).search(string)
TypeError: expected string or bytes-like object

Config:

Occupancy Simulator:
  module: occusim
  class: OccuSim
  log: 1
  notify: 1
  enable: input_boolean.vacation,on
  # test = 1
  dump_times: 1
  reset_time: 02:00:00

  step_morning_name: Morning
  step_morning_start: 06:30:00
  step_morning_end: 08:45:00
  step_morning_on_1: switch.badkamer_licht_255
  step_morning_on_2: scene.sfeer
  step_morning_on_3: scene.kitchen_timer

  step_day_name: Day
  step_day_start: sunrise + 00:15:00
  step_day_off_1: switch.badkamer_licht_255
  step_day_off_2: scene.sfeer_off
  step_day_off_3: scene.kitchen_timer_off

  step_evening_name: Evening
  step_evening_start: sunset - 00:45:00
  step_evening_on_1: scene.sfeer
  step_evening_on_2: switch.greenwave_powernode_1_port_switch_31_0

  step_night_name: Night
  step_night_start: 22:30:00 
  step_night_end: 23:30:00
  step_night_on_1: scene.sleeptime
  step_night_on_2: light.trappenhuis_24
  step_night_on_3: switch.badkamer_licht_255
  step_night_on_4: light.slaapkamer_ouders_177

  step_upstairs_hall_name: Upstairs Hall Off
  step_upstairs_hall_relative: Night
  step_upstairs_hall_start_offset: 00:01:00
  step_upstairs_hall_end_offset: 00:10:00
  step_upstairs_hall_off_1: light.trappenhuis_24
  step_upstairs_hall_off_2: switch.badkamer_licht_255

  step_lightsout_name: Lights Out
  step_lightsout_relative: Night
  step_lightsout_start_offset: 00:01:00
  step_lightsout_end_offset: 00:20:00
  step_lightsout_off_1: light.slaapkamer_ouders_177

  random_toilet_name: Evening toilet
  random_toilet_start: Evening
  random_toilet_end: Night
  random_toilet_minduration: 00:03:00
  random_toilet_maxduration: 00:05:00
  random_toilet_number: '3'
  random_toilet_on_1: light.licht_toilet_176
  random_toilet_off_1: light.licht_toilet_176

  random_kitchen_name: Evening kitchen
  random_kitchen_start: Evening
  random_kitchen_end: Night
  random_kitchen_minduration: 00:02:00
  random_kitchen_maxduration: 00:10:00
  random_kitchen_number: '5'
  random_kitchen_on_1: scene.kitchen_timer
  random_kitchen_off_1: scene.kitchen_timer_off

  random_attic_name: Evening attic
  random_attic_start: Evening
  random_attic_end: Lights Out
  random_attic_minduration: 00:02:00
  random_attic_maxduration: 00:05:00
  random_attic_number: '3'
  random_attic_on_1: switch.qubino_zmnhbdx_flush_2_relays_switch_12_0_2
  random_attic_off_1: switch.qubino_zmnhbdx_flush_2_relays_switch_12_0_2

The Ocusim app is identical to this post

My first thinking was a YAML formatting thing for the times but formatting is identical then this post so I doubt its a formatting (times between quotes etc).

Any idea?

Hi Rene,

All working fine now altough I have an error in the logs. It does not seem to effect Appdeamon but if I can get rid of it, I would be happy.

Error:

2018-03-26 20:25:20.374506 WARNING AppDaemon: ------------------------------------------------------------
2018-03-26 20:25:20.375410 WARNING AppDaemon: Unexpected error loading config file: /config/appdaemon/apps/._apps.yaml
2018-03-26 20:25:20.376831 WARNING AppDaemon: ------------------------------------------------------------
2018-03-26 20:25:20.379451 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 1609, in read_config_file
    config_file_contents = yamlfd.read()
  File "/usr/lib/python3.6/codecs.py", line 321, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb0 in position 37: invalid start byte

somehow this temperary file is created and bugging you:

/config/appdaemon/apps/._apps.yaml

delete that.

Thanks for your examples.

I get this error (but occusim works as expected):

File “/usr/lib/python3.6/site-packages/appdaemon/appdaemon.py”, line 1677, in read_config_file
config_file_contents = yamlfd.read()
File “/usr/lib/python3.6/codecs.py”, line 321, in decode
(result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: ‘utf-8’ codec can’t decode byte 0xb0 in position 37: invalid start byte

It’s a little offtopic, but could someone explain how “notify” works with this.
Where does it send the notification and which information?