diff --git a/.github/workflows/main_matrix.yml b/.github/workflows/main_matrix.yml index ab0d9a1bc..6538368db 100644 --- a/.github/workflows/main_matrix.yml +++ b/.github/workflows/main_matrix.yml @@ -153,12 +153,9 @@ jobs: with: repo: "meshtastic/Meshtastic-OTA" file: "firmware.bin" + target: "release/bleota.bin" token: ${{ secrets.GITHUB_TOKEN }} - - name: Move OTA Firmware - run: | - mv firmware.bin release/bleota.bin - - name: Get release version string run: echo "::set-output name=version::$(./bin/buildinfo.py long)" id: version @@ -371,7 +368,7 @@ jobs: id: version - name: Move files up - run: mv -b -t ./ ./*tbeam-1*/littlefs*.bin ./*tbeam-1*/bleota.bin ./*tbeam-1*/system-info.bin ./*tbeam-1*/partitions.bin ./**/firmware*.bin ./**/*.uf2 ./**/*.elf ./**/meshtasticd_linux_amd64 ./*native*/*device-*.sh ./*native*/*device-*.bat + run: mv -b -t ./ ./*tbeam-1*/littlefs*.bin ./*tbeam-1*/bleota.bin ./**/firmware*.bin ./**/*.uf2 ./**/*.elf ./**/meshtasticd_linux_amd64 ./*native*/*device-*.sh ./*native*/*device-*.bat - name: Repackage in single firmware zip uses: actions/upload-artifact@v2 diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 101752e8b..04f57c924 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -31,7 +31,7 @@ lib_deps = ${arduino_base.lib_deps} ${networking_base.lib_deps} ${environmental_base.lib_deps} - https://github.com/caveman99/esp32_https_server.git + https://github.com/meshtastic/esp32_https_server.git h2zero/NimBLE-Arduino@1.4.0 arduino-libraries/NTPClient@^3.1.0 https://github.com/lewisxhe/XPowersLib.git diff --git a/bin/build-esp32.sh b/bin/build-esp32.sh index 153638425..40a6c3aee 100755 --- a/bin/build-esp32.sh +++ b/bin/build-esp32.sh @@ -29,14 +29,11 @@ SRCELF=.pio/build/$1/firmware.elf cp $SRCELF $OUTDIR/$basename.elf echo "Copying ESP32 bin file" -SRCBIN=.pio/build/$1/firmware.bin +SRCBIN=.pio/build/$1/firmware.factory.bin cp $SRCBIN $OUTDIR/$basename.bin echo "Building Filesystem for ESP32 targets" pio run --environment tbeam -t buildfs cp .pio/build/tbeam/littlefs.bin $OUTDIR/littlefs-$VERSION.bin -cp images/system-info.bin $OUTDIR/system-info.bin - -cp .pio/build/$1/partitions.bin $OUTDIR/partitions.bin cp bin/device-install.* $OUTDIR cp bin/device-update.* $OUTDIR diff --git a/bin/device-install.bat b/bin/device-install.bat index 763cdac85..419e8d6bd 100755 --- a/bin/device-install.bat +++ b/bin/device-install.bat @@ -29,13 +29,11 @@ IF "__%FILENAME%__" == "____" ( IF EXIST %FILENAME% ( echo Trying to flash update %FILENAME%, but first erasing and writing system information" %PYTHON% -m esptool --baud 115200 erase_flash - %PYTHON% -m esptool --baud 115200 write_flash 0x1000 system-info.bin - %PYTHON% -m esptool --baud 115200 write_flash 0x8000 partitions.bin + %PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME% + %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin for %%f in (littlefs-*.bin) do ( %PYTHON% -m esptool --baud 115200 write_flash 0x300000 %%f ) - %PYTHON% -m esptool --baud 115200 write_flash 0x10000 %FILENAME% - %PYTHON% -m esptool --baud 115200 write_flash 0x260000 bleota.bin ) else ( echo "Invalid file: %FILENAME%" goto HELP diff --git a/bin/device-install.sh b/bin/device-install.sh index 163499d7b..a55e553c7 100755 --- a/bin/device-install.sh +++ b/bin/device-install.sh @@ -47,11 +47,10 @@ shift "$((OPTIND-1))" if [ -f "${FILENAME}" ]; then echo "Trying to flash ${FILENAME}, but first erasing and writing system information" "$PYTHON" -m esptool erase_flash - "$PYTHON" -m esptool write_flash 0x1000 system-info.bin - "$PYTHON" -m esptool write_flash 0x8000 partitions.bin + "$PYTHON" -m esptool write_flash 0x00 ${FILENAME} + "$PYTHON" -m esptool write_flash 0x260000 bleota.bin "$PYTHON" -m esptool write_flash 0x300000 littlefs-*.bin - "$PYTHON" -m esptool write_flash 0x260000 bleota.bin - "$PYTHON" -m esptool write_flash 0x10000 ${FILENAME} + else echo "Invalid file: ${FILENAME}" show_help diff --git a/bin/device-update.bat b/bin/device-update.bat index c9ec0c4c9..40def6caf 100755 --- a/bin/device-update.bat +++ b/bin/device-update.bat @@ -28,7 +28,7 @@ IF "__%FILENAME%__" == "____" ( ) IF EXIST %FILENAME% ( echo Trying to flash update %FILENAME% - %PYTHON% -m esptool --baud 115200 write_flash 0x10000 %FILENAME% + %PYTHON% -m esptool --baud 115200 write_flash 0x00 %FILENAME% ) else ( echo "Invalid file: %FILENAME%" goto HELP diff --git a/bin/device-update.sh b/bin/device-update.sh index a233d61dd..a6d80c200 100755 --- a/bin/device-update.sh +++ b/bin/device-update.sh @@ -44,7 +44,7 @@ shift "$((OPTIND-1))" if [ -f "${FILENAME}" ]; then echo "Trying to flash update ${FILENAME}." - $PYTHON -m esptool --baud 115200 write_flash 0x10000 ${FILENAME} + $PYTHON -m esptool --baud 115200 write_flash 0x00 ${FILENAME} else echo "Invalid file: ${FILENAME}" show_help diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py index 951d89b0a..e03a35e3a 100644 --- a/bin/platformio-custom.py +++ b/bin/platformio-custom.py @@ -1,17 +1,70 @@ - - import subprocess import configparser import traceback import sys +from os.path import join from readprops import readProps +Import("env") +platform = env.PioPlatform() + +def esp32_create_combined_bin(source, target, env): + # this sub is borrowed from ESPEasy build toolchain. It's licensed under GPL V3 + # https://github.com/letscontrolit/ESPEasy/blob/mega/tools/pio/post_esp32.py + print("Generating combined binary for serial flashing") + + app_offset = 0x10000 + + new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin") + sections = env.subst(env.get("FLASH_EXTRA_IMAGES")) + firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") + chip = env.get("BOARD_MCU") + flash_size = env.BoardConfig().get("upload.flash_size") + flash_freq = env.BoardConfig().get("build.f_flash", '40m') + flash_freq = flash_freq.replace('000000L', 'm') + flash_mode = env.BoardConfig().get("build.flash_mode", "dio") + memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi") + if flash_mode == "qio" or flash_mode == "qout": + flash_mode = "dio" + if memory_type == "opi_opi" or memory_type == "opi_qspi": + flash_mode = "dout" + cmd = [ + "--chip", + chip, + "merge_bin", + "-o", + new_file_name, + "--flash_mode", + flash_mode, + "--flash_freq", + flash_freq, + "--flash_size", + flash_size, + ] + + print(" Offset | File") + for section in sections: + sect_adr, sect_file = section.split(" ", 1) + print(f" - {sect_adr} | {sect_file}") + cmd += [sect_adr, sect_file] + + print(f" - {hex(app_offset)} | {firmware_name}") + cmd += [hex(app_offset), firmware_name] + + print('Using esptool.py arguments: %s' % ' '.join(cmd)) + + esptool.main(cmd) + +if (platform.name == "espressif32"): + sys.path.append(join(platform.get_package_dir("tool-esptoolpy"))) + import esptool + env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) + Import("projenv") prefsLoc = projenv["PROJECT_DIR"] + "/version.properties" verObj = readProps(prefsLoc) print("Using meshtastic platformio-custom.py, firmware version " + verObj['long']) -# print("path is" + ','.join(sys.path)) # General options that are passed to the C and C++ compilers projenv.Append(CCFLAGS=[ diff --git a/images/system-info.bin b/images/system-info.bin deleted file mode 100644 index 85acfca15..000000000 Binary files a/images/system-info.bin and /dev/null differ diff --git a/platformio.ini b/platformio.ini index 23102e005..2929ce092 100644 --- a/platformio.ini +++ b/platformio.ini @@ -65,7 +65,7 @@ framework = arduino lib_deps = ${env.lib_deps} ; Portduino is using meshtastic fork for now - https://github.com/jgromes/RadioLib.git@5.3.0 + https://github.com/jgromes/RadioLib.git@5.4.0 build_flags = ${env.build_flags} -Os # -DRADIOLIB_GODMODE diff --git a/protobufs b/protobufs index 0328a5269..dabfdfb90 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit 0328a5269f8368f1eaa617d0e39f886d03d44c76 +Subproject commit dabfdfb90c62bd862536157431083f99c8fde003 diff --git a/src/main.cpp b/src/main.cpp index c5e6e8b56..3430cb129 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,7 +36,7 @@ #include "nimble/NimbleBluetooth.h" #endif -#if HAS_WIFI +#if HAS_WIFI || defined(ARCH_PORTDUINO) #include "mesh/wifi/WiFiServerAPI.h" #include "mqtt/MQTT.h" #endif @@ -45,6 +45,9 @@ #include "RF95Interface.h" #include "SX1262Interface.h" #include "SX1268Interface.h" +#if !HAS_RADIO && defined(ARCH_PORTDUINO) +#include "platform/portduino/SimRadio.h" +#endif #if HAS_BUTTON #include "ButtonThread.h" @@ -385,7 +388,7 @@ void setup() } #endif -#if !HAS_RADIO +#ifdef ARCH_PORTDUINO if (!rIf) { rIf = new SimRadio; if (!rIf->init()) { @@ -411,7 +414,7 @@ void setup() #endif #ifdef ARCH_PORTDUINO - initApiServer(); + initApiServer(TCPPort); #endif // Start airtime logger thread. diff --git a/src/main.h b/src/main.h index e1142d36c..c92379274 100644 --- a/src/main.h +++ b/src/main.h @@ -20,6 +20,8 @@ extern bool isUSBPowered; extern uint8_t nodeTelemetrySensorsMap[TelemetrySensorType_LPS22+1]; +extern int TCPPort; // set by Portduino + // Global Screen singleton. extern graphics::Screen *screen; // extern Observable newPowerStatus; //TODO: move this to main-esp32.cpp somehow or a helper class diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp index a4ef25d2a..63f2a4358 100644 --- a/src/mesh/MeshModule.cpp +++ b/src/mesh/MeshModule.cpp @@ -112,7 +112,7 @@ void MeshModule::callPlugins(const MeshPacket &mp, RxSource src) bool rxChannelOk = !pi.boundChannel || (mp.from == 0) || !ch || strlen(ch->settings.name) > 0 || - strcmp(ch->settings.name, pi.boundChannel); + (strcmp(ch->settings.name, pi.boundChannel) == 0); if (!rxChannelOk) { // no one should have already replied! diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp index 825252402..99411f91d 100644 --- a/src/mesh/MeshService.cpp +++ b/src/mesh/MeshService.cpp @@ -123,6 +123,29 @@ void MeshService::reloadOwner() */ void MeshService::handleToRadio(MeshPacket &p) { + #ifdef ARCH_PORTDUINO + // Simulates device is receiving a packet via the LoRa chip + if (p.decoded.portnum == PortNum_SIMULATOR_APP) { + // Simulator packet (=Compressed packet) is encapsulated in a MeshPacket, so need to unwrap first + Compressed scratch; + Compressed *decoded = NULL; + if (p.which_payload_variant == MeshPacket_decoded_tag) { + memset(&scratch, 0, sizeof(scratch)); + p.decoded.payload.size = pb_decode_from_bytes(p.decoded.payload.bytes, p.decoded.payload.size, &Compressed_msg, &scratch); + if (p.decoded.payload.size) { + decoded = &scratch; + // Extract the original payload and replace + memcpy(&p.decoded.payload, &decoded->data, sizeof(decoded->data)); + // Switch the port from PortNum_SIMULATOR_APP back to the original PortNum + p.decoded.portnum = decoded->portnum; + } else + DEBUG_MSG("Error decoding protobuf for simulator message!\n"); + } + // Let SimRadio receive as if it did via its LoRa chip + SimRadio::instance->startReceive(&p); + return; + } + #endif if (p.from != 0) { // We don't let phones assign nodenums to their sent messages DEBUG_MSG("Warning: phone tried to pick a nodenum, we don't allow that.\n"); p.from = 0; diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 45559d3a2..d886a57a9 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -10,6 +10,9 @@ #include "MeshTypes.h" #include "Observer.h" #include "PointerQueue.h" +#ifdef ARCH_PORTDUINO +#include "../platform/portduino/SimRadio.h" +#endif /** * Top level app for this service. keeps the mesh, the radio config and the queue of received packets. diff --git a/src/mesh/PhoneAPI.cpp b/src/mesh/PhoneAPI.cpp index b3f500f01..a725e6283 100644 --- a/src/mesh/PhoneAPI.cpp +++ b/src/mesh/PhoneAPI.cpp @@ -117,7 +117,7 @@ bool PhoneAPI::handleToRadio(const uint8_t *buf, size_t bufLength) size_t PhoneAPI::getFromRadio(uint8_t *buf) { if (!available()) { - DEBUG_MSG("getFromRadio=not available\n"); + // DEBUG_MSG("getFromRadio=not available\n"); return 0; } // In case we send a FromRadio packet @@ -319,7 +319,7 @@ bool PhoneAPI::available() if (!packetForPhone) packetForPhone = service.getForPhone(); bool hasPacket = !!packetForPhone; - DEBUG_MSG("available hasPacket=%d\n", hasPacket); + // DEBUG_MSG("available hasPacket=%d\n", hasPacket); return hasPacket; } default: diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index b17b4562e..da32af9f3 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -461,12 +461,6 @@ void RadioInterface::limitPower() DEBUG_MSG("Set radio: final power level=%d\n", power); } -ErrorCode SimRadio::send(MeshPacket *p) -{ - DEBUG_MSG("SimRadio.send\n"); - packetPool.release(p); - return ERRNO_OK; -} void RadioInterface::deliverToReceiver(MeshPacket *p) { diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 898ef20e4..e9f725c89 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -212,11 +212,6 @@ class RadioInterface } }; -class SimRadio : public RadioInterface -{ - public: - virtual ErrorCode send(MeshPacket *p) override; -}; /// Debug printing for packets void printPacket(const char *prefix, const MeshPacket *p); diff --git a/src/mesh/generated/portnums.pb.h b/src/mesh/generated/portnums.pb.h index b65ac86f0..3c68e9bb0 100644 --- a/src/mesh/generated/portnums.pb.h +++ b/src/mesh/generated/portnums.pb.h @@ -74,6 +74,11 @@ typedef enum _PortNum { Maintained by Github user a-f-G-U-C (a Meshtastic contributor) Project files at https://github.com/a-f-G-U-C/Meshtastic-ZPS */ PortNum_ZPS_APP = 68, + /* Used to let multiple instances of Linux native applications communicate + as if they did using their LoRa chip. + Maintained by GitHub user GUVWAF. + Project files at https://github.com/GUVWAF/Meshtasticator */ + PortNum_SIMULATOR_APP = 69, /* Private applications should use portnums >= 256. To simplify initial development and testing you can use "PRIVATE_APP" in your code without needing to rebuild protobuf files (via [regen-protos.sh](https://github.com/meshtastic/Meshtastic-device/blob/master/bin/regen-protos.sh)) */ diff --git a/src/mesh/wifi/WiFiServerAPI.cpp b/src/mesh/wifi/WiFiServerAPI.cpp index 6014f8324..34a15f71b 100644 --- a/src/mesh/wifi/WiFiServerAPI.cpp +++ b/src/mesh/wifi/WiFiServerAPI.cpp @@ -4,11 +4,12 @@ static WiFiServerPort *apiPort; -void initApiServer() +void initApiServer(int port) { // Start API server on port 4403 if (!apiPort) { - apiPort = new WiFiServerPort(); + apiPort = new WiFiServerPort(port); + DEBUG_MSG("API server listening on TCP port %d\n", port); apiPort->init(); } } @@ -56,13 +57,11 @@ void WiFiServerPort::debugOut(char c) apiPort->openAPI->debugOut(c); } -#define MESHTASTIC_PORTNUM 4403 -WiFiServerPort::WiFiServerPort() : WiFiServer(MESHTASTIC_PORTNUM), concurrency::OSThread("ApiServer") {} +WiFiServerPort::WiFiServerPort(int port) : WiFiServer(port), concurrency::OSThread("ApiServer") {} void WiFiServerPort::init() { - DEBUG_MSG("API server listening on TCP port %d\n", MESHTASTIC_PORTNUM); begin(); } @@ -80,4 +79,4 @@ int32_t WiFiServerPort::runOnce() } return 100; // only check occasionally for incoming connections -} \ No newline at end of file +} diff --git a/src/mesh/wifi/WiFiServerAPI.h b/src/mesh/wifi/WiFiServerAPI.h index d3750e8c0..84a23be06 100644 --- a/src/mesh/wifi/WiFiServerAPI.h +++ b/src/mesh/wifi/WiFiServerAPI.h @@ -44,7 +44,7 @@ class WiFiServerPort : public WiFiServer, private concurrency::OSThread WiFiServerAPI *openAPI = NULL; public: - WiFiServerPort(); + explicit WiFiServerPort(int port); void init(); @@ -55,4 +55,4 @@ class WiFiServerPort : public WiFiServer, private concurrency::OSThread int32_t runOnce() override; }; -void initApiServer(); +void initApiServer(int port=4403); diff --git a/src/platform/portduino/PortduinoGlue.cpp b/src/platform/portduino/PortduinoGlue.cpp index 46abd6b85..b041391f8 100644 --- a/src/platform/portduino/PortduinoGlue.cpp +++ b/src/platform/portduino/PortduinoGlue.cpp @@ -51,13 +51,42 @@ class PolledIrqPin : public GPIOPin static GPIOPin *loraIrq; +int TCPPort = 4403; + +static error_t parse_opt(int key, char *arg, struct argp_state *state) { + switch (key) { + case 'p': + if (sscanf(arg, "%d", &TCPPort) < 1) + return ARGP_ERR_UNKNOWN; + else + printf("Using TCP port %d\n", TCPPort); + break; + case ARGP_KEY_ARG: + return 0; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +void portduinoCustomInit() { + static struct argp_option options[] = {{"port", 'p', "PORT", 0, "The TCP port to use."}, {0}}; + static void *childArguments; + static char doc[] = "Meshtastic native build."; + static char args_doc[] = "..."; + static struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; + const struct argp_child child = {&argp, OPTION_ARG_OPTIONAL, 0, 0}; + portduinoAddArguments(child, childArguments); +} + + /** apps run under portduino can optionally define a portduinoSetup() to * use portduino specific init code (such as gpioBind) to setup portduino on their host machine, * before running 'arduino' code. */ void portduinoSetup() { - printf("Setting up Meshtastic on Porduino...\n"); + printf("Setting up Meshtastic on Portduino...\n"); #ifdef PORTDUINO_LINUX_HARDWARE SPI.begin(); // We need to create SPI @@ -86,6 +115,9 @@ void portduinoSetup() #endif { + // Set the random seed equal to TCPPort to have a different seed per instance + randomSeed(TCPPort); + auto fakeBusy = new SimGPIOPin(SX126X_BUSY, "fakeBusy"); fakeBusy->writePin(LOW); fakeBusy->setSilent(true); diff --git a/src/platform/portduino/SimRadio.cpp b/src/platform/portduino/SimRadio.cpp new file mode 100644 index 000000000..ccfaa3b03 --- /dev/null +++ b/src/platform/portduino/SimRadio.cpp @@ -0,0 +1,250 @@ +#include "SimRadio.h" +#include "MeshService.h" +#include "Router.h" + +SimRadio::SimRadio() +{ + instance = this; +} + +SimRadio *SimRadio::instance; + +ErrorCode SimRadio::send(MeshPacket *p) +{ + printPacket("enqueuing for send", p); + + ErrorCode res = txQueue.enqueue(p) ? ERRNO_OK : ERRNO_UNKNOWN; + + if (res != ERRNO_OK) { // we weren't able to queue it, so we must drop it to prevent leaks + packetPool.release(p); + return res; + } + + // set (random) transmit delay to let others reconfigure their radio, + // to avoid collisions and implement timing-based flooding + DEBUG_MSG("Set random delay before transmitting.\n"); + setTransmitDelay(); + return res; +} + +void SimRadio::setTransmitDelay() +{ + MeshPacket *p = txQueue.getFront(); + // 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. + + /* We assume if rx_snr = 0 and rx_rssi = 0, the packet was generated locally. + * This assumption is valid because of the offset generated by the radio to account for the noise + * floor. + */ + if (p->rx_snr == 0 && p->rx_rssi == 0) { + startTransmitTimer(true); + } else { + // If there is a SNR, start a timer scaled based on that SNR. + DEBUG_MSG("rx_snr found. hop_limit:%d rx_snr:%f\n", p->hop_limit, p->rx_snr); + startTransmitTimerSNR(p->rx_snr); + } +} + +void SimRadio::startTransmitTimer(bool withDelay) +{ + // If we have work to do and the timer wasn't already scheduled, schedule it now + if (!txQueue.empty()) { + uint32_t delayMsec = !withDelay ? 1 : getTxDelayMsec(); + // DEBUG_MSG("xmit timer %d\n", delay); + delay(delayMsec); + onNotify(TRANSMIT_DELAY_COMPLETED); + } else { + DEBUG_MSG("TX QUEUE EMPTY!\n"); + } +} + +void SimRadio::startTransmitTimerSNR(float snr) +{ + // If we have work to do and the timer wasn't already scheduled, schedule it now + if (!txQueue.empty()) { + uint32_t delayMsec = getTxDelayMsecWeighted(snr); + // DEBUG_MSG("xmit timer %d\n", delay); + delay(delayMsec); + onNotify(TRANSMIT_DELAY_COMPLETED); + } +} + +void SimRadio::handleTransmitInterrupt() +{ + // This can be null if we forced the device to enter standby mode. In that case + // ignore the transmit interrupt + if (sendingPacket) + completeSending(); +} + +void SimRadio::completeSending() +{ + // We are careful to clear sending packet before calling printPacket because + // that can take a long time + auto p = sendingPacket; + sendingPacket = NULL; + + if (p) { + txGood++; + printPacket("Completed sending", p); + + // We are done sending that packet, release it + packetPool.release(p); + // DEBUG_MSG("Done with send\n"); + } +} + + +/** Could we send right now (i.e. either not actively receving or transmitting)? */ +bool SimRadio::canSendImmediately() +{ + // We wait _if_ we are partially though receiving a packet (rather than just merely waiting for one). + // To do otherwise would be doubly bad because not only would we drop the packet that was on the way in, + // we almost certainly guarantee no one outside will like the packet we are sending. + bool busyTx = sendingPacket != NULL; + bool busyRx = isReceiving && isActivelyReceiving(); + + if (busyTx || busyRx) { + if (busyTx) + DEBUG_MSG("Can not send yet, busyTx\n"); + if (busyRx) + DEBUG_MSG("Can not send yet, busyRx\n"); + return false; + } else + return true; +} + +bool SimRadio::isActivelyReceiving() +{ + return false; // TODO check how this should be simulated +} + +bool SimRadio::isChannelActive() +{ + return false; // TODO ask simulator +} + +/** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */ +bool SimRadio::cancelSending(NodeNum from, PacketId id) +{ + auto p = txQueue.remove(from, id); + if (p) + packetPool.release(p); // free the packet we just removed + + bool result = (p != NULL); + DEBUG_MSG("cancelSending id=0x%x, removed=%d\n", id, result); + return result; +} + + +void SimRadio::onNotify(uint32_t notification) +{ + switch (notification) { + case ISR_TX: + handleTransmitInterrupt(); + DEBUG_MSG("tx complete - starting timer\n"); + startTransmitTimer(); + break; + case ISR_RX: + DEBUG_MSG("rx complete - starting timer\n"); + break; + case TRANSMIT_DELAY_COMPLETED: + DEBUG_MSG("delay done\n"); + + // If we are not currently in receive mode, then restart the random delay (this can happen if the main thread + // has placed the unit into standby) FIXME, how will this work if the chipset is in sleep mode? + if (!txQueue.empty()) { + if (!canSendImmediately()) { + // DEBUG_MSG("Currently Rx/Tx-ing: set random delay\n"); + setTransmitDelay(); // currently Rx/Tx-ing: reset random delay + } else { + if (isChannelActive()) { // check if there is currently a LoRa packet on the channel + // DEBUG_MSG("Channel is active: set random delay\n"); + setTransmitDelay(); // reset random delay + } else { + // Send any outgoing packets we have ready + MeshPacket *txp = txQueue.dequeue(); + assert(txp); + startSend(txp); + // Packet has been sent, count it toward our TX airtime utilization. + uint32_t xmitMsec = getPacketTime(txp); + airTime->logAirtime(TX_LOG, xmitMsec); + completeSending(); + } + } + } else { + // DEBUG_MSG("done with txqueue\n"); + } + break; + default: + assert(0); // We expected to receive a valid notification from the ISR + } +} + +/** start an immediate transmit */ +void SimRadio::startSend(MeshPacket * txp) +{ + printPacket("Starting low level send", txp); + size_t numbytes = beginSending(txp); + MeshPacket* p = packetPool.allocCopy(*txp); + perhapsDecode(p); + Compressed c = Compressed_init_default; + c.portnum = p->decoded.portnum; + // DEBUG_MSG("Sending back to simulator with portNum %d\n", p->decoded.portnum); + if (p->decoded.payload.size <= sizeof(c.data.bytes)) { + memcpy(&c.data.bytes, p->decoded.payload.bytes, p->decoded.payload.size); + c.data.size = p->decoded.payload.size; + } else { + DEBUG_MSG("Payload size is larger than compressed message allows! Sending empty payload.\n"); + } + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), Compressed_fields, &c); + p->decoded.portnum = PortNum_SIMULATOR_APP; + service.sendToPhone(p); // Sending back to simulator +} + + +void SimRadio::startReceive(MeshPacket *p) { + isReceiving = true; + handleReceiveInterrupt(p); +} + + +void SimRadio::handleReceiveInterrupt(MeshPacket *p) +{ + DEBUG_MSG("HANDLE RECEIVE INTERRUPT\n"); + uint32_t xmitMsec; + assert(isReceiving); + isReceiving = false; + + // read the number of actually received bytes + size_t length = getPacketLength(p); + xmitMsec = getPacketTime(length); + // DEBUG_MSG("Payload size %d vs length (includes header) %d\n", p->decoded.payload.size, length); + + MeshPacket *mp = packetPool.allocCopy(*p); // keep a copy in packtPool + mp->which_payload_variant = MeshPacket_decoded_tag; // Mark that the payload is already decoded + + printPacket("Lora RX", mp); + + airTime->logAirtime(RX_LOG, xmitMsec); + + deliverToReceiver(mp); +} + +size_t SimRadio::getPacketLength(MeshPacket *mp) { + auto &p = mp->decoded; + return (size_t)p.payload.size+sizeof(PacketHeader); +} + +int16_t SimRadio::readData(uint8_t* data, size_t len) { + int16_t state = RADIOLIB_ERR_NONE; + + if(state == RADIOLIB_ERR_NONE) { + // add null terminator + data[len] = 0; + } + + return state; +} \ No newline at end of file diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h new file mode 100644 index 000000000..dad419c62 --- /dev/null +++ b/src/platform/portduino/SimRadio.h @@ -0,0 +1,87 @@ +#pragma once + +#include "RadioInterface.h" +#include "MeshPacketQueue.h" +#include "wifi/WiFiServerAPI.h" + +#define RADIOLIB_EXCLUDE_HTTP +#include + +class SimRadio : public RadioInterface +{ + enum PendingISR { ISR_NONE = 0, ISR_RX, ISR_TX, TRANSMIT_DELAY_COMPLETED }; + + /** + * Debugging counts + */ + uint32_t rxBad = 0, rxGood = 0, txGood = 0; + + MeshPacketQueue txQueue = MeshPacketQueue(MAX_TX_QUEUE); + + public: + SimRadio(); + + /** MeshService needs this to find our active instance + */ + static SimRadio *instance; + + + virtual ErrorCode send(MeshPacket *p) override; + + /** can we detect a LoRa preamble on the current channel? */ + virtual bool isChannelActive(); + + /** are we actively receiving a packet (only called during receiving state) + * This method is only public to facilitate debugging. Do not call. + */ + virtual bool isActivelyReceiving(); + + /** Attempt to cancel a previously sent packet. Returns true if a packet was found we could cancel */ + virtual bool cancelSending(NodeNum from, PacketId id) override; + + /** + * Start waiting to receive a message + * + * External functions can call this method to wake the device from sleep. + */ + virtual void startReceive(MeshPacket *p); + + protected: + /// are _trying_ to receive a packet currently (note - we might just be waiting for one) + bool isReceiving = false; + + private: + + void setTransmitDelay(); + + /** random timer with certain min. and max. settings */ + void startTransmitTimer(bool withDelay = true); + + /** timer scaled to SNR of to be flooded packet */ + void startTransmitTimerSNR(float snr); + + void handleTransmitInterrupt(); + void handleReceiveInterrupt(MeshPacket *p); + + void onNotify(uint32_t notification); + + // start an immediate transmit + virtual void startSend(MeshPacket *txp); + + // derive packet length + size_t getPacketLength(MeshPacket *p); + + int16_t readData(uint8_t* str, size_t len); + + protected: + /** Could we send right now (i.e. either not actively receiving or transmitting)? */ + virtual bool canSendImmediately(); + + + /** + * If a send was in progress finish it and return the buffer to the pool */ + void completeSending(); + +}; + +extern SimRadio *simRadio; \ No newline at end of file