mirror of
https://github.com/meshtastic/firmware.git
synced 2026-01-09 11:27:29 +00:00
Compare commits
124 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea40bd991c | ||
|
|
e19dd46f0f | ||
|
|
532b06c280 | ||
|
|
0cf7aaffff | ||
|
|
e2e1819ef1 | ||
|
|
31b89e2932 | ||
|
|
a021ff7eb8 | ||
|
|
bb5d0fac90 | ||
|
|
df5ed64514 | ||
|
|
9db5f9ff67 | ||
|
|
ca83a78e13 | ||
|
|
13eef9a309 | ||
|
|
2a8ac2c0c6 | ||
|
|
c97342db99 | ||
|
|
d7b2a0ed79 | ||
|
|
af0a1b5db5 | ||
|
|
9cf030d587 | ||
|
|
c04d70d5e5 | ||
|
|
2a47819fd6 | ||
|
|
4516c8f9b5 | ||
|
|
e4fdf26dc7 | ||
|
|
39d14fedc2 | ||
|
|
1da38fc748 | ||
|
|
b5f50efdcd | ||
|
|
046e691d4e | ||
|
|
e72531b090 | ||
|
|
81e320c9cf | ||
|
|
fa8cc74141 | ||
|
|
c7d9ff7cc0 | ||
|
|
8704a9d08f | ||
|
|
c0d27e2ce9 | ||
|
|
84b9028ecb | ||
|
|
4fda7098c0 | ||
|
|
8e8264efb0 | ||
|
|
54e780a6ca | ||
|
|
125eb2b784 | ||
|
|
6ea9cdc83b | ||
|
|
c0711fde69 | ||
|
|
20b8d2c4a5 | ||
|
|
73ae151971 | ||
|
|
f4806c9dd7 | ||
|
|
79532210e8 | ||
|
|
d7f26493a5 | ||
|
|
b9d025dd58 | ||
|
|
f435086a5a | ||
|
|
3dcdf372d7 | ||
|
|
cd84f2867c | ||
|
|
cafe00e463 | ||
|
|
6da4e30215 | ||
|
|
dbf0569e29 | ||
|
|
18220b88b3 | ||
|
|
665da2fb00 | ||
|
|
57ffe6622d | ||
|
|
485fec9649 | ||
|
|
bd85736226 | ||
|
|
4ec8986934 | ||
|
|
b963216764 | ||
|
|
813fd95bc8 | ||
|
|
3598c91c29 | ||
|
|
507cd1dd20 | ||
|
|
e39506824d | ||
|
|
f68a31ab28 | ||
|
|
b1181deb58 | ||
|
|
89b32dd7ee | ||
|
|
c54e87f9a2 | ||
|
|
eee7e1de57 | ||
|
|
3c60df1565 | ||
|
|
a827017bd2 | ||
|
|
95c502c658 | ||
|
|
0f573901d5 | ||
|
|
fdc9bf5783 | ||
|
|
37e0f9a325 | ||
|
|
0c06d8db3c | ||
|
|
0be4bbb369 | ||
|
|
f02ab88393 | ||
|
|
c9d4de8808 | ||
|
|
adb912b665 | ||
|
|
3f5da1e03e | ||
|
|
0a40d920e3 | ||
|
|
39311f1e40 | ||
|
|
9cd24a5646 | ||
|
|
1c0efde315 | ||
|
|
c82905bbdd | ||
|
|
275eace968 | ||
|
|
5688c8b81e | ||
|
|
8b2798abd5 | ||
|
|
6d977923b6 | ||
|
|
52dacaed37 | ||
|
|
7a381eaea1 | ||
|
|
69391e186b | ||
|
|
06f8beaa17 | ||
|
|
3798f4ca5b | ||
|
|
4fd243a6e4 | ||
|
|
d458f673be | ||
|
|
cfcb00b943 | ||
|
|
977e47d109 | ||
|
|
cfeb40f36d | ||
|
|
4fcc3ac1de | ||
|
|
f4afa6931b | ||
|
|
71be71d63d | ||
|
|
de9f7e6c39 | ||
|
|
7c8db2b501 | ||
|
|
cd653f9434 | ||
|
|
74bc05936d | ||
|
|
7aacfd66ef | ||
|
|
3636b87db0 | ||
|
|
d6bd328576 | ||
|
|
0af5b225c4 | ||
|
|
f7dcef39ce | ||
|
|
07042178d2 | ||
|
|
243878f2a0 | ||
|
|
d3f8a76cce | ||
|
|
20131a51a2 | ||
|
|
1c9a369774 | ||
|
|
dcb426f58f | ||
|
|
35bcb5297a | ||
|
|
84e3d7c276 | ||
|
|
9b03f0ac8e | ||
|
|
eb402809e2 | ||
|
|
e9c9e40624 | ||
|
|
01eed97b91 | ||
|
|
94a47dba7d | ||
|
|
8295b88d96 | ||
|
|
925829dc58 |
@@ -14,7 +14,7 @@ BOARDS_ESP32="tlora-v2 tlora-v1 tlora-v2-1-1.6 tbeam heltec tbeam0.7"
|
||||
# FIXME note nrf52840dk build is for some reason only generating a BIN file but not a HEX file nrf52840dk-geeksville is fine
|
||||
BOARDS_NRF52="lora-relay-v1"
|
||||
|
||||
NUM_JOBS=2
|
||||
NUM_JOBS=2 || true
|
||||
|
||||
OUTDIR=release/latest
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
We use the same channel maps as LoRaWAN (though this is not LoRaWAN).
|
||||
|
||||

|
||||

|
||||
|
||||
See [this site](https://www.rfwireless-world.com/Tutorials/LoRa-channels-list.html) for more information.
|
||||
|
||||
|
||||
@@ -39,9 +39,20 @@ cd Meshtastic-device
|
||||
|
||||
## Decoding stack traces
|
||||
|
||||
### Option 1
|
||||
|
||||
If you get a crash, you can decode the addresses from the `Backtrace:` line:
|
||||
|
||||
1. Save the `Backtrace: 0x....` line to a file, e.g., `backtrace.txt`.
|
||||
2. Run `bin/exception_decoder.py backtrace.txt` (this uses symbols from the
|
||||
last `firmware.elf`, so you must be running the same binary that's still in
|
||||
your `.pio/build` directory).
|
||||
|
||||
### Option 2
|
||||
|
||||
You can run the exception decoder to monitor the serial output and decode backtraces in real time.
|
||||
|
||||
1. From within PlatformIO, open a new terminal.
|
||||
2. At the the terminal, enter:
|
||||
`pio device monitor --port /dev/cu.SLAB_USBtoUART -f esp32_exception_decoder`
|
||||
Replace the value of port with the location of your serial port.
|
||||
|
||||
@@ -15,10 +15,10 @@ Packets can be sent/received either as raw binary structures or as [Protobufs](h
|
||||
The relevant bits of the class heirarchy are as follows
|
||||
|
||||
* [MeshPlugin](/src/mesh/MeshPlugin.h) (in src/mesh/MeshPlugin.h) - you probably don't want to use this baseclass directly
|
||||
* [SinglePortPlugin](/src/mesh/SinglePortPlugin.h) (in src/mesh/SinglePortPlugin.h) - for plugins that receive from a single port number (the normal case)
|
||||
* [ProtobufPlugin](/src/mesh/ProtobufPlugin.h) (in src/mesh/ProtobufPlugin.h) - for plugins that are sending/receiving a single particular Protobuf type. Inherit from this if you are using protocol buffers in your plugin.
|
||||
* [SinglePortPlugin](/src/mesh/SinglePortPlugin.h) (in src/mesh/SinglePortPlugin.h) - for plugins that send/receive from a single port number (the normal case)
|
||||
* [ProtobufPlugin](/src/mesh/ProtobufPlugin.h) (in src/mesh/ProtobufPlugin.h) - for plugins that send/receive a single particular Protobuf type. Inherit from this if you are using protocol buffers in your plugin.
|
||||
|
||||
You will typically want to inherit from either SinglePortPlugin (if you are just sending raw bytes) or ProtobufPlugin (if you are sending protobufs). You'll implement your own handleReceived/handleReceivedProtobuf - probably based on the example code.
|
||||
You will typically want to inherit from either SinglePortPlugin (if you are just sending/receiving raw bytes) or ProtobufPlugin (if you are sending/receiving protobufs). You'll implement your own handleReceived/handleReceivedProtobuf - probably based on the example code.
|
||||
|
||||
If your plugin needs to perform any operations at startup you can override and implement the setup() method to run your code.
|
||||
|
||||
@@ -45,25 +45,30 @@ A number of [key services](/src/plugins) are implemented using the plugin API, t
|
||||
The easiest way to get started is:
|
||||
|
||||
* [Build and install](build-instructions.md) the standard codebase from github.
|
||||
* Copy [src/plugins/ReplyPlugin.*](/src/plugins/ReplyPlugin.cpp) into src/plugins/YourPlugin.*. Then change the port number from REPLY_APP to PRIVATE_APP.
|
||||
* Copy [src/plugins/ReplyPlugin.*](/src/plugins/ReplyPlugin.cpp) into src/plugins/YourPlugin.*. Then change the port number from *PortNum_REPLY_APP* to *PortNum_PRIVATE_APP*.
|
||||
* Edit plugins/Plugins.cpp:setupPlugins() to add a call to create an instance of your plugin (see comment at head of that function)
|
||||
* Rebuild with your new messaging goodness and install on the device
|
||||
* Use the [meshtastic commandline tool](https://github.com/meshtastic/Meshtastic-python) to send a packet to your board "meshtastic --dest 1234 --ping"
|
||||
* Use the [meshtastic commandline tool](https://github.com/meshtastic/Meshtastic-python) to send a packet to your board, for example "*meshtastic --dest 1234 --sendping*", where *1234* is another mesh node to send the ping to.
|
||||
|
||||
## Threading
|
||||
|
||||
It is very common that you would like your plugin to be invoked periodically.
|
||||
We use a crude/basic cooperative threading system to allow this on any of our supported platforms. Simply inherit from OSThread and implement runOnce(). See the OSThread [documentation](/src/concurrency/OSThread.h) for more details. For an example consumer of this API see RemoteHardwarePlugin::runOnce.
|
||||
|
||||
## Sending messages
|
||||
|
||||
If you would like to proactively send messages (rather than just responding to them), just call service.sendToMesh(). For an example of this see [NodeInfoPlugin::sendOurNodeInfo(...)](/src/plugins/NodeInfoPlugin.cpp).
|
||||
|
||||
## Picking a port number
|
||||
|
||||
For any new 'apps' that run on the device or via sister apps on phones/PCs they should pick and use a unique 'portnum' for their application.
|
||||
|
||||
If you are making a new app using meshtastic, please send in a pull request to add your 'portnum' to [the master list](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/portnums.proto). PortNums should be assigned in the following range:
|
||||
|
||||
* 0-63 Core Meshtastic use, do not use for third party apps
|
||||
* 64-127 Registered 3rd party apps, send in a pull request that adds a new entry to portnums.proto to register your application
|
||||
* 256-511 Use one of these portnums for your private applications that you don't want to register publically
|
||||
* 1024-66559 Are reserved for use by IP tunneling (see FIXME for more information)
|
||||
* **0-63** Core Meshtastic use; do not use for third party apps
|
||||
* **64-127** Registered 3rd party apps. Send in a pull request that adds a new entry to portnums.proto to register your application
|
||||
* **256-511** Use one of these portnums for your private applications that you don't want to register publically
|
||||
* **1024-66559** Are reserved for use by IP tunneling (see *FIXME* for more information)
|
||||
|
||||
All other values are reserved.
|
||||
|
||||
@@ -73,4 +78,4 @@ If you would like to use protocol buffers to define the structures you send over
|
||||
|
||||
* Create a new .proto file in the protos directory. You can use the existing [remote_hardware.proto](https://github.com/meshtastic/Meshtastic-protobufs/blob/master/remote_hardware.proto) file as an example.
|
||||
* Run "bin/regen-protos.sh" to regenerate the C code for accessing the protocol buffers. If you don't have the required nanopb tool, follow the instructions printed by the script to get it.
|
||||
* Done! You can now use your new protobuf just like any of the existing protobufs in meshtastic.
|
||||
* Done! You can now use your new protobuf just like any of the existing protobufs in meshtastic.
|
||||
|
||||
@@ -9,8 +9,13 @@
|
||||
; https://docs.platformio.org/page/projectconf.html
|
||||
|
||||
[platformio]
|
||||
;default_envs = tbeam
|
||||
;default_envs = tbeam0.7
|
||||
;default_envs = heltec
|
||||
;default_envs = tlora-v1
|
||||
;default_envs = tlora-v2
|
||||
;default_envs = lora-relay-v1 # nrf board
|
||||
default_envs = linux # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
;default_envs = heltec # lora-relay-v1 # nrf52840dk-geeksville # linux # or if you'd like to change the default to something like lora-relay-v1 put that here
|
||||
|
||||
[common]
|
||||
; common is not currently used
|
||||
@@ -180,10 +185,10 @@ build_type = debug ; I'm debugging with ICE a lot now
|
||||
build_flags =
|
||||
${arduino_base.build_flags} -Wno-unused-variable
|
||||
-Isrc/nrf52
|
||||
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.3
|
||||
-Isdk-nrfxlib/crypto/nrf_oberon/include -Lsdk-nrfxlib/crypto/nrf_oberon/lib/cortex-m4/hard-float/ -lliboberon_3.0.7
|
||||
;-DCFG_DEBUG=3
|
||||
src_filter =
|
||||
${arduino_base.src_filter} -<esp32/> -<nimble/> -<meshwifi/> -<mesh/wifi/>
|
||||
${arduino_base.src_filter} -<esp32/> -<nimble/> -<mesh/wifi/> -<mesh/http/>
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
monitor_port = /dev/ttyACM1
|
||||
@@ -310,7 +315,7 @@ lib_deps =
|
||||
; The Portduino based sim environment on top of linux
|
||||
[env:linux]
|
||||
platform = https://github.com/geeksville/platform-portduino.git
|
||||
src_filter = ${env.src_filter} -<esp32/> -<nimble/> -<nrf52/> -<meshwifi/>
|
||||
src_filter = ${env.src_filter} -<esp32/> -<nimble/> -<nrf52/> -<mesh/http/>
|
||||
build_flags = ${arduino_base.build_flags} -O0
|
||||
framework = arduino
|
||||
board = linux_x86_64
|
||||
|
||||
2
proto
2
proto
Submodule proto updated: dfe7bc1217...855da8701e
Submodule sdk-nrfxlib updated: 17e8453553...e6e02cb83d
@@ -1,4 +1,5 @@
|
||||
#include "power.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "main.h"
|
||||
#include "sleep.h"
|
||||
@@ -268,9 +269,42 @@ bool Power::axp192Init()
|
||||
DEBUG_MSG("DCDC3: %s\n", axp.isDCDC3Enable() ? "ENABLE" : "DISABLE");
|
||||
DEBUG_MSG("Exten: %s\n", axp.isExtenEnable() ? "ENABLE" : "DISABLE");
|
||||
|
||||
//axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA); // actual limit (in HW) on the tbeam is 450mA
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_450MA); // There's no HW limit on the tbeam. Setting to 450mz to be a good neighbor on the usb bus.
|
||||
|
||||
if (radioConfig.preferences.charge_current == ChargeCurrent_MAUnset) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_450MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA100) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_100MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA190) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_190MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA280) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_280MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA360) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_360MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA450) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_450MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA550) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_550MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA630) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_630MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA700) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_700MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA780) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_780MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA880) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_880MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA960) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_960MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1000) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1000MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1080) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1080MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1160) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1160MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1240) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1240MA);
|
||||
} else if (radioConfig.preferences.charge_current == ChargeCurrent_MA1320) {
|
||||
axp.setChargeControlCur(AXP1XX_CHARGE_CUR_1320MA);
|
||||
}
|
||||
|
||||
#if 0
|
||||
|
||||
// Not connected
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace meshtastic
|
||||
*/
|
||||
enum OptionalBool { OptFalse = 0, OptTrue = 1, OptUnknown = 2 };
|
||||
|
||||
/// Describes the state of the GPS system.
|
||||
/// Describes the state of the Power system.
|
||||
class PowerStatus : public Status
|
||||
{
|
||||
|
||||
|
||||
@@ -3,33 +3,35 @@
|
||||
|
||||
#define periodsToLog 48
|
||||
|
||||
// A reminder that there are 3600 seconds in an hour so I don't have
|
||||
// to keep googling it.
|
||||
// This can be changed to a smaller number to speed up testing.
|
||||
//
|
||||
AirTime *airTime;
|
||||
|
||||
uint32_t secondsPerPeriod = 3600;
|
||||
uint32_t lastMillis = 0;
|
||||
uint32_t secSinceBoot = 0;
|
||||
|
||||
// AirTime at;
|
||||
|
||||
// Don't read out of this directly. Use the helper functions.
|
||||
struct airtimeStruct {
|
||||
uint16_t periodTX[periodsToLog];
|
||||
uint16_t periodRX[periodsToLog];
|
||||
uint16_t periodRX_ALL[periodsToLog];
|
||||
uint32_t periodTX[periodsToLog]; // AirTime transmitted
|
||||
uint32_t periodRX[periodsToLog]; // AirTime received and repeated (Only valid mesh packets)
|
||||
uint32_t periodRX_ALL[periodsToLog]; // AirTime received regardless of valid mesh packet. Could include noise.
|
||||
uint8_t lastPeriodIndex;
|
||||
} airtimes;
|
||||
|
||||
void logAirtime(reportTypes reportType, uint32_t airtime_ms)
|
||||
void AirTime::logAirtime(reportTypes reportType, uint32_t airtime_ms)
|
||||
{
|
||||
|
||||
if (reportType == TX_LOG) {
|
||||
airtimes.periodTX[0] = airtimes.periodTX[0] + round(airtime_ms / 1000);
|
||||
DEBUG_MSG("AirTime - Packet transmitted : %ums\n", airtime_ms);
|
||||
airtimes.periodTX[0] = airtimes.periodTX[0] + airtime_ms;
|
||||
} else if (reportType == RX_LOG) {
|
||||
airtimes.periodRX[0] = airtimes.periodRX[0] + round(airtime_ms / 1000);
|
||||
DEBUG_MSG("AirTime - Packet received : %ums\n", airtime_ms);
|
||||
airtimes.periodRX[0] = airtimes.periodRX[0] + airtime_ms;
|
||||
} else if (reportType == RX_ALL_LOG) {
|
||||
airtimes.periodRX_ALL[0] = airtimes.periodRX_ALL[0] + round(airtime_ms / 1000);
|
||||
DEBUG_MSG("AirTime - Packet received (noise?) : %ums\n", airtime_ms);
|
||||
airtimes.periodRX_ALL[0] = airtimes.periodRX_ALL[0] + airtime_ms;
|
||||
} else {
|
||||
// Unknown report type
|
||||
DEBUG_MSG("AirTime - Unknown report time. This should never happen!!\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,29 +40,27 @@ uint8_t currentPeriodIndex()
|
||||
return ((getSecondsSinceBoot() / secondsPerPeriod) % periodsToLog);
|
||||
}
|
||||
|
||||
void airtimeCalculator()
|
||||
void airtimeRotatePeriod()
|
||||
{
|
||||
if (millis() - lastMillis > 1000) {
|
||||
lastMillis = millis();
|
||||
secSinceBoot++;
|
||||
if (airtimes.lastPeriodIndex != currentPeriodIndex()) {
|
||||
for (int i = periodsToLog - 2; i >= 0; --i) {
|
||||
airtimes.periodTX[i + 1] = airtimes.periodTX[i];
|
||||
airtimes.periodRX[i + 1] = airtimes.periodRX[i];
|
||||
airtimes.periodRX_ALL[i + 1] = airtimes.periodRX_ALL[i];
|
||||
}
|
||||
airtimes.periodTX[0] = 0;
|
||||
airtimes.periodRX[0] = 0;
|
||||
airtimes.periodRX_ALL[0] = 0;
|
||||
|
||||
airtimes.lastPeriodIndex = currentPeriodIndex();
|
||||
if (airtimes.lastPeriodIndex != currentPeriodIndex()) {
|
||||
DEBUG_MSG("Rotating airtimes to a new period = %u\n", currentPeriodIndex());
|
||||
|
||||
for (int i = periodsToLog - 2; i >= 0; --i) {
|
||||
airtimes.periodTX[i + 1] = airtimes.periodTX[i];
|
||||
airtimes.periodRX[i + 1] = airtimes.periodRX[i];
|
||||
airtimes.periodRX_ALL[i + 1] = airtimes.periodRX_ALL[i];
|
||||
}
|
||||
airtimes.periodTX[0] = 0;
|
||||
airtimes.periodRX[0] = 0;
|
||||
airtimes.periodRX_ALL[0] = 0;
|
||||
|
||||
airtimes.lastPeriodIndex = currentPeriodIndex();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t *airtimeReport(reportTypes reportType)
|
||||
uint32_t *airtimeReport(reportTypes reportType)
|
||||
{
|
||||
// currentHourIndexReset();
|
||||
|
||||
if (reportType == TX_LOG) {
|
||||
return airtimes.periodTX;
|
||||
@@ -86,3 +86,22 @@ uint32_t getSecondsSinceBoot()
|
||||
{
|
||||
return secSinceBoot;
|
||||
}
|
||||
|
||||
AirTime::AirTime() : concurrency::OSThread("AirTime") {}
|
||||
|
||||
int32_t AirTime::runOnce()
|
||||
{
|
||||
//DEBUG_MSG("AirTime::runOnce()\n");
|
||||
|
||||
airtimeRotatePeriod();
|
||||
secSinceBoot++;
|
||||
|
||||
/*
|
||||
This actually doesn't need to be run once per second but we currently use it for the
|
||||
secSinceBoot counter.
|
||||
|
||||
If we have a better counter of how long the device has been online (and not millis())
|
||||
then we can change this to something less frequent. Maybe once ever 5 seconds?
|
||||
*/
|
||||
return (1000 * 1);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
@@ -18,7 +19,7 @@
|
||||
TX_LOG + RX_LOG = Total air time for a perticular meshtastic channel.
|
||||
|
||||
TX_LOG + RX_LOG = Total air time for a perticular meshtastic channel, including
|
||||
other lora radios.
|
||||
other lora radios.
|
||||
|
||||
RX_ALL_LOG - RX_LOG = Other lora radios on our frequency channel.
|
||||
*/
|
||||
@@ -26,13 +27,28 @@ enum reportTypes { TX_LOG, RX_LOG, RX_ALL_LOG };
|
||||
|
||||
void logAirtime(reportTypes reportType, uint32_t airtime_ms);
|
||||
|
||||
void airtimeCalculator();
|
||||
void airtimeRotatePeriod();
|
||||
|
||||
uint8_t currentPeriodIndex();
|
||||
uint8_t getPeriodsToLog();
|
||||
|
||||
uint32_t getSecondsSinceBoot();
|
||||
|
||||
uint16_t *airtimeReport(reportTypes reportType);
|
||||
uint32_t *airtimeReport(reportTypes reportType);
|
||||
|
||||
uint32_t getSecondsPerPeriod();
|
||||
uint32_t getSecondsPerPeriod();
|
||||
|
||||
class AirTime : private concurrency::OSThread
|
||||
{
|
||||
|
||||
public:
|
||||
AirTime();
|
||||
|
||||
void logAirtime(reportTypes reportType, uint32_t airtime_ms);
|
||||
|
||||
protected:
|
||||
|
||||
virtual int32_t runOnce();
|
||||
};
|
||||
|
||||
extern AirTime *airTime;
|
||||
@@ -28,6 +28,8 @@ void OSThread::setup()
|
||||
OSThread::OSThread(const char *_name, uint32_t period, ThreadController *_controller)
|
||||
: Thread(NULL, period), controller(_controller)
|
||||
{
|
||||
assertIsSetup();
|
||||
|
||||
ThreadName = _name;
|
||||
|
||||
if (controller)
|
||||
@@ -81,4 +83,36 @@ void OSThread::run()
|
||||
currentThread = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* This flag is set **only** when setup() starts, to provide a way for us to check for sloppy static constructor calls.
|
||||
* Call assertIsSetup() to force a crash if someone tries to create an instance too early.
|
||||
*
|
||||
* it is super important to never allocate those object statically. instead, you should explicitly
|
||||
* new them at a point where you are guaranteed that other objects that this instance
|
||||
* depends on have already been created.
|
||||
*
|
||||
* in particular, for OSThread that means "all instances must be declared via new() in setup() or later" -
|
||||
* this makes it guaranteed that the global mainController is fully constructed first.
|
||||
*/
|
||||
bool hasBeenSetup;
|
||||
|
||||
void assertIsSetup()
|
||||
{
|
||||
|
||||
/**
|
||||
* Dear developer comrade - If this assert fails() that means you need to fix the following:
|
||||
*
|
||||
* This flag is set **only** when setup() starts, to provide a way for us to check for sloppy static constructor calls.
|
||||
* Call assertIsSetup() to force a crash if someone tries to create an instance too early.
|
||||
*
|
||||
* it is super important to never allocate those object statically. instead, you should explicitly
|
||||
* new them at a point where you are guaranteed that other objects that this instance
|
||||
* depends on have already been created.
|
||||
*
|
||||
* in particular, for OSThread that means "all instances must be declared via new() in setup() or later" -
|
||||
* this makes it guaranteed that the global mainController is fully constructed first.
|
||||
*/
|
||||
assert(hasBeenSetup);
|
||||
}
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -17,14 +17,14 @@ extern InterruptableDelay mainDelay;
|
||||
|
||||
/**
|
||||
* @brief Base threading
|
||||
*
|
||||
*
|
||||
* This is a pseudo threading layer that is super easy to port, well suited to our slow network and very ram & power efficient.
|
||||
*
|
||||
* TODO FIXME @geeksville
|
||||
*
|
||||
* move more things into OSThreads
|
||||
* remove lock/lockguard
|
||||
*
|
||||
*
|
||||
* move typedQueue into concurrency
|
||||
* remove freertos from typedqueue
|
||||
*/
|
||||
@@ -42,7 +42,6 @@ class OSThread : public Thread
|
||||
static bool showWaiting;
|
||||
|
||||
public:
|
||||
|
||||
/// For debug printing only (might be null)
|
||||
static const OSThread *currentThread;
|
||||
|
||||
@@ -71,4 +70,19 @@ class OSThread : public Thread
|
||||
virtual void run();
|
||||
};
|
||||
|
||||
/**
|
||||
* This flag is set **only** when setup() starts, to provide a way for us to check for sloppy static constructor calls.
|
||||
* Call assertIsSetup() to force a crash if someone tries to create an instance too early.
|
||||
*
|
||||
* it is super important to never allocate those object statically. instead, you should explicitly
|
||||
* new them at a point where you are guaranteed that other objects that this instance
|
||||
* depends on have already been created.
|
||||
*
|
||||
* in particular, for OSThread that means "all instances must be declared via new() in setup() or later" -
|
||||
* this makes it guaranteed that the global mainController is fully constructed first.
|
||||
*/
|
||||
extern bool hasBeenSetup;
|
||||
|
||||
void assertIsSetup();
|
||||
|
||||
} // namespace concurrency
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "EInkDisplay.h"
|
||||
#include "SPILock.h"
|
||||
#include "epd1in54.h" // Screen specific library
|
||||
#include "graphics/configs.h"
|
||||
#include <SPI.h>
|
||||
#include <TFT_eSPI.h> // Graphics library and Sprite class
|
||||
|
||||
|
||||
@@ -31,11 +31,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include "plugins/TextMessagePlugin.h"
|
||||
#include "target_specific.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#endif
|
||||
|
||||
using namespace meshtastic; /** @todo remove */
|
||||
|
||||
namespace graphics
|
||||
@@ -746,7 +749,7 @@ void Screen::setup()
|
||||
powerStatusObserver.observe(&powerStatus->onNewStatus);
|
||||
gpsStatusObserver.observe(&gpsStatus->onNewStatus);
|
||||
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
|
||||
textMessageObserver.observe(&textMessagePlugin);
|
||||
textMessageObserver.observe(textMessagePlugin);
|
||||
}
|
||||
|
||||
void Screen::forceDisplay()
|
||||
@@ -895,10 +898,12 @@ void Screen::setFrames()
|
||||
// call a method on debugInfoScreen object (for more details)
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoSettingsTrampoline;
|
||||
|
||||
#ifndef NO_ESP32
|
||||
if (isWifiAvailable()) {
|
||||
// call a method on debugInfoScreen object (for more details)
|
||||
normalFrames[numframes++] = &Screen::drawDebugInfoWiFiTrampoline;
|
||||
}
|
||||
#endif
|
||||
|
||||
ui.setFrames(normalFrames, numframes);
|
||||
ui.enableAllIndicators();
|
||||
|
||||
48
src/main.cpp
48
src/main.cpp
@@ -19,8 +19,7 @@
|
||||
#include "concurrency/Periodic.h"
|
||||
#include "graphics/Screen.h"
|
||||
#include "main.h"
|
||||
#include "meshwifi/meshhttp.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include "plugins/Plugins.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include <OneButton.h>
|
||||
@@ -28,6 +27,8 @@
|
||||
// #include <driver/rtc_io.h>
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "mesh/http/WebServer.h"
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#include "nimble/BluetoothUtil.h"
|
||||
#endif
|
||||
|
||||
@@ -275,6 +276,8 @@ RadioInterface *rIf = NULL;
|
||||
|
||||
void setup()
|
||||
{
|
||||
concurrency::hasBeenSetup = true;
|
||||
|
||||
#ifdef SEGGER_STDOUT_CH
|
||||
SEGGER_RTT_ConfigUpBuffer(SEGGER_STDOUT_CH, NULL, NULL, 1024, SEGGER_RTT_MODE_NO_BLOCK_TRIM);
|
||||
#endif
|
||||
@@ -300,17 +303,20 @@ void setup()
|
||||
digitalWrite(RESET_OLED, 1);
|
||||
#endif
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
#ifndef NO_ESP32
|
||||
// If BUTTON_PIN is held down during the startup process,
|
||||
// force the device to go into a SoftAP mode.
|
||||
bool forceSoftAP = 0;
|
||||
#ifdef BUTTON_PIN
|
||||
#ifndef NO_ESP32
|
||||
pinMode(BUTTON_PIN, INPUT);
|
||||
#ifdef BUTTON_NEED_PULLUP
|
||||
gpio_pullup_en((gpio_num_t)BUTTON_PIN);
|
||||
#endif
|
||||
|
||||
// BUTTON_PIN is pulled high by a 12k resistor.
|
||||
if (!digitalRead(BUTTON_PIN)) {
|
||||
forceSoftAP = 1;
|
||||
DEBUG_MSG("-------------------- Setting forceSoftAP = 1\n");
|
||||
DEBUG_MSG("Setting forceSoftAP = 1\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -390,15 +396,15 @@ void setup()
|
||||
readFromRTC(); // read the main CPU RTC at first (in case we can't get GPS time)
|
||||
|
||||
#ifdef GENIEBLOCKS
|
||||
//gps setup
|
||||
pinMode (GPS_RESET_N, OUTPUT);
|
||||
// gps setup
|
||||
pinMode(GPS_RESET_N, OUTPUT);
|
||||
pinMode(GPS_EXTINT, OUTPUT);
|
||||
digitalWrite(GPS_RESET_N, HIGH);
|
||||
digitalWrite(GPS_EXTINT, LOW);
|
||||
//battery setup
|
||||
// battery setup
|
||||
// If we want to read battery level, we need to set BATTERY_EN_PIN pin to low.
|
||||
// ToDo: For low power consumption after read battery level, set that pin to high.
|
||||
pinMode (BATTERY_EN_PIN, OUTPUT);
|
||||
pinMode(BATTERY_EN_PIN, OUTPUT);
|
||||
digitalWrite(BATTERY_EN_PIN, LOW);
|
||||
#endif
|
||||
|
||||
@@ -439,14 +445,17 @@ void setup()
|
||||
|
||||
service.init();
|
||||
|
||||
// Now that the mesh service is created, create any plugins
|
||||
setupPlugins();
|
||||
|
||||
// Do this after service.init (because that clears error_code)
|
||||
#ifdef AXP192_SLAVE_ADDRESS
|
||||
if(!axp192_found)
|
||||
if (!axp192_found)
|
||||
recordCriticalError(CriticalErrorCode_NoAXP192); // Record a hardware fault for missing hardware
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Don't call screen setup until after nodedb is setup (because we need
|
||||
// the current region name)
|
||||
// Don't call screen setup until after nodedb is setup (because we need
|
||||
// the current region name)
|
||||
#if defined(ST7735_CS) || defined(HAS_EINK)
|
||||
screen->setup();
|
||||
#else
|
||||
@@ -507,9 +516,17 @@ void setup()
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NO_ESP32
|
||||
// Initialize Wifi
|
||||
initWifi(forceSoftAP);
|
||||
|
||||
// Start web server thread.
|
||||
webServerThread = new WebServerThread();
|
||||
#endif
|
||||
|
||||
// Start airtime logger thread.
|
||||
airTime = new AirTime();
|
||||
|
||||
if (!rIf)
|
||||
recordCriticalError(CriticalErrorCode_NoRadio);
|
||||
else
|
||||
@@ -570,7 +587,7 @@ void loop()
|
||||
#endif
|
||||
|
||||
// TODO: This should go into a thread handled by FreeRTOS.
|
||||
handleWebResponse();
|
||||
// handleWebResponse();
|
||||
|
||||
service.loop();
|
||||
|
||||
@@ -583,7 +600,4 @@ void loop()
|
||||
// We want to sleep as long as possible here - because it saves power
|
||||
mainDelay.delay(delayMsec);
|
||||
// if (didWake) DEBUG_MSG("wake!\n");
|
||||
|
||||
// Handles cleanup for the airtime calculator.
|
||||
airtimeCalculator();
|
||||
}
|
||||
|
||||
@@ -60,7 +60,8 @@ static int32_t sendOwnerCb()
|
||||
currentGeneration = radioGeneration;
|
||||
|
||||
DEBUG_MSG("Sending our nodeinfo to mesh (wantReplies=%d)\n", requestReplies);
|
||||
nodeInfoPlugin.sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies)
|
||||
assert(nodeInfoPlugin);
|
||||
nodeInfoPlugin->sendOurNodeInfo(NODENUM_BROADCAST, requestReplies); // Send our info (don't request replies)
|
||||
|
||||
return getPref_send_owner_interval() * getPref_position_broadcast_secs() * 1000;
|
||||
}
|
||||
@@ -134,7 +135,8 @@ bool MeshService::reloadConfig()
|
||||
/// The owner User record just got updated, update our node DB and broadcast the info into the mesh
|
||||
void MeshService::reloadOwner()
|
||||
{
|
||||
nodeInfoPlugin.sendOurNodeInfo();
|
||||
assert(nodeInfoPlugin);
|
||||
nodeInfoPlugin->sendOurNodeInfo();
|
||||
nodeDB.saveToDisk();
|
||||
}
|
||||
|
||||
@@ -193,17 +195,40 @@ void MeshService::sendNetworkPing(NodeNum dest, bool wantReplies)
|
||||
assert(node);
|
||||
|
||||
DEBUG_MSG("Sending network ping to 0x%x, with position=%d, wantReplies=%d\n", dest, node->has_position, wantReplies);
|
||||
assert(positionPlugin && nodeInfoPlugin);
|
||||
if (node->has_position)
|
||||
positionPlugin.sendOurPosition(dest, wantReplies);
|
||||
positionPlugin->sendOurPosition(dest, wantReplies);
|
||||
else
|
||||
nodeInfoPlugin.sendOurNodeInfo(dest, wantReplies);
|
||||
nodeInfoPlugin->sendOurNodeInfo(dest, wantReplies);
|
||||
}
|
||||
|
||||
|
||||
NodeInfo *MeshService::refreshMyNodeInfo() {
|
||||
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
|
||||
assert(node);
|
||||
|
||||
// We might not have a position yet for our local node, in that case, at least try to send the time
|
||||
if(!node->has_position) {
|
||||
memset(&node->position, 0, sizeof(node->position));
|
||||
node->has_position = true;
|
||||
}
|
||||
|
||||
Position &position = node->position;
|
||||
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
position.time = getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||
|
||||
position.battery_level = powerStatus->getBatteryChargePercent();
|
||||
updateBatteryLevel(position.battery_level);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
||||
{
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
|
||||
Position pos = Position_init_default;
|
||||
NodeInfo *node = refreshMyNodeInfo();
|
||||
Position pos = node->position;
|
||||
|
||||
if (gps->hasLock()) {
|
||||
if (gps->altitude != 0)
|
||||
@@ -214,21 +239,16 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
||||
else {
|
||||
// The GPS has lost lock, if we are fixed position we should just keep using
|
||||
// the old position
|
||||
if(radioConfig.preferences.fixed_position) {
|
||||
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
|
||||
assert(node);
|
||||
assert(node->has_position);
|
||||
pos = node->position;
|
||||
if(!radioConfig.preferences.fixed_position) {
|
||||
DEBUG_MSG("WARNING: Using fixed position\n");
|
||||
} else {
|
||||
// throw away old position
|
||||
pos.latitude_i = 0;
|
||||
pos.longitude_i = 0;
|
||||
pos.altitude = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pos.time = getValidTime(RTCQualityGPS);
|
||||
|
||||
// Include our current battery voltage in our position announcement
|
||||
pos.battery_level = powerStatus->getBatteryChargePercent();
|
||||
updateBatteryLevel(pos.battery_level);
|
||||
|
||||
DEBUG_MSG("got gps notify time=%u, lat=%d, bat=%d\n", pos.latitude_i, pos.time, pos.battery_level);
|
||||
|
||||
// Update our current position in the local DB
|
||||
@@ -247,7 +267,8 @@ int MeshService::onGPSChanged(const meshtastic::GPSStatus *unused)
|
||||
currentGeneration = radioGeneration;
|
||||
|
||||
DEBUG_MSG("Sending position to mesh (wantReplies=%d)\n", requestReplies);
|
||||
positionPlugin.sendOurPosition(NODENUM_BROADCAST, requestReplies);
|
||||
assert(positionPlugin);
|
||||
positionPlugin->sendOurPosition(NODENUM_BROADCAST, requestReplies);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -79,6 +79,9 @@ class MeshService
|
||||
/// cache
|
||||
void sendToMesh(MeshPacket *p);
|
||||
|
||||
/// Pull the latest power and time info into my nodeinfo
|
||||
NodeInfo *refreshMyNodeInfo();
|
||||
|
||||
private:
|
||||
|
||||
/// Called when our gps position has changed - updates nodedb and sends Location message out into the mesh
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "FS.h"
|
||||
|
||||
#include "CryptoEngine.h"
|
||||
#include "FSCommon.h"
|
||||
#include "GPS.h"
|
||||
#include "MeshRadio.h"
|
||||
#include "NodeDB.h"
|
||||
@@ -15,11 +16,13 @@
|
||||
#include "configuration.h"
|
||||
#include "error.h"
|
||||
#include "mesh-pb-constants.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include "FSCommon.h"
|
||||
#include <pb_decode.h>
|
||||
#include <pb_encode.h>
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#endif
|
||||
|
||||
NodeDB nodeDB;
|
||||
|
||||
// we have plenty of ram so statically alloc this tempbuf (for now)
|
||||
@@ -28,7 +31,7 @@ MyNodeInfo &myNodeInfo = devicestate.my_node;
|
||||
RadioConfig &radioConfig = devicestate.radio;
|
||||
ChannelSettings &channelSettings = radioConfig.channel_settings;
|
||||
|
||||
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
|
||||
/** The current change # for radio settings. Starts at 0 on boot and any time the radio settings
|
||||
* might have changed is incremented. Allows others to detect they might now be on a new channel.
|
||||
*/
|
||||
uint32_t radioGeneration;
|
||||
@@ -41,8 +44,6 @@ DeviceState versions used to be defined in the .proto file but really only this
|
||||
#define DEVICESTATE_CUR_VER 11
|
||||
#define DEVICESTATE_MIN_VER DEVICESTATE_CUR_VER
|
||||
|
||||
|
||||
|
||||
// FIXME - move this somewhere else
|
||||
extern void getMacAddr(uint8_t *dmac);
|
||||
|
||||
@@ -90,7 +91,7 @@ const char *getChannelName()
|
||||
static char buf[32];
|
||||
|
||||
char suffix;
|
||||
if(channelSettings.psk.size != 1) {
|
||||
if (channelSettings.psk.size != 1) {
|
||||
// We have a standard PSK, so generate a letter based hash.
|
||||
uint8_t code = 0;
|
||||
for (int i = 0; i < activePSKSize; i++)
|
||||
@@ -140,32 +141,40 @@ bool NodeDB::resetRadioConfig()
|
||||
}
|
||||
|
||||
// Convert the old string "Default" to our new short representation
|
||||
if(strcmp(channelSettings.name, "Default") == 0)
|
||||
if (strcmp(channelSettings.name, "Default") == 0)
|
||||
*channelSettings.name = '\0';
|
||||
|
||||
// Convert the short "" representation for Default into a usable string
|
||||
channelName = channelSettings.name;
|
||||
if(!*channelName) { // emptystring
|
||||
if (!*channelName) { // emptystring
|
||||
// Per mesh.proto spec, if bandwidth is specified we must ignore modemConfig enum, we assume that in that case
|
||||
// the app fucked up and forgot to set channelSettings.name
|
||||
channelName = "Unset";
|
||||
if(channelSettings.bandwidth == 0) switch(channelSettings.modem_config) {
|
||||
|
||||
if (channelSettings.bandwidth != 0)
|
||||
channelName = "Unset";
|
||||
else
|
||||
switch (channelSettings.modem_config) {
|
||||
case ChannelSettings_ModemConfig_Bw125Cr45Sf128:
|
||||
channelName = "Medium"; break;
|
||||
channelName = "Medium";
|
||||
break;
|
||||
case ChannelSettings_ModemConfig_Bw500Cr45Sf128:
|
||||
channelName = "ShortFast"; break;
|
||||
channelName = "ShortFast";
|
||||
break;
|
||||
case ChannelSettings_ModemConfig_Bw31_25Cr48Sf512:
|
||||
channelName = "LongAlt"; break;
|
||||
channelName = "LongAlt";
|
||||
break;
|
||||
case ChannelSettings_ModemConfig_Bw125Cr48Sf4096:
|
||||
channelName = "LongSlow"; break;
|
||||
channelName = "LongSlow";
|
||||
break;
|
||||
default:
|
||||
channelName = "Invalid"; break;
|
||||
}
|
||||
channelName = "Invalid";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert any old usage of the defaultpsk into our new short representation.
|
||||
if(channelSettings.psk.size == sizeof(defaultpsk) &&
|
||||
memcmp(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk)) == 0) {
|
||||
if (channelSettings.psk.size == sizeof(defaultpsk) &&
|
||||
memcmp(channelSettings.psk.bytes, defaultpsk, sizeof(defaultpsk)) == 0) {
|
||||
*channelSettings.psk.bytes = 1;
|
||||
channelSettings.psk.size = 1;
|
||||
}
|
||||
@@ -173,10 +182,10 @@ bool NodeDB::resetRadioConfig()
|
||||
// Convert the short single byte variants of psk into variant that can be used more generally
|
||||
memcpy(activePSK, channelSettings.psk.bytes, channelSettings.psk.size);
|
||||
activePSKSize = channelSettings.psk.size;
|
||||
if(activePSKSize == 1) {
|
||||
if (activePSKSize == 1) {
|
||||
uint8_t pskIndex = activePSK[0];
|
||||
DEBUG_MSG("Expanding short PSK #%d\n", pskIndex);
|
||||
if(pskIndex == 0)
|
||||
if (pskIndex == 0)
|
||||
activePSKSize = 0; // Turn off encryption
|
||||
else {
|
||||
memcpy(activePSK, defaultpsk, sizeof(defaultpsk));
|
||||
@@ -271,12 +280,13 @@ void NodeDB::init()
|
||||
myNodeInfo.node_num_bits = sizeof(NodeNum) * 8;
|
||||
myNodeInfo.packet_id_bits = sizeof(PacketId) * 8;
|
||||
|
||||
myNodeInfo.error_code = CriticalErrorCode_None; // For the error code, only show values from this boot (discard value from flash)
|
||||
myNodeInfo.error_code =
|
||||
CriticalErrorCode_None; // For the error code, only show values from this boot (discard value from flash)
|
||||
myNodeInfo.error_address = 0;
|
||||
|
||||
// likewise - we always want the app requirements to come from the running appload
|
||||
myNodeInfo.min_app_version = 20120; // format is Mmmss (where M is 1+the numeric major number. i.e. 20120 means 1.1.20
|
||||
|
||||
|
||||
// Note! We do this after loading saved settings, so that if somehow an invalid nodenum was stored in preferences we won't
|
||||
// keep using that nodenum forever. Crummy guess at our nodenum (but we will check against the nodedb to avoid conflicts)
|
||||
pickNewNodeNum();
|
||||
|
||||
@@ -134,6 +134,8 @@ size_t PhoneAPI::getFromRadio(uint8_t *buf)
|
||||
fromRadioScratch.which_variant = FromRadio_my_info_tag;
|
||||
fromRadioScratch.variant.my_info = myNodeInfo;
|
||||
state = STATE_SEND_RADIO;
|
||||
|
||||
service.refreshMyNodeInfo(); // Update my NodeInfo because the client will be asking for it soon.
|
||||
break;
|
||||
|
||||
case STATE_SEND_RADIO:
|
||||
|
||||
@@ -97,7 +97,8 @@ ErrorCode RadioLibInterface::send(MeshPacket *p)
|
||||
|
||||
// Count the packet toward our TX airtime utilization.
|
||||
// We only count it if it can be added to the TX queue.
|
||||
logAirtime(TX_LOG, xmitMsec);
|
||||
airTime->logAirtime(TX_LOG, xmitMsec);
|
||||
//airTime.logAirtime(TX_LOG, xmitMsec);
|
||||
|
||||
// We want all sending/receiving to be done by our daemon thread, We use a delay here because this packet might have been sent
|
||||
// in response to a packet we just received. So we want to make sure the other side has had a chance to reconfigure its radio
|
||||
@@ -216,7 +217,8 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
size_t length = iface->getPacketLength();
|
||||
|
||||
xmitMsec = getPacketTime(length);
|
||||
logAirtime(RX_ALL_LOG, xmitMsec);
|
||||
airTime->logAirtime(RX_ALL_LOG, xmitMsec);
|
||||
//airTime.logAirtime(RX_ALL_LOG, xmitMsec);
|
||||
|
||||
int state = iface->readData(radiobuf, length);
|
||||
if (state != ERR_NONE) {
|
||||
@@ -258,7 +260,8 @@ void RadioLibInterface::handleReceiveInterrupt()
|
||||
printPacket("Lora RX", mp);
|
||||
|
||||
xmitMsec = getPacketTime(mp);
|
||||
logAirtime(RX_LOG, xmitMsec);
|
||||
airTime->logAirtime(RX_LOG, xmitMsec);
|
||||
//airTime.logAirtime(RX_LOG, xmitMsec);
|
||||
|
||||
deliverToReceiver(mp);
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ extern const pb_msgdesc_t DeviceState_msg;
|
||||
#define DeviceState_fields &DeviceState_msg
|
||||
|
||||
/* Maximum encoded size of messages (where known) */
|
||||
#define DeviceState_size 6176
|
||||
#define DeviceState_size 6206
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -57,3 +57,4 @@ PB_BIND(ToRadio, ToRadio, 2)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,26 @@ typedef enum _RegionCode {
|
||||
RegionCode_TW = 8
|
||||
} RegionCode;
|
||||
|
||||
typedef enum _ChargeCurrent {
|
||||
ChargeCurrent_MAUnset = 0,
|
||||
ChargeCurrent_MA100 = 1,
|
||||
ChargeCurrent_MA190 = 2,
|
||||
ChargeCurrent_MA280 = 3,
|
||||
ChargeCurrent_MA360 = 4,
|
||||
ChargeCurrent_MA450 = 5,
|
||||
ChargeCurrent_MA550 = 6,
|
||||
ChargeCurrent_MA630 = 7,
|
||||
ChargeCurrent_MA700 = 8,
|
||||
ChargeCurrent_MA780 = 9,
|
||||
ChargeCurrent_MA880 = 10,
|
||||
ChargeCurrent_MA960 = 11,
|
||||
ChargeCurrent_MA1000 = 12,
|
||||
ChargeCurrent_MA1080 = 13,
|
||||
ChargeCurrent_MA1160 = 14,
|
||||
ChargeCurrent_MA1240 = 15,
|
||||
ChargeCurrent_MA1320 = 16
|
||||
} ChargeCurrent;
|
||||
|
||||
typedef enum _GpsOperation {
|
||||
GpsOperation_GpsOpUnset = 0,
|
||||
GpsOperation_GpsOpMobile = 2,
|
||||
@@ -145,6 +165,7 @@ typedef struct _RadioConfig_UserPreferences {
|
||||
char wifi_password[64];
|
||||
bool wifi_ap_mode;
|
||||
RegionCode region;
|
||||
ChargeCurrent charge_current;
|
||||
LocationSharing location_share;
|
||||
GpsOperation gps_operation;
|
||||
uint32_t gps_update_interval;
|
||||
@@ -156,6 +177,11 @@ typedef struct _RadioConfig_UserPreferences {
|
||||
bool debug_log_enabled;
|
||||
pb_size_t ignore_incoming_count;
|
||||
uint32_t ignore_incoming[3];
|
||||
bool serialplugin_enabled;
|
||||
bool serialplugin_echo;
|
||||
uint32_t serialplugin_rxd;
|
||||
uint32_t serialplugin_txd;
|
||||
uint32_t serialplugin_timeout;
|
||||
} RadioConfig_UserPreferences;
|
||||
|
||||
typedef struct _RouteDiscovery {
|
||||
@@ -265,6 +291,10 @@ typedef struct _ToRadio {
|
||||
#define _RegionCode_MAX RegionCode_TW
|
||||
#define _RegionCode_ARRAYSIZE ((RegionCode)(RegionCode_TW+1))
|
||||
|
||||
#define _ChargeCurrent_MIN ChargeCurrent_MAUnset
|
||||
#define _ChargeCurrent_MAX ChargeCurrent_MA1320
|
||||
#define _ChargeCurrent_ARRAYSIZE ((ChargeCurrent)(ChargeCurrent_MA1320+1))
|
||||
|
||||
#define _GpsOperation_MIN GpsOperation_GpsOpUnset
|
||||
#define _GpsOperation_MAX GpsOperation_GpsOpDisabled
|
||||
#define _GpsOperation_ARRAYSIZE ((GpsOperation)(GpsOperation_GpsOpDisabled+1))
|
||||
@@ -299,7 +329,7 @@ extern "C" {
|
||||
#define MeshPacket_init_default {0, 0, 0, {SubPacket_init_default}, 0, 0, 0, 0, 0, 0}
|
||||
#define ChannelSettings_init_default {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0, 0, 0, 0}
|
||||
#define RadioConfig_init_default {false, RadioConfig_UserPreferences_init_default, false, ChannelSettings_init_default}
|
||||
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
|
||||
#define RadioConfig_UserPreferences_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0}
|
||||
#define NodeInfo_init_default {0, false, User_init_default, false, Position_init_default, 0, 0}
|
||||
#define MyNodeInfo_init_default {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define LogRecord_init_default {"", 0, "", _LogRecord_Level_MIN}
|
||||
@@ -313,7 +343,7 @@ extern "C" {
|
||||
#define MeshPacket_init_zero {0, 0, 0, {SubPacket_init_zero}, 0, 0, 0, 0, 0, 0}
|
||||
#define ChannelSettings_init_zero {0, _ChannelSettings_ModemConfig_MIN, {0, {0}}, "", 0, 0, 0, 0, 0, 0, 0}
|
||||
#define RadioConfig_init_zero {false, RadioConfig_UserPreferences_init_zero, false, ChannelSettings_init_zero}
|
||||
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}}
|
||||
#define RadioConfig_UserPreferences_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "", "", 0, _RegionCode_MIN, _ChargeCurrent_MIN, _LocationSharing_MIN, _GpsOperation_MIN, 0, 0, 0, 0, 0, 0, 0, 0, {0, 0, 0}, 0, 0, 0, 0, 0}
|
||||
#define NodeInfo_init_zero {0, false, User_init_zero, false, Position_init_zero, 0, 0}
|
||||
#define MyNodeInfo_init_zero {0, 0, 0, "", "", "", _CriticalErrorCode_MIN, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define LogRecord_init_zero {"", 0, "", _LogRecord_Level_MIN}
|
||||
@@ -371,6 +401,7 @@ extern "C" {
|
||||
#define RadioConfig_UserPreferences_wifi_password_tag 13
|
||||
#define RadioConfig_UserPreferences_wifi_ap_mode_tag 14
|
||||
#define RadioConfig_UserPreferences_region_tag 15
|
||||
#define RadioConfig_UserPreferences_charge_current_tag 16
|
||||
#define RadioConfig_UserPreferences_location_share_tag 32
|
||||
#define RadioConfig_UserPreferences_gps_operation_tag 33
|
||||
#define RadioConfig_UserPreferences_gps_update_interval_tag 34
|
||||
@@ -381,6 +412,11 @@ extern "C" {
|
||||
#define RadioConfig_UserPreferences_factory_reset_tag 100
|
||||
#define RadioConfig_UserPreferences_debug_log_enabled_tag 101
|
||||
#define RadioConfig_UserPreferences_ignore_incoming_tag 103
|
||||
#define RadioConfig_UserPreferences_serialplugin_enabled_tag 120
|
||||
#define RadioConfig_UserPreferences_serialplugin_echo_tag 121
|
||||
#define RadioConfig_UserPreferences_serialplugin_rxd_tag 122
|
||||
#define RadioConfig_UserPreferences_serialplugin_txd_tag 123
|
||||
#define RadioConfig_UserPreferences_serialplugin_timeout_tag 124
|
||||
#define RouteDiscovery_route_tag 2
|
||||
#define User_id_tag 1
|
||||
#define User_long_name_tag 2
|
||||
@@ -533,6 +569,7 @@ X(a, STATIC, SINGULAR, STRING, wifi_ssid, 12) \
|
||||
X(a, STATIC, SINGULAR, STRING, wifi_password, 13) \
|
||||
X(a, STATIC, SINGULAR, BOOL, wifi_ap_mode, 14) \
|
||||
X(a, STATIC, SINGULAR, UENUM, region, 15) \
|
||||
X(a, STATIC, SINGULAR, UENUM, charge_current, 16) \
|
||||
X(a, STATIC, SINGULAR, UENUM, location_share, 32) \
|
||||
X(a, STATIC, SINGULAR, UENUM, gps_operation, 33) \
|
||||
X(a, STATIC, SINGULAR, UINT32, gps_update_interval, 34) \
|
||||
@@ -542,7 +579,12 @@ X(a, STATIC, SINGULAR, BOOL, is_low_power, 38) \
|
||||
X(a, STATIC, SINGULAR, BOOL, fixed_position, 39) \
|
||||
X(a, STATIC, SINGULAR, BOOL, factory_reset, 100) \
|
||||
X(a, STATIC, SINGULAR, BOOL, debug_log_enabled, 101) \
|
||||
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103)
|
||||
X(a, STATIC, REPEATED, UINT32, ignore_incoming, 103) \
|
||||
X(a, STATIC, SINGULAR, BOOL, serialplugin_enabled, 120) \
|
||||
X(a, STATIC, SINGULAR, BOOL, serialplugin_echo, 121) \
|
||||
X(a, STATIC, SINGULAR, UINT32, serialplugin_rxd, 122) \
|
||||
X(a, STATIC, SINGULAR, UINT32, serialplugin_txd, 123) \
|
||||
X(a, STATIC, SINGULAR, UINT32, serialplugin_timeout, 124)
|
||||
#define RadioConfig_UserPreferences_CALLBACK NULL
|
||||
#define RadioConfig_UserPreferences_DEFAULT NULL
|
||||
|
||||
@@ -654,13 +696,13 @@ extern const pb_msgdesc_t ToRadio_msg;
|
||||
#define SubPacket_size 275
|
||||
#define MeshPacket_size 320
|
||||
#define ChannelSettings_size 95
|
||||
#define RadioConfig_size 319
|
||||
#define RadioConfig_UserPreferences_size 219
|
||||
#define RadioConfig_size 349
|
||||
#define RadioConfig_UserPreferences_size 249
|
||||
#define NodeInfo_size 132
|
||||
#define MyNodeInfo_size 106
|
||||
#define LogRecord_size 81
|
||||
#define FromRadio_size 329
|
||||
#define ToRadio_size 323
|
||||
#define FromRadio_size 358
|
||||
#define ToRadio_size 353
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* extern "C" */
|
||||
|
||||
@@ -18,13 +18,15 @@ typedef enum _PortNum {
|
||||
PortNum_NODEINFO_APP = 4,
|
||||
PortNum_REPLY_APP = 32,
|
||||
PortNum_IP_TUNNEL_APP = 33,
|
||||
PortNum_PRIVATE_APP = 256
|
||||
PortNum_SERIAL_APP = 64,
|
||||
PortNum_PRIVATE_APP = 256,
|
||||
PortNum_ATAK_FORWARDER = 257
|
||||
} PortNum;
|
||||
|
||||
/* Helper constants for enums */
|
||||
#define _PortNum_MIN PortNum_UNKNOWN_APP
|
||||
#define _PortNum_MAX PortNum_PRIVATE_APP
|
||||
#define _PortNum_ARRAYSIZE ((PortNum)(PortNum_PRIVATE_APP+1))
|
||||
#define _PortNum_MAX PortNum_ATAK_FORWARDER
|
||||
#define _PortNum_ARRAYSIZE ((PortNum)(PortNum_ATAK_FORWARDER+1))
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
#include "meshwifi/meshhttp.h"
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
#include "airtime.h"
|
||||
#include "configuration.h"
|
||||
#include "esp_task_wdt.h"
|
||||
#include "main.h"
|
||||
#include "meshhttpStatic.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include "mesh/http/ContentHelper.h"
|
||||
#include "mesh/http/ContentStatic.h"
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#include "power.h"
|
||||
#include "sleep.h"
|
||||
#include <HTTPBodyParser.hpp>
|
||||
#include <HTTPMultipartBodyParser.hpp>
|
||||
#include <HTTPURLEncodedBodyParser.hpp>
|
||||
#include <SPIFFS.h>
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
// Persistant Data Storage
|
||||
#include <Preferences.h>
|
||||
Preferences prefs;
|
||||
#ifndef NO_ESP32
|
||||
#include "esp_task_wdt.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
Including the esp32_https_server library will trigger a compile time error. I've
|
||||
@@ -42,40 +39,7 @@ Preferences prefs;
|
||||
// The HTTPS Server comes in a separate namespace. For easier use, include it here.
|
||||
using namespace httpsserver;
|
||||
|
||||
SSLCert *cert;
|
||||
HTTPSServer *secureServer;
|
||||
HTTPServer *insecureServer;
|
||||
|
||||
// Our API to handle messages to and from the radio.
|
||||
HttpAPI webAPI;
|
||||
|
||||
// Declare some handler functions for the various URLs on the server
|
||||
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleStyleCSS(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleHotspot(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleFavicon(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleRoot(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleStaticPost(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleStatic(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleRestart(HTTPRequest *req, HTTPResponse *res);
|
||||
void handle404(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleFormUpload(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleScanNetworks(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleBlinkLED(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleReport(HTTPRequest *req, HTTPResponse *res);
|
||||
|
||||
void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
|
||||
void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
|
||||
void middlewareSession(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
|
||||
|
||||
bool isWebServerReady = 0;
|
||||
bool isCertReady = 0;
|
||||
|
||||
uint32_t timeSpeedUp = 0;
|
||||
#include "mesh/http/ContentHandler.h"
|
||||
|
||||
// We need to specify some content-type mapping, so the resources get delivered with the
|
||||
// right content type and are displayed correctly in the browser
|
||||
@@ -86,161 +50,32 @@ char contentTypes[][2][32] = {{".txt", "text/plain"}, {".html", "text/html"}
|
||||
{".css", "text/css"}, {".ico", "image/vnd.microsoft.icon"},
|
||||
{".svg", "image/svg+xml"}, {"", ""}};
|
||||
|
||||
void handleWebResponse()
|
||||
// Our API to handle messages to and from the radio.
|
||||
HttpAPI webAPI;
|
||||
|
||||
uint32_t numberOfRequests = 0;
|
||||
uint32_t timeSpeedUp = 0;
|
||||
|
||||
uint32_t getTimeSpeedUp()
|
||||
{
|
||||
if (isWifiAvailable() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isWebServerReady) {
|
||||
// We're going to handle the DNS responder here so it
|
||||
// will be ignored by the NRF boards.
|
||||
handleDNSResponse();
|
||||
|
||||
secureServer->loop();
|
||||
insecureServer->loop();
|
||||
}
|
||||
|
||||
/*
|
||||
Slow down the CPU if we have not received a request within the last few
|
||||
seconds.
|
||||
*/
|
||||
if (millis() - timeSpeedUp >= (25 * 1000)) {
|
||||
setCpuFrequencyMhz(80);
|
||||
timeSpeedUp = millis();
|
||||
}
|
||||
return timeSpeedUp;
|
||||
}
|
||||
|
||||
void taskCreateCert(void *parameter)
|
||||
void setTimeSpeedUp()
|
||||
{
|
||||
|
||||
prefs.begin("MeshtasticHTTPS", false);
|
||||
|
||||
// Delete the saved certs
|
||||
if (0) {
|
||||
DEBUG_MSG("Deleting any saved SSL keys ...\n");
|
||||
// prefs.clear();
|
||||
prefs.remove("PK");
|
||||
prefs.remove("cert");
|
||||
}
|
||||
|
||||
size_t pkLen = prefs.getBytesLength("PK");
|
||||
size_t certLen = prefs.getBytesLength("cert");
|
||||
|
||||
DEBUG_MSG("Checking if we have a previously saved SSL Certificate.\n");
|
||||
|
||||
if (pkLen && certLen) {
|
||||
DEBUG_MSG("Existing SSL Certificate found!\n");
|
||||
} else {
|
||||
DEBUG_MSG("Creating the certificate. This may take a while. Please wait...\n");
|
||||
yield();
|
||||
cert = new SSLCert();
|
||||
yield();
|
||||
int createCertResult = createSelfSignedCert(*cert, KEYSIZE_2048, "CN=meshtastic.local,O=Meshtastic,C=US",
|
||||
"20190101000000", "20300101000000");
|
||||
yield();
|
||||
|
||||
if (createCertResult != 0) {
|
||||
DEBUG_MSG("Creating the certificate failed\n");
|
||||
|
||||
// Serial.printf("Creating the certificate failed. Error Code = 0x%02X, check SSLCert.hpp for details",
|
||||
// createCertResult);
|
||||
// while (true)
|
||||
// delay(500);
|
||||
} else {
|
||||
DEBUG_MSG("Creating the certificate was successful\n");
|
||||
|
||||
DEBUG_MSG("Created Private Key: %d Bytes\n", cert->getPKLength());
|
||||
// for (int i = 0; i < cert->getPKLength(); i++)
|
||||
// Serial.print(cert->getPKData()[i], HEX);
|
||||
// Serial.println();
|
||||
|
||||
DEBUG_MSG("Created Certificate: %d Bytes\n", cert->getCertLength());
|
||||
// for (int i = 0; i < cert->getCertLength(); i++)
|
||||
// Serial.print(cert->getCertData()[i], HEX);
|
||||
// Serial.println();
|
||||
|
||||
prefs.putBytes("PK", (uint8_t *)cert->getPKData(), cert->getPKLength());
|
||||
prefs.putBytes("cert", (uint8_t *)cert->getCertData(), cert->getCertLength());
|
||||
}
|
||||
}
|
||||
|
||||
isCertReady = 1;
|
||||
vTaskDelete(NULL);
|
||||
timeSpeedUp = millis();
|
||||
}
|
||||
|
||||
void createSSLCert()
|
||||
void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer)
|
||||
{
|
||||
|
||||
if (isWifiAvailable() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new process just to handle creating the cert.
|
||||
// This is a workaround for Bug: https://github.com/fhessel/esp32_https_server/issues/48
|
||||
// jm@casler.org (Oct 2020)
|
||||
xTaskCreate(taskCreateCert, /* Task function. */
|
||||
"createCert", /* String with name of task. */
|
||||
16384, /* Stack size in bytes. */
|
||||
NULL, /* Parameter passed as input of the task */
|
||||
16, /* Priority of the task. */
|
||||
NULL); /* Task handle. */
|
||||
|
||||
DEBUG_MSG("Waiting for SSL Cert to be generated.\n");
|
||||
while (!isCertReady) {
|
||||
DEBUG_MSG(".");
|
||||
delay(1000);
|
||||
yield();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
DEBUG_MSG("SSL Cert Ready!\n");
|
||||
}
|
||||
|
||||
void initWebServer()
|
||||
{
|
||||
DEBUG_MSG("Initializing Web Server ...\n");
|
||||
|
||||
prefs.begin("MeshtasticHTTPS", false);
|
||||
|
||||
size_t pkLen = prefs.getBytesLength("PK");
|
||||
size_t certLen = prefs.getBytesLength("cert");
|
||||
|
||||
DEBUG_MSG("Checking if we have a previously saved SSL Certificate.\n");
|
||||
|
||||
if (pkLen && certLen) {
|
||||
|
||||
uint8_t *pkBuffer = new uint8_t[pkLen];
|
||||
prefs.getBytes("PK", pkBuffer, pkLen);
|
||||
|
||||
uint8_t *certBuffer = new uint8_t[certLen];
|
||||
prefs.getBytes("cert", certBuffer, certLen);
|
||||
|
||||
cert = new SSLCert(certBuffer, certLen, pkBuffer, pkLen);
|
||||
|
||||
DEBUG_MSG("Retrieved Private Key: %d Bytes\n", cert->getPKLength());
|
||||
// DEBUG_MSG("Retrieved Private Key: " + String(cert->getPKLength()) + " Bytes");
|
||||
// for (int i = 0; i < cert->getPKLength(); i++)
|
||||
// Serial.print(cert->getPKData()[i], HEX);
|
||||
// Serial.println();
|
||||
|
||||
DEBUG_MSG("Retrieved Certificate: %d Bytes\n", cert->getCertLength());
|
||||
// for (int i = 0; i < cert->getCertLength(); i++)
|
||||
// Serial.print(cert->getCertData()[i], HEX);
|
||||
// Serial.println();
|
||||
} else {
|
||||
DEBUG_MSG("Web Server started without SSL keys! How did this happen?\n");
|
||||
}
|
||||
|
||||
// We can now use the new certificate to setup our server as usual.
|
||||
secureServer = new HTTPSServer(cert);
|
||||
insecureServer = new HTTPServer();
|
||||
|
||||
// For every resource available on the server, we need to create a ResourceNode
|
||||
// The ResourceNode links URL and HTTP method to a handler function
|
||||
|
||||
ResourceNode *nodeAPIv1ToRadioOptions = new ResourceNode("/api/v1/toradio", "OPTIONS", &handleAPIv1ToRadio);
|
||||
ResourceNode *nodeAPIv1ToRadio = new ResourceNode("/api/v1/toradio", "PUT", &handleAPIv1ToRadio);
|
||||
ResourceNode *nodeAPIv1FromRadio = new ResourceNode("/api/v1/fromradio", "GET", &handleAPIv1FromRadio);
|
||||
|
||||
ResourceNode *nodeHotspot = new ResourceNode("/hotspot-detect.html", "GET", &handleHotspot);
|
||||
ResourceNode *nodeFavicon = new ResourceNode("/favicon.ico", "GET", &handleFavicon);
|
||||
ResourceNode *nodeRoot = new ResourceNode("/", "GET", &handleRoot);
|
||||
@@ -253,7 +88,7 @@ void initWebServer()
|
||||
ResourceNode *nodeJsonScanNetworks = new ResourceNode("/json/scanNetworks", "GET", &handleScanNetworks);
|
||||
ResourceNode *nodeJsonBlinkLED = new ResourceNode("/json/blink", "POST", &handleBlinkLED);
|
||||
ResourceNode *nodeJsonReport = new ResourceNode("/json/report", "GET", &handleReport);
|
||||
ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static/", "GET", &handleSpiffsBrowseStatic);
|
||||
ResourceNode *nodeJsonSpiffsBrowseStatic = new ResourceNode("/json/spiffs/browse/static", "GET", &handleSpiffsBrowseStatic);
|
||||
ResourceNode *nodeJsonDelete = new ResourceNode("/json/spiffs/delete/static", "DELETE", &handleSpiffsDeleteStatic);
|
||||
|
||||
// Secure nodes
|
||||
@@ -297,16 +132,6 @@ void initWebServer()
|
||||
insecureServer->setDefaultNode(node404);
|
||||
|
||||
insecureServer->addMiddleware(&middlewareSpeedUp160);
|
||||
|
||||
DEBUG_MSG("Starting Web Servers...\n");
|
||||
secureServer->start();
|
||||
insecureServer->start();
|
||||
if (secureServer->isRunning() && insecureServer->isRunning()) {
|
||||
DEBUG_MSG("HTTP and HTTPS Web Servers Ready! :-) \n");
|
||||
isWebServerReady = 1;
|
||||
} else {
|
||||
DEBUG_MSG("HTTP and HTTPS Web Servers Failed! ;-( \n");
|
||||
}
|
||||
}
|
||||
|
||||
void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function<void()> next)
|
||||
@@ -319,7 +144,9 @@ void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function<voi
|
||||
powerFSM.trigger(EVENT_CONTACT_FROM_PHONE);
|
||||
|
||||
setCpuFrequencyMhz(240);
|
||||
timeSpeedUp = millis();
|
||||
setTimeSpeedUp();
|
||||
|
||||
numberOfRequests++;
|
||||
}
|
||||
|
||||
void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function<void()> next)
|
||||
@@ -337,7 +164,108 @@ void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function<voi
|
||||
if (getCpuFrequencyMhz() != 240) {
|
||||
setCpuFrequencyMhz(160);
|
||||
}
|
||||
timeSpeedUp = millis();
|
||||
setTimeSpeedUp();
|
||||
|
||||
numberOfRequests++;
|
||||
}
|
||||
|
||||
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
|
||||
DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1FromRadio\n");
|
||||
|
||||
/*
|
||||
For documentation, see:
|
||||
https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
|
||||
https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
|
||||
|
||||
Example:
|
||||
http://10.10.30.198/api/v1/fromradio
|
||||
*/
|
||||
|
||||
// Get access to the parameters
|
||||
ResourceParameters *params = req->getParams();
|
||||
|
||||
// std::string paramAll = "all";
|
||||
std::string valueAll;
|
||||
|
||||
// Status code is 200 OK by default.
|
||||
res->setHeader("Content-Type", "application/x-protobuf");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "GET");
|
||||
res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto");
|
||||
|
||||
uint8_t txBuf[MAX_STREAM_BUF_SIZE];
|
||||
uint32_t len = 1;
|
||||
|
||||
if (params->getQueryParameter("all", valueAll)) {
|
||||
|
||||
// If all is ture, return all the buffers we have available
|
||||
// to us at this point in time.
|
||||
if (valueAll == "true") {
|
||||
while (len) {
|
||||
len = webAPI.getFromRadio(txBuf);
|
||||
res->write(txBuf, len);
|
||||
}
|
||||
|
||||
// Otherwise, just return one protobuf
|
||||
} else {
|
||||
len = webAPI.getFromRadio(txBuf);
|
||||
res->write(txBuf, len);
|
||||
}
|
||||
|
||||
// the param "all" was not spcified. Return just one protobuf
|
||||
} else {
|
||||
len = webAPI.getFromRadio(txBuf);
|
||||
res->write(txBuf, len);
|
||||
}
|
||||
|
||||
DEBUG_MSG("--------------- webAPI handleAPIv1FromRadio, len %d\n", len);
|
||||
}
|
||||
|
||||
void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1ToRadio\n");
|
||||
|
||||
/*
|
||||
For documentation, see:
|
||||
https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
|
||||
https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
|
||||
|
||||
Example:
|
||||
http://10.10.30.198/api/v1/toradio
|
||||
*/
|
||||
|
||||
// Status code is 200 OK by default.
|
||||
|
||||
res->setHeader("Content-Type", "application/x-protobuf");
|
||||
res->setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS");
|
||||
res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto");
|
||||
|
||||
if (req->getMethod() == "OPTIONS") {
|
||||
res->setStatusCode(204); // Success with no content
|
||||
res->print("");
|
||||
return;
|
||||
}
|
||||
|
||||
byte buffer[MAX_TO_FROM_RADIO_SIZE];
|
||||
size_t s = req->readBytes(buffer, MAX_TO_FROM_RADIO_SIZE);
|
||||
|
||||
DEBUG_MSG("Received %d bytes from PUT request\n", s);
|
||||
webAPI.handleToRadio(buffer, s);
|
||||
|
||||
res->write(buffer, s);
|
||||
DEBUG_MSG("--------------- webAPI handleAPIv1ToRadio\n");
|
||||
}
|
||||
|
||||
void handleFavicon(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
// Set Content-Type
|
||||
res->setHeader("Content-Type", "image/vnd.microsoft.icon");
|
||||
// Write data from header file
|
||||
res->write(FAVICON_DATA, FAVICON_LENGTH);
|
||||
}
|
||||
|
||||
void handleStaticPost(HTTPRequest *req, HTTPResponse *res)
|
||||
@@ -414,7 +342,8 @@ void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
// jm
|
||||
|
||||
res->setHeader("Content-Type", "application/json");
|
||||
// res->setHeader("Content-Type", "text/html");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "GET");
|
||||
|
||||
File root = SPIFFS.open("/");
|
||||
|
||||
@@ -469,6 +398,8 @@ void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
std::string paramValDelete;
|
||||
|
||||
res->setHeader("Content-Type", "application/json");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "DELETE");
|
||||
if (params->getQueryParameter("delete", paramValDelete)) {
|
||||
std::string pathDelete = "/" + paramValDelete;
|
||||
if (SPIFFS.remove(pathDelete.c_str())) {
|
||||
@@ -487,6 +418,67 @@ void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
To convert text to c strings:
|
||||
|
||||
https://tomeko.net/online_tools/cpp_text_escape.php?lang=en
|
||||
*/
|
||||
void handleRoot(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
res->setHeader("Content-Type", "text/html");
|
||||
|
||||
res->setHeader("Set-Cookie",
|
||||
"mt_session=" + httpsserver::intToString(random(1, 9999999)) + "; Expires=Wed, 20 Apr 2049 4:20:00 PST");
|
||||
|
||||
std::string cookie = req->getHeader("Cookie");
|
||||
// String cookieString = cookie.c_str();
|
||||
// uint8_t nameIndex = cookieString.indexOf("mt_session");
|
||||
// DEBUG_MSG(cookie.c_str());
|
||||
|
||||
std::string filename = "/static/index.html";
|
||||
std::string filenameGzip = "/static/index.html.gz";
|
||||
|
||||
if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) {
|
||||
// Send "404 Not Found" as response, as the file doesn't seem to exist
|
||||
res->setStatusCode(404);
|
||||
res->setStatusText("Not found");
|
||||
res->println("404 Not Found");
|
||||
res->printf("<p>File not found: %s</p>\n", filename.c_str());
|
||||
res->printf("<p></p>\n");
|
||||
res->printf("<p>You have gotten this error because the filesystem for the web server has not been loaded.</p>\n");
|
||||
res->printf("<p>Please review the 'Common Problems' section of the <a "
|
||||
"href=https://github.com/meshtastic/Meshtastic-device/wiki/"
|
||||
"How-to-use-the-Meshtastic-Web-Interface-over-WiFi>web interface</a> documentation.</p>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to open the file from SPIFFS
|
||||
File file;
|
||||
|
||||
if (SPIFFS.exists(filename.c_str())) {
|
||||
file = SPIFFS.open(filename.c_str());
|
||||
if (!file.available()) {
|
||||
DEBUG_MSG("File not available - %s\n", filename.c_str());
|
||||
}
|
||||
|
||||
} else if (SPIFFS.exists(filenameGzip.c_str())) {
|
||||
file = SPIFFS.open(filenameGzip.c_str());
|
||||
res->setHeader("Content-Encoding", "gzip");
|
||||
if (!file.available()) {
|
||||
DEBUG_MSG("File not available\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Read the file from SPIFFS and write it to the HTTP response body
|
||||
size_t length = 0;
|
||||
do {
|
||||
char buffer[256];
|
||||
length = file.read((uint8_t *)buffer, 256);
|
||||
std::string bufferString(buffer, length);
|
||||
res->write((uint8_t *)bufferString.c_str(), bufferString.size());
|
||||
} while (length > 0);
|
||||
}
|
||||
|
||||
void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
// Get access to the parameters
|
||||
@@ -494,6 +486,9 @@ void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res)
|
||||
std::string paramValDelete;
|
||||
std::string paramValEdit;
|
||||
|
||||
DEBUG_MSG("Static Browse - Disabling keep-alive\n");
|
||||
res->setHeader("Connection", "close");
|
||||
|
||||
// Set a default content type
|
||||
res->setHeader("Content-Type", "text/html");
|
||||
|
||||
@@ -700,6 +695,11 @@ void handleStatic(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
|
||||
DEBUG_MSG("Form Upload - Disabling keep-alive\n");
|
||||
res->setHeader("Connection", "close");
|
||||
|
||||
DEBUG_MSG("Form Upload - Set frequency to 240mhz\n");
|
||||
// The upload process is very CPU intensive. Let's speed things up a bit.
|
||||
setCpuFrequencyMhz(240);
|
||||
|
||||
@@ -708,6 +708,7 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
|
||||
// Then we select the body parser based on the encoding.
|
||||
// Actually we do this only for documentary purposes, we know the form is going
|
||||
// to be multipart/form-data.
|
||||
DEBUG_MSG("Form Upload - Creating body parser reference\n");
|
||||
HTTPBodyParser *parser;
|
||||
std::string contentType = req->getHeader("Content-Type");
|
||||
|
||||
@@ -723,6 +724,7 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
// Now, we can decide based on the content type:
|
||||
if (contentType == "multipart/form-data") {
|
||||
DEBUG_MSG("Form Upload - multipart/form-data\n");
|
||||
parser = new HTTPMultipartBodyParser(req);
|
||||
} else {
|
||||
Serial.printf("Unknown POST Content-Type: %s\n", contentType.c_str());
|
||||
@@ -757,21 +759,21 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
|
||||
|
||||
// Double check that it is what we expect
|
||||
if (name != "file") {
|
||||
DEBUG_MSG("Skipping unexpected field");
|
||||
DEBUG_MSG("Skipping unexpected field\n");
|
||||
res->println("<p>No file found.</p>");
|
||||
return;
|
||||
}
|
||||
|
||||
// Double check that it is what we expect
|
||||
if (filename == "") {
|
||||
DEBUG_MSG("Skipping unexpected field");
|
||||
DEBUG_MSG("Skipping unexpected field\n");
|
||||
res->println("<p>No file found.</p>");
|
||||
return;
|
||||
}
|
||||
|
||||
// SPIFFS limits the total lenth of a path + file to 31 characters.
|
||||
if (filename.length() + 8 > 31) {
|
||||
DEBUG_MSG("Uploaded filename too long!");
|
||||
DEBUG_MSG("Uploaded filename too long!\n");
|
||||
res->println("<p>Uploaded filename too long! Limit of 23 characters.</p>");
|
||||
delete parser;
|
||||
return;
|
||||
@@ -806,10 +808,10 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
|
||||
return;
|
||||
}
|
||||
|
||||
//if (readLength) {
|
||||
file.write(buf, readLength);
|
||||
fileLength += readLength;
|
||||
DEBUG_MSG("File Length %i\n", fileLength);
|
||||
// if (readLength) {
|
||||
file.write(buf, readLength);
|
||||
fileLength += readLength;
|
||||
DEBUG_MSG("File Length %i\n", fileLength);
|
||||
//}
|
||||
}
|
||||
// enableLoopWDT();
|
||||
@@ -824,6 +826,118 @@ void handleFormUpload(HTTPRequest *req, HTTPResponse *res)
|
||||
delete parser;
|
||||
}
|
||||
|
||||
void handleReport(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
|
||||
ResourceParameters *params = req->getParams();
|
||||
std::string content;
|
||||
|
||||
if (!params->getQueryParameter("content", content)) {
|
||||
content = "json";
|
||||
}
|
||||
|
||||
if (content == "json") {
|
||||
res->setHeader("Content-Type", "application/json");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "GET");
|
||||
|
||||
} else {
|
||||
res->setHeader("Content-Type", "text/html");
|
||||
res->println("<pre>");
|
||||
}
|
||||
|
||||
res->println("{");
|
||||
|
||||
res->println("\"data\": {");
|
||||
|
||||
res->println("\"airtime\": {");
|
||||
|
||||
uint32_t *logArray;
|
||||
|
||||
res->print("\"tx_log\": [");
|
||||
|
||||
logArray = airtimeReport(TX_LOG);
|
||||
for (int i = 0; i < getPeriodsToLog(); i++) {
|
||||
uint32_t tmp;
|
||||
tmp = *(logArray + i);
|
||||
res->printf("%d", tmp);
|
||||
if (i != getPeriodsToLog() - 1) {
|
||||
res->print(", ");
|
||||
}
|
||||
}
|
||||
|
||||
res->println("],");
|
||||
res->print("\"rx_log\": [");
|
||||
|
||||
logArray = airtimeReport(RX_LOG);
|
||||
for (int i = 0; i < getPeriodsToLog(); i++) {
|
||||
uint32_t tmp;
|
||||
tmp = *(logArray + i);
|
||||
res->printf("%d", tmp);
|
||||
if (i != getPeriodsToLog() - 1) {
|
||||
res->print(", ");
|
||||
}
|
||||
}
|
||||
|
||||
res->println("],");
|
||||
res->print("\"rx_all_log\": [");
|
||||
|
||||
logArray = airtimeReport(RX_ALL_LOG);
|
||||
for (int i = 0; i < getPeriodsToLog(); i++) {
|
||||
uint32_t tmp;
|
||||
tmp = *(logArray + i);
|
||||
res->printf("%d", tmp);
|
||||
if (i != getPeriodsToLog() - 1) {
|
||||
res->print(", ");
|
||||
}
|
||||
}
|
||||
|
||||
res->println("],");
|
||||
res->printf("\"seconds_since_boot\": %u,\n", getSecondsSinceBoot());
|
||||
res->printf("\"seconds_per_period\": %u,\n", getSecondsPerPeriod());
|
||||
res->printf("\"periods_to_log\": %u\n", getPeriodsToLog());
|
||||
|
||||
res->println("},");
|
||||
|
||||
res->println("\"wifi\": {");
|
||||
|
||||
res->printf("\"web_request_count\": %d,\n", numberOfRequests);
|
||||
res->println("\"rssi\": " + String(WiFi.RSSI()) + ",");
|
||||
|
||||
if (radioConfig.preferences.wifi_ap_mode || isSoftAPForced()) {
|
||||
res->println("\"ip\": \"" + String(WiFi.softAPIP().toString().c_str()) + "\"");
|
||||
} else {
|
||||
res->println("\"ip\": \"" + String(WiFi.localIP().toString().c_str()) + "\"");
|
||||
}
|
||||
|
||||
res->println("},");
|
||||
|
||||
res->println("\"memory\": {");
|
||||
res->printf("\"heap_total\": %d,\n", ESP.getHeapSize());
|
||||
res->printf("\"heap_free\": %d,\n", ESP.getFreeHeap());
|
||||
res->printf("\"psram_total\": %d,\n", ESP.getPsramSize());
|
||||
res->printf("\"psram_free\": %d,\n", ESP.getFreePsram());
|
||||
res->println("\"spiffs_total\" : " + String(SPIFFS.totalBytes()) + ",");
|
||||
res->println("\"spiffs_used\" : " + String(SPIFFS.usedBytes()) + ",");
|
||||
res->println("\"spiffs_free\" : " + String(SPIFFS.totalBytes() - SPIFFS.usedBytes()));
|
||||
res->println("},");
|
||||
|
||||
res->println("\"power\": {");
|
||||
res->printf("\"battery_percent\": %u,\n", powerStatus->getBatteryChargePercent());
|
||||
res->printf("\"battery_voltage_mv\": %u,\n", powerStatus->getBatteryVoltageMv());
|
||||
res->printf("\"has_battery\": %s,\n", BoolToString(powerStatus->getHasBattery()));
|
||||
res->printf("\"has_usb\": %s,\n", BoolToString(powerStatus->getHasUSB()));
|
||||
res->printf("\"is_charging\": %s\n", BoolToString(powerStatus->getIsCharging()));
|
||||
res->println("}");
|
||||
|
||||
res->println("},");
|
||||
|
||||
res->println("\"status\": \"ok\"");
|
||||
res->println("}");
|
||||
}
|
||||
|
||||
// --------
|
||||
|
||||
void handle404(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
|
||||
@@ -861,165 +975,18 @@ void handleHotspot(HTTPRequest *req, HTTPResponse *res)
|
||||
// Status code is 200 OK by default.
|
||||
// We want to deliver a simple HTML page, so we send a corresponding content type:
|
||||
res->setHeader("Content-Type", "text/html");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "GET");
|
||||
|
||||
// res->println("<!DOCTYPE html>");
|
||||
res->println("<meta http-equiv=\"refresh\" content=\"0;url=/\" />\n");
|
||||
}
|
||||
|
||||
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
|
||||
DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1FromRadio\n");
|
||||
|
||||
/*
|
||||
For documentation, see:
|
||||
https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
|
||||
https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
|
||||
|
||||
Example:
|
||||
http://10.10.30.198/api/v1/fromradio
|
||||
*/
|
||||
|
||||
// Get access to the parameters
|
||||
ResourceParameters *params = req->getParams();
|
||||
|
||||
// std::string paramAll = "all";
|
||||
std::string valueAll;
|
||||
|
||||
// Status code is 200 OK by default.
|
||||
res->setHeader("Content-Type", "application/x-protobuf");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "PUT, GET");
|
||||
res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto");
|
||||
|
||||
uint8_t txBuf[MAX_STREAM_BUF_SIZE];
|
||||
uint32_t len = 1;
|
||||
|
||||
if (params->getQueryParameter("all", valueAll)) {
|
||||
|
||||
// If all is ture, return all the buffers we have available
|
||||
// to us at this point in time.
|
||||
if (valueAll == "true") {
|
||||
while (len) {
|
||||
len = webAPI.getFromRadio(txBuf);
|
||||
res->write(txBuf, len);
|
||||
}
|
||||
|
||||
// Otherwise, just return one protobuf
|
||||
} else {
|
||||
len = webAPI.getFromRadio(txBuf);
|
||||
res->write(txBuf, len);
|
||||
}
|
||||
|
||||
// the param "all" was not spcified. Return just one protobuf
|
||||
} else {
|
||||
len = webAPI.getFromRadio(txBuf);
|
||||
res->write(txBuf, len);
|
||||
}
|
||||
|
||||
DEBUG_MSG("--------------- webAPI handleAPIv1FromRadio, len %d\n", len);
|
||||
}
|
||||
|
||||
void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
DEBUG_MSG("+++++++++++++++ webAPI handleAPIv1ToRadio\n");
|
||||
|
||||
/*
|
||||
For documentation, see:
|
||||
https://github.com/meshtastic/Meshtastic-device/wiki/HTTP-REST-API-discussion
|
||||
https://github.com/meshtastic/Meshtastic-device/blob/master/docs/software/device-api.md
|
||||
|
||||
Example:
|
||||
http://10.10.30.198/api/v1/toradio
|
||||
*/
|
||||
|
||||
// Status code is 200 OK by default.
|
||||
|
||||
res->setHeader("Content-Type", "application/x-protobuf");
|
||||
res->setHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "PUT, OPTIONS");
|
||||
res->setHeader("X-Protobuf-Schema", "https://raw.githubusercontent.com/meshtastic/Meshtastic-protobufs/master/mesh.proto");
|
||||
|
||||
if (req->getMethod() == "OPTIONS") {
|
||||
res->setStatusCode(204); // Success with no content
|
||||
res->print("");
|
||||
return;
|
||||
}
|
||||
|
||||
byte buffer[MAX_TO_FROM_RADIO_SIZE];
|
||||
size_t s = req->readBytes(buffer, MAX_TO_FROM_RADIO_SIZE);
|
||||
|
||||
DEBUG_MSG("Received %d bytes from PUT request\n", s);
|
||||
webAPI.handleToRadio(buffer, s);
|
||||
|
||||
res->write(buffer, s);
|
||||
DEBUG_MSG("--------------- webAPI handleAPIv1ToRadio\n");
|
||||
}
|
||||
|
||||
/*
|
||||
To convert text to c strings:
|
||||
|
||||
https://tomeko.net/online_tools/cpp_text_escape.php?lang=en
|
||||
*/
|
||||
void handleRoot(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
res->setHeader("Content-Type", "text/html");
|
||||
|
||||
res->setHeader("Set-Cookie",
|
||||
"mt_session=" + httpsserver::intToString(random(1, 9999999)) + "; Expires=Wed, 20 Apr 2049 4:20:00 PST");
|
||||
|
||||
std::string cookie = req->getHeader("Cookie");
|
||||
// String cookieString = cookie.c_str();
|
||||
// uint8_t nameIndex = cookieString.indexOf("mt_session");
|
||||
// DEBUG_MSG(cookie.c_str());
|
||||
|
||||
std::string filename = "/static/index.html";
|
||||
std::string filenameGzip = "/static/index.html.gz";
|
||||
|
||||
if (!SPIFFS.exists(filename.c_str()) && !SPIFFS.exists(filenameGzip.c_str())) {
|
||||
// Send "404 Not Found" as response, as the file doesn't seem to exist
|
||||
res->setStatusCode(404);
|
||||
res->setStatusText("Not found");
|
||||
res->println("404 Not Found");
|
||||
res->printf("<p>File not found: %s</p>\n", filename.c_str());
|
||||
res->printf("<p></p>\n");
|
||||
res->printf("<p>You have gotten this error because the filesystem for the web server has not been loaded.</p>\n");
|
||||
res->printf("<p>Please review the 'Common Problems' section of the <a "
|
||||
"href=https://github.com/meshtastic/Meshtastic-device/wiki/How-to-use-the-Meshtastic-Web-Interface-over-WiFi>web interface</a> documentation.</p>\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to open the file from SPIFFS
|
||||
File file;
|
||||
|
||||
if (SPIFFS.exists(filename.c_str())) {
|
||||
file = SPIFFS.open(filename.c_str());
|
||||
if (!file.available()) {
|
||||
DEBUG_MSG("File not available - %s\n", filename.c_str());
|
||||
}
|
||||
|
||||
} else if (SPIFFS.exists(filenameGzip.c_str())) {
|
||||
file = SPIFFS.open(filenameGzip.c_str());
|
||||
res->setHeader("Content-Encoding", "gzip");
|
||||
if (!file.available()) {
|
||||
DEBUG_MSG("File not available\n");
|
||||
}
|
||||
}
|
||||
|
||||
// Read the file from SPIFFS and write it to the HTTP response body
|
||||
size_t length = 0;
|
||||
do {
|
||||
char buffer[256];
|
||||
length = file.read((uint8_t *)buffer, 256);
|
||||
std::string bufferString(buffer, length);
|
||||
res->write((uint8_t *)bufferString.c_str(), bufferString.size());
|
||||
} while (length > 0);
|
||||
}
|
||||
|
||||
void handleRestart(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
res->setHeader("Content-Type", "text/html");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "GET");
|
||||
|
||||
DEBUG_MSG("***** Restarted on HTTP(s) Request *****\n");
|
||||
res->println("Restarting");
|
||||
@@ -1030,6 +997,8 @@ void handleRestart(HTTPRequest *req, HTTPResponse *res)
|
||||
void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
res->setHeader("Content-Type", "application/json");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "POST");
|
||||
|
||||
ResourceParameters *params = req->getParams();
|
||||
std::string blink_target;
|
||||
@@ -1058,100 +1027,11 @@ void handleBlinkLED(HTTPRequest *req, HTTPResponse *res)
|
||||
res->println("}");
|
||||
}
|
||||
|
||||
void handleReport(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
|
||||
ResourceParameters *params = req->getParams();
|
||||
std::string content;
|
||||
|
||||
if (!params->getQueryParameter("content", content)) {
|
||||
content = "json";
|
||||
}
|
||||
|
||||
if (content == "json") {
|
||||
res->setHeader("Content-Type", "application/json");
|
||||
|
||||
} else {
|
||||
res->setHeader("Content-Type", "text/html");
|
||||
res->println("<pre>");
|
||||
}
|
||||
|
||||
res->println("{");
|
||||
|
||||
res->println("\"data\": {");
|
||||
|
||||
res->println("\"airtime\": {");
|
||||
|
||||
uint16_t *logArray;
|
||||
|
||||
res->print("\"tx_log\": [");
|
||||
|
||||
logArray = airtimeReport(TX_LOG);
|
||||
for (int i = 0; i < getPeriodsToLog(); i++) {
|
||||
uint16_t tmp;
|
||||
tmp = *(logArray + i);
|
||||
res->printf("%d", tmp);
|
||||
if (i != getPeriodsToLog() - 1) {
|
||||
res->print(", ");
|
||||
}
|
||||
}
|
||||
|
||||
res->println("],");
|
||||
res->print("\"rx_log\": [");
|
||||
|
||||
logArray = airtimeReport(RX_LOG);
|
||||
for (int i = 0; i < getPeriodsToLog(); i++) {
|
||||
uint16_t tmp;
|
||||
tmp = *(logArray + i);
|
||||
res->printf("%d", tmp);
|
||||
if (i != getPeriodsToLog() - 1) {
|
||||
res->print(", ");
|
||||
}
|
||||
}
|
||||
|
||||
res->println("],");
|
||||
res->print("\"rx_all_log\": [");
|
||||
|
||||
logArray = airtimeReport(RX_ALL_LOG);
|
||||
for (int i = 0; i < getPeriodsToLog(); i++) {
|
||||
uint16_t tmp;
|
||||
tmp = *(logArray + i);
|
||||
res->printf("%d", tmp);
|
||||
if (i != getPeriodsToLog() - 1) {
|
||||
res->print(", ");
|
||||
}
|
||||
}
|
||||
|
||||
res->println("],");
|
||||
res->printf("\"seconds_since_boot\": %u,\n", getSecondsSinceBoot());
|
||||
res->printf("\"seconds_per_period\": %u,\n", getSecondsPerPeriod());
|
||||
res->printf("\"periods_to_log\": %u\n", getPeriodsToLog());
|
||||
|
||||
res->println("},");
|
||||
|
||||
res->println("\"wifi\": {");
|
||||
|
||||
res->println("\"rssi\": " + String(WiFi.RSSI()) + ",");
|
||||
|
||||
if (radioConfig.preferences.wifi_ap_mode || isSoftAPForced()) {
|
||||
res->println("\"ip\": \"" + String(WiFi.softAPIP().toString().c_str()) + "\"");
|
||||
} else {
|
||||
res->println("\"ip\": \"" + String(WiFi.localIP().toString().c_str()) + "\"");
|
||||
}
|
||||
|
||||
res->println("},");
|
||||
|
||||
res->println("\"test\": 123");
|
||||
|
||||
res->println("},");
|
||||
|
||||
res->println("\"status\": \"ok\"");
|
||||
res->println("}");
|
||||
}
|
||||
|
||||
void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
res->setHeader("Content-Type", "application/json");
|
||||
res->setHeader("Access-Control-Allow-Origin", "*");
|
||||
res->setHeader("Access-Control-Allow-Methods", "GET");
|
||||
// res->setHeader("Content-Type", "text/html");
|
||||
|
||||
int n = WiFi.scanNetworks();
|
||||
@@ -1191,22 +1071,3 @@ void handleScanNetworks(HTTPRequest *req, HTTPResponse *res)
|
||||
res->println("\"status\": \"ok\"");
|
||||
res->println("}");
|
||||
}
|
||||
|
||||
void handleFavicon(HTTPRequest *req, HTTPResponse *res)
|
||||
{
|
||||
// Set Content-Type
|
||||
res->setHeader("Content-Type", "image/vnd.microsoft.icon");
|
||||
// Write data from header file
|
||||
res->write(FAVICON_DATA, FAVICON_LENGTH);
|
||||
}
|
||||
|
||||
void replaceAll(std::string &str, const std::string &from, const std::string &to)
|
||||
{
|
||||
if (from.empty())
|
||||
return;
|
||||
size_t start_pos = 0;
|
||||
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||
str.replace(start_pos, from.length(), to);
|
||||
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||
}
|
||||
}
|
||||
46
src/mesh/http/ContentHandler.h
Normal file
46
src/mesh/http/ContentHandler.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
void registerHandlers(HTTPServer *insecureServer, HTTPSServer *secureServer);
|
||||
|
||||
// Declare some handler functions for the various URLs on the server
|
||||
void handleAPIv1FromRadio(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleAPIv1ToRadio(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleStyleCSS(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleHotspot(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleRoot(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleStaticBrowse(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleStaticPost(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleStatic(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleRestart(HTTPRequest *req, HTTPResponse *res);
|
||||
void handle404(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleFormUpload(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleScanNetworks(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleSpiffsBrowseStatic(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleSpiffsDeleteStatic(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleBlinkLED(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleReport(HTTPRequest *req, HTTPResponse *res);
|
||||
void handleFavicon(HTTPRequest *req, HTTPResponse *res);
|
||||
|
||||
void middlewareSpeedUp240(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
|
||||
void middlewareSpeedUp160(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
|
||||
void middlewareSession(HTTPRequest *req, HTTPResponse *res, std::function<void()> next);
|
||||
|
||||
uint32_t getTimeSpeedUp();
|
||||
void setTimeSpeedUp();
|
||||
|
||||
|
||||
// Interface to the PhoneAPI to access the protobufs with messages
|
||||
class HttpAPI : public PhoneAPI
|
||||
{
|
||||
|
||||
public:
|
||||
// Nothing here yet
|
||||
|
||||
private:
|
||||
// Nothing here yet
|
||||
|
||||
protected:
|
||||
// Nothing here yet
|
||||
};
|
||||
|
||||
|
||||
14
src/mesh/http/ContentHelper.cpp
Normal file
14
src/mesh/http/ContentHelper.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "mesh/http/ContentHelper.h"
|
||||
//#include <Arduino.h>
|
||||
//#include "main.h"
|
||||
|
||||
void replaceAll(std::string &str, const std::string &from, const std::string &to)
|
||||
{
|
||||
if (from.empty())
|
||||
return;
|
||||
size_t start_pos = 0;
|
||||
while ((start_pos = str.find(from, start_pos)) != std::string::npos) {
|
||||
str.replace(start_pos, from.length(), to);
|
||||
start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
|
||||
}
|
||||
}
|
||||
8
src/mesh/http/ContentHelper.h
Normal file
8
src/mesh/http/ContentHelper.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
#define BoolToString(x) ((x) ? "true" : "false")
|
||||
|
||||
|
||||
void replaceAll(std::string &str, const std::string &from, const std::string &to);
|
||||
|
||||
@@ -2,14 +2,7 @@
|
||||
#include <functional>
|
||||
|
||||
/*
|
||||
Steps:
|
||||
- Compress the .js file to .js.gz
|
||||
- Convert to hex:
|
||||
http://tomeko.net/online_tools/file_to_hex.php?lang=en
|
||||
- Paste into the array
|
||||
- Note the filesize of your .gz file and write the file
|
||||
size into the length int.
|
||||
|
||||
This file contains static content.
|
||||
*/
|
||||
|
||||
// Length of the binary data
|
||||
231
src/mesh/http/WebServer.cpp
Normal file
231
src/mesh/http/WebServer.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
#include "mesh/http/WebServer.h"
|
||||
#include "NodeDB.h"
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#include <HTTPBodyParser.hpp>
|
||||
#include <HTTPMultipartBodyParser.hpp>
|
||||
#include <HTTPURLEncodedBodyParser.hpp>
|
||||
|
||||
|
||||
#include <WebServer.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "esp_task_wdt.h"
|
||||
#endif
|
||||
|
||||
|
||||
// Persistant Data Storage
|
||||
#include <Preferences.h>
|
||||
Preferences prefs;
|
||||
|
||||
/*
|
||||
Including the esp32_https_server library will trigger a compile time error. I've
|
||||
tracked it down to a reoccurrance of this bug:
|
||||
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=57824
|
||||
The work around is described here:
|
||||
https://forums.xilinx.com/t5/Embedded-Development-Tools/Error-with-Standard-Libaries-in-Zynq/td-p/450032
|
||||
|
||||
Long story short is we need "#undef str" before including the esp32_https_server.
|
||||
- Jm Casler (jm@casler.org) Oct 2020
|
||||
*/
|
||||
#undef str
|
||||
|
||||
// Includes for the https server
|
||||
// https://github.com/fhessel/esp32_https_server
|
||||
#include <HTTPRequest.hpp>
|
||||
#include <HTTPResponse.hpp>
|
||||
#include <HTTPSServer.hpp>
|
||||
#include <HTTPServer.hpp>
|
||||
#include <SSLCert.hpp>
|
||||
|
||||
// The HTTPS Server comes in a separate namespace. For easier use, include it here.
|
||||
using namespace httpsserver;
|
||||
#include "mesh/http/ContentHandler.h"
|
||||
|
||||
SSLCert *cert;
|
||||
HTTPSServer *secureServer;
|
||||
HTTPServer *insecureServer;
|
||||
|
||||
|
||||
|
||||
|
||||
bool isWebServerReady = 0;
|
||||
bool isCertReady = 0;
|
||||
|
||||
|
||||
void handleWebResponse()
|
||||
{
|
||||
if (isWifiAvailable() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isWebServerReady) {
|
||||
// We're going to handle the DNS responder here so it
|
||||
// will be ignored by the NRF boards.
|
||||
handleDNSResponse();
|
||||
|
||||
secureServer->loop();
|
||||
insecureServer->loop();
|
||||
}
|
||||
|
||||
/*
|
||||
Slow down the CPU if we have not received a request within the last few
|
||||
seconds.
|
||||
*/
|
||||
|
||||
if (millis() - getTimeSpeedUp() >= (25 * 1000)) {
|
||||
setCpuFrequencyMhz(80);
|
||||
setTimeSpeedUp();
|
||||
}
|
||||
}
|
||||
|
||||
void taskCreateCert(void *parameter)
|
||||
{
|
||||
|
||||
prefs.begin("MeshtasticHTTPS", false);
|
||||
|
||||
// Delete the saved certs
|
||||
if (0) {
|
||||
DEBUG_MSG("Deleting any saved SSL keys ...\n");
|
||||
// prefs.clear();
|
||||
prefs.remove("PK");
|
||||
prefs.remove("cert");
|
||||
}
|
||||
|
||||
size_t pkLen = prefs.getBytesLength("PK");
|
||||
size_t certLen = prefs.getBytesLength("cert");
|
||||
|
||||
DEBUG_MSG("Checking if we have a previously saved SSL Certificate.\n");
|
||||
|
||||
if (pkLen && certLen) {
|
||||
DEBUG_MSG("Existing SSL Certificate found!\n");
|
||||
} else {
|
||||
DEBUG_MSG("Creating the certificate. This may take a while. Please wait...\n");
|
||||
yield();
|
||||
cert = new SSLCert();
|
||||
yield();
|
||||
int createCertResult = createSelfSignedCert(*cert, KEYSIZE_2048, "CN=meshtastic.local,O=Meshtastic,C=US",
|
||||
"20190101000000", "20300101000000");
|
||||
yield();
|
||||
|
||||
if (createCertResult != 0) {
|
||||
DEBUG_MSG("Creating the certificate failed\n");
|
||||
|
||||
// Serial.printf("Creating the certificate failed. Error Code = 0x%02X, check SSLCert.hpp for details",
|
||||
// createCertResult);
|
||||
// while (true)
|
||||
// delay(500);
|
||||
} else {
|
||||
DEBUG_MSG("Creating the certificate was successful\n");
|
||||
|
||||
DEBUG_MSG("Created Private Key: %d Bytes\n", cert->getPKLength());
|
||||
// for (int i = 0; i < cert->getPKLength(); i++)
|
||||
// Serial.print(cert->getPKData()[i], HEX);
|
||||
// Serial.println();
|
||||
|
||||
DEBUG_MSG("Created Certificate: %d Bytes\n", cert->getCertLength());
|
||||
// for (int i = 0; i < cert->getCertLength(); i++)
|
||||
// Serial.print(cert->getCertData()[i], HEX);
|
||||
// Serial.println();
|
||||
|
||||
prefs.putBytes("PK", (uint8_t *)cert->getPKData(), cert->getPKLength());
|
||||
prefs.putBytes("cert", (uint8_t *)cert->getCertData(), cert->getCertLength());
|
||||
}
|
||||
}
|
||||
|
||||
isCertReady = 1;
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void createSSLCert()
|
||||
{
|
||||
|
||||
if (isWifiAvailable() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a new process just to handle creating the cert.
|
||||
// This is a workaround for Bug: https://github.com/fhessel/esp32_https_server/issues/48
|
||||
// jm@casler.org (Oct 2020)
|
||||
xTaskCreate(taskCreateCert, /* Task function. */
|
||||
"createCert", /* String with name of task. */
|
||||
16384, /* Stack size in bytes. */
|
||||
NULL, /* Parameter passed as input of the task */
|
||||
16, /* Priority of the task. */
|
||||
NULL); /* Task handle. */
|
||||
|
||||
DEBUG_MSG("Waiting for SSL Cert to be generated.\n");
|
||||
while (!isCertReady) {
|
||||
DEBUG_MSG(".");
|
||||
delay(1000);
|
||||
yield();
|
||||
esp_task_wdt_reset();
|
||||
}
|
||||
DEBUG_MSG("SSL Cert Ready!\n");
|
||||
}
|
||||
|
||||
WebServerThread *webServerThread;
|
||||
|
||||
WebServerThread::WebServerThread() : concurrency::OSThread("WebServerThread") {}
|
||||
|
||||
int32_t WebServerThread::runOnce()
|
||||
{
|
||||
// DEBUG_MSG("WebServerThread::runOnce()\n");
|
||||
handleWebResponse();
|
||||
|
||||
// Loop every 5ms.
|
||||
return (5);
|
||||
}
|
||||
|
||||
void initWebServer()
|
||||
{
|
||||
DEBUG_MSG("Initializing Web Server ...\n");
|
||||
|
||||
prefs.begin("MeshtasticHTTPS", false);
|
||||
|
||||
size_t pkLen = prefs.getBytesLength("PK");
|
||||
size_t certLen = prefs.getBytesLength("cert");
|
||||
|
||||
DEBUG_MSG("Checking if we have a previously saved SSL Certificate.\n");
|
||||
|
||||
if (pkLen && certLen) {
|
||||
|
||||
uint8_t *pkBuffer = new uint8_t[pkLen];
|
||||
prefs.getBytes("PK", pkBuffer, pkLen);
|
||||
|
||||
uint8_t *certBuffer = new uint8_t[certLen];
|
||||
prefs.getBytes("cert", certBuffer, certLen);
|
||||
|
||||
cert = new SSLCert(certBuffer, certLen, pkBuffer, pkLen);
|
||||
|
||||
DEBUG_MSG("Retrieved Private Key: %d Bytes\n", cert->getPKLength());
|
||||
// DEBUG_MSG("Retrieved Private Key: " + String(cert->getPKLength()) + " Bytes");
|
||||
// for (int i = 0; i < cert->getPKLength(); i++)
|
||||
// Serial.print(cert->getPKData()[i], HEX);
|
||||
// Serial.println();
|
||||
|
||||
DEBUG_MSG("Retrieved Certificate: %d Bytes\n", cert->getCertLength());
|
||||
// for (int i = 0; i < cert->getCertLength(); i++)
|
||||
// Serial.print(cert->getCertData()[i], HEX);
|
||||
// Serial.println();
|
||||
} else {
|
||||
DEBUG_MSG("Web Server started without SSL keys! How did this happen?\n");
|
||||
}
|
||||
|
||||
// We can now use the new certificate to setup our server as usual.
|
||||
secureServer = new HTTPSServer(cert);
|
||||
insecureServer = new HTTPServer();
|
||||
|
||||
registerHandlers(insecureServer, secureServer);
|
||||
|
||||
DEBUG_MSG("Starting Web Servers...\n");
|
||||
secureServer->start();
|
||||
insecureServer->start();
|
||||
if (secureServer->isRunning() && insecureServer->isRunning()) {
|
||||
DEBUG_MSG("HTTP and HTTPS Web Servers Ready! :-) \n");
|
||||
isWebServerReady = 1;
|
||||
} else {
|
||||
DEBUG_MSG("HTTP and HTTPS Web Servers Failed! ;-( \n");
|
||||
}
|
||||
}
|
||||
|
||||
26
src/mesh/http/WebServer.h
Normal file
26
src/mesh/http/WebServer.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "PhoneAPI.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
void initWebServer();
|
||||
void createSSLCert();
|
||||
|
||||
|
||||
void handleWebResponse();
|
||||
|
||||
|
||||
class WebServerThread : private concurrency::OSThread
|
||||
{
|
||||
|
||||
public:
|
||||
WebServerThread();
|
||||
|
||||
protected:
|
||||
|
||||
virtual int32_t runOnce();
|
||||
};
|
||||
|
||||
extern WebServerThread *webServerThread;
|
||||
@@ -1,9 +1,9 @@
|
||||
#include "meshwifi.h"
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#include "NodeDB.h"
|
||||
#include "mesh/wifi/WiFiServerAPI.h"
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
#include "meshwifi/meshhttp.h"
|
||||
#include "mesh/http/WebServer.h"
|
||||
#include "mesh/wifi/WiFiServerAPI.h"
|
||||
#include "target_specific.h"
|
||||
#include <DNSServer.h>
|
||||
#include <ESPmDNS.h>
|
||||
@@ -1,38 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "PhoneAPI.h"
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
void initWebServer();
|
||||
void createSSLCert();
|
||||
|
||||
void handleNotFound();
|
||||
|
||||
void handleWebResponse();
|
||||
|
||||
void handleJSONChatHistory();
|
||||
|
||||
void notifyWebUI();
|
||||
|
||||
void handleHotspot();
|
||||
|
||||
void handleStyleCSS();
|
||||
void handleRoot();
|
||||
void handleScriptsScriptJS();
|
||||
void handleJSONChatHistoryDummy();
|
||||
|
||||
void replaceAll(std::string& str, const std::string& from, const std::string& to);
|
||||
|
||||
class HttpAPI : public PhoneAPI
|
||||
{
|
||||
|
||||
public:
|
||||
// Nothing here yet
|
||||
|
||||
private:
|
||||
// Nothing here yet
|
||||
|
||||
protected:
|
||||
// Nothing here yet
|
||||
};
|
||||
@@ -7,13 +7,16 @@
|
||||
#include "esp_bt.h"
|
||||
#include "host/util/util.h"
|
||||
#include "main.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
#include "nimble/NimbleDefs.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "services/gatt/ble_svc_gatt.h"
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
|
||||
#ifndef NO_ESP32
|
||||
#include "mesh/http/WiFiAPClient.h"
|
||||
#endif
|
||||
|
||||
static bool pinShowing;
|
||||
static uint32_t doublepressed;
|
||||
|
||||
@@ -545,7 +548,9 @@ void setBluetoothEnable(bool on)
|
||||
if (firstTime) {
|
||||
firstTime = 0;
|
||||
} else {
|
||||
initWifi(0);
|
||||
#ifndef NO_ESP32
|
||||
initWifi(0);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -557,7 +562,9 @@ void setBluetoothEnable(bool on)
|
||||
*/
|
||||
|
||||
// shutdown wifi
|
||||
#ifndef NO_ESP32
|
||||
deinitWifi();
|
||||
#endif
|
||||
|
||||
// We have to totally teardown our bluetooth objects to prevent leaks
|
||||
deinitBLE();
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "meshwifi/meshhttp.h"
|
||||
#include "meshwifi/meshwifi.h"
|
||||
//#include "mesh/wifi/WebServer.h"
|
||||
#include "configuration.h"
|
||||
|
||||
#ifndef NO_ESP32
|
||||
|
||||
//#include "mesh/wifi/WiFiAPClient.h"
|
||||
|
||||
void initWifi(bool forceSoftAP) {}
|
||||
|
||||
@@ -10,7 +14,4 @@ bool isWifiAvailable()
|
||||
return false;
|
||||
}
|
||||
|
||||
void handleWebResponse() {}
|
||||
|
||||
/// Perform idle loop processing required by the wifi layer
|
||||
void loopWifi() {}
|
||||
#endif
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
|
||||
NodeInfoPlugin nodeInfoPlugin;
|
||||
NodeInfoPlugin *nodeInfoPlugin;
|
||||
|
||||
bool NodeInfoPlugin::handleReceivedProtobuf(const MeshPacket &mp, const User &p)
|
||||
{
|
||||
|
||||
@@ -29,4 +29,4 @@ class NodeInfoPlugin : public ProtobufPlugin<User>
|
||||
virtual MeshPacket *allocReply();
|
||||
};
|
||||
|
||||
extern NodeInfoPlugin nodeInfoPlugin;
|
||||
extern NodeInfoPlugin *nodeInfoPlugin;
|
||||
29
src/plugins/Plugins.cpp
Normal file
29
src/plugins/Plugins.cpp
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "plugins/NodeInfoPlugin.h"
|
||||
#include "plugins/PositionPlugin.h"
|
||||
#include "plugins/RemoteHardwarePlugin.h"
|
||||
#include "plugins/ReplyPlugin.h"
|
||||
#include "plugins/SerialPlugin.h"
|
||||
#include "plugins/TextMessagePlugin.h"
|
||||
|
||||
/**
|
||||
* Create plugin instances here. If you are adding a new plugin, you must 'new' it here (or somewhere else)
|
||||
*/
|
||||
void setupPlugins()
|
||||
{
|
||||
nodeInfoPlugin = new NodeInfoPlugin();
|
||||
positionPlugin = new PositionPlugin();
|
||||
textMessagePlugin = new TextMessagePlugin();
|
||||
|
||||
// Note: if the rest of meshtastic doesn't need to explicitly use your plugin, you do not need to assign the instance
|
||||
// to a global variable.
|
||||
|
||||
new RemoteHardwarePlugin();
|
||||
new ReplyPlugin();
|
||||
|
||||
#ifndef NO_ESP32
|
||||
// Only run on an esp32 based device.
|
||||
|
||||
new SerialPlugin(); // Maintained by MC Hamster (Jm Casler) jm@casler.org
|
||||
#endif
|
||||
|
||||
}
|
||||
6
src/plugins/Plugins.h
Normal file
6
src/plugins/Plugins.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Create plugin instances here. If you are adding a new plugin, you must 'new' it here (or somewhere else)
|
||||
*/
|
||||
void setupPlugins();
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
|
||||
PositionPlugin positionPlugin;
|
||||
PositionPlugin *positionPlugin;
|
||||
|
||||
bool PositionPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Position &p)
|
||||
{
|
||||
@@ -29,21 +29,10 @@ bool PositionPlugin::handleReceivedProtobuf(const MeshPacket &mp, const Position
|
||||
|
||||
MeshPacket *PositionPlugin::allocReply()
|
||||
{
|
||||
NodeInfo *node = nodeDB.getNode(nodeDB.getNodeNum());
|
||||
assert(node);
|
||||
|
||||
// We might not have a position yet for our local node, in that case, at least try to send the time
|
||||
if(!node->has_position) {
|
||||
memset(&node->position, 0, sizeof(node->position));
|
||||
node->has_position = true;
|
||||
}
|
||||
NodeInfo *node = service.refreshMyNodeInfo(); // should guarantee there is now a position
|
||||
assert(node->has_position);
|
||||
|
||||
Position &position = node->position;
|
||||
|
||||
// Update our local node info with our position (even if we don't decide to update anyone else)
|
||||
position.time = getValidTime(RTCQualityGPS); // This nodedb timestamp might be stale, so update it if our clock is valid.
|
||||
|
||||
return allocDataProtobuf(position);
|
||||
return allocDataProtobuf(node->position);
|
||||
}
|
||||
|
||||
void PositionPlugin::sendOurPosition(NodeNum dest, bool wantReplies)
|
||||
|
||||
@@ -30,4 +30,4 @@ class PositionPlugin : public ProtobufPlugin<Position>
|
||||
virtual MeshPacket *allocReply();
|
||||
};
|
||||
|
||||
extern PositionPlugin positionPlugin;
|
||||
extern PositionPlugin *positionPlugin;
|
||||
@@ -6,8 +6,6 @@
|
||||
#include "configuration.h"
|
||||
#include "main.h"
|
||||
|
||||
RemoteHardwarePlugin remoteHardwarePlugin;
|
||||
|
||||
#define NUM_GPIOS 64
|
||||
|
||||
// Because (FIXME) we currently don't tell API clients status on sent messages
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
// Create an a static instance of our plugin - this registers with the plugin system
|
||||
ReplyPlugin replyPlugin;
|
||||
|
||||
MeshPacket *ReplyPlugin::allocReply()
|
||||
{
|
||||
assert(currentRequest); // should always be !NULL
|
||||
|
||||
194
src/plugins/SerialPlugin.cpp
Normal file
194
src/plugins/SerialPlugin.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "SerialPlugin.h"
|
||||
#include "MeshService.h"
|
||||
#include "NodeDB.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
SerialPlugin
|
||||
A simple interface to send messages over the mesh network by sending strings
|
||||
over a serial port.
|
||||
|
||||
Default is to use RX GPIO 16 and TX GPIO 17.
|
||||
|
||||
Need help with this plugin? Post your question on the Meshtastic Discourse:
|
||||
https://meshtastic.discourse.group
|
||||
|
||||
Basic Usage:
|
||||
|
||||
1) Enable the plugin by setting serialplugin_enabled to 1.
|
||||
2) Set the pins (serialplugin_rxd / serialplugin_rxd) for your preferred RX and TX GPIO pins.
|
||||
On tbeam, recommend to use:
|
||||
RXD 35
|
||||
TXD 15
|
||||
3) Set serialplugin_timeout to the amount of time to wait before we consider
|
||||
your packet as "done".
|
||||
4) (Optional) In SerialPlugin.h set the port to PortNum_TEXT_MESSAGE_APP if you want to
|
||||
send messages to/from the general text message channel.
|
||||
5) Connect to your device over the serial interface at 38400 8N1.
|
||||
6) Send a packet up to 240 bytes in length. This will get relayed over the mesh network.
|
||||
7) (Optional) Set serialplugin_echo to 1 and any message you send out will be echoed back
|
||||
to your device.
|
||||
|
||||
TODO (in this order):
|
||||
* Define a verbose RX mode to report on mesh and packet infomration.
|
||||
- This won't happen any time soon.
|
||||
|
||||
KNOWN PROBLEMS
|
||||
* Until the plugin is initilized by the startup sequence, the TX pin is in a floating
|
||||
state. Device connected to that pin may see this as "noise".
|
||||
* Will not work on NRF and the Linux device targets.
|
||||
|
||||
|
||||
*/
|
||||
|
||||
#define RXD2 16
|
||||
#define TXD2 17
|
||||
#define SERIALPLUGIN_RX_BUFFER 128
|
||||
#define SERIALPLUGIN_STRING_MAX Constants_DATA_PAYLOAD_LEN
|
||||
#define SERIALPLUGIN_TIMEOUT 250
|
||||
#define SERIALPLUGIN_BAUD 38400
|
||||
#define SERIALPLUGIN_ACK 1
|
||||
|
||||
SerialPlugin *serialPlugin;
|
||||
SerialPluginRadio *serialPluginRadio;
|
||||
|
||||
SerialPlugin::SerialPlugin() : concurrency::OSThread("SerialPlugin") {}
|
||||
|
||||
char serialStringChar[Constants_DATA_PAYLOAD_LEN];
|
||||
|
||||
int32_t SerialPlugin::runOnce()
|
||||
{
|
||||
#ifndef NO_ESP32
|
||||
|
||||
/*
|
||||
Uncomment the preferences below if you want to use the plugin
|
||||
without having to configure it from the PythonAPI or WebUI.
|
||||
*/
|
||||
|
||||
// radioConfig.preferences.serialplugin_enabled = 1;
|
||||
// radioConfig.preferences.serialplugin_rxd = 35;
|
||||
// radioConfig.preferences.serialplugin_txd = 15;
|
||||
// radioConfig.preferences.serialplugin_timeout = 1000;
|
||||
// radioConfig.preferences.serialplugin_echo = 1;
|
||||
|
||||
if (radioConfig.preferences.serialplugin_enabled) {
|
||||
|
||||
if (firstTime) {
|
||||
|
||||
// Interface with the serial peripheral from in here.
|
||||
DEBUG_MSG("Initializing serial peripheral interface\n");
|
||||
|
||||
if (radioConfig.preferences.serialplugin_rxd && radioConfig.preferences.serialplugin_txd) {
|
||||
Serial2.begin(SERIALPLUGIN_BAUD, SERIAL_8N1, radioConfig.preferences.serialplugin_rxd,
|
||||
radioConfig.preferences.serialplugin_txd);
|
||||
|
||||
} else {
|
||||
Serial2.begin(SERIALPLUGIN_BAUD, SERIAL_8N1, RXD2, TXD2);
|
||||
}
|
||||
|
||||
if (radioConfig.preferences.serialplugin_timeout) {
|
||||
Serial2.setTimeout(
|
||||
radioConfig.preferences.serialplugin_timeout); // Number of MS to wait to set the timeout for the string.
|
||||
|
||||
} else {
|
||||
Serial2.setTimeout(SERIALPLUGIN_TIMEOUT); // Number of MS to wait to set the timeout for the string.
|
||||
}
|
||||
|
||||
Serial2.setRxBufferSize(SERIALPLUGIN_RX_BUFFER);
|
||||
|
||||
serialPluginRadio = new SerialPluginRadio();
|
||||
|
||||
firstTime = 0;
|
||||
|
||||
} else {
|
||||
String serialString;
|
||||
|
||||
while (Serial2.available()) {
|
||||
serialString = Serial2.readString();
|
||||
serialString.toCharArray(serialStringChar, Constants_DATA_PAYLOAD_LEN);
|
||||
|
||||
serialPluginRadio->sendPayload();
|
||||
|
||||
DEBUG_MSG("Received: %s\n", serialStringChar);
|
||||
}
|
||||
}
|
||||
|
||||
return (10);
|
||||
} else {
|
||||
DEBUG_MSG("Serial Plugin Disabled\n");
|
||||
|
||||
return (INT32_MAX);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
MeshPacket *SerialPluginRadio::allocReply()
|
||||
{
|
||||
|
||||
auto reply = allocDataPacket(); // Allocate a packet for sending
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
void SerialPluginRadio::sendPayload(NodeNum dest, bool wantReplies)
|
||||
{
|
||||
MeshPacket *p = allocReply();
|
||||
p->to = dest;
|
||||
p->decoded.want_response = wantReplies;
|
||||
|
||||
p->want_ack = SERIALPLUGIN_ACK;
|
||||
|
||||
p->decoded.data.payload.size = strlen(serialStringChar); // You must specify how many bytes are in the reply
|
||||
memcpy(p->decoded.data.payload.bytes, serialStringChar, p->decoded.data.payload.size);
|
||||
|
||||
service.sendToMesh(p);
|
||||
}
|
||||
|
||||
bool SerialPluginRadio::handleReceived(const MeshPacket &mp)
|
||||
{
|
||||
#ifndef NO_ESP32
|
||||
|
||||
if (radioConfig.preferences.serialplugin_enabled) {
|
||||
|
||||
auto &p = mp.decoded.data;
|
||||
// DEBUG_MSG("Received text msg self=0x%0x, from=0x%0x, to=0x%0x, id=%d, msg=%.*s\n",
|
||||
// nodeDB.getNodeNum(), mp.from, mp.to, mp.id, p.payload.size, p.payload.bytes);
|
||||
|
||||
if (mp.from == nodeDB.getNodeNum()) {
|
||||
|
||||
/*
|
||||
* If radioConfig.preferences.serialplugin_echo is true, then echo the packets that are sent out back to the TX
|
||||
* of the serial interface.
|
||||
*/
|
||||
if (radioConfig.preferences.serialplugin_echo) {
|
||||
|
||||
// For some reason, we get the packet back twice when we send out of the radio.
|
||||
// TODO: need to find out why.
|
||||
if (lastRxID != mp.id) {
|
||||
lastRxID = mp.id;
|
||||
// DEBUG_MSG("* * Message came this device\n");
|
||||
// Serial2.println("* * Message came this device");
|
||||
Serial2.printf("%s", p.payload.bytes);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// DEBUG_MSG("* * Message came from the mesh\n");
|
||||
// Serial2.println("* * Message came from the mesh");
|
||||
Serial2.printf("%s", p.payload.bytes);
|
||||
}
|
||||
|
||||
} else {
|
||||
DEBUG_MSG("Serial Plugin Disabled\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
return true; // Let others look at this message also if they want
|
||||
}
|
||||
54
src/plugins/SerialPlugin.h
Normal file
54
src/plugins/SerialPlugin.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include "SinglePortPlugin.h"
|
||||
#include "concurrency/OSThread.h"
|
||||
#include "configuration.h"
|
||||
#include <Arduino.h>
|
||||
#include <functional>
|
||||
|
||||
class SerialPlugin : private concurrency::OSThread
|
||||
{
|
||||
bool firstTime = 1;
|
||||
|
||||
public:
|
||||
SerialPlugin();
|
||||
|
||||
protected:
|
||||
virtual int32_t runOnce();
|
||||
};
|
||||
|
||||
extern SerialPlugin *serialPlugin;
|
||||
|
||||
/*
|
||||
* Radio interface for SerialPlugin
|
||||
*
|
||||
*/
|
||||
class SerialPluginRadio : public SinglePortPlugin
|
||||
{
|
||||
uint32_t lastRxID;
|
||||
|
||||
public:
|
||||
/*
|
||||
TODO: Switch this to PortNum_SERIAL_APP once the change is able to be merged back here
|
||||
from the main code.
|
||||
*/
|
||||
|
||||
// SerialPluginRadio() : SinglePortPlugin("SerialPluginRadio", PortNum_TEXT_MESSAGE_APP) {}
|
||||
SerialPluginRadio() : SinglePortPlugin("SerialPluginRadio", PortNum_SERIAL_APP) {}
|
||||
|
||||
/**
|
||||
* Send our payload into the mesh
|
||||
*/
|
||||
void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false);
|
||||
|
||||
protected:
|
||||
virtual MeshPacket *allocReply();
|
||||
|
||||
/** Called to handle a particular incoming message
|
||||
|
||||
@return true if you've guaranteed you've handled this message and no other handlers should be considered for it
|
||||
*/
|
||||
virtual bool handleReceived(const MeshPacket &mp);
|
||||
};
|
||||
|
||||
extern SerialPluginRadio *serialPluginRadio;
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "NodeDB.h"
|
||||
#include "PowerFSM.h"
|
||||
|
||||
TextMessagePlugin textMessagePlugin;
|
||||
TextMessagePlugin *textMessagePlugin;
|
||||
|
||||
bool TextMessagePlugin::handleReceived(const MeshPacket &mp)
|
||||
{
|
||||
|
||||
@@ -22,4 +22,4 @@ class TextMessagePlugin : public SinglePortPlugin, public Observable<const MeshP
|
||||
virtual bool handleReceived(const MeshPacket &mp);
|
||||
};
|
||||
|
||||
extern TextMessagePlugin textMessagePlugin;
|
||||
extern TextMessagePlugin *textMessagePlugin;
|
||||
@@ -1,4 +1,4 @@
|
||||
[VERSION]
|
||||
major = 1
|
||||
minor = 1
|
||||
build = 32
|
||||
build = 33
|
||||
|
||||
Reference in New Issue
Block a user