Unable to use Led.Digits with Multple Boards
chrisl8 opened this issue · 4 comments
I am running Johnny-Five on a Raspberry Pi connected to two Arduino Mega 3's using the Firmata.
LEDs, Buttons, Potentiometers and switches all work fine following the instructions here:
https://johnny-five.io/examples/board-multi/
However, HT16K33 Led.Digits
do not. I can set up multiple boards, but only the first Led.Digits
instance that I add will work, the others remain blank/off.
I am following the instructions here:
https://johnny-five.io/examples/led-digits-clock-HT16K33/
and then adding the board
option, and I can use EITHER board, but if I try to add both, only the first one added works.
Here is the code I am using to test:
const { Boards, Led } = require("johnny-five");
const displays = [
{
id: "one",
port: "/dev/ttyACM3",
repl: false,
},
{
id: "two",
port: "/dev/ttyACM0",
repl: false,
},
];
const boards = new Boards(displays);
boards.on("ready", () => {
// This is only here to demonstrate that both boards work
const boardOneLED = new Led({ board: boards.byId("one"), pin: 13 });
boardOneLED.blink(500);
// This is only here to demonstrate that both boards work
const boardTwoLED = new Led({ board: boards.byId("two"), pin: 13 });
boardTwoLED.blink(500);
// Only the first one will work, the second one added will not.
// Reverse their order to demonstrate.
const boardOneDigits = new Led.Digits({
controller: "HT16K33",
board: boards.byId("one"),
});
const boardTwoDigits = new Led.Digits({
controller: "HT16K33",
board: boards.byId("two"),
});
boardOneDigits.on();
boardOneDigits.print("1111");
boardTwoDigits.on();
boardTwoDigits.print("2222");
process.on("SIGINT", () => {
console.log("Turning off displays.");
boardOneDigits.off();
boardTwoDigits.off();
});
});
This is interesting. Here's what I think is happening...
The HT16K33 is an addressable I2C device. Both of yours start at 0x70, which I believe is the default value. If no address or array of addresses passed, a built-in list of available addresses is used. That list is not built with consideration of multi-board environments, so 0x70 gets shifted off for the first one instantiated and J5 thinks the second one is at the next available address.
I think if you explicitly pass the address or addresses, it will not use the built-in list.
const boardOneDigits = new Led.Digits({
controller: "HT16K33",
board: boards.byId("one"),
address: 0x70
});
const boardTwoDigits = new Led.Digits({
controller: "HT16K33",
board: boards.byId("two"),
address: 0x70
});
Thanks, that makes sense, but when I try it I get this error:
Error: Invalid HT16K33 controller address: 112
It looks like the code has a list of valid addresses, and removes those addresses as they are used here:
https://github.com/rwaldron/johnny-five/blob/094bf6cceb8b9ec424306da8e98308ebd6fa2252/lib/led/ledcontrol.js#L461C37-L461C37
So trying to use the same address twice fails.
I tried passing in a new list of addresses with each call
const boardOneDigits = new five.Led.Digits({
controller: "HT16K33",
board: boards.byId("one"),
address: 0x70,
addresses: [0x70],
});
const boardTwoDigits = new five.Led.Digits({
controller: "HT16K33",
board: boards.byId("two"),
address: 0x70,
addresses: [0x70],
});
but the result is the same.
Oh wait, is this a bug or a feature?
Look at the code here:
https://github.com/rwaldron/johnny-five/blob/094bf6cceb8b9ec424306da8e98308ebd6fa2252/lib/led/ledcontrol.js#L429C1-L462C12
let addresses = new Set([0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77]);
const Controllers = {
HT16K33: {
OP: {
value: {
SHUTDOWN: 0x20,
BRIGHTNESS: 0xE0,
BLINK: 0x80
}
},
initialize: {
writable: true,
value(options) {
const state = priv.get(this);
const available = Array.from(addresses);
if (available.length === 0) {
throw new Error("There are no available HT16K33 controller addresses");
}
this.addresses = options.addresses || (options.address ? [options.address] : null);
// use default range of addresses if addresses aren't specified
if (this.addresses === null) {
this.addresses = available.slice(0, state.devices);
}
this.addresses.forEach(address => {
if (!addresses.has(address)) {
throw new Error(`Invalid HT16K33 controller address: ${address}`);
}
addresses.delete(address);
});
this.addresses
is set with options.addresses
so I can inject my own list of addresses, however:
It iterates over this.addresses
, which I can inject, but then it checks addresses
which is hard coded, and it deletes the entry from addresses
, so the injected options.addresses
seems useless.
Not sure if that is a bug or a feature, but it seems like I can never use 0x70 (112) twice in the code ever.
I'm not sure what the intended behavior is here. Should line 458 check this.addresses
?
Oh and now I see addresses
is a Set, so it really is intended as a hard coded set of addresses to always be referenced.
Do you see any other way round this without editing ledcontrol.js
?
I'm going to experiment with editing ledcontrol.js
to get around this. If I find a solution that works for me I can make a PR, but I realize my use case and my code might actually break things for others?
You could change the address on one of the HT16K33's to 0x71. I imagine it just takes a little bit of solder.
You could change the address on one of the HT16K33's to 0x71. I imagine it just takes a little bit of solder.
Haha, so I could, but for a number of reasons I need this to work without further modifying the hardware on the various installations.