Jump to content

Welcome to Smart Home Forum by FIBARO

Dear Guest,

 

as you can notice parts of Smart Home Forum by FIBARO is not available for you. You have to register in order to view all content and post in our community. Don't worry! Registration is a simple free process that requires minimal information for you to sign up. Become a part of of Smart Home Forum by FIBARO by creating an account.

 

As a member you can:

  •     Start new topics and reply to others
  •     Follow topics and users to get email updates
  •     Get your own profile page and make new friends
  •     Send personal messages
  •     ... and learn a lot about our system!

 

Regards,

Smart Home Forum by FIBARO Team


  • 0

EventProxy


jgab

Question

-There is always this issue with device IDs needed to be declared in the header. Then a device is reincluded and gets a new id and code breaks.

-Then there is the issue of an integer being a bad way to refer to devices when coding and thus we build hometables or declare variables.

 

This code is an example of trying to solve both issues. It works but is more of a concept for the moment as some more checks and more type of triggers need to be supported. (It supports value, power,sceneActivity and simple CentralSceneEvent for now)

 

Anyway, we have a scene that builds a "hometable", lets call it the "HomeTableBuilder". It builds the table from meta data stored in the 'Device description' field for devices (not for virtual devices, they don't have a description field and they don't trigger scenes anyway).

 

The format is very flexible to specify where in the home table 'tree' the deviceId should be inserted..

Please login or register to see this attachment.

'Device description' = ["dev.%room.tableLamp.%id"]

will set HomeTable.dev.kitchen.tableLamp=417, if the device is in room 'kitchen' and has id 417

'Device description' = ["dev.%section.%room.tableLamp.%id"]

will set HomeTable.dev.upstairs.kitchen.tableLamp=417, if the device is in section 'upstairs' and room 'kitchen' and has id 417

'Device description' = ["dev.remote.%id"]

will set HomeTable.dev.remote=362, if the device is a fibaro remote with id 362

The code in the HomeTableBuilder that needs to be setup is:

Please login or register to see this code.

So this take care of creating a hometable (HT in the code) from meta data stored in the devices themselves. We can then add more static configuration data to the hometable etc.

After that we declare which of our other scenes should receive events. (Test1 has id 313, and Test2 has id 316 in this example). sceneId(string) is a convenience function to get the ID from a scene name.

Please login or register to see this code.

This says that we want to receive property.value events from "tablelamp" and send it to the scenes named "Test1" and scene named "Test2"

We declare the same for "power"

And our fibaro remote should send its events to "Test1" and "Test2" too.

 

The HomeTableBuilder then dynamically creates another scene in the HC2 named "EventProxy"..softwareVersion. (it's always created hidden for me!?, do "show hidden")

with the following code:

Please login or register to see this code.

This "EventProxy" scene receive triggers for the devices declared and send them to the scene we declared having interest in these events, Test1 and Test2 in this case.

The code in a receiving scene (like Test1) then looks like below. (No declared triggers in the header, it gets all triggers from startScene from the EventProxy)

Please login or register to see this code.

Scenes should still declare globals, weather etc... triggers that are not connected to devices IDs.

 

So, what have we achieved? 

-We don't have to deal with numerical device IDs in our code

-We can add meta data to devices that tells where in the HomeTable they should be inserted

-We don't have to declare numerical IDs in headers

 

Drawback: A small performance penalty to let the EventProxy trigger other scenes instead of they being triggered directly. (Seems to be very small penalty though, logs are always on the same second in EventProxy and receiving scenes for me). One could generate different EventProxy scenes for different triggers to distribute the load a bit and ensure that the EventProxy doesn't hit the maximum 10 active instances... However, an activated EventProxy instance is very quick to dispatch the event and then terminate.

 

Improvement: Let scenes dynamically register which events they want when they start up. Only issue is how often the EventProxy need to be rebuilt...

 

Code for the complete HomeTableBuilder:

Please login or register to see this code.

 

Edited by jgab
  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Recommended Posts

  • 0

trying your code and get this error on line 221

Please login or register to see this code.

 

Link to comment
Share on other sites

  • 0
  • Inquirer
  • Ok, works for me... I'm not sure where line 221 is in your scene. Is it the part building the home table? Do you have some device that I'm not covering in the code?  Have you added meta data to a device, in the right format? 

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Do you get something strange back from api.get("/sections") ?

    Don't you have any sections? Would assume that fibaro:getSectionID would return 0 then?

     

    Ok, I get it. I didn't understand the section data. It is not an ordered array like it happened to be for me. Need to search for matching ID.

     

    Ok, patched the original code.

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Ok, should never have published this :)

    You probably never call sceneId to resolve scene ids and thus the cache never gets initialized.

    if you move _sceneCache ={}

    before the declaration of function sceneId it should work. Writing this on my iPhone so I have trouble editing the code

     

     

    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Sorry, wrote that with a smiley attached. Just a reminder to myself how hard it is to share unfinished code...

    Better then to thank you for nailing two bugs that could have come back and bite me in the future. Thank you!.

    The patch was not as easy as I wrote in my previous post (difficult to debug code while riding the subway...), but I have fixed it in the code in the first post now.

     

    Anyway, we are not allowed to have 'loadstring' but we can have scenes that writes scenes... that opens up for some possibilities. Maybe scheduling rules expressed in some natural language syntax that is compiled to a lua scene? 

     

    Link to comment
    Share on other sites

    • 0

    When it creates the EventProxy it looks like this, shows table instead of id

     

    Please login or register to see this code.

     

    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • What is your first argument to T.trigger ? Is it a table?

      T.trigger(HT.dev.kitchen.tablelamp,T.value,{sceneId("Test1"),sceneId("Test2")})

    I have patched the code (was still an issue with the section code) and a test in T.trigger so it is ensures a numerical ID as first arg.

     

     

    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Thank you! :)

    I made a small optimization to the generated EventProxy code to not call json.encode for every startScene.

    The code doesn't make a lot of error checks as you have encountered so let me know if you find some more issues.

    ...and as you "play" with it,  if you can think of any improvements in general that would make this easier to use....

    Link to comment
    Share on other sites

    • 0

    Not sure what is causing it but when i fill more then 1 ID with desctription value  ["dev.%room.tableLamp.%id"] i get Invalid deviceID

    Please login or register to see this code.

    Please login or register to see this code.

    so i have 2 device in same room with description value

    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • ...could this be the problem?:

    if you have two devices (e.g. id 200 and id 201) in the same room with the same description string e.g.

    ["dev.%room.tableLamp.%id"]

    it will be merged to dev.room.tableLamp={200, 201}

    The idea was to be able to define home table "groups" (there is a flag in the code to not merge and the last definition overwrites the previous)

    You have to have different names for the lamps in the room, e.g.

    ["dev.%room.tableLamp.%id"] for id 200

    ["dev.%room.roofLamp.%id"] for id 201

    Then you get dev.room.tableLamp=200 and dev.room.roofLamp = 201

     

    (There is also a flag that makes the table only using lowercase keys no matter what is used in the description field, that is true by default)

    Edited by jgab
    Link to comment
    Share on other sites

    • 0
    3 minutes ago, jgab said:

    ...could this be the problem?:

    if you have two devices (e.g. id 200 and id 201) in the same room with the same description string e.g.

    ["dev.%room.tableLamp.%id"]

    it will be merged to dev.room.tableLamp={200, 201}

    The idea was to be able to define home table "groups" (there is a flag in the code to not merge and the last definition "wins")

    You have to have different names for the lamps in the room, e.g.

    ["dev.%room.tableLamp.%id"] for id 200

    ["dev.%room.roofLamp.%id"] for id 201

    Then you get dev.room.tableLamp=200 and dev.room.roofLamp = 201

    Thanks for clarifying this, now i think i got it. No errors so far :)

    Link to comment
    Share on other sites

    • 0
    10 hours ago, jgab said:

    Sorry, wrote that with a smiley attached. Just a reminder to myself how hard it is to share unfinished code...

     

    Thank you for sharing that code. I have read it with great interest. I am probably never going to use any code that tries to solve the ID problem (because the problem does not bother me), but that does not lower the value of your shared code! It is a personal thing. And even if someone does not want to use your code, it is very interesting to see different solutions, different coding style, ... I learn something new on this forum every day.

     

    For those who are not following, I think this is a key element of your code:

    Please login or register to see this code.

     

    I do use this kind of scene calling, but not for such a global case. I use it to set a parameter on a "Dimmer 1". Instead of replicating that code in 5 scenes, I have one scene that takes "ID" and "level" as an argument. It is a kind of "global functions" - something quite a few people have asked for.

     

    BTW I would say, it is hard to share "unfinished code", but also hard to share "*finished* code"... That is, assuming that such a thing exists ;-)

    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • 1 hour ago, petergebruers said:

     

    Thank you for sharing that code. I have read it with great interest. I am probably never going to use any code that tries to solve the ID problem (because the problem does not bother me), but that does not lower the value of your shared code! It is a personal thing. And even if someone does not want to use your code, it is very interesting to see different solutions, different coding style, ... I learn something new on this forum every day.

     

    For those who are not following, I think this is a key element of your code:

    Please login or register to see this code.

     

    I do use this kind of scene calling, but not for such a global case. I use it to set a parameter on a "Dimmer 1". Instead of replicating that code in 5 scenes, I have one scene that takes "ID" and "level" as an argument. It is a kind of "global functions" - something quite a few people have asked for.

     

    BTW I would say, it is hard to share "unfinished code", but also hard to share "*finished* code"... That is, assuming that such a thing exists ;-)

     

    Thanks. I like to share ideas because I'm always hoping someone will get inspired and come up with something better. I haven't started to use this myself (I do use the description field to make the home table though). It would be better to declare the triggers in the scene that actually use them instead of having them all in the HomeTableBuilder. Maybe piggyback on the standard header declaration and reparse it?

    Anyway, a couple of coders here are submitting scenes that use the startScene to pass arguments like loggers and notifiers. 

     

    I'm since some time back using my "

    Please login or register to see this link.

    " to code scenes, and because in that model the scenes are constantly running (setTimeout loop, and incoming triggers are "posted" back to the main loop via a shared  global var) which means that the scenes can keep "state". That allows me to do tricks like this;

    Please login or register to see this code.

    and I can call scenes and get a result back in the same local scope (The Event:remote fun does a startScene and adds an '_from' key with the ID of the calling scene so that the receiving scene knows where to return the answer)

    It is surprisingly convenient ;)

    Link to comment
    Share on other sites

    • 0
    18 hours ago, jgab said:

    Thanks. I like to share ideas because I'm always hoping someone will get inspired and come up with something better.

     

    Thank you for sharing!

     

    After 5 years, the more I try, the less I know ;-)

     

    Psychology explains it like this: the more you learn about something, the more you get a sense of what you do not know. This can lead to a feeling of ... not knowing...

     

    Larger system also means "larger interaction" and "larger decision tree". I haven't found the magic bullet to to tackle complexity yet, and I am not too optimistic.

     

    But please keep posting ideas!

     

    18 hours ago, jgab said:

    I'm since some time back using my "

    Please login or register to see this link.

    " to code scenes, and because in that model the scenes are constantly running (setTimeout loop, and incoming triggers are "posted" back to the main loop via a shared  global var) which means that the scenes can keep "state".

     

    I think you touch upon a key point: managing events (triggers) and state (what am I doing? what have I done before this) is problematic.

     

    I do keep a few scenes that are single instance, autostart and run a timed loop, sleep time is above 5s. So I do not use them to handle user events. I did have 1s loops, the HC can handle that, but it did not feel "right" - but because they keep state I think they are easier to understand.

     

    What if your global variable wasn't a single value? What if it was actually a queue and you could peek/pop/push date? I've toyed with that idea long ago, but I've never implemented it because I think every system needs either a locking mechanism, a primitive queue or an atomic command (aka "compare and swap") to avoid synchronization issues. I think you'll run into issues on a HC2, because the system has at least two physical execution units, so it is possible to have odd issues due to parallel execution.

     

    Have you tried to do some load testing on that adder-event-concept? I've got a gut feeling, it will break down, because it needs synchronization primitives as well. I do not want to underestimate you, I am just being curious. Maybe you have some sort of locking mechanism already, or maybe it is possible to design one. I am not sure it can be done, because I do not know how locking of global variables works.

    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • (we are diverging from the event proxy now but I have an idea how to merge the EventProxy with the EventModel...)

     

    So, the

    Please login or register to see this link.

    I use to code my scenes in nowadays do solve the synchronization pretty ok (for 6+ month). I have a producer/consumer pattern with a shared variable that has not given me any race conditions under stress test - which seems to imply that read/write to a global is kind of atomic... Having that, it is possible to implement a synchronized mailbox between producer/consumer "threads" (see code below)

    People that use global variables to send parameter between scenes are in the risk zone to have race conditions because it is easy that the producer (sending scene) writes a variable twice before the consumer (the receiving scene) reads it. So that doesn't work. (Using startScene with args solves that somewhat because new instances are started of the receiving scene... but you get multiple instances... more on that later).

     

    The code below showcase how to get around it.

     

    In main() goes the scene specific code and below that the standard boilerplate Producer/Consumer code that runs the event model logic.

    -main() gets called from the "Consumer part"'s setTimout thread with incoming triggers and events, and main() can keep a state in variables outside main() or in closures.

    -It gives an example of a timer to turn off light with the variable "timer" keeping the state of the timer.

    -Another example triggers on incoming changes to a global variable "counter" and keeps it own state in a variable "counter2"

     

    The last example is interesting because if you from another scene run

    Please login or register to see this code.

    You see that the scene gets all the events but it will always read "10" as the values are overwritten before we can read them (however, the local counter is updated). Very quick triggering as in this case will create new parallel instances and you thus need  set max instances to 10 in this case. More than 10 quick invocations and you will loose events - but that you will do with an ordinary scene anyway. Just a small delay after setGlobal will make the receiving scene keep up because the Consumer is very quick to read events and re-post them (the delay of 250ms can be varied but it has worked well for me). The incoming "Producer" instances of the scene will in worst case buffer up (to max 10 instances) until the Consumer reads them so I guess that is your stack...

     

    The load (CPU) on the system is very low and smooth for me - I have 5-6 scenes running this model, some of them quite large, and also doing "inter-scene" communication with events (very little RAM too). The only busy wait is the "Producers" but they terminate on average very quickly and run only on incoming triggers/events anyway (can't see any difference between 2 or 6 scenes, and I believe the spikes are GC or housekeeping stuff...).

    Please login or register to see this attachment.

     

    This model, besides allowing me to keep local states in-between triggers invocations, have a lot of other advantages.

    -I can do some heavy initialisation in the code (like setting up home tables or other things) as it is only done once when the "main" scene starts

    -I can mix and match fibaro events with my own events and even send events between scenes that arrives as normal events to main(...)

    -- I can easily have "subroutine patterns" with return values (e.g.  ping/pong patterns for scenes to make sure that scenes are alive....)

    -I can easily post my own versions of fibaro's events to simulate and test the logic in the code

    -The post function allows for delayed post so I can set up triggers in the future...

     

    The big

    Please login or register to see this link.

    has a lot of other features for pattern matching events, flexible time format for posts, and some complex event processing (CEP, i.e. matching that not an event happens, or that events happens in a certain order), scripts, and various scheduler test (cron etc.)

    Please login or register to see this code.

     

    Edited by jgab
    Used an old version of the locking code
    Link to comment
    Share on other sites

    • 0
  • Inquirer
  • Come to think of it, I believe that the EventModel for the average programmer can cope with a higher onslaught of triggers before hitting that dreaded "To many instances" (shouldn't that be "Too many instances"?). The reason being that new triggered instances are quickly translated to "timer threads" in the main scene (the startup instance), and they can be as many as memory allows. The risk is usually that scenes take too long to execute (sleeping waiting for device changes etc) that max number of instances are exceeded. On the other hand, spawning a lot of parallel instances is usually a sign of something being wrong anyway...

    Edited by jgab
    Link to comment
    Share on other sites

    Join the conversation

    You can post now and register later. If you have an account, sign in now to post with your account.

    Guest
    Answer this question...

    ×   Pasted as rich text.   Paste as plain text instead

      Only 75 emoji are allowed.

    ×   Your link has been automatically embedded.   Display as a link instead

    ×   Your previous content has been restored.   Clear editor

    ×   You cannot paste images directly. Upload or insert images from URL.

    ×
    ×
    • Create New...