Node-red-contrib-mcp23017chip

The Function node provides some convenience wrappers such as the global object. You can't just drop code from a Function node into a custom node's js file.

The docs on creating nodes explain how to access context: https://nodered.org/docs/creating-nodes/context

Thank you :smiley: !
You were right, the global. word needed to be deleted, since it's a 'config' part.
var i2c = require("i2c");

New error:
Error: Cannot find module 'i2c' (line:4)
at package.json file I've added:

  "dependencies": {
    "i2c-bus": "~5.2.1",
	"i2c"    : "~5.2.1"
  },

but did not help.

Did you npm install after adding that line to package.json?

No. @zenofmud told me it's not needed:

I'll try now.

  • Do I have to put it into a different directory first, or can it stay where it is?
    (Yes, I've red this, but it's not clear, what happens if it stays where it was before? Will it skip creating the symbolic link? Or link it back to intself recursively?)

OK, at least now I see what's the problem.
npm ERR! notarget No matching version found for i2c@~5.2.1.

That's what I was afraid of...
:~/.node-red$ npm install node_modules/node-red-contrib-mcp23017chip
DELETED the directory.

Good thing, I had a backup.


I had to re-copy the missing folder and restart the nodered.
Now I'm back to the beginning":
26 Dec 16:58:55 - [error] [mcp23017chip:89585dd9.33852] Error: Invalid I2C bus number 11

Where have you got the source of your new node? It should not be in the .node-red folder heirarchy. Put it somewhere else then run the npm install command from .node-red, referring to wherever you have the source. That installs it into node_modules from the source.

1 Like

Thanks, I've learned the lesson!

This is detailed, but perhaps not clearly enough in Creating Your First Node under https://nodered.org/docs/creating-nodes/

That was in response to you asking about applying a change to settings.js

Anyway, that whole i2c thing seems to be totally unstable.
The function node I've posted here stopped working too.
Even, if I try to scan Bus-1 (not 11).

< OFF >

Thanks, I've red it again during this afternoon. I've also refered to it 2 hours ago:

@zenofmud No, that was a response to @Steve-Mcl.
@Colin joined later.
But thanks for trying to help :slight_smile:

I don't understand what you mean by that.

OK, found the problem:
When I've edited the settings.js file to add Dark-Theme to NR, I took an original file, not the modified one, that includes this extra line:

    functionGlobalContext: {
		i2c:require('i2c-bus')

So now, at least the Function node with script inside works again, it can scan the i2c-11 bus. :slight_smile:

But the component still not:
[error] [mcp23017chip:89585dd9.33852] Error: Invalid I2C bus number 11

OK, address-scan function also works nicely from Function node:

27 Dec 07:52:10 - [info] Started modified nodes
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- 51 -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Flow:
kƩp

[{"id":"ddbdc4be.f2f5b8","type":"function","z":"34aad6f7.b85c6a","name":"I2C 1 addr scan","func":"'use strict';\n\n// When run, this program will output the same information as the\n// command 'i2cdetect -y -r 1'\nvar   fs = '-'; // = global.get('fs');\nconst i2c = global.get('i2c');\nconst i2c1 = i2c.openSync(11);\n\nconst EBUSY = 16; /* Device or resource busy */\n\nconst scan = (first, last) => {\n  fs = '     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f';\n\n  for (let addr = 0; addr <= 127; addr += 1) {\n    if (addr % 16 === 0) {\n      fs += '\\n' + (addr === 0 ? '0' : '');\n      fs += addr.toString(16) + ':';\n    }\n\n    if (addr < first || addr > last) {\n      fs += '   ';\n    } else {\n      try {\n        i2c1.receiveByteSync(addr);\n        fs += ' ' + addr.toString(16); // device found, print addr\n      } catch (e) {\n        if (e.errno === EBUSY) {\n          fs += ' UU';\n        } else {\n          fs += ' --';\n        }\n      }\n    }\n  }\n\n  fs += '\\n';\n};\n\nscan(0x3, 0x77);\n\nconsole.log(fs);\nmsg.payload = fs;\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":520,"wires":[["c4cdd96b.8ccbf"]]},{"id":"c4cdd96b.8ccbf","type":"debug","z":"34aad6f7.b85c6a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":550,"y":520,"wires":[]},{"id":"87286abf.36c54","type":"inject","z":"34aad6f7.b85c6a","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":180,"y":520,"wires":[["ddbdc4be.f2f5b8"]]}]

+1 step forward.

Is it possible to check functionGlobalContext ?

So if someone is modifiying the settings.js file, and deleting the i2c:require('i2c-bus') entry,
the node could report an error about it.
(Not just swallowing it silently while not doing anything.)


I was able to accidentally reproduce this error running it inside a Function node.
As it turned out: 11 was a string, not a number. :face_with_head_bandage:
I hope something like this will be the reason for the component not working too...
(But theoretically the Config tab is by default a number already. So I'm not sure yet.)

	<div class="form-row">
		<label for="node-config-input-busNum"><i class="icon-bookmark"></i> I2C Bus Number</label>
		<input type="number" id="node-config-input-busNum">
	</div>

If you write a custom node you dont need to require the modules you use in the settings.js file. In a custom node you would use the require directly in the nodes js and also add the module to the dependencies in the package json of that custom node.
The require in the settings.js is only necessary for using modules in function nodes.
Did you read the linked documentation above about building custom nodes?
It explains the whole business about how to retrieve configurations entered into the nodes client side ui from the server side js file.
I think you will have to wrap your head around the fact that custom node function differently to function nodes in many regards.

1 Like

Thank you very much! This is very useful info.
It should be copied to docs/creating-nodes/packaging too.


About Function, you are right, I've just found this at the official doc...
BUT,
it is not clearified:

Why does the name differ?

At the example there is 'os', but you have to address it as 'osModule'.
In my case the 'i2c-bus' module can be addressed with global.get('i2c');

This is totally confusing and not explained in the help!
Especially in this case, because the whole project is called 'i2c-bus' while a bus is just a sub-thing of it.

In the settings.js inside functionGlobalContext{...} some //comment lines would be nice too to clarify this more...

This is because when you load a module like the os module you assign it to a variable which of course has a name.
You than access the functionality of the module via the variable it was assigned to.
A function node in nodered canā€™t access installed modules directly via require as its kind of sandboxes.
So an extra step via the settings.js file is necessary.
Here you load the modules you want to use in function nodes into a context that is accessible by the function node. So you assign it to a key within that context in the settings.js which doesnā€™t have to have anything to do with the module name.
You than access the module in a function node via that key in global context with the global.get().
So in a function node you assign the module to a var via the global get.
In a custom node or node module you assign directly with require.
So its really up to you and what you add to the settings.js or in a custom node what name the var has you require the module with what that name you will access the functionality with in the code will be.

1 Like

Success :slight_smile:

The problem was the string / numeric conversion. I do not understand, why it became a sting...
This function solved it:
this.busNum = parseInt(n.busNum, 10);

new problem:

If the USB/I2C adapter gets unplugged, the whole NR restarts:

27 Dec 14:42:01 - [red] Uncaught Exception:
27 Dec 14:42:01 - Error: , Remote I/O error
    at Bus.readByteSync (/home/openhabian/.node-red/node_modules/i2c-bus/i2c-bus.js:289:16)
    at Timeout.myTimer [as _onTimeout] (/home/openhabian/.node-red/node_modules/node-red-contrib-mcp23017chip.atirt/mcp23017chip/mcp23017chip.js:57:27)
    at ontimeout (timers.js:438:13)
    at tryOnTimeout (timers.js:300:5)
    at listOnTimeout (timers.js:263:5)
    at Timer.processTimers (timers.js:223:10)
nodered.service: Main process exited, code=exited, status=1/FAILURE

Now I have to learn everything about error handling, because the prev. programmer forgot to wrote any try/catch into it.

Question:

Shouldn't be the I2C bus always Closed immediately after each read / write?
I'm thinking about speed. IMHO even with 10Kbaud the delay can be neglected compared to safety.

... Although if it would be possible to implement INTERRUPTS somehow,
that would minimise the CPU usage.
I'm not sure if 'i2c-bus' module can listen to any change without re-re-re-polling results within an interval. (Which can be currently set in the component.)

"the prev. programmer" is not a programmer, just someone who put some code together that has run all the heating and lighting in our house for many years on multiple devices. Node-Red is great at allowing those with limited coding ability to provide amazing functionality.
I made the code available to others and some people have found it helpful. Given you comments, I am not sure if amateur contributions are a good idea
As for Node-red-contrib-mcp23017chip, I would be happy to accept a pull request (I will need a little coaching on GitHub & NPM)

1 Like