Skip to content

Latest commit

 

History

History
225 lines (162 loc) · 7.57 KB

NODE-RED.md

File metadata and controls

225 lines (162 loc) · 7.57 KB

This documents a few strategies for including reusable code in Node-RED.

ES6 and Typescript Code Compatibility with Node-RED

The code in this repository is written in Typescript, tested and bundled with bun. This code will work in Node-RED, but not using the standard loading technique. Bun currently does not create modules that can be nested loaded, and so we do things a bit differently here.

~ NODE-RED will not import ES6 modules. Babel was used in this project to convert the ES6 modules to CommonJS that can be loaded using require. ~

~ Equally, you can use transpiled Typescript code, as I have shown when I add my general utilities package to Node-RED. ~

For classes it is perhaps easiest to add a constructor method outside of the scope of the class:

export function newHA(options) {
  return new HA(options);
}

export class HA {
  constructor(options) {
    this._ha = options.global.get('homeassistant');
  }

  get ha() {
    return this._ha;
  }
} 

Including published packages with Node-RED

The the most direct technique here is to add these packages to the function editor > Setup tab.

Moment

Now you can reference the code by the 'import as' value:

let m = moment(new Date()).format('YYYY-MM-DD')

You do not need to restart the Node-RED add-on to use this code. It will be imported when you deploy.

Including unpublished packages with Node-RED

You can use the previous technique for loading unpublished git packages. This refers to packages that are in public repositories, but that haven't been published to npm.

However the previous technique has limitations:

  • Lots of git URL cut and pasting
  • You can't use SSH URLs from within the Node-RED Home Assistant add-on
  • Version and tag specifiers don't seem to work with the add-on (I need to spend more time to confirm this to be the case)

The git URL cannot not use SSH, because othewise you will run into ssh authentication issues, if your instance of Node-RED is an add-on to Home Assistant.

Standalone Node-RED, not coupled with Home Assistant

You may find it easiest to manually install and update unpublished packages using npm, then reference these packages from the function editor > Setup tab. For example

npm install git@github.com:jpravetz/epdoc-util.git

This allows you to control updates, and to use git URLs as dependencies, with no tag or version specifier limitations.

This is only applicable when you have full access to the machine where you are running Node-RED, and you have all your usual tools installed on this machine. Node-RED is actually quite easy to install locally.

Home Assistant with the Node-RED add-on

In this situation Node-RED is (I believe) running in a container where you don't have access to the tools needed (git, yarn, ssh keys). The best way to add unpublished packages is to do so on the Home Assistant Settings > Addons > Node-RED > Configuration page.

npm_packages:
  - git+https://jpravetz@github.com/jpravetz/epdoc-node-red-utils

Limitation: No Updates!

This has a limitation in that you have no control over updates. You can't change an entry to add a version specifier, and packages don't get updated to the latest.

Both the Node-RED and Node-RED addon documentation are not transparent about what happens with updates. I need to do further investation by looking at the code.

Ugly workarounds are required.

What I did at one ugly point in time was to just copy the entire text contents of updated files from my dev editor, open the same file in Home Assistant (using the Sudio Code Server addon) and paste/overwrite the file contents of the old file.

Another solution is to scp your files across and hand edit the package.json and package-lock.json files to reflect the new commit values. I did this a couple of times with success.

Eventually I published the code to npm, referenced it from the package.json file, and restarted Node-RED within Home Assistant (this is done from the Settings > Add-ons > Node-RED page). For updates, I delete the appropriate subfolder underneath node_modules, and restart Node-RED.

Adding code directly to Node-RED

If you don't need to develop and test your code in a separate project, you can just include reusable code in Node-RED by adding it to a functions node, then adding that code to the global context.

This is really the easier solution, except it restricts you editing code within the Node-RED editor, you can't use Typescript, and this means you can run unit tests.

To do this you might wish to create a new flow tab, then add an inject and function node to the flow. The inject node is configured to inject once after 3 seconds, and connects to the function node. The function node contains code such as the following:

const gHA = global.get("homeassistant");
const ha = gHA.homeAssistant;

const global_functions = {

	googleDate: function (jsDate) {
		const d = new Date(jsDate);
		const tNull = new Date(Date.UTC(1899, 11, 30, 0, 0, 0, 0)); // the starting value for Google
		return ((d.getTime() - tNull.getTime()) / 60000 - d.getTimezoneOffset()) / 1440;
	}
}
global.set("global_functions", global_functions);

Then, to use this code in other function nodes:

const g = global.get("global_functions");
node.warn(g.googleDate(new Date()));

Using external code

This refers to the actual use of external modules from within your function nodes.

There are a few strategies for dealing with packages from external sources, but I found it easist to load my modules and attach them to the global context. Otherwise you are repeating setup to add your packages to every function.

For libraries that you can include using require, here are the required changes to settings.json:

module.export = {
  // other stuff, not shown here

  functionGlobalContext: {
    "epdoc-node-red-utils": require('epdoc-node-red-utils'),
    "epdoc-util": require('epdoc-util')
  }
};

For libraries that need to be imported, I use the following modified settings.js code:

let settings = {
  // other stuff, not shown here

  functionGlobalContext: {
    "moment": require('moment')   // moment can use require
  }
}

function loadModules() {
  settings.functionGlobalContext["epdoc-util"] = await import('epdoc-utils');
  settings.functionGlobalContext[]"epdoc-node-red-utils"] = await import('epdoc-node-red-utils');
}

loadModules();

module.exports = settings;

Then, to use this code in other function, it's again a matter of accessing the global context:

const g = global.get("epdoc-node-red-utils");
node.warn(g.googleDate(new Date()));

There is also the functionExternalModules that can make it easier to use external modules. I haven't experimented with this.

Unfortunately, when editing code within the Function Node, there is no editor code completion using any of the techniques described here for including external code.