mirror of
https://github.com/meshtastic/firmware.git
synced 2025-04-23 09:06:02 +00:00
Merge pull request #112 from geeksville/serialproto
Serial/USB API for stress testing and other API clients
This commit is contained in:
commit
93f69d5a94
10
.vscode/launch.json
vendored
10
.vscode/launch.json
vendored
@ -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"
|
||||
}
|
||||
]
|
||||
|
@ -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
|
||||
|
@ -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
13
src/RedirectablePrint.cpp
Normal 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
34
src/RedirectablePrint.h
Normal 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
32
src/SerialConsole.cpp
Normal 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
24
src/SerialConsole.h
Normal 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;
|
@ -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__)
|
||||
|
@ -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
|
||||
|
@ -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()
|
@ -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
70
src/mesh/StreamAPI.cpp
Normal 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
66
src/mesh/StreamAPI.h
Normal 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;
|
||||
};
|
Loading…
Reference in New Issue
Block a user