Labs | Two Way Serial Communication

Labs | Two Way Serial Communication

Reading the Accelerometer Data from Nano IOT

Instead of using an accelerometer sensor separately I'm going to utilise the in-built version

For that I have to add #include <Arduino_LSM6DS3.h>

Link to p5 sketch.

Code Listings

Arduino

The code listings make sense on their own. I've mostly utilised existing code for the sketch based on the labs and the accelerometer code from the reference listed below.

let serialHelper;
let inData;

function setup() {
    createCanvas(200, 200, WEBGL);

    // Create a helper for managing p5 Serial Processing
    serialHelper = new SerialHelper({
        autoSelectPortName: true,
        // onData: _.throttle(d => onData(d), 10, {
        //     leading: false,
        //     trailing: false,
        // // }),
        onData: onData,
        pollFn: (serial) => {
            serial.write(byte('\n'))
        },
        pollInterval: 1,
    });

}

function draw() {
    orbitControl();
    
    background(240);
    fill('black');

    if (inData !== undefined) {
        let [ax, ay, az, gx, gy, gz, btn] = inData.split(",").map(i =>  Number(i))

        if (btn == 1) {
            background('black');
        }
        rotateX(-0.3);
        rotateY(-2);

        push();
        fill('white');
        rotateX(ay);
        // rotateY(az);
        rotateZ(ax);
        box(50, 50, 50)
        pop();
        
    }
}


function onData(data) {
    if (data !== undefined) {
        inData = data;
    }
}

p5.js

For sketch.js I have been working on a way to simplify the framework around reading and writing the data from p5 in a consistent way since I don't want to set it up from scratch.

Challenge

One problem Im running into that I can't quite solve yet is correctly debouncing the onData trigger.

If I send 38 characters, for example, then the p5.serialmotor.js code triggers the onData function 38 times, even if I end up making a single call to readLine() on it.

I have tried to th

class SerialHelper {
    constructor(opts = {}) {
        this.opts = opts;

        // Requires portName
        // - Otherwise don't create the object
        if (!opts.portName) {
            console.log("INFO: No portname provided.")

            // List serial ports & return
            // - Autoselects if that configuration is opted for
            const serial = new p5.SerialPort();
            serial.on('list', this.onList.bind(this));
            serial.list();
            return
        }

        // Run as usual
        // ------------

        // Baud Rate
        const portName = opts.portName
        const baudRate = opts.baudRate || 9600;

        // Create the serial helper object
        this.serial = new p5.SerialPort();

        // Attach functions for
        this.serial.on('connected', this.onConnected.bind(this));
        this.serial.on('open', this.onOpen.bind(this));
        this.serial.on('data', _.throttle(this.onData.bind(this), 6, {
            leading: true,
            trailing: false
        })),
        this.serial.on('error', this.onError.bind(this));
        this.serial.on('close', this.onClose.bind(this));

        // Open the port
        this.serial.open(portName, {
            baudRate: baudRate
        });

        // Request data every 300 miliseconds
        setInterval(() => {
            this.serial.write("\n");
        }, 1000 / 12);
    }

    onList(portList) {
        // Display the list of ports
        portList.forEach((port, i) => {
            console.log(`${i} | ${port}`);
        })

        // Select default portname (using magic)
        const opts = this.opts;
        if (opts.autoSelectPortName) {
            this.handleAutomaticPortSelection(portList);
        } else {
            console.log("ERR: Please specify a portname from the list below.");
            return; // Break - Since we cannot determine portname
        }

        return portList;
    }

    handleAutomaticPortSelection(portList) {
        const opts = this.opts;
        const validPortNames = portList.filter(i => i.indexOf('tty.usbmodem') >= 0);
        console.log(`INFO: Selecting portname automatically from ${validPortNames}`)
        if (validPortNames.length > 0) {
            opts.portName = validPortNames[0];
            this.serial = new SerialHelper(opts);
            console.log(`INFO: Selected portname ${opts.portName}`)
        } else {
            console.log("ERR: Autoselect Failed. Please specify a portname from the list below.");
            return; // Break - Since we cannot determine portname
        }
    }

    onConnected() {
        if (isValidFunction(this.opts, 'onConnected')) {
            this.opts.onConnected();
            this.serial.clear();
        } else {
            console.log('INFO: onConnected not defined | Running default')
            console.log('INFO: The serial port opened.')
        }
    }

    onOpen() {
        if (isValidFunction(this.opts, 'onOpen')) {
            this.opts.onOpen();
            this.serial.clear();
        } else {
            console.log('INFO: onOpen not defined | Running default')
            console.log('INFO: Connected to server. Clearing serial buffer...');
            this.serial.clear();
        }
    }

    onData() {
        if (isValidFunction(this.opts, 'onData')) {
            let inData = this.serial.readLine();
            this.opts.onData(inData);
        } else {
            console.log('INFO: onData not defined | Running default')
        }
    }

    onError() {
        if (isValidFunction(this.opts, 'onError')) {2
            this.opts.onError();
        } else {
            console.log('INFO: onError not defined | Running default')
        }
    }

    onClose() {
        if (isValidFunction(this.opts, 'onClose')) {
            this.opts.onClose();
        } else {
            console.log('INFO: onClose not defined | Running default')
        }
    }
}

function isValidFunction(obj, fnName) {
    return obj[fnName] !== undefined && obj[fnName] instanceof Function
}

SerialHelper Class

It's a little unnecessarily abstracted and there aren't proper mechanisms to hook into the various functions yet.

One thing this code does is automatically pulls up the USB port without having to manually specify it.

Additionally, there's some debouncing and general code here that should be ready for reuse across additional projects.

Challenge

Currently only a specific style of two way communication is supported out of the box and I would want there to be more ways to (reading bytes, etc etc) handle this incoming data.

class SerialHelper {
    constructor(opts = {}) {
        this.opts = opts;

        // Requires portName
        // - Otherwise don't create the object
        if (!opts.portName) {
            console.log("INFO: No portname provided.")

            // List serial ports & return
            // - Autoselects if that configuration is opted for
            const serial = new p5.SerialPort();
            serial.on('list', this.onList.bind(this));
            serial.list();
            return
        }

        // Run as usual
        // ------------

        // Baud Rate
        const portName = opts.portName
        const baudRate = opts.baudRate || 9600;

        // Create the serial helper object
        this.serial = new p5.SerialPort();

        // Attach functions for
        this.serial.on('connected', this.onConnected.bind(this));
        this.serial.on('open', this.onOpen.bind(this));
        this.serial.on('data', _.throttle(this.onData.bind(this), 6, {
            leading: true,
            trailing: false
        })),
        this.serial.on('error', this.onError.bind(this));
        this.serial.on('close', this.onClose.bind(this));

        // Open the port
        this.serial.open(portName, {
            baudRate: baudRate
        });

        // Request data every 300 miliseconds
        setInterval(() => {
            this.serial.write("\n");
        }, 1000 / 12);
    }

    onList(portList) {
        // Display the list of ports
        portList.forEach((port, i) => {
            console.log(`${i} | ${port}`);
        })

        // Select default portname (using magic)
        const opts = this.opts;
        if (opts.autoSelectPortName) {
            this.handleAutomaticPortSelection(portList);
        } else {
            console.log("ERR: Please specify a portname from the list below.");
            return; // Break - Since we cannot determine portname
        }

        return portList;
    }

    handleAutomaticPortSelection(portList) {
        const opts = this.opts;
        const validPortNames = portList.filter(i => i.indexOf('tty.usbmodem') >= 0);
        console.log(`INFO: Selecting portname automatically from ${validPortNames}`)
        if (validPortNames.length > 0) {
            opts.portName = validPortNames[0];
            this.serial = new SerialHelper(opts);
            console.log(`INFO: Selected portname ${opts.portName}`)
        } else {
            console.log("ERR: Autoselect Failed. Please specify a portname from the list below.");
            return; // Break - Since we cannot determine portname
        }
    }

    onConnected() {
        if (isValidFunction(this.opts, 'onConnected')) {
            this.opts.onConnected();
            this.serial.clear();
        } else {
            console.log('INFO: onConnected not defined | Running default')
            console.log('INFO: The serial port opened.')
        }
    }

    onOpen() {
        if (isValidFunction(this.opts, 'onOpen')) {
            this.opts.onOpen();
            this.serial.clear();
        } else {
            console.log('INFO: onOpen not defined | Running default')
            console.log('INFO: Connected to server. Clearing serial buffer...');
            this.serial.clear();
        }
    }

    onData() {
        if (isValidFunction(this.opts, 'onData')) {
            inData = this.serial.readLine();
        } else {
            console.log('INFO: onData not defined | Running default')
        }
    }

    onError() {
        if (isValidFunction(this.opts, 'onError')) {2
            this.opts.onError();
        } else {
            console.log('INFO: onError not defined | Running default')
        }
    }

    onClose() {
        if (isValidFunction(this.opts, 'onClose')) {
            this.opts.onClose();
        } else {
            console.log('INFO: onClose not defined | Running default')
        }
    }
}

function isValidFunction(obj, fnName) {
    return obj[fnName] !== undefined && obj[fnName] instanceof Function
}