5.4 Customize Protocol

The communication protocol of mBlock can be very flexible. A protocol is configured through pattern matching, so any protocol can be customized through pattern matching configuration. Following are some practical examples:

Restricted head and tail

head + body + tail: for instance, 0xff55 as the head, and 0x0d0a as the tail, the body for data matching

const fake_ff55 = {
    id: 'fake_ff55', // protocol name
    pack (body) { // pack data
        return [0xff, 0x55].concat(body).concat([0x0d, 0x0a])
    },
    unpack () { // data unpack methods
        return {
            type: 'all', // match all following
            pattern: [
                {
                    type: 'bytesEq', // match following
                    pattern: [0xff, 0x55]
                },
                {
                    type: 'body' // match data body
                },
                {
                    type: 'bytesEq', // match following
                    pattern: [0x0d, 0x0a]
                }
            ]
        }
    }
}


device.registProtocol(fake_ff55)

Checksum with restricted body and tail

head + body + checksum(body) + tail: for instance, 0xff55 as the head, and 0x0d0a as the tail, the body uses checksum for data matching

const fchecksum = (data) => data.reduce((acc, cur) => (cur + acc) & 0xff, 0);
const fake_ff55_1 = {
    id: 'fake_ff55_1',
    pack (body) {
        return [0xff, 0x55].concat(body).concat([fchecksum(body), 0x0d, 0x0a])
    },
    unpack () {
        let checksum = 0
        return {
            type: 'all',
            pattern: [
                {
                    type: 'bytesEq',
                    pattern: [0xff, 0x55]
                },
                {
                    type: 'body',
                    hook: (data, getBytes) => { // hook function, to obtain checksum
                        checksum = fchecksum(getBytes());
                    }
                },
                {
                    type: 'lazy', // lazy pack following, to ensure checksum matching during running
                    pattern: () => {
                        return {
                            type: 'byteEq', // match checksum
                            pattern: checksum
                        }
                    }
                },
                {
                    type: 'bytesEq',
                    pattern: [0x0d, 0x0a]
                }
            ]
        }
    }
}


device.registProtocol(fake_ff55_1)

Magic head + len + body

head + len + body: for instance, 'magic_head10086' as the head

const strMagicHead = 'magic_head10086';
const magicHead = Array.from(strMagicHead).map(c => c.charCodeAt(0));

const magic_head10086 = {
    id: 'magic_head10086',
    pack (body) {
        return [...magicHead, body.length].concat(body)
    },
    unpack () {
        let len = 0;
        let buff = []
        return {
            type: 'all',
            pattern: [
                {
                    type: 'bytesEq',
                    pattern: magicHead
                },
                {
                    type: 'byte',
                    hook: (byte) => {
                        len = byte;
                        return byte;
                    }
                },
                {
                    type: 'body',
                    hook: (data, getBytes) => {
                        buff = getBytes();
                        return data
                    }
                },
                {
                    type: 'assert', // assert matching, check following
                    pattern: () => buff.length == len
                }
            ]
        }
    }
}


device.registProtocol(magic_head10086)

F3F4 protocol

The following is a more sophisticated f3f4 protocol:

const my_f3f4 = {
    id: 'my_f3f4 ',
    pack (body){
        let len = body.length
        let len1 = len & 0xff; // 2-byte length
        let len2 = len >> 8;
        let headChecksum = fchecksum([0xf3, len1, len2]) & 0xff;// head checksum
        let head = [0xf3, headChecksum, len1, len2];
        let bodyChecksum = fchecksum(body) & 0xff; // data body checksum
        return head.concat(body, bodyChecksum, [0xf4]);
    },
    unpack () {
        let head = 0xf3;
        let len1 = 0
        let len2 = 0
        let headChecksum = 0
        let checksum = 0;
        return {
            type: 'all',
            pattern: [
                {
                    type: 'byteEq',
                    pattern: 0xf3
                },
                {
                    type: 'bytesN', // match 3 bytes
                    pattern: 3,
                    hook: (data) => { // hook function, to obtain  len1, len2 and headChecksum
                        let [headChecksum_, len1_, len2_] = data
                        headChecksum = headChecksum_
                        len1 = len1_
                        len2 = len2_
                        return data;
                    }
                },
                {
                    type: 'assert', // check following statements
                    pattern: () => {
                        return (head == 0xf3) && (headChecksum == fchecksum([head, len1, len2]))
                    }
                },
                {
                    type: 'body',
                    hook: (rawData, getBytes) => {
                        let bytes = getBytes();
                        checksum = fchecksum(bytes)
                    }
                },
                {
                    type: 'lazy', // Use obtained checksum for matching (lazy matching)
                    pattern: () => {
                        return {
                            type: 'byteEq',
                            pattern: checksum
                        }
                    }
                },
                {
                    type: 'byteEq',
                    pattern: 0xf4
                }
            ]
        }
    }
}
device.registProtocol(magic_head10086)

We can also simply match any protocol. asycWriteProtocol also works for pattern.

const anyData = {
    id: 'anyData', // Any data matching. is it amazing?
    pack (body) {
        return body
    },
    unpack () {
        return {
            type: 'all',
            pattern: [{
                type: 'body' // Use only the body for matching
            }]
        }
    }
}

////////////////////////////////////////////////////////////////////
test('anyData.write', async () => {
    let pp = new ProtocolReactor();
    pp.addProtocol(anyData);
    let data = await pp.asyncWriteProtocol('anyData', [3, 4, 5, 6])
    expect(data).toEqual([3, 4, 5, 6]);
}, 800)

test('anyData.read', async (done) => {
    let pp = new ProtocolReactor();
    pp.addProtocol(anyData);
    setTimeout(() => {
        pp.feedBytes(hexStr2ByteArray('61626565656364')); // abeeecd
    }, 300);
    let data = await pp.asyncReadProtocol('anyData', [
        {
            type: 'till', pattern: { // backward searching until `cd` pattern is matched, to generate  [previous data, pattern data]
                match: {
                    type: 'stringEq',
                    pattern: 'cd'
                },
                max_length:128 // Forward searching (max 128 bytes)
            }
        }
    ])
    expect(data).toEqual([[[0x61, 0x62, 0x65, 0x65, 0x65], 'cd']]);
    done()
    return
}, 800)

Protocol define

// Protocol configuration
interface IProtocol {
    id:string
    pack:(data:any)=>number[]
    unpack:()=>IProtocolNode
}

// Pattern matching
type IProtocolNode = {
    type:string
    hook? : (datas:any[], bytes:()=>number[])=>any // hook for successful matching
}

// Match all (recursive structure)
interface IAll extends IProtocolNode {
    type:'all'
    pattern: IProtocolNode[]
}

// Optional matching (recursive structure)
interface IAny extends IProtocolNode {
    type:'any'
    pattern: IProtocolNode[]
}

// Negation matching (recursive structure)
interface INot extends IProtocolNode {
    type:'not'
    pattern: IProtocolNode;
}

// Multiple bytes matching
interface IBytesEq extends IProtocolNode {
    type:'bytesEq'
    pattern: number[]
}

// 1 byte matching
interface IByte extends IProtocolNode {
    type:'byte'
    pattern: number
}

// N bytes matching
interface IBytesN extends IProtocolNode {
    type:'bytesN'
    pattern: number
}


// Single byte matching
interface IByteEq extends IProtocolNode {
    type:'byteEq'
    pattern: number;
}

// String matching
interface IStringEq extends IProtocolNode {
    type:'stringEq'
    pattern: string;
}

// Regular string matching
interface IRegex extends IProtocolNode {
    type:'regex'
    pattern: RegExp | string;
}

// Assertion matching
interface IAssert extends IProtocolNode {
    type:'assert'
    pattern:()=>boolean
}

// matching during running (for failed matching in static configuration, recursive structure)
interface ILazy extends IProtocolNode {
    type:'lazy'
    pattern: ()=> IProtocolNode
}

// Data body matching (loading pattern of asyncReadProtocol)
interface IBody extends IProtocolNode {
    type:'body'
}

// Backward-search matching (recursive structure)
interface ITill extends IProtocolNode {
    type:'till'
    pattern:{
        match:IProtocolNode,
        max_length:number
    }
}

// Byte string value matching
interface ITillBytes extends IProtocolNode {
    type:'till_bytes'
    pattern:(bytes:number[])=>boolean
}

results matching ""

    No results matching ""