mirror of
https://github.com/meshtastic/firmware.git
synced 2025-04-28 10:42:08 +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",
|
"type": "platformio-debug",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "PIO Debug",
|
"name": "PIO Debug",
|
||||||
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/nrf52dk/firmware.elf",
|
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf",
|
||||||
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin",
|
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
|
||||||
"svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd",
|
|
||||||
"preLaunchTask": {
|
"preLaunchTask": {
|
||||||
"type": "PlatformIO",
|
"type": "PlatformIO",
|
||||||
"task": "Pre-Debug"
|
"task": "Pre-Debug"
|
||||||
@ -25,9 +24,8 @@
|
|||||||
"type": "platformio-debug",
|
"type": "platformio-debug",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "PIO Debug (skip Pre-Debug)",
|
"name": "PIO Debug (skip Pre-Debug)",
|
||||||
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/nrf52dk/firmware.elf",
|
"executable": "/home/kevinh/development/meshtastic/meshtastic-esp32/.pio/build/tbeam/firmware.elf",
|
||||||
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-gccarmnoneeabi/bin",
|
"toolchainBinDir": "/home/kevinh/.platformio/packages/toolchain-xtensa32/bin",
|
||||||
"svdPath": "/home/kevinh/.platformio/platforms/nordicnrf52/misc/svd/nrf52840.svd",
|
|
||||||
"internalConsoleOptions": "openOnSessionStart"
|
"internalConsoleOptions": "openOnSessionStart"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -3,4 +3,4 @@
|
|||||||
echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.1"
|
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!
|
# the nanopb tool seems to require that the .options file be in the current directory!
|
||||||
cd proto
|
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
|
; 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
|
; 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
|
-DAXP_DEBUG_PORT=Serial
|
||||||
-DHW_VERSION_${sysenv.COUNTRY}
|
-DHW_VERSION_${sysenv.COUNTRY}
|
||||||
-DAPP_VERSION=${sysenv.APP_VERSION}
|
-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"
|
#include "SEGGER_RTT.h"
|
||||||
#define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
|
#define DEBUG_MSG(...) SEGGER_RTT_printf(0, __VA_ARGS__)
|
||||||
#else
|
#else
|
||||||
#define DEBUG_PORT Serial // Serial debug port
|
#include "SerialConsole.h"
|
||||||
|
|
||||||
|
#define DEBUG_PORT console // Serial debug port
|
||||||
|
|
||||||
#ifdef DEBUG_PORT
|
#ifdef DEBUG_PORT
|
||||||
#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__)
|
#define DEBUG_MSG(...) DEBUG_PORT.printf(__VA_ARGS__)
|
||||||
|
@ -126,7 +126,7 @@ void setup()
|
|||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
#ifdef DEBUG_PORT
|
#ifdef DEBUG_PORT
|
||||||
DEBUG_PORT.begin(SERIAL_BAUD);
|
DEBUG_PORT.init(); // Set serial baud rate and init our mesh console
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
initDeepSleep();
|
initDeepSleep();
|
||||||
@ -234,6 +234,10 @@ void loop()
|
|||||||
periodicScheduler.loop();
|
periodicScheduler.loop();
|
||||||
// axpDebugOutput.loop();
|
// axpDebugOutput.loop();
|
||||||
|
|
||||||
|
#ifdef DEBUG_PORT
|
||||||
|
DEBUG_PORT.loop(); // Send/receive protobufs over the serial port
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef NO_ESP32
|
#ifndef NO_ESP32
|
||||||
esp32Loop();
|
esp32Loop();
|
||||||
#endif
|
#endif
|
||||||
|
@ -5,9 +5,8 @@
|
|||||||
|
|
||||||
PhoneAPI::PhoneAPI()
|
PhoneAPI::PhoneAPI()
|
||||||
{
|
{
|
||||||
// Make sure that we never let our packets grow too large for one BLE packet
|
assert(FromRadio_size <= MAX_TO_FROM_RADIO_SIZE);
|
||||||
assert(FromRadio_size <= 512);
|
assert(ToRadio_size <= MAX_TO_FROM_RADIO_SIZE);
|
||||||
assert(ToRadio_size <= 512);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PhoneAPI::init()
|
void PhoneAPI::init()
|
@ -5,6 +5,9 @@
|
|||||||
#include "mesh.pb.h"
|
#include "mesh.pb.h"
|
||||||
#include <string>
|
#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
|
* Provides our protobuf based API which phone/PC clients can use to talk to our device
|
||||||
* over UDP, bluetooth or serial.
|
* over UDP, bluetooth or serial.
|
||||||
@ -54,12 +57,12 @@ class PhoneAPI
|
|||||||
PhoneAPI();
|
PhoneAPI();
|
||||||
|
|
||||||
/// Do late init that can't happen at constructor time
|
/// Do late init that can't happen at constructor time
|
||||||
void init();
|
virtual void init();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a ToRadio protobuf
|
* 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
|
* 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