I’m now developing some features and can’t do maneuvers. later I will be able to. as far as the expansion board is self-built and works well on rpi3.
I'm still puzzled, because that promise function does not get called unless clicking on the search icon for i2c bus when setting up a port expander chip. So I don't know why it is showing up when running node-red-start
.
Looking more closely, finding the i2c busses uses the spawnSync
module. The spawnSync module is called right at the beginning of the .js file with
const spawnSync = require('node:child_process').spawnSync;
Could this result in the error @pin32 is finding?
pin32@Node-Red:~ $ node-red-log
9 Jan 11:59:20 - [info] Loading palette nodes
9 Jan 11:59:23 - [red] Uncaught Exception:
9 Jan 11:59:23 - [error] TypeError: Cannot read properties of null (reading 'toString')
at /home/pin32/.node-red/node_modules/@joe-ab1do/mcp-pcf-aio/mcp-pcf-aio.js:957:40
at new Promise (<anonymous>)
at module.exports (/home/pin32/.node-red/node_modules/@joe-ab1do/mcp-pcf-aio/mcp-pcf-aio.js:952:24)
at loadNodeSet (/usr/lib/node_modules/node-red/node_modules/@node-red/registry/lib/loader.js:359:27)
at /usr/lib/node_modules/node-red/node_modules/@node-red/registry/lib/loader.js:453:31
at Array.forEach (<anonymous>)
at loadNodeSetList (/usr/lib/node_modules/node-red/node_modules/@node-red/registry/lib/loader.js:448:11)
at /usr/lib/node_modules/node-red/node_modules/@node-red/registry/lib/loader.js:145:16
nodered.service: Main process exited, code=exited, status=1/FAILURE
nodered.service: Failed with result 'exit-code'.
nodered.service: Consumed 8.366s CPU time.
nodered.service: Scheduled restart job, restart counter is at 2.
Stopped nodered.service - Node-RED graphical event wiring tool.
nodered.service: Consumed 8.366s CPU time.
Started nodered.service - Node-RED graphical event wiring tool.
9 Jan 11:59:46 - [info]
Welcome to Node-RED
===================
9 Jan 11:59:46 - [info] Node-RED version: v4.0.8
9 Jan 11:59:46 - [info] Node.js version: v20.18.1
9 Jan 11:59:46 - [info] Linux 6.6.51+rpt-rpi-v8 arm LE
9 Jan 11:59:47 - [info] Loading palette nodes
9 Jan 11:59:50 - [red] Uncaught Exception:
9 Jan 11:59:50 - [error] TypeError: Cannot read properties of null (reading 'toString')
at /home/pin32/.node-red/node_modules/@joe-ab1do/mcp-pcf-aio/mcp-pcf-aio.js:957:40
at new Promise (<anonymous>)
at module.exports (/home/pin32/.node-red/node_modules/@joe-ab1do/mcp-pcf-aio/mcp-pcf-aio.js:952:24)
at loadNodeSet (/usr/lib/node_modules/node-red/node_modules/@node-red/registry/lib/loader.js:359:27)
at /usr/lib/node_modules/node-red/node_modules/@node-red/registry/lib/loader.js:453:31
at Array.forEach (<anonymous>)
at loadNodeSetList (/usr/lib/node_modules/node-red/node_modules/@node-red/registry/lib/loader.js:448:11)
at /usr/lib/node_modules/node-red/node_modules/@node-red/registry/lib/loader.js:145:16
nodered.service: Main process exited, code=exited, status=1/FAILURE
nodered.service: Failed with result 'exit-code'.
nodered.service: Consumed 8.370s CPU time.
nodered.service: Scheduled restart job, restart counter is at 3.
Stopped nodered.service - Node-RED graphical event wiring tool.
nodered.service: Consumed 8.370s CPU time.
Started nodered.service - Node-RED graphical event wiring tool.
9 Jan 12:00:13 - [info]
this with clean installation and only node "@joe-ab1do/mcp-pcf-aio"
pin32@Node-Red:~ $ i2cdetect -l
-bash: i2cdetect: command not found
pin32@Node-Red:~ $ sudo apt-get install i2c-tools
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following additional packages will be installed:
libi2c0 read-edid
Suggested packages:
libi2c-dev python3-smbus
The following NEW packages will be installed:
i2c-tools libi2c0 read-edid
0 upgraded, 3 newly installed, 0 to remove and 17 not upgraded.
Need to get 102 kB of archives.
After this operation, 670 kB of additional disk space will be used.
Do you want to continue? [Y/n]
Get:1 http://raspbian.raspberrypi.com/raspbian bookworm/main armhf libi2c0 armhf 4.3-2+b2 [9,156 B]
Get:2 http://raspbian.raspberrypi.com/raspbian bookworm/main armhf i2c-tools armhf 4.3-2+b2 [77.6 kB]
Get:3 http://ftp.arnes.si/mirrors/raspbian/raspbian bookworm/main armhf read-edid armhf 3.0.2-1.1 [14.9 kB]
Fetched 102 kB in 1s (131 kB/s)
Selecting previously unselected package libi2c0:armhf.
(Reading database ... 70373 files and directories currently installed.)
Preparing to unpack .../libi2c0_4.3-2+b2_armhf.deb ...
Unpacking libi2c0:armhf (4.3-2+b2) ...
Selecting previously unselected package i2c-tools.
Preparing to unpack .../i2c-tools_4.3-2+b2_armhf.deb ...
Unpacking i2c-tools (4.3-2+b2) ...
Selecting previously unselected package read-edid.
Preparing to unpack .../read-edid_3.0.2-1.1_armhf.deb ...
Unpacking read-edid (3.0.2-1.1) ...
Setting up libi2c0:armhf (4.3-2+b2) ...
Setting up read-edid (3.0.2-1.1) ...
Setting up i2c-tools (4.3-2+b2) ...
Processing triggers for man-db (2.11.2-2) ...
Processing triggers for libc-bin (2.36-9+rpt2+deb12u9) ...
pin32@Node-Red:~ $ i2cdetect -l
i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter
i2c-20 i2c fef04500.i2c I2C adapter
i2c-21 i2c fef09500.i2c I2C adapter
pin32@Node-Red:~ $
found the problem...
Regardless of why this is happening, a node should be written so that async errors are handled and should NOT crash Node-RED.
This line points to the code:
And that function is called WITHOUT a catch on the promise as seen here:
You can raise an issue to inform the author here: Sign in to GitHub · GitHub
One thing is sure, the response from i2cdetect
in i2cbuses.stdout
on line 957
is null
(hence the error TypeError: Cannot read properties of null (reading 'toString')
). May be a clue?
this is my first use of node-red, I’m not even a programmer... but just a hobbyist sistemista... I don’t understand what you posted.
I think the solution to the problem that I found is an update to the installation package including the missing dependency "i2c-tools". or clearly indicate this on the help page as a requirement.
if I can provide more info I’m happy to cooperate but you must use requests type step by step because I am not expert, consider that I do not speak English and use a translator that complicates the understanding of the requests.
Anyway thanks for the help, I remain available
@pin32 I'm glad you found the problem: you were missing i2cdetect
which resulted in the null error. My guess is that when you installed ncd-red-mcp23017 that installation installed i2cdetect
so the null error no longer occurred. When I created this node, it didn't even occur to me that i2cdetect
would not be included as part of the linux distribution. In fact you are the first user I know of who has run into this problem.
@Steve-Mcl You are indeed correct and there should be a catch. But then I am not a programmer/software engineer and as I stated know enough to be dangerous . I will look into adding a catch on the promise. If you can point me in the right direction, I will take it from there. Thanks!
Your promise should have a catch handler that then returns an appropriate status code (probably a 500) & if you know why an exception occurred, include some additional info in the response body.
I haven't looked but I assume you have a button or something that calls the endpoint? If so, then in the front end handler, you should check the status code and perhaps alert the user using the notify function?
I'm not at a computer to write code for you but hopefully the above points you in the right direction? (Let me know if you need more direction)
This is what I have changed it to:
const i2cBusList = new Promise((resolve,reject) => {
var i2cbuses = spawnSync('i2cdetect', ['-l'], { encoding: 'utf-8' });
if ((i2cbuses.stderr && i2cbuses.stderr !=="")||(i2cbuses.stdout == null)) {
reject("An error occurred while searching for i2c-busses. Make sure 'i2cdetect' command is available");
} else {
i2cbuses = i2cbuses.stdout.toString().trim().match(/i2c-\d*/g); //<only get i2c bus name
var labels = i2cbuses.filter(x => !(i2cbuses.filter(ele => ele.match(/2\d/g))).includes(x)).sort(); //<remove i2c-20 & i2c-21
//change labels from i2c-n to /dev/i2c-n
for (let i=0; i < labels.length; i++) {
labels[i] = '/dev/'+labels[i];
}
var values = labels.map(x => x.match(/\d+$/)[0]);
var buses = [];
for (let i = 0; i < values.length; i++) {
var myObject={};
myObject["label"] = labels[i];
myObject["value"] = values[i];
buses[i]=myObject;
}
resolve(buses);
}
});
RED.httpAdmin.get("/mcp-pcf-aio", function(req,res) {
i2cBusList.then(buses => {
res.json(buses);
});
i2cBusList.catch((e) => {console.log(e)});
});
In essence two things changed: The if-statement with the reject also checks for i2cbuses.stdout == null. A catch has been added that console-logs the error. Not quite sure where to put the "appropriate status code".
You are correct: This function only gets called when clicking on a search button to search for available i2c-busses, which is why it surprised me that the node could not be installed. The function only gets called when the used clicks on the search button.
In this specific case installation of the node broke because @pin32 did not have the i2cdetect command available in his linux-repo and had to install the i2c-tools package first, which solved the issue. This is the first time I have come across this: it was my (apparently incorrect) assumption that i2cdetect
was included by default.
Aside from the changes to the code above, I have also added language to the README.md file that states that this node uses i2cdetect
and that the user should first run i2cdetect -l
to see if it works and what to do if a command not found error occurs.
That ^ is not right
It should be something like...
i2cBusList.then(buses => {
res.json(buses);
}).catch((e) => {
console.log(e)
res.status(500).send(e.message);
});
And your client side code should check the status code (200 ok, otherwise not ok) and let the user know there was a problem.
you could also check for the existence of the i2cdetect executable when you load the node using something like nodes process.execSync function against "which i2cdetect" or some such and raise an error in the log at load time and set a flag that you can test before you even attempt the query.
Got it, thanks!
I'm still not sure why the node installation breaks because i2cdetect command is unknown. At the point of installation, the user has not clicked the search button yet, so the command has not yet been invoked. Unless there is some kind of test running in the background on installation...?
If I understand you correctly, that is what I would ideally like to do: test if i2cdetect command is known, if so proceed, if not install i2c-tools package. I think I can catch the error (if i2cdetect is unknown, then the result of spawnSync (which is used here) is null. That null can then be caught and ideally i2c-tools installed. But I haven't found a way to install the i2c-tools package automatically, like e.g. i2c-bus, which is a module and not a package (hope this makes sense). Or just install an i2c-tools module, like I do with i2c-bus, just to make sure.
You can't really force an install as at run time Node-RED won't (or shouldn't) have root access to run the install... (or indeed cant install on a different OS) - best I think you can do is a) note it as a pre-req in your README. b) do the detection at load time (as per above) and raise an error in the log, and set a flag in your code. c) when something then triggers the node - rather than try the command at all - just raise a node.error with appropriate message saying i2cdetect executable not found.
EG see the pi-gpio node around these lines - node-red-nodes/hardware/PiGpio/36-rpi-gpio.js at 442576e838a267cde7ee3dd2fad634ea39eb5b0d · node-red/node-red-nodes · GitHub
(though in this case we just warn rather than error) - and then we use the AllOK flag eg line 174, and set the status to grey (unavailable) on line 222
Yeah, that's what I thought.
a) Done (just not yet uploaded to GitHub). b) working on it. This should enable installation of the node to proceed. c) interesting, will think about it.
Thanks for the example, will take a look at it. I know I always learn a lot from examples like this (and can be even more dangerous ).
What I still do not understand is why installation of the node fails if i2cdetect is not available. At that point, the user has not had a chance to click on the button that invokes the function that executes (spawns) i2cdetect...
I implemented your suggestion
RED.httpAdmin.get("/mcp-pcf-aio", function(req,res) {
i2cBusList.then(buses => {
res.json(buses);
}).catch((e) => {
console.log(e)
res.status(500).send(e.message)});
});
One question I have is if e.message is equal the reject("message") in the Promise function. If not, do I need to set e.message somewhere?
Client side this Promise function is called by the jQuery method $.getJSON:
$.getJSON('mcp-pcf-aio',function(data) {//< get list of i2cBuses
$("#node-config-lookup-i2cbus").removeClass('disabled');
var buses = data || [];
var e = $.Event('keyup');//define a keyup event
$("#node-config-input-busNum").autocomplete({
source:buses,
minLength:0,
close: function(event, ui) {
$("#node-config-input-busNum").autocomplete("destroy").focus().trigger(e);
}
}).autocomplete("search","");//< create dropdown list of i2c buses
}).fail(function(jqXHR) {
if (jqXHR.status == 500) {
alert("i2cdetect command unknown. Please install i2c-tools package to proceed.");
}
});
I added the .fail()
option to this method to capture if something went wrong. As you can see I am capturing the status and if it equals 500 showing an alert. While I am pretty confident (famous last words!) this will work, I was wondering if instead I could show something like
alert(e.message)
given that e.message is being sent by the server? Or perhaps I could even not send (e.message) server side and reserve status 500 for unknown i2cdetect?
Message is set in the Error constructor when it is created (by whatever throws the error)
I suggest you do not use alert but instead your the built in notify API as I mentioned before
Understood, and I intend to use the notify function. My question was more related to the e.message part of the alert() function. As I understand it now, the Error constructor in the Promise function is
reject("i2cdetect command unavailable. To make it available, in a terminal run sudo apt install i2c-tools");
and e.message refers to the text within reject(). This text is sent in the catch of the promise and sent to the client, where Red.notify displays the message as shown below:
}).fail(function(jqXHR) {
if (jqXHR.status == 500) {
let myNotification = RED.notify(e.message, {
modal: true,
fixed: true,
type: 'error',
buttons: [
{
text: "cancel",
click: function(e) {
myNotification.close();
}
},
{
text: "okay",
class:"primary",
click: function(e) {
myNotification.close();
}
}
]
});
}
});
As you can see, rather than typing in a whole new message, the RED.notify() function refers to e.message, which was sent by the server.
Is this how it works?