Use JavaScript for your automations

This is an external engine that exposes Home Assistant entities and services to JavaScript scripts. It works by connecting to HASS WebSocket API, and encapsulates all available entities as JS objects to be able to simply interact with them using JavaScript.

Scripts are constantly monitored in the scripts directory. They will be loaded when the service is started, reloaded when modified, and unloaded when deleted (also before reload).

JSON files will also be monitored and (re)loaded automatically. Scripts are notified when this happens. This provides a way to configure the scripts (and change their configuration on-the-fly), if your scripts want to support this.

Why

If you are used to JavaScript and do not want to go through the learning curve of Python and YAML templates (or just prefer JS for your automations), this comes to be a very handy tool that adds the capability to use JavaScript for your more complex automations, that may not be easily (or possible at all) implemented using templates.

After waiting for a proper JS integration and seeing that attempts were stalled for a long time, I decided to run my own one.

Installation and usage

Please refer to the github project page for details.

This is functional but still under development. Please let me know if you have some issues, including with the installation steps.

If you find it useful and give it a try, please let me know if you face some issues with the installation process. Of course, also glad to know if it works fine in different scenarios. :wink:

2 Likes

Ha! I had a similar idea myself! Although I went a little bit different direction with the api, and your runtime is more polished in general. I would not recommend anyone to use my project, but maybe someone will be inspired to continue the trend of js integrations)

Well, the topic itself seems to raise some interest :wink: at least if only by looking into the clones and activity on my github for an otherwise unadvertised project.

Happy to receive feedback on installation process and possible future enhancements.

I do not have an opinion about the use of this integration, but you should get your facts straight. Automations in Home Assistant have never been in Python, nor do you need any knowledge of it. Templates are not yaml, and you can write automations without templates or yaml using the automation editor. So the why just boils down to you like JavaScript better, which is fine by me.

2 Likes

Always a fan of projects like this. Python is my jam so i’m going to stick to Pyscript in HACS, but glad to see there’s options for those who prefer JS!

You are right: probably not the most accurate wording. It should have probably been ā€œfor those not wanting to go through the learning curve of HA templating and Python for complex automationsā€. :wink:

There’s a little bit more of reasoning for this than just than liking JS more than HA templating. Templating and HA automations are great, but sometimes you need more flexibility or a real programming language behind. This is where JS vs Python preference and learning curve comes into the topic.

1 Like

I understand the merits, but my neurodiverse brain is having a hard time with inaccuracies :slight_smile: I did some pretty complex stuff using templating (like charge the car at the time of the maximum predicted solar yield, optimizing charging speed and always ready before the next calendar appointment, with enough juice for the distance of the calendar location). So I can appreciate templating is not ideal for the harder stuff. It works, but it isn’t pretty.

Agreed.

I started doing some hard automations with templating and decided to switch to JS. As you say, complex automations with templates may be ugly and hard to maintain sometimes. Here is where being able to make automations in Python (or JS, for the present case) comes to be handy.

Apart, of course, from being able to interoperate with basically any existing library (Python or JS) in the world. Some automations are indeed programs, so better a programming language for those (my opinion, of course) :slight_smile:

If you happen to give it a try and run some JS scripting with this, and it works for you, please let me know :slight_smile:

2 Likes

Hi PuzzleStar,

Thank you so much for your work. It looks really good and I am eager to adopt it. Need to set up the right environment for it still. In the meantime, maybe my note here could help someone (like me before).

About a month back I started with HA and I was dismayed by the automation interfaces. I found the kettlepod thread early on (totally resonated), and of course your work. But I had barely started. I knew the HA concepts and had tinkered, but I wasn’t ready to go outside the box so soon.

It’s about a month later, and I have some pretty darn complicated automations going with almost pure JS code. I came up with mechanisms that work ok. I think this counts as a healthy stepping stone to adopting your work. For others new to the scene and want to just write JS … this is what I think would have helped me.

My setup:

  • HAOS install (anyone new definitely start with that).
  • Node-Red and the Node-Red companion

the rest is about how to use Node Red, such that you get close to an environment that FE developers would be ok with. Node Red has a learning curve but not that bad:

The key tips:

Emulate JS Modules (export and import):

  • use function nodes like JS modules. Define reusable code and use global.set() in place of export.
  • Likewise, use global.get() in place of import to pull code in.

So library code in a function node looks like this:

const { debug } = global.get('core');  // import a core routine

const lib = {
  add: (x, y) => {
    debug(`i'm adding ${x} and ${y}`);
    return x+y;
  },
  ...
};
global.set('lib', lib);  // export
return msg;    // you need this

Get those dependencies to run in the right order

You need to ensure dependencies run first. Don’t use ā€˜Setup’, ā€˜on Start’ or ā€˜On Stop’ in the function node. Have all code defined in the standard ā€˜On Message’ tab. You just set up an inject node to inject once after ā€œ0.1 secondsā€ (not 0). and tie your dependences together. If your dependency tree gets a little complicated like mine, use join nodes (set mode to manual, and ā€˜after a number of msg parts’ set to number of nodes it is listening to). See below for the picture.

Get console.log() working for you

Console.log is available. Debug nodes aren’t great. In HAOS, Node Red runs in a separate docker container but you can totally access the logs for it and tail in a terminal:

  • ā€œUse Advanced SSH & Web Terminalā€ Add On, with ā€˜Protection mode’ disabled
  • SSH to HA … and run ā€œdocker logs -f addon_a0d7b954_noderedā€ … it tails the console.log.

Like every better environment, wrap console.log so it spits out the date, the module the code is in etc.

Understand the limitations

You can do a lot with just JS code. You have access to the entire state of HA
(global.get(ā€˜homeassistant’)). But listening to state changes, and issuing commands/actions - best to do that with separate nodes. I mostly use just three:

  • inject node (interval)
  • events: state (listen to state change)
  • action node (to make changes)

For all the actions I form in JS code (just an object) and the action node I pass the object to and it sends it to HA. I don’t have to finangle with JSONata or anything like that. It’s a little annoying to have to figure it out these JSON structs, but chatgpt usually gets it right, and you can figure them out from the developer tools in HA. There’s a pattern to it and you build up your library for all the types of entities you need to control.

For more complicated things:

  • expose parameters in dashboards via HA helpers (input_number, input_boolean, schedule etc). Think of HA as your state and UI, and Node Red as automation.
  • store internal state in Node Red using global/flow/context. You can persist to disk (ā€˜file’ setting, ask chatgpt).
  • you can trigger and use all the HA things … AND … you have access to node red stuff too from the Node red community catalogue. I pulled in SunCalc JS code + doing math myself, but still installed ā€œthingzi-logic-timersā€ for sun timers cause it’s so convenient.

Some things I dislike which makes me want to switch away:

  • No typescript, no code completion/understanding of code outside the immediate function node. So it is easy to make typos and harder to maintain.
  • No version control.
  • The editing isn’t bad. I got tab size to 2 spaces (took some searching) and multi-line editing is there (VScode editor under the hood). But it’s limited.
  • If you open multiple browser windows so you can look at code in one function node while editing another, it can get hairy with those getting out of sync with each other. You have to be careful not to lose changes.

Hope this helps.

  • Glen
1 Like

Hi @thebayleyfam ,

Thanks for your comments. In my opinion, this is probably too much if you just want to integrate JS automations (just my opinion, of course).

I know indeed several people using Node-Red, and after speking to them, I decided I just wanted to keep away from setting up some components I do not really need just to get some JS integration, so that’s why I developed my ā€œpure-hass-javascript-engineā€ (this one :wink: ).

I do find much more simple something like (of course, this is a very dummy example…):

"use strict";

module.exports = {
        'entity-{switch.my_dummy_switch}-state-changed-to-{on}': function (id, state, old_state, entity, old_entity) {
                JSEngine.Entities['light.my_dummy_light'].turn_on();
        },
        'entity-{switch.my_dummy_switch}-state-changed-to-{off}': function (id, state, old_state, entity, old_entity) {
                JSEngine.Entities['light.my_dummy_light'].turn_off();
        },
}

This runs nearly out of the box with my JS engine (only need to set-up credentials to access HASS), which exposes all entity available actions as direct methods for every entity object.

Please let me know if you finally use it and get it working. It should be pretty musch simpler than using Node-Red just for running JS scripts.

1 Like

Happy to see something like this!

I will try it in the next days. I have no will of picking up again the YAML/Templating/Jinja/NodeRED/JsoNata extravaganza once more in my life :sweat:

I’d also be happy to support with development should you need a hand :smiling_face:

Thanks, Salvatore,

Please let me know if it works OK for you or whether there is something I should check / fix.

I had the same idea, and wanted a bit more:

  1. a typescript API, with every domain available with its actions (let’s say button.press())
  2. an easy way to setup and to edit my automations

That’s why I created an combo package/app (add-on) I just talked about on Javascript/Typescript Automations app (add-on)

This is really early alpha non-stable stage, and I would not recommend anyone to use it on production, but I hope it will evolve with feedbacks to a stable version…

Hi @lalex,

Things like light.turn_on() should work with the JSEngine pretty out of the box. It is not strong typed, I agree (so JS instead of TS), but it is exposing the HASS entities and their services as functions via JS object proxies.

By the way, i am currently working on creating and app, now that I need to move move to Container or HAOS as Core is not supported anymore.

I really like the proxy idea! :sunglasses:
Actually, I love JS proxies, and this is the perfect use case when you think about it: it allows not to have to implement every domain and to stay opened to newly created domain and/or custom ones…
I think I’m gonna take that path for my project…

But I must admit I’m not really comfortable with the whole experience of the way you structured the project: installation, and the way you have to write automations. My starting point is how I will use it and I know every dev has its own way…

My DX goal is the following, and that’s the way it (kinda :sweat_smile:) works currently:

  • having an IDE integrated to HA. Currently, each file save is reloading the script, but in a second step, I’d like to create a custom VSCode extensions to handle script start/stop/reload (a long way to go)

  • have my own DX: instead of having to export an object like { started: () => {...} } I prefer the event listener/emitter way connection.on('started', () => {}). For now, I’m focusing on entities…

  • it was important to me to have strong typing: it helps preventing typos (and do typos all the time! :sweat_smile:) and code-completion allows to write code faster

  • each file should be a separate process, allowing to reload juste automations on one file

I made a few POC on the proxy part which are promising, and your project is great inspiration, thanks a lot! :pray:

In case it is useful for you (or at least to look at as a starting point for script reload), you can have a look at the JSRundir npm module I use in my project: it reloads the scripts automatically when modified (custom made, anyway :wink: ).

Of course, if you only manage the scripts from the GUI, it is not necessary.

I understand separate processes would use a websocket API connection per process, which may not escalate well if you end up with many scripts. I would recommend to keep a single process while still reloading scripts one by one as needed (which by the way, is what JSEngine does).

As you said, there are nearly as many developer preferences as developers are :slight_smile: , all of them probably good choices.

For JSEngine I used the started: () => {...} style because it simplifies the definition of very targeted events like: entity-{light.living_room}-state-changed or entity-{lock.*}-state-changed-to-{unlocked}, while still keeping simple events like entity-state-changed

This way it ends up being very ā€œvisualā€ in the script file. :slight_smile:

On the installation point I totally agree that a Hass App would be useful, but as I am currently using Core, I had no no point in containerization or creating it till now.

As the Core installation method is now deprecated, I am likely to move to Container o even HAOS on my next upgrade: once I do, I will simplify the installation method by creating an App.

Hopefully as an App there must be a way to automate the creation of the credentials for the WebSocket API.

Other than that, installing the JSEngine is not any harder than just installing nodejs / npm, which I hope just any JS developer should be able to do. :slight_smile:

I simply use chokidar with TSX and nodejs fork : works quite well with minimal code… TSX allows you to run TS and/or JS a transparent way…

I came to the same conclusion but it was important to me because it can reduce the overhead by dispatching scripts between CPU cores: that’s why I only have one websocket instance in the main process (the one equivalent to your JSRundir) and every child process is handling it via IPC communication channel… for now, the main process dispatch the whole state to every child (and service call responses), but I plan to optimize the amount of data by sending only listened entities to each process
There’s a lot to be done if I want to be sure not to send useless data through the communication channels…

And there’s a lot to be done also to have as much time in se semi-prod environment like you did: your project must be rock-solid now, I envy you a bit! :sweat_smile: