Merge pull request #112 from geeksville/serialproto

Serial/USB API for stress testing and other API clients
This commit is contained in:
Kevin Hester 2020-04-27 09:40:10 -07:00 committed by GitHub
commit 93f69d5a94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 260 additions and 15 deletions

10
.vscode/launch.json vendored
View File

@ -12,9 +12,8 @@
"type": "platformio-debug",
"request": "launch",
"name": "PIO Debug",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/nrf52dk/firmware.elf",
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin",
"svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf",
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
"preLaunchTask": {
"type": "PlatformIO",
"task": "Pre-Debug"
@ -25,9 +24,8 @@
"type": "platformio-debug",
"request": "launch",
"name": "PIO Debug (skip Pre-Debug)",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/nrf52dk/firmware.elf",
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin",
"svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd",
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf",
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
"internalConsoleOptions": "openOnSessionStart"
}
]

View File

@ -3,4 +3,4 @@
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1"
# the nanopb tool seems to require that the .options file be in the current directory!
cd proto
../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src -I=../proto mesh.proto
../../nanopb-0.4.1-linux-x86/generator-bin/protoc --nanopb_out=-v:../src/mesh -I=../proto mesh.proto

View File

@ -31,7 +31,7 @@ board_build.partitions = partition-table.csv
; note: we add src to our include search path so that lmic_project_config can override
; FIXME: fix lib/BluetoothOTA dependency back on src/ so we can remove -Isrc
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Os -Wl,-Map,.pio/build/output.map
build_flags = -Wno-missing-field-initializers -Isrc -Isrc/rf95 -Isrc/mesh -Ilib/nanopb/include -Os -Wl,-Map,.pio/build/output.map
-DAXP_DEBUG_PORT=Serial
-DHW_VERSION_${sysenv.COUNTRY}
-DAPP_VERSION=${sysenv.APP_VERSION}

13
src/RedirectablePrint.cpp Normal file
View File

@ -0,0 +1,13 @@
#include "RedirectablePrint.h"
#include <assert.h>
/**
* A printer that doesn't go anywhere
*/
NoopPrint noopPrint;
void RedirectablePrint::setDestination(Print *_dest)
{
assert(_dest);
dest = _dest;
}

34
src/RedirectablePrint.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <Print.h>
/**
* A Printable that can be switched to squirt its bytes to a different sink.
* This class is mostly useful to allow debug printing to be redirected away from Serial
* to some other transport if we switch Serial usage (on the fly) to some other purpose.
*/
class RedirectablePrint : public Print
{
Print *dest;
public:
RedirectablePrint(Print *_dest) : dest(_dest) {}
/**
* Set a new destination
*/
void setDestination(Print *dest);
virtual size_t write(uint8_t c) { return dest->write(c); }
};
class NoopPrint : public Print
{
public:
virtual size_t write(uint8_t c) { return 1; }
};
/**
* A printer that doesn't go anywhere
*/
extern NoopPrint noopPrint;

32
src/SerialConsole.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "SerialConsole.h"
#include "configuration.h"
#include <Arduino.h>
#define Port Serial
SerialConsole console;
SerialConsole::SerialConsole() : StreamAPI(&Port), RedirectablePrint(&Port)
{
canWrite = false; // We don't send packets to our port until it has talked to us first
// setDestination(&noopPrint);
}
/// Do late init that can't happen at constructor time
void SerialConsole::init()
{
Port.begin(SERIAL_BAUD);
StreamAPI::init();
}
/**
* we override this to notice when we've received a protobuf over the serial stream. Then we shunt off
* debug serial output.
*/
void SerialConsole::handleToRadio(const uint8_t *buf, size_t len)
{
setDestination(&noopPrint);
canWrite = true;
StreamAPI::handleToRadio(buf, len);
}

24
src/SerialConsole.h Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include "RedirectablePrint.h"
#include "StreamAPI.h"
/**
* Provides both debug printing and, if the client starts sending protobufs to us, switches to send/receive protobufs
* (and starts dropping debug printing - FIXME, eventually those prints should be encapsulated in protobufs).
*/
class SerialConsole : public StreamAPI, public RedirectablePrint
{
public:
SerialConsole();
/// Do late init that can't happen at constructor time
virtual void init();
/**
* we override this to notice when we've received a protobuf over the serial stream. Then we shunt off
* debug serial output.
*/
virtual void handleToRadio(const uint8_t *buf, size_t len);
};
extern SerialConsole console;

View File

@ -237,7 +237,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "SEGGER_RTT.h"
#define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
#else
#define DEBUG_PORT Serial // Serial debug port
#include "SerialConsole.h"
#define DEBUG_PORT console // Serial debug port
#ifdef DEBUG_PORT
#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__)

View File

@ -126,7 +126,7 @@ void setup()
// Debug
#ifdef DEBUG_PORT
DEBUG_PORT.begin(SERIAL_BAUD);
DEBUG_PORT.init(); // Set serial baud rate and init our mesh console
#endif
initDeepSleep();
@ -234,6 +234,10 @@ void loop()
periodicScheduler.loop();
// axpDebugOutput.loop();
#ifdef DEBUG_PORT
DEBUG_PORT.loop(); // Send/receive protobufs over the serial port
#endif
#ifndef NO_ESP32
esp32Loop();
#endif

View File

@ -5,9 +5,8 @@
PhoneAPI::PhoneAPI()
{
// Make sure that we never let our packets grow too large for one BLE packet
assert(FromRadio_size <= 512);
assert(ToRadio_size <= 512);
assert(FromRadio_size <= MAX_TO_FROM_RADIO_SIZE);
assert(ToRadio_size <= MAX_TO_FROM_RADIO_SIZE);
}
void PhoneAPI::init()

View File

@ -5,6 +5,9 @@
#include "mesh.pb.h"
#include <string>
// Make sure that we never let our packets grow too large for one BLE packet
#define MAX_TO_FROM_RADIO_SIZE 512
/**
* Provides our protobuf based API which phone/PC clients can use to talk to our device
* over UDP, bluetooth or serial.
@ -54,12 +57,12 @@ class PhoneAPI
PhoneAPI();
/// Do late init that can't happen at constructor time
void init();
virtual void init();
/**
* Handle a ToRadio protobuf
*/
void handleToRadio(const uint8_t *buf, size_t len);
virtual void handleToRadio(const uint8_t *buf, size_t len);
/**
* Get the next packet we want to send to the phone

70
src/mesh/StreamAPI.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "StreamAPI.h"
#define START1 0x94
#define START2 0xc3
#define HEADER_LEN 4
void StreamAPI::loop()
{
writeStream();
readStream();
}
/**
* Read any rx chars from the link and call handleToRadio
*/
void StreamAPI::readStream()
{
while (stream->available()) { // Currently we never want to block
uint8_t c = stream->read();
// Use the read pointer for a little state machine, first look for framing, then length bytes, then payload
size_t ptr = rxPtr++; // assume we will probably advance the rxPtr
rxBuf[ptr] = c; // store all bytes (including framing)
if (ptr == 0) { // looking for START1
if (c != START1)
rxPtr = 0; // failed to find framing
} else if (ptr == 1) { // looking for START2
if (c != START2)
rxPtr = 0; // failed to find framing
} else if (ptr >= HEADER_LEN) { // we have at least read our 4 byte framing
uint16_t len = (rxBuf[2] << 8) + rxBuf[3]; // big endian 16 bit length follows framing
if (ptr == HEADER_LEN) {
// we _just_ finished our 4 byte header, validate length now (note: a length of zero is a valid
// protobuf also)
if (len > MAX_TO_FROM_RADIO_SIZE)
rxPtr = 0; // length is bogus, restart search for framing
}
if (rxPtr != 0 && ptr == len + HEADER_LEN) {
// If we didn't just fail the packet and we now have the right # of bytes, parse it
handleToRadio(rxBuf + HEADER_LEN, len);
}
}
}
}
/**
* call getFromRadio() and deliver encapsulated packets to the Stream
*/
void StreamAPI::writeStream()
{
if (canWrite) {
uint32_t len;
do {
// Send every packet we can
len = getFromRadio(txBuf + HEADER_LEN);
if (len != 0) {
txBuf[0] = START1;
txBuf[1] = START2;
txBuf[2] = (len >> 8) & 0xff;
txBuf[3] = len & 0xff;
stream->write(txBuf, len + HEADER_LEN);
}
} while (len);
}
}

66
src/mesh/StreamAPI.h Normal file
View File

@ -0,0 +1,66 @@
#pragma once
#include "PhoneAPI.h"
#include "Stream.h"
// A To/FromRadio packet + our 32 bit header
#define MAX_STREAM_BUF_SIZE (MAX_TO_FROM_RADIO_SIZE + sizeof(uint32_t))
/**
* A version of our 'phone' API that talks over a Stream. So therefore well suited to use with serial links
* or TCP connections.
*
* ## Wire encoding
When sending protobuf packets over serial or TCP each packet is preceded by uint32 sent in network byte order (big endian).
The upper 16 bits must be 0x94C3. The lower 16 bits are packet length (this encoding gives room to eventually allow quite large
packets).
Implementations validate length against the maximum possible size of a BLE packet (our lowest common denominator) of 512 bytes. If
the length provided is larger than that we assume the packet is corrupted and begin again looking for 0x4403 framing.
The packets flowing towards the device are ToRadio protobufs, the packets flowing from the device are FromRadio protobufs.
The 0x94C3 marker can be used as framing to (eventually) resync if packets are corrupted over the wire.
Note: the 0x94C3 framing was chosen to prevent confusion with the 7 bit ascii character set. It also doesn't collide with any
valid utf8 encoding. This makes it a bit easier to start a device outputting regular debug output on its serial port and then only
after it has received a valid packet from the PC, turn off unencoded debug printing and switch to this packet encoding.
*/
class StreamAPI : public PhoneAPI
{
/**
* The stream we read/write from
*/
Stream *stream;
uint8_t rxBuf[MAX_STREAM_BUF_SIZE];
size_t rxPtr = 0;
uint8_t txBuf[MAX_STREAM_BUF_SIZE];
public:
StreamAPI(Stream *_stream) : stream(_stream) {}
/**
* Currently we require frequent invocation from loop() to check for arrived serial packets and to send new packets to the
* phone.
* FIXME, to support better power behavior instead move to a thread and block on serial reads.
*/
void loop();
private:
/**
* Read any rx chars from the link and call handleToRadio
*/
void readStream();
/**
* call getFromRadio() and deliver encapsulated packets to the Stream
*/
void writeStream();
protected:
/// Are we allowed to write packets to our output stream (subclasses can turn this off - i.e. SerialConsole)
bool canWrite = true;
};