Talking to the ESP

Before I try to send any messages, perhaps I'd better try to discover what the messages are. This seems to be a good place to find out what is going on:

https://github.com/espressif/esptool/wiki/Serial-Protocol

Each command is a SLIP packet (ah - that brings back memories) which starts with a 0 which is followed by an opcode, size field, checksum and then a bunch of data. If I dig into the esptool.py file and at line 425 I find the command method, which is used in the program to send a command to the ESP device. After a bit of setup stuff I find this statement:

pkt = struct.pack(b'<BBHI', 0x00, op, len(data), chk) + data

Aha. this looks like the statement that builds a command. What does struct.pack do?

https://docs.python.org/3/library/struct.html

It's all explained here. It is how Python manages the movement of data from variables in the program into bytes of data. The '<BBHI' data-preserve-html-node="true" string tells pack the format of the data it is to pack, in this case it says:

  • < - use "little endian" (in other words when assembling values over more than one byte put the lower bytes first)
  • B - pack a single 8 bit value - a byte
  • H - this is an unsigned "short" int that is stored in 16 bits, that is two bytes of data
  • I - this is an unsigned int that is stored in 32 bits, that is two bytes of data.

From this I can work out that the 0 at the start and op code are single byte values, the length of the data is sent over two bytes and the checksum is sent over four bytes. The statement above that assembles the packet then puts a thing called "data" on the end, which must be the block of data to be sent.

Assembling a command

JavaScript handles 8 bit data using a special type called a Uint8Array. This is my first attempt at packing a command into such an array.

packCommand(opCode, dataBlock, check) {
    let dataLength = dataBlock.length;
    let totalLength = dataLength + 8;
    const message = new Uint8Array(totalLength);
    let pos = 0;
    message[pos++] = 0;
    message[pos++] = opCode;
    message[pos++] = dataLength & 0xff;
    message[pos++] = (dataLength >> 8) & 0xff;
    message[pos++] = check;
    pos += 3;
    for (let i = 0; i < dataLength; i++) {
        let value = dataBlock[i];
        message[pos++] = value & 0xFF;
    }
    return message;
}

The packCommand method takes in an opcode, a block of data and a checksum value. It returns a Uint8Array with the command at the top and the data. It splits out the parts of each value by masking off the required bits and shifting them into position. It's probably not an optimal way of doing this, but it should work.

Sending a sync

The first command that is sent is a sync message that allows the host computer and the ESP device to talk to each other. Serial data (which is what we are using to exchange messages) is sent at a particular speed, called the baud rate. We set the baud rate when we created our JavaScript serial connection. Now we have to send a message to the ESP device so that it can work out what baud rate we are using and then start receiving at that rate. The communications protocol has a sync message which contains data values which have been chosen to produce a nice sequence of pulses for the ESP device to look at and work out the data rate. I've had a look at the packet and carefully assembled the data that we need. This is my sync command in JavaScript:

async sync() {
    console.log("Syncing the connection");
    const params = {
        op: ESPToolJS.ESP_SYNC,
        dataBlock: [0x07, 0x07, 0x12, 0x20,
            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
            0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55],
        check: 0,
        waitResponse: true,
        timeout: ESPToolJS.DEFAULT_TIMEOUT
    };
    let { val, data } = await this.command(params);
    return data;
}

I've copied all the command numbers into the program as static values, the value of ESpToolJS.ESP_SYNC is 0x08, which is the sync command opcode. The datablock is not delivering any data as such, but it does contain a lot of 0x55 values. If you look at the binary for the value 0x55 you find that it is 0101010101, which means that the serial data signal will bounce up and down in a square wave which is perfect for the ESP to use to work out the serial data speed.

This sync configuration means that the first sync command from the computer probably won't get a response, as the ESP device will use this to work out what speed it should listen. The pytool code sends multiple sync commands, so I'll have to as well.