November 09, 2017

Micro-bit logging module

One more utility package.

When trying to balance the robot, it is hard to know what is going on without logging the numbers from every iteration of the control loop. However, there is no straightforward way to log 6 data points 100 times per seconds for 10 seconds. Bluetooth and radio are too slow and lossy. Attaching a serial line affects the balance of the robot. Try the simple way of adding numbers to an array and it runs out of memory quickly.

Well, there's an undocumented Buffer class that can store an array of bytes. This Logging class is a wrapper around that class. It provides a simple interface to repeatedly log a line consisting of a fixed number of numbers. In the constructor, specify the number of lines to keep and a list of byte sizes, one for each number. Then call add() to log a line of data. If the logger runs out of space, it'll wraparound and throw away the oldest data. Finally, call the sendToSerial() method to write the buffer to the serial line in human readable format. Each call to add() becomes a line of numbers separated by space.

There is enough memory for about 8000 bytes.

Add this to the end of your custom.ts.


/**
 * Public domain. Use at your own risk!
 * Logging functions
 */
namespace Log {
    export class Log {
        private line_count: number;
        private labels: Array;
        private sizes: Array;
        private line_size: number;
        private buf_size: number;
        private buf: Buffer;
        private tail: number;
        private full: boolean;

        /**
         * Creates a logging object
         * @param line_count Number of lines, eg: 1000
         * @param sizes Array of number byte size per line, eg [4, 1, 1, 2]
         */
        constructor(line_count: number, labels: Array, sizes: Array) {
            this.line_count = line_count;
            this.labels = labels;
            this.sizes = sizes;
            control.assert(this.labels.length == this.sizes.length);
            this.line_size = 0;
            for (let i = 0; i < sizes.length; i++) {
                let s = sizes[i];
                control.assert(s == 1 || s == 2 || s == 4);
                this.line_size = this.line_size + s;
            }
            this.buf_size = this.line_size * this.line_count;
            this.buf = pins.createBuffer(this.buf_size);
            this.tail = 0;
            this.full = false;
        }

        /**Adds the list of numbers to the log according to the byte sizes array in the constructor.
         * @param l List of numbers to be added
         */
        add(l: Array) {
            let p = this.tail * this.line_size;
            for (let i = 0; i < this.sizes.length; i++) {
                let s = this.sizes[i];
                let n = l[i];
                switch (s) {
                    case 1:
                        this.buf.setNumber(NumberFormat.Int8LE, p, n);
                        break;
                    case 2:
                        this.buf.setNumber(NumberFormat.Int16LE, p, n);
                        break;
                    case 4:
                        this.buf.setNumber(NumberFormat.Int32LE, p, n);
                        break;
                }
                p = p + s;
            }
            control.assert(p == (this.tail + 1) * this.line_size);
            this.tail = this.tail + 1;
            if (this.tail >= this.line_count) {
                this.tail = 0;
                this.full = true;
            }
        }

        clear() {
            this.tail = 0;
            this.full = false;
        }

        //%
        sendToSerial() {
            let start = 0;
            let count = this.tail;
            if (this.full) {
                count = this.line_count;
                start = this.tail;
            }
            for (let i = 0; i < this.labels.length; i++) {
                serial.writeString(this.labels[i]);
                if (i == this.labels.length - 1) {
                    serial.writeLine("");
                } else {
                    serial.writeString(",");
                }
            }
            for (let i = 0; i < count; i++) {
                this.sendLine((i + start) % this.line_count);
            }
        }

        private sendLine(index: number) {
            let p = index * this.line_size;
            for (let i = 0; i < this.sizes.length; i++) {
                let s = this.sizes[i];
                switch (s) {
                    case 1:
                        serial.writeNumber(this.buf.getNumber(NumberFormat.Int8LE, p));
                        break;
                    case 2:
                        serial.writeNumber(this.buf.getNumber(NumberFormat.Int16LE, p));
                        break;
                    case 4:
                        serial.writeNumber(this.buf.getNumber(NumberFormat.Int32LE, p));
                        break;
                }
                p = p + s;
                if (i == this.sizes.length - 1) {
                    serial.writeLine("");
                } else {
                    serial.writeString(",");
                }
            }
        }
    }
}


Example code:


// Each line is consists of 3 numbers: [4 bytes, 1 byte, 2 bytes]
let l = new Log.Log(1000, ["time", "delta", "x"], [4, 1, 2])

input.onButtonPressed(Button.A, () => {
    serial.writeLine("START");
    l.sendToSerial();
    serial.writeLine("END");
})

basic.showIcon(IconNames.Heart)
let last = input.runningTime();
let x = 0;
while (true) {
    let t = input.runningTime();
    l.add([t, t - last, x]);
    last = t;
    x = x + 1;
    basic.pause(1);
}

No comments:

Post a Comment