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

Hey there!

I am getting an error while trying to set up my occusim instance.
The error.log gives me:

2018-03-01 23:46:35.082643 WARNING AppDaemon: ------------------------------------------------------------
2018-03-01 23:59:00.082696 WARNING AppDaemon: ------------------------------------------------------------
2018-03-01 23:59:00.082754 WARNING AppDaemon: Unexpected error in worker for App Occupancy Simulator:
2018-03-01 23:59:00.082810 WARNING AppDaemon: Worker Ags: {'name': 'Occupancy Simulator', 'id': UUID('c1c8fe77-bbba-4829-8237-3eab03e990cf'), 'type': 'timer', 'function': <bound method OccuSim.execute_step of <occusim.OccuSim object at 0x7fa13c0f2240>>, 'kwargs': {'random_wohnzimmerabends_off_1': 'scene.occusim_nachts_kuche_off', 'step': 'wohnzimmerabends_off_2', 'constrain_input_boolean': 'input_boolean.occusim_switch,on'}}
2018-03-01 23:59:00.082847 WARNING AppDaemon: ------------------------------------------------------------
2018-03-01 23:59:00.082977 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 501, in worker
    funcref(self.sanitize_timer_kwargs(app, args["kwargs"]))
  File "/home/smartassi/appdaemon/apps/occusim.py", line 222, in execute_step
    self.activate(kwargs[arg], "off")
  File "/home/smartassi/appdaemon/apps/occusim.py", line 242, in activate
    if not self.test: self.turn_on(entity)
AttributeError: 'OccuSim' object has no attribute 'turn_on'

apps.yaml:

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


  random_treppenhaus_name: Treppenhaus
  random_treppenhaus_start: Day
  random_treppenhaus_end: Evening
  random_treppenhaus_minduration: '00:01:00'
  random_treppenhaus_maxduration: '00:02:00'
  random_treppenhaus_number: 50  #TagsĆ¼ber 50 mal
  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_oreppenhaus_ug_off
  random_treppenhaus_on_3: scene.occusim_treppenhaus_eg_on
  random_treppenhaus_off_3: scene.occusim_treppenhause_g_off


  random_kuchenachts_name: KucheNachts
  random_kuchenachts_start: Bedtime
  random_kuchenachts_end: Day
  random_kuchenachts_minduration: '00:01:00'
  random_kuchenachts_maxduration: '00:10:00'
  random_kuchenachts_number: 2
  random_kuchenachts_on_1: scene.occusim_nachts_kuche_on
  random_kuchenachts_off_1: scene.occusim_nachts_kuche_off


  random_wohnzimmerabends_name: wohnzimmerabends
  random_wohnzimmerabends_start: Evening
  random_wohnzimmerabends_end: BeforeBedtime
  random_wohnzimmerabends_minduration: '00:10:00'
  random_wohnzimmerabends_maxduration: '03:00:00'
  random_wohnzimmerabends_number: 3
  random_wohnzimmerabends_on_1: scene.occusim_nachts_kuche_on
  random_wohnzimmerabends_off_1: scene.occusim_nachts_kuche_off

  step_beforebedtime_start: '23:59:0'
  step_beforebedtime_name: BeforeBedtime

  step_night_name: Night
  step_night_start: 'sunset - 00:10:00'
  #step_night_end: 'sunrise - 00:10:00'

  step_daytime_name: Day
  step_daytime_start: 'sunrise - 00:45:00'
# step_daytime_end: 'sunset - 00:45:00'

  step_evening_start: '17:30:00'
  step_evening_end: '23:29:59'
  step_evening_name: Evening

  step_bedtime_start: '00:00:01'
  step_bedtime_name: Bedtime
  step_bedtime_end: '00:05:30'

appdaemon.yaml:

secrets: /home/smartassi/.homeassistant/secrets.yaml
log:
  accessfile: /home/smartassi/appdaemon/logs/access.log
  errorfile: /home/smartassi/appdaemon/logs/error.log
  logfile: /home/smartassi/appdaemon/logs/appdaemon.log
  log_generations: 1
  log_size: 1000000
appdaemon:
  threads: 10
  plugins:
    HASS:
      type: hass
      ha_url: https://xyz.homeassistant.org
      ha_key: !secret http_password
      cert_verify: True
      app_dir: /home/smartassi/appdaemon/

Can someone spot my mistake?

I am craving for help here :frowning:

Thank you very much in advance!
Walter

Looks like you are running 3.0 beta - OccuSim App is still for 2.0.

It will work fine if you change the import statement as directed in this post:

Thanks for your answer aimc!

To be honest, I was not even aware of that until now.
I followed your instructions, now the error changed as follows:

2018-03-02 20:10:40.343925 INFO AppDaemon Version 3.0.0b3 starting
2018-03-02 20:10:40.344155 INFO Configuration read from: /home/smartassi/appdaemon/appdaemon.yaml
2018-03-02 20:10:40.344583 INFO AppDaemon: Starting Apps
2018-03-02 20:10:40.346893 INFO AppDaemon: Loading Plugin HASS using class HassPlugin from module hassplugin
2018-03-02 20:10:40.351171 INFO AppDaemon: HASS: HASS Plugin Initializing
2018-03-02 20:10:40.351472 INFO AppDaemon: HASS: HASS Plugin initialization complete
2018-03-02 20:10:40.351628 INFO Dashboards are disabled
2018-03-02 20:10:40.351700 INFO API is disabled
2018-03-02 20:10:40.543076 INFO AppDaemon: HASS: Connected to Home Assistant 0.63.3
2018-03-02 20:10:40.581852 INFO AppDaemon: Got initial state from namespace default
2018-03-02 20:10:42.561184 INFO AppDaemon: Reading config
2018-03-02 20:10:42.561399 WARNING AppDaemon: ------------------------------------------------------------
2018-03-02 20:10:42.561454 WARNING AppDaemon: Unexpected error loading config file: /home/smartassi/appdaemon/apps/apps.yaml
2018-03-02 20:10:42.561488 WARNING AppDaemon: ------------------------------------------------------------
2018-03-02 20:10:42.561922 WARNING AppDaemon: Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/appdaemon/appdaemon.py", line 1523, in read_config_file
    config_file_contents = yamlfd.read()
  File "/usr/local/lib/python3.6/encodings/ascii.py", line 26, in decode
    return codecs.ascii_decode(input, self.errors)[0]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 415: ordinal not in range(128)

2018-03-02 20:10:42.561985 WARNING AppDaemon: ------------------------------------------------------------
2018-03-02 20:10:42.562029 WARNING AppDaemon: File '/home/smartassi/appdaemon/apps/apps.yaml' invalid structure - ignoring
2018-03-02 20:10:42.562064 INFO AppDaemon: /home/smartassi/appdaemon/apps/apps.yaml added or modified
2018-03-02 20:10:42.562092 INFO AppDaemon: /home/smartassi/appdaemon/apps/apps.yaml added or modified
2018-03-02 20:10:42.562151 INFO AppDaemon: Adding /home/smartassi/appdaemon/apps to module import path
2018-03-02 20:10:42.562222 INFO AppDaemon: Found module /home/smartassi/appdaemon/apps/occusim.py
2018-03-02 20:10:42.562267 WARNING AppDaemon: No app description found for: /home/smartassi/appdaemon/apps/occusim.py - ignoring
2018-03-02 20:10:42.562554 INFO AppDaemon: App initialization complete

I am not completely sure if my configs (appdaemon.yaml + apps.yaml) are built correctly.

Hi, have you been able to get this working for Appdeamon 3? If yes, could you share the latest Occucsim app?

If you follow the instructions in my post above it should work fine - Iā€™ll update it for 3.0 soon.

The error above is likely caused by a non ascii character in the yaml file and isn;t an issue with the app running under 3.0

1 Like

@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.