404background / node-red-contrib-mcu-serial

Node to use serial communication with Node-RED MCU
MIT License
0 stars 0 forks source link

Serial instance management #1

Closed phoddie closed 5 months ago

phoddie commented 5 months ago

There appear to be a couple of problems with the implementation:

I think both of these can be solved by reorganizing the code to consolidate the creation of the serial instance. I did a quick revision to show how that might work. The code is below. But... I have't run this, so it might require a little debugging.

import {Node} from "nodered"

class Serial {
    static #cache = new Map;

    static add(config, reader) {
        const cachePort = 'mcu_serial' + Number(config.port);
        let serial = this.#cache.get(cachePort);
        if (!serial) {
            serial = new device.io.Serial({
                baud: Number(config.baud),
                port: Number(config.port),
                receive: Number(config.rx),
                transmit: Number(config.tx),
                onReadable() {
                    let msg = String.fromArrayBuffer(this.read());
                    msg = msg.trimEnd()
                    this.readers.forEach(reader => {
                        reader.send({payload: msg})
                    });
                }
            })
            cache.set(cachePort, serial)
            serial.readers = [];
        }
        if (reader)
            serial.readers.push(reader);
        return serial;
    }
}

class Serial_in extends Node {
    onStart(config) {
        super.onStart(config)

        try {
            Serial.add(config, this);
        }
        catch {
            this.status({fill: "red", shape: "dot", text: "node-red:common.status.error"})
        }
    }

    static type = "mcu_serial_in"
    static {
        RED.nodes.registerType(this.type, this)
    }
}

class Serial_out extends Node {
    #serial

    onStart(config) {
        super.onStart(config)

        try {
            this.#serial = Serial.add(config);
        }
        catch {
            this.status({fill: "red", shape: "dot", text: "node-red:common.status.error"})
        }
    }

    onMessage(msg, done) {
        if (msg.payload != null){
            this.#serial.write(ArrayBuffer.fromString(msg.payload + "\n"))
        }
        this.send(msg)
        done()
    }

    static type = "mcu_serial_out"
    static {
        RED.nodes.registerType(this.type, this)
    }
}
404background commented 5 months ago

Thank you @phoddie !

I fixed the program based on the code you suggested. I made changes to the issue#1 branch at the following URL: https://github.com/404background/node-red-contrib-mcu-serial/tree/issue%231

I changed the declaration of the cache variable. Here is my code:

import {Node} from "nodered"
let cache

class Serial {
    static add(config, reader) {
        const cachePort = 'mcu_serial' + Number(config.port);
        cache ??= new Map;
        let serial = cache.get(cachePort);
        if (!serial) {
            serial = new device.io.Serial({
                baud: Number(config.baud),
                port: Number(config.port),
                receive: Number(config.rx),
                transmit: Number(config.tx),
                onReadable() {
                    let msg = String.fromArrayBuffer(this.read());
                    msg = msg.trimEnd()
                    this.readers.forEach(reader => {
                        reader.send({payload: msg})
                    });
                }
            })
            cache.set(cachePort, serial)
            serial.readers = [];
        }
        if (reader)
            serial.readers.push(reader);
        return serial;
    }
}

class Serial_in extends Node {
    onStart(config) {
        super.onStart(config)

        try {
            Serial.add(config, this);
        }
        catch {
            this.status({fill: "red", shape: "dot", text: "node-red:common.status.error"})
        }
    }

    static type = "mcu_serial_in"
    static {
        RED.nodes.registerType(this.type, this)
    }
}

class Serial_out extends Node {
    #serial

    onStart(config) {
        super.onStart(config)

        try {
            this.#serial = Serial.add(config);
        }
        catch {
            this.status({fill: "red", shape: "dot", text: "node-red:common.status.error"})
        }
    }

    onMessage(msg, done) {
        if (msg.payload != null){
            this.#serial.write(ArrayBuffer.fromString(msg.payload + "\n"))
        }
        this.send(msg)
        done()
    }

    static type = "mcu_serial_out"
    static {
        RED.nodes.registerType(this.type, this)
    }
}
404background commented 5 months ago

I compared the behavior of the main branch and the issue#1 branch. The serial out node worked the same in both cases.

On the other hand, the serial in node received only one node in the main branch. In the issue#1 branch, four nodes received properly.

There is a video of each branch. main branch: main

issue#1 branch: issue#1

phoddie commented 5 months ago

Great test! Cool to see the multiple Serial In nodes working correctly like that.

Do you plan to merge the branch?

404background commented 5 months ago

@phoddie Yes! If there is no problem, I will merge them.

I have one question. When I tried to use Port 0 and Port 2 at the same time in the ESP32 serial communication, I got an error.

Here is my flow:

[{"id":"8b1fa7b8a67af26b","type":"debug","z":"2eb8ae4cb799d590","name":"Port2 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":280,"wires":[]},{"id":"3d6157a653b67692","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"one","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":320,"wires":[["339b61347fce7654"]]},{"id":"c89a7915711bfdeb","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":290,"y":280,"wires":[["8b1fa7b8a67af26b"]]},{"id":"339b61347fce7654","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":560,"y":320,"wires":[]},{"id":"c2ecbb9ad97eb951","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":290,"y":240,"wires":[["3e015ac89dd2d7c7"]]},{"id":"3e015ac89dd2d7c7","type":"debug","z":"2eb8ae4cb799d590","name":"Port2 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":240,"wires":[]},{"id":"177c2c3449b7cf91","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"two","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":360,"wires":[["2a8a61ef546a55a5","339b61347fce7654"]]},{"id":"2a8a61ef546a55a5","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":560,"y":360,"wires":[]},{"id":"29b69e6ea1d47b25","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":560,"y":440,"wires":[]},{"id":"65ae2dc4ee97c76a","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"four","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":480,"wires":[["ac9bc3d9e001354c","29b69e6ea1d47b25","2a8a61ef546a55a5","339b61347fce7654"]]},{"id":"ac9bc3d9e001354c","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":560,"y":480,"wires":[]},{"id":"b9bd6be923e97f86","type":"debug","z":"2eb8ae4cb799d590","name":"Port2 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":200,"wires":[]},{"id":"876dad9e9d96429e","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":290,"y":200,"wires":[["b9bd6be923e97f86"]]},{"id":"acea8e5c05d25374","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":290,"y":160,"wires":[["9277468d9337d91c"]]},{"id":"9277468d9337d91c","type":"debug","z":"2eb8ae4cb799d590","name":"Port2 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":160,"wires":[]},{"id":"c5f1494b658b3189","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"two","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":780,"wires":[["d2c338a2a7c651a7","e21beb58cbcd545f"]]},{"id":"3c1959db7312756a","type":"debug","z":"2eb8ae4cb799d590","name":"Port0 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":700,"wires":[]},{"id":"d9f4cd3b754be5c0","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"one","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":740,"wires":[["d2c338a2a7c651a7"]]},{"id":"d2c338a2a7c651a7","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"1","rx":"3","baud":"115200","port":"0","_mcu":{"mcu":true},"x":560,"y":740,"wires":[]},{"id":"b417cd6de3aa689b","type":"debug","z":"2eb8ae4cb799d590","name":"Port0 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":660,"wires":[]},{"id":"ea5592c8964b4472","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"four","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":900,"wires":[["d2c338a2a7c651a7","e21beb58cbcd545f","371bcdb0d5ebb8f0","e4eb27aab5762f9f"]]},{"id":"4fa1b84c726a7d14","type":"debug","z":"2eb8ae4cb799d590","name":"Port0 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":620,"wires":[]},{"id":"4ac79b2549d24954","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"1","rx":"3","baud":"115200","port":"0","_mcu":{"mcu":true},"x":290,"y":580,"wires":[["d29834a11cc8e2ad"]]},{"id":"d29834a11cc8e2ad","type":"debug","z":"2eb8ae4cb799d590","name":"Port0 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":580,"wires":[]},{"id":"4432dea87ac606f9","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"1","rx":"3","baud":"115200","port":"0","_mcu":{"mcu":true},"x":290,"y":620,"wires":[["4fa1b84c726a7d14"]]},{"id":"f2ba96fb4aeaa845","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"1","rx":"3","baud":"115200","port":"0","_mcu":{"mcu":true},"x":290,"y":660,"wires":[["b417cd6de3aa689b"]]},{"id":"fa8a6190f990b627","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"1","rx":"3","baud":"115200","port":"0","_mcu":{"mcu":true},"x":290,"y":700,"wires":[["3c1959db7312756a"]]},{"id":"e21beb58cbcd545f","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"1","rx":"3","baud":"115200","port":"0","_mcu":{"mcu":true},"x":560,"y":780,"wires":[]},{"id":"371bcdb0d5ebb8f0","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"1","rx":"3","baud":"115200","port":"0","_mcu":{"mcu":true},"x":560,
"y":860,"wires":[]},{"id":"e4eb27aab5762f9f","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"1","rx":"3","baud":"115200","port":"0","_mcu":{"mcu":true},"x":560,"y":900,"wires":[]}]

multiple-port

And I was getting this error: "Serial: in use!" serial-in-use

Is it possible to handle multiple ports in serial class of Moddable SDK?

phoddie commented 5 months ago

Is it possible to handle multiple ports in serial class of Moddable SDK?

Of course.

But, you have to remember that one serial port is used to for debugger communication between the ESP32 and computer. This is how you are able to see the error "Serial in use!" in xsbug, for example.

The pins for the debugger communication are reserved at start-up in debug builds.

Your test flow seem to be using pins 1 & 3 for serial, which are the debugging pins. Please try to use others pins, pins that are not already in use.

404background commented 5 months ago

Thank you! I see that it is used for debugging. I will try it with other pins.

404background commented 5 months ago

@phoddie I confirmed that Port 1 and Port 2 are working at the same time on ESP32! Added a sample flow and an image to the issue#1 branch. https://github.com/404background/node-red-contrib-mcu-serial/commit/da89df848d17c7ce8e07f7f01f1b3991588c5dbc

Here is my flow:

[{"id":"c89a7915711bfdeb","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":290,"y":280,"wires":[["8b1fa7b8a67af26b"]]},{"id":"339b61347fce7654","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":560,"y":320,"wires":[]},{"id":"c2ecbb9ad97eb951","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":290,"y":240,"wires":[["3e015ac89dd2d7c7"]]},{"id":"2a8a61ef546a55a5","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":560,"y":360,"wires":[]},{"id":"29b69e6ea1d47b25","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":560,"y":440,"wires":[]},{"id":"ac9bc3d9e001354c","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":560,"y":480,"wires":[]},{"id":"876dad9e9d96429e","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":290,"y":200,"wires":[["b9bd6be923e97f86"]]},{"id":"acea8e5c05d25374","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"17","rx":"16","baud":"115200","port":"2","_mcu":{"mcu":true},"x":290,"y":160,"wires":[["9277468d9337d91c"]]},{"id":"d2c338a2a7c651a7","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"32","rx":"33","baud":"115200","port":"1","_mcu":{"mcu":true},"x":560,"y":740,"wires":[]},{"id":"4ac79b2549d24954","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"32","rx":"33","baud":"115200","port":"1","_mcu":{"mcu":true},"x":290,"y":580,"wires":[["d29834a11cc8e2ad"]]},{"id":"e21beb58cbcd545f","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"32","rx":"33","baud":"115200","port":"1","_mcu":{"mcu":true},"x":560,"y":780,"wires":[]},{"id":"371bcdb0d5ebb8f0","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"32","rx":"33","baud":"115200","port":"1","_mcu":{"mcu":true},"x":560,"y":860,"wires":[]},{"id":"e4eb27aab5762f9f","type":"mcu_serial_out","z":"2eb8ae4cb799d590","name":"","tx":"32","rx":"33","baud":"115200","port":"1","_mcu":{"mcu":true},"x":560,"y":900,"wires":[]},{"id":"305d1470cfcd1b67","type":"comment","z":"2eb8ae4cb799d590","name":"Port 1","info":"","_mcu":{"mcu":true},"x":150,"y":580,"wires":[]},{"id":"6f42bcd6ce0555eb","type":"comment","z":"2eb8ae4cb799d590","name":"Port 2","info":"","_mcu":{"mcu":true},"x":150,"y":160,"wires":[]},{"id":"3d6157a653b67692","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"one","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":320,"wires":[["339b61347fce7654"]]},{"id":"177c2c3449b7cf91","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"two","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":360,"wires":[["2a8a61ef546a55a5","339b61347fce7654"]]},{"id":"65ae2dc4ee97c76a","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"four","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":480,"wires":[["ac9bc3d9e001354c","29b69e6ea1d47b25","2a8a61ef546a55a5","339b61347fce7654"]]},{"id":"c5f1494b658b3189","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"two","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":780,"wires":[["d2c338a2a7c651a7","e21beb58cbcd545f"]]},{"id":"d9f4cd3b754be5c0","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"one","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":740,"wires":[["d2c338a2a7c651a7"]]},{"id":"ea5592c8964b4472","type":"inject","z":"2eb8ae4cb799d590","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"four","payloadType":"str","_mcu":{"mcu":true},"x":290,"y":900,"wires":[["d2c338a2a7c651a7","e21beb58cbcd545f","371bcdb0d5ebb8f0","e4eb27aab5762f9f"]]},{"id":"8b1fa7b8a67af26b","type":"debug","z":"2eb8ae4cb799d590","name":"Port2 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":280,"wires":[]},{"id":"3e015ac89dd2d7c7","type":"debug","z":"2eb8ae4cb799d590","name":"Port2 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":240,"wires":[]},{"id":"b9bd6be923e97f86","type":"debug","z":"2eb8ae4cb799d590","name":"Port2 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":200,"wires":[]},{"id":"9277468d9337d91c","type":"debug","z":"2eb8ae4cb799d590","name":"Port2 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":160,"wires":[]},{"id":"3c1959db7312756a","type":"debug","z":"2eb8ae4cb799d590","name":"Port0 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":700,"wires":[]},{"id":"b417cd6de3aa689b","type":"debug","z":"2eb8ae4cb799d590","name":"Port0 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":660,"wires":[]},{"id":"4fa1b84c726a7d14","type":"debug","z":"2eb8ae4cb799d590","name":"Port0 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":620,"wires":[]},{"id":"d29834a11cc8e2ad","type":"debug","z":"2eb8ae4cb799d590","name":"Port0 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","_mcu":{"mcu":true},"x":440,"y":580,"wires":[]},{"id":"d6bd987f16963f4e","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"32","rx":"33","baud":"115200","port":"1","_mcu":{"mcu":true},"x":290,"y":620,"wires":[["4fa1b84c726a7d14"]]},{"id":"5e44451b2d96f012","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"32","rx":"33","baud":"115200","port":"1","_mcu":{"mcu":true},"x":290,"y":660,"wires":[["b417cd6de3aa689b"]]},{"id":"1041e276a55d5f3d","type":"mcu_serial_in","z":"2eb8ae4cb799d590","name":"","tx":"32","rx":"33","baud":"115200","port":"1","_mcu":{"mcu":true},"x":290,"y":700,"wires":[["3c1959db7312756a"]]}]

Here is a video: serial_multiple

If there is no issue with the program, I will merge it!

phoddie commented 5 months ago

Looks great. Nice work.