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


Recommended Posts

Posted (edited)

YACL: Flexible Cron-Like Scheduler for Fibaro

After discovering the possibility of using timers to generate events and process them with a custom event handler, I decided to create my own task scheduler – similar "in spirit" to the Linux CRON tool.

This isn’t the first solution of its kind for Fibaro HC3 – there are already several similar tools available here, some of them quite sophisticated. YACL isn’t meant to compete with them. Instead, it represents an approach that I personally find interesting and would like to share with the community. YACL is a simple QuickApp-based framework that provides users with an easy way to define time-based tasks using plain Lua functions. User can modify "main" tab of YACL QuickApp where configuration is stored.

 

Main Features:

  • Tasks are just Lua functions – no need to learn a new DSL or syntax. You write standard Lua code using the Fibaro HC3 API, as you would in any QuickApp. And add it to the schedule.
  • Schedules tasks weekly or monthly – for example, every Monday and Friday, or on the 15th, every last day of the month. Longer intervals like "every 15 may once a year" could be handled, but seem unnecessary in practice.
  • Overcomes Fibaro setTimeout limit (~25 days) – this is essential for handling monthly tasks.
  • Supports sunrise and sunset schedules – unlike native Fibaro functions, which only return today’s times, YACL calculates the sunrise/sunset for the actual day of task execution. So, a task scheduled for sunrise once a month will always trigger at the correct time for that specific day.
  • Adds support for dawn and dusk – these are often more useful than sunrise/sunset, as they better reflect the start and end of true darkness. Coordinates for sunrise/sunset/dawn/dusk are taken directly from the HC3 location configuration. It should recognise polar day/night (when it's no sunset/sunrise), however It's not tested by me... :) I checked it with help of Garmin watch in my location and many various dates. Algorithm to compute named NOAA is taken from the net.
  • Unique error-checking mechanism before scheduling – since a function might be scheduled for execution days or weeks in the future, it's good to know if it's error free. YACL can test it before registration to catch syntax errors early. You can choose between:
    • "full" mode – runs the function once, normally e.g. all Fibaro calls as hub.call(...) will actually run,
    • "dry" mode – simulates execution by suppressing calls to objects: hub.*, fibaro.*, and json.* using a setmetatable-based mechanism.
  • error in user task (any LUA error) does not terminates other tasks, message with many details is printed on console and script continues.
  • consumes CPU time only in a moment of executing tasks, otherwise is transparent to the HC3.
  • tasks invoked every defined amount of time (ex. start from now and every 15 minutes) can also be defined and combined with daily and monthly planning.
  • works "out-of the-box" with OCVC (Virtual Console)

In next message I provide the user description and QA for download.

Edited by Łukasz997
  • Like 4
  • Topic Author
  • Posted (edited)

    YACL. CRON and Interval Task Scheduler manual.

     

    User always have to start creating new scheduler object:

    Please login or register to see this code.

    This creates a new scheduler instance. You can create many Cron instances, which - as it turns out later - can be useful not only for grouping the tasks.

    Parameters are optional:

    debug – global debug level (default: 0). Control of how verbose YACL will be. Many internal messages can be printed out (at task execution, when it will be executed again, errors & more). Value is 2 - all messages to -1 (no messages (errors only). 

    validateMode – (default: "dry") "none", "dry", or "full" to control task validation. Validation, if allowed, refers to any new task added to the created instance. However it can be later changed via "helper" function.

     

    When we have our mySched instance created we can add tasks to them. It can be as many tasks as we need. However it can be useful to create few instances and get together logically close tasks.

    Please login or register to see this code.

    First parameter is reference to a function. Above example shows so called no-name function. This have to be used when called function has a parameter(s) or our task definition is so short that we need to write it in addCron call.

    When function has no parameter the construction can be simpler:

    Please login or register to see this code.

    I intentionally put larger piece of code to show practical situation and power of Lua + Fibaro API used to define the task. This mails gas counter to user '2'(admin) every last day of month - assumes such variable in user configuration :)

    syntax of the command addCron:

    addCron(fn, opts)

    fn - reference to the function to be scheduled, or in-line no name function as shown above.

    Options (opts):

    name – unique task name

    time – either a single string or an array of strings defining when the task should launch (see examples below)

    log – optional message to show on each execution

    debug – task-specific debug level (overrides global). Meaning: same as in the defining mySched instance

    color – simple HTML color name; all messages referring to this task will be shown in this color

     

    time requires more explanations. Time specification in one of the following formats:

    "HH:MM" – daily at specific time (every single day)

    "HH:MM@DAYS" – at specified time weekly, at defined days only (Mon=1 .. Sun=7), e.g. "07:30@15". No weekday separators; one after one or single

    "HH:MM#MONTH_DAYS" – monthly, e.g. "11:00#1,15,L". List of month day number separated by ','. Letter 'L' stands for last day of month, as it will vary m-to-m

    "sunrise[±HH:MM]" – sunrise with optional offset

    "sunset[±HH:MM]" – sunset with optional offset

    "dawn[±HH:MM]" – civil dawn with optional offset

    "dusk[±HH:MM]" – civil dusk with optional offset

     Notes:

     - For weekly (`@`) scheduling, use digits 1–7 (Mon–Sun)

    -  For monthly (`#`) scheduling, use 1–31 and/or "L" (last day of month). When month has no listed day (31 February), execution skip to next valid date. When no next valid date - next execution after 31 days.

    - Multiple schedules can be passed as an array of strings

    - offset is allowed only with sunset/sunrise/dawn/dusk

     

    Ex.

    Please login or register to see this code.

     

    When we define all tasks we need to start the scheduler for object:

    Please login or register to see this code.

    Until this not executed - tasks are defined but still not run. There's other command:

    Please login or register to see this code.

    that stop executing all tasks defined in instance mySched. All other instances will remain running. Task definition is still in memory and tasks can be again run. How useful it can be - it turns out at the end of description.

     

    At the end complete example, turning on the lamp during darktime±offset:

    Please login or register to see this code.

    Remarks:

    - I've done a lot to ensure validation of parameters (non-existing month day ex. 30 Feb, duplicating days, common syntax error). But please fill the parameters carefully, may miss something.

    - when debug = 2 framework will print the time of the nearest run of all added tasks after executing run(). This is useful for verification of proper definitions

    - I have no idea what happen to task (except message from computeSunTimes function) when it's polar day/night. Greetings to north Earth regions users (all)...

     

    This is not all, more to come. However it's enough to start.

    Remaining: description of addInterval method for adding within-day repeatable task, internal testing of tasks, errors in scripts and helper functions.

    Please login or register to see this attachment.

    Edited by Łukasz997
    • Like 1
  • Topic Author
  • Posted (edited)

    YACL manual part 2.

     

    Daily Interval Tasks.

    There's also method to add short-interval task to be executed daily, every HH:MM period. This can be done by:

     

    addInterval(fn, opts)

    fn - reference to a function, the same as in addCron

    opts - options are the same as in addCron, but time parameter is much simpler:

    "HH:MM/HH:MM" – First HH:MM is exact time when the task should first run today (or tomorrow - when "now" is after entered launch time). Second HH:MM is interval (how often to repeat).

    Instead of first HH:MM word "now" can be entered. It assumes first run to be at once - but not after adding the task. "now" refers to the time of enable defined task(s) by instance:run() command!

    There's only one time string allowed, sending time(s) as a table (time = {...}) is not valid here. 

    Ex.

    Please login or register to see this code.

    Execution once started by object:run() never stop, until object:stop() command is invoked (the same as with addCron).

     

    Using addCron and addInterval together.

    As you see, syntax of time parameter def. in addInterval is very simple. One may ask: what if I want to launch task every 15 minutes, but only on 1th, 15th and last day of month, between 16:00 and 18:00. Neither addCron nor addInterval alone makes it possible.

    That's true, but this method can be combined to achieve more complex results. We have to create two instances of Cron objects where one will manage other:

    Please login or register to see this code.

    As a result interval every 15 minutes will be started and stopped according to user need (defined in those two addCron calls). This only works because "now" used to start setInterval schedule refers to slaveI:run() timestamp.

    This hack allows to define very complex rule. The schedules can be nested as deep as you want - until user knows what's happening here...

     

    More to come.

    Edited by Łukasz997
  • Topic Author
  • Posted (edited)

    Few remarks about callback function arguments (for advanced users).

    if we have:

    Please login or register to see this code.

    what parameters are passed when calling the task function?

    One and only parameter passed is reference to object for whom addCron is called. Simpler: just self (pointer to the 'obj')

    In above example function() has no parameter defined, so self is thrown away (no local var who can take it). But function foo will be called with its parameter "abc".

    If user would like to make use with self inside callback - it's necessary to declare it as parameter:
     

    Please login or register to see this code.

     

    clog is one of the helper function in framework; calling it is the situation when self is usable within callback. self:clog(text) prints message enriched with selected in addCron color and unique name of the task.

    Above ex. should produce something like:

    [Foo function] Hejho! function is called

    Edited by Łukasz997
    Posted (edited)

    I think I got the speed header working in hc3emu2 now (v2.0.28)

    Please login or register to see this code.


    Please login or register to see this attachment.

    Edited by jgab
  • Topic Author
  • Posted (edited)

    Speeding up very useful in debugging such application like this.

    What's exactly this number after "speed"?

     

    By the way: colors in YACL messages works very limited in vscode console (sometimes, some colors, rest - white). However, on real HC3 console (and OCVC) it is working ok.

    Edited by Łukasz997
    Posted
    47 minutes ago, Łukasz997 said:

    Speeding up very useful in debugging such application like this.

    What's exactly this number after "speed"?

     

    By the way: colors in YACL messages works very limited in vscode console (sometimes, some colors, rest - white). However, on real HC3 console (and OCVC) it is working ok.

    It's hours to run in speed. After that time the emulate start to run in "normal" time.

     

    Yes, the vscode terminal don't support html colors so I have to map it to ANSI escape sequences. 

    I can only do that by having a pre-made table from names to ANSI escape sequences.

     

    In v2.0.31 just push I expose that colormap in fibaro.hc3emu.colors

     

    Please login or register to see this code.

    So colors consist of two tables, COLORMAP with standard colors and EXTRA with colors that can be used in vscode (they can't be used in zerobrane)

    The font color mapper looks in COLORMAP and then in EXTRA to find the escape sequence.

    In the example above I pick the pink3 color in extra and assign it to the color fopp in COLORMAP. I can then use fopp as a color in my code (which will be the same as pink3)

     

    So,  if you have favorite colors you use you could set them up in the emulator.

    You can also submit them to me with the mapping to an existing EXTRA color or an ASCII escape sequence and I can add them to the log table...

    Posted

    This way you can print out all colors in the EXTRA map
     

    Please login or register to see this code.

     

    • Thanks 1
  • Topic Author
  • Posted

    v. 1.09 Before we go back to discussion there's an update to download.

    There are minor changes (schedules from older version will work).

    Update is necessary to run next advanced examples.

    - changes to the messaging system (some messages has no task name, log set in interval-style task was printed only once etc.)

    - changes to run(), stop() and one internal function. Now script can be handled (stopped, started) from within callback function, what gives much more possibilities.

    Please login or register to see this attachment.

  • Topic Author
  • Posted (edited)

    Run only in version 1.09 and later. For advanced users.

    More about stop() and  run().

    I didn’t mention it before, but both of the above methods can take an optional parameter. Without a parameter, they stop or start the scheduler object along with all of its tasks.

    When called like mySched:run("task_name") it only starts (or stops) the task named "task_name", allowing you to control each task individually.

    If you combine this with the technique from the previous post - passing a reference to the scheduler object into your task function - you get a very powerful tool for dynamically steering execution.

    Example:
    We have one scheduler instance and an interval task that disables itself once a certain condition is met (in this case, after its third consecutive run). A cron task (configured to fire twice) re-enables it.

     

    UPDATED DESCRIPTION:

    self is always automatically passed to user task function - user should only receive it to a variable. In out example it's done by parameter  p -  function interval(p)

    Then, you can call p:stop("Interval task") inside task function to halt further executions. You can extend this pattern to implement arbitrarily complex logic driven by sensors, variables, and other runtime data. 

     

    The interval task will be launched by addCron step every Sunday 18:15 3 times (and stops) and and 18:22 also 3 times.

    Please login or register to see this code.

     

     

    Edited by Łukasz997
  • Topic Author
  • Posted (edited)

    It’s time to wrap up this rather long-winded "tale".

    For every user, not only advanced. Actually, previous section was also for all. Maybe understand the mechanism is more difficult, but it's not needed to use it.

     

    Error checking mechanism.

    it is set initially when instance is defined (via validateMode).

    Please login or register to see this code.

    This parameter can be one of three values (parameter is optional - default "dry"):

    "none" – No validation is performed for any tasks in the mySched instance.

    "full" – Each task function is executed once before being registered via addCron or addInterval. All calls to the Fibaro API will be performed normally. For example, if hub.call is used to turn on a light, the light will actually be turned on. All API functions return real values. Any potential errors during this validation run will be printed to the console with detailed information.

    "dry" – The task function is also executed, but all calls to hub.*, fibaro.*, or json.* are mocked. No devices will change their state. Any Fibaro API function that normally returns a value will return nil. Suitable only for static testing. Errors are handled in the same way as in "full" mode.

     

    Validation errors do not stop the script – even malfunctioning tasks are still registered and will be executed when their scheduled time arrives.

    This is all handled using Lua’s pcall function – no magic involved.

    But this is quite powerful: reveal syntax error (like: if a = b then...), typo in function calls (like hub.csll) et cetera. 

    Likewise, normal task execution is also wrapped in pcall, ensuring that a single faulty task won’t crash or interfere with the others.

     

    Validation mode initiated in instance can be locally changed:

    Please login or register to see this code.

    The single task can be verify "on demand" (task_name is that chosen in the add... method):

    Please login or register to see this code.

    "mode" is text like in validateMode definition. Returns the same values as pcall does. User should handle the result.

     

    mySched:clog("text")

    described before, just to recall: emit text message to www console and OCVC (if you have it installed) preceded by task name and in task color. (Intended to use in task functions). For proper functioning, the user task function must receive self as a parameter - see the earlier post about run() and stop().

     

    That's all about YACL.

    This framework along with my other "invention" - event driven script processor called ANIEL - supervises, by eye, over 80% activity of HC3 in my ~100 devices home.

    Enjoy.

    Edited by Łukasz997

    Join the conversation

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

    Guest
    Reply to this topic...

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