From bd2bfd682235c8b0e4df46b2586fc8c2a8c25757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 27 Nov 2022 14:03:50 +0100 Subject: [PATCH 01/31] update board definition, update copy/paste errors, fix SX1280. --- src/main.cpp | 26 ++++++++++++++++++- src/mesh/InterfacesTemplates.cpp | 3 --- src/mesh/RadioInterface.h | 2 ++ src/mesh/SX1280Interface.cpp | 4 --- src/mesh/SX1280Interface.h | 3 --- src/mesh/SX128xInterface.cpp | 44 +++++++++++++++++--------------- src/mesh/SX128xInterface.h | 12 ++++----- variants/tlora_v2_1_16/variant.h | 5 ---- variants/tlora_v2_1_18/variant.h | 12 ++------- 9 files changed, 59 insertions(+), 52 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 98f22ecf1..692837ad8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -387,7 +387,7 @@ void setup() } #endif -#if defined(USE_SX1280) && !defined(ARCH_PORTDUINO) +#if defined(USE_SX1280) if (!rIf) { rIf = new SX1280Interface(SX128X_CS, SX128X_DIO1, SX128X_RESET, SX128X_BUSY, SPI); if (!rIf->init()) { @@ -452,6 +452,30 @@ void setup() } #endif +// check if the radio chip matches the selected region + +if((config.lora.region == Config_LoRaConfig_RegionCode_LORA_24) && (!rIf->wideLora())){ + DEBUG_MSG("Warning: Radio chip does not support 2.4GHz LoRa. Reverting to unset.\n"); + config.lora.region = Config_LoRaConfig_RegionCode_UNSET; + nodeDB.saveToDisk(SEGMENT_CONFIG); + if(!rIf->reconfigure()) { + DEBUG_MSG("Reconfigure failed, rebooting\n"); + screen->startRebootScreen(); + rebootAtMsec = millis() + 5000; + } +} + +if((config.lora.region != Config_LoRaConfig_RegionCode_LORA_24) && (rIf->wideLora())){ + DEBUG_MSG("Warning: Radio chip only supports 2.4GHz LoRa. Adjusting Region.\n"); + config.lora.region = Config_LoRaConfig_RegionCode_LORA_24; + nodeDB.saveToDisk(SEGMENT_CONFIG); + if(!rIf->reconfigure()) { + DEBUG_MSG("Reconfigure failed, rebooting\n"); + screen->startRebootScreen(); + rebootAtMsec = millis() + 5000; + } +} + #if HAS_WIFI || HAS_ETHERNET mqttInit(); #endif diff --git a/src/mesh/InterfacesTemplates.cpp b/src/mesh/InterfacesTemplates.cpp index 9602525b5..0d2246428 100644 --- a/src/mesh/InterfacesTemplates.cpp +++ b/src/mesh/InterfacesTemplates.cpp @@ -7,7 +7,4 @@ template class SX126xInterface; template class SX126xInterface; template class SX126xInterface; - -#if defined(RADIOLIB_GODMODE) template class SX128xInterface; -#endif \ No newline at end of file diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index e9f725c89..437f294a4 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -97,6 +97,8 @@ class RadioInterface */ virtual bool canSleep() { return true; } + virtual bool wideLora() { return false; } + /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. virtual bool sleep() { return true; } diff --git a/src/mesh/SX1280Interface.cpp b/src/mesh/SX1280Interface.cpp index 97a3febe3..7fc6b45e1 100644 --- a/src/mesh/SX1280Interface.cpp +++ b/src/mesh/SX1280Interface.cpp @@ -2,12 +2,8 @@ #include "SX1280Interface.h" #include "error.h" -#if defined(RADIOLIB_GODMODE) - SX1280Interface::SX1280Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi) : SX128xInterface(cs, irq, rst, busy, spi) { } - -#endif \ No newline at end of file diff --git a/src/mesh/SX1280Interface.h b/src/mesh/SX1280Interface.h index a9661501a..190ca3cf4 100644 --- a/src/mesh/SX1280Interface.h +++ b/src/mesh/SX1280Interface.h @@ -6,12 +6,9 @@ * Our adapter for SX1280 radios */ -#if defined(RADIOLIB_GODMODE) class SX1280Interface : public SX128xInterface { public: SX1280Interface(RADIOLIB_PIN_TYPE cs, RADIOLIB_PIN_TYPE irq, RADIOLIB_PIN_TYPE rst, RADIOLIB_PIN_TYPE busy, SPIClass &spi); }; - -#endif \ No newline at end of file diff --git a/src/mesh/SX128xInterface.cpp b/src/mesh/SX128xInterface.cpp index 36eb0bb94..0a51d618a 100644 --- a/src/mesh/SX128xInterface.cpp +++ b/src/mesh/SX128xInterface.cpp @@ -2,8 +2,6 @@ #include "SX128xInterface.h" #include "error.h" -#if defined(RADIOLIB_GODMODE) - // Particular boards might define a different max power based on what their hardware can do #ifndef SX128X_MAX_POWER #define SX128X_MAX_POWER 13 @@ -27,11 +25,11 @@ bool SX128xInterface::init() pinMode(SX128X_POWER_EN, OUTPUT); #endif -#ifdef SX128X_RXEN // set not rx or tx mode +#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // set not rx or tx mode digitalWrite(SX128X_RXEN, LOW); // Set low before becoming an output pinMode(SX128X_RXEN, OUTPUT); #endif -#ifdef SX128X_TXEN +#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) digitalWrite(SX128X_TXEN, LOW); pinMode(SX128X_TXEN, OUTPUT); #endif @@ -46,6 +44,8 @@ bool SX128xInterface::init() limitPower(); + preambleLength = 12; // 12 is the default for this chip, 32 does not RX at all + int res = lora.begin(getFreq(), bw, sf, cr, syncWord, power, preambleLength); // \todo Display actual typename of the adapter, not just `SX128x` DEBUG_MSG("SX128x init result %d\n", res); @@ -54,12 +54,6 @@ bool SX128xInterface::init() DEBUG_MSG("Bandwidth set to %f\n", bw); DEBUG_MSG("Power output set to %d\n", power); -#ifdef SX128X_TXEN - // lora.begin sets Dio2 as RF switch control, which is not true if we are manually controlling RX and TX - if (res == RADIOLIB_ERR_NONE) - res = lora.setDio2AsRfSwitch(true); -#endif - if (res == RADIOLIB_ERR_NONE) res = lora.setCRC(2); @@ -122,18 +116,28 @@ void INTERRUPT_ATTR SX128xInterface::disableInterrupt() lora.clearDio1Action(); } +template +bool SX128xInterface::wideLora() +{ + return true; +} + template void SX128xInterface::setStandby() { checkNotification(); // handle any pending interrupts before we force standby int err = lora.standby(); + + if (err != RADIOLIB_ERR_NONE) + DEBUG_MSG("SX128x standby failed with error %d\n", err); + assert(err == RADIOLIB_ERR_NONE); -#ifdef SX128X_RXEN // we have RXEN/TXEN control - turn off RX and TX power +#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn off RX and TX power digitalWrite(SX128X_RXEN, LOW); #endif -#ifdef SX128X_TXEN +#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) digitalWrite(SX128X_TXEN, LOW); #endif @@ -158,10 +162,10 @@ void SX128xInterface::addReceiveMetadata(MeshPacket *mp) template void SX128xInterface::configHardwareForSend() { -#ifdef SX128X_TXEN // we have RXEN/TXEN control - turn on TX power / off RX power +#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on TX power / off RX power digitalWrite(SX128X_TXEN, HIGH); #endif -#ifdef SX128X_RXEN +#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) digitalWrite(SX128X_RXEN, LOW); #endif @@ -180,10 +184,10 @@ void SX128xInterface::startReceive() setStandby(); -#ifdef SX128X_RXEN // we have RXEN/TXEN control - turn on RX power / off TX power +#if defined(SX128X_RXEN) && (SX128X_RXEN != RADIOLIB_NC) // we have RXEN/TXEN control - turn on RX power / off TX power digitalWrite(SX128X_RXEN, HIGH); #endif -#ifdef SX128X_TXEN +#if defined(SX128X_TXEN) && (SX128X_TXEN != RADIOLIB_NC) digitalWrite(SX128X_TXEN, LOW); #endif @@ -219,11 +223,13 @@ bool SX128xInterface::isChannelActive() template bool SX128xInterface::isActivelyReceiving() { - // return isChannelActive(); - +#ifdef RADIOLIB_GODMODE uint16_t irq = lora.getIrqStatus(); bool hasPreamble = (irq & RADIOLIB_SX128X_IRQ_HEADER_VALID); return hasPreamble; +#else + return isChannelActive(); +#endif } template @@ -248,5 +254,3 @@ bool SX128xInterface::sleep() return true; } - -#endif \ No newline at end of file diff --git a/src/mesh/SX128xInterface.h b/src/mesh/SX128xInterface.h index 9994ce850..5a6ed95f9 100644 --- a/src/mesh/SX128xInterface.h +++ b/src/mesh/SX128xInterface.h @@ -1,7 +1,5 @@ #pragma once -#if defined(RADIOLIB_GODMODE) - #include "RadioLibInterface.h" /** @@ -19,6 +17,8 @@ class SX128xInterface : public RadioLibInterface /// \return true if initialisation succeeded. virtual bool init() override; + virtual bool wideLora() override; + /// Apply any radio provisioning changes /// Make sure the Driver is properly configured before calling init(). /// \return true if initialisation succeeded. @@ -27,9 +27,11 @@ class SX128xInterface : public RadioLibInterface /// Prepare hardware for sleep. Call this _only_ for deep sleep, not needed for light sleep. virtual bool sleep() override; - protected: +#ifdef RADIOLIB_GODMODE + bool isIRQPending() override { return lora.getIrqStatus() != 0; } +#endif - float currentLimit = 140; // Higher OCP limit for SX128x PA + protected: /** * Specific module instance @@ -71,5 +73,3 @@ class SX128xInterface : public RadioLibInterface private: }; - -#endif \ No newline at end of file diff --git a/variants/tlora_v2_1_16/variant.h b/variants/tlora_v2_1_16/variant.h index a58b10b81..14175f48b 100644 --- a/variants/tlora_v2_1_16/variant.h +++ b/variants/tlora_v2_1_16/variant.h @@ -3,8 +3,6 @@ #define GPS_RX_PIN 15 // per @der_bear on the forum, 36 is incorrect for this board type and 15 is a better pick #define GPS_TX_PIN 13 -#define EXT_NOTIFY_OUT 2 // Default pin to use for Ext Notify Module. - #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 2.0 (R42=100k, R43=100k) #define ADC_MULTIPLIER 2.11 // 2.0 + 10% for correction of display undervoltage. @@ -12,9 +10,6 @@ #define I2C_SDA 21 // I2C pins for this board #define I2C_SCL 22 -// #define RESET_OLED 16 // If defined, this pin will be used to reset the display controller. Crashes on newer ESP-IDF and not needed per schematic - -#define VEXT_ENABLE 21 // active low, powers the oled display and the lora antenna boost #define LED_PIN 25 // If defined we will blink this LED #define BUTTON_PIN 12 // If defined, this will be used for user button presses, diff --git a/variants/tlora_v2_1_18/variant.h b/variants/tlora_v2_1_18/variant.h index dd94847be..95d699767 100644 --- a/variants/tlora_v2_1_18/variant.h +++ b/variants/tlora_v2_1_18/variant.h @@ -1,9 +1,5 @@ #undef GPS_RX_PIN #undef GPS_TX_PIN -#define GPS_RX_PIN 15 // per @der_bear on the forum, 36 is incorrect for this board type and 15 is a better pick -#define GPS_TX_PIN 13 - -#define EXT_NOTIFY_OUT 2 // Default pin to use for Ext Notify Module. #define BATTERY_PIN 35 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage // ratio of voltage divider = 2.0 (R42=100k, R43=100k) @@ -20,11 +16,7 @@ #define USE_SX1280 #define LORA_RESET 23 -#define SX128X_CS 18 // FIXME - we really should define LORA_CS instead +#define SX128X_CS 18 #define SX128X_DIO1 26 -#define SX128X_DIO2 33 #define SX128X_BUSY 32 -#define SX128X_RESET LORA_RESET -#define SX128X_E22 // Not really an E22 but TTGO seems to be trying to clone that -// Internally the TTGO module hooks the SX1280-DIO2 in to control the TX/RX switch (which is the default for the sx1280interface -// code) +#define SX128X_RESET LORA_RESET \ No newline at end of file From efc3f4c0eec18448dd447956c99ad40811855882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Mon, 28 Nov 2022 21:58:30 +0100 Subject: [PATCH 02/31] remove a few DSR Router bits for S&F Module --- src/main.cpp | 3 --- src/mesh/ReliableRouter.cpp | 6 ++---- src/mesh/ReliableRouter.h | 6 ------ src/mesh/Router.cpp | 1 - src/sleep.cpp | 2 +- 5 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 692837ad8..c0fcaf96e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,8 +8,6 @@ #include "configuration.h" #include "error.h" #include "power.h" -// #include "rom/rtc.h" -//#include "DSRRouter.h" #include "ReliableRouter.h" // #include "debug.h" #include "FSCommon.h" @@ -217,7 +215,6 @@ void setup() fsInit(); - // router = new DSRRouter(); router = new ReliableRouter(); #ifdef I2C_SDA1 diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp index 2807178b8..7933a7920 100644 --- a/src/mesh/ReliableRouter.cpp +++ b/src/mesh/ReliableRouter.cpp @@ -90,8 +90,7 @@ void ReliableRouter::sniffReceived(const MeshPacket *p, const Routing *c) { NodeNum ourNode = getNodeNum(); - if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability - // - not DSR routing) + if (p->to == ourNode) { // ignore ack/nak/want_ack packets that are not address to us (we only handle 0 hop reliability) if (p->want_ack) { if (MeshModule::currentReply) DEBUG_MSG("Some other module has replied to this message, no need for a 2nd ack\n"); @@ -200,8 +199,7 @@ int32_t ReliableRouter::doRetransmissions() DEBUG_MSG("Reliable send failed, returning a nak for fr=0x%x,to=0x%x,id=0x%x\n", p.packet->from, p.packet->to, p.packet->id); sendAckNak(Routing_Error_MAX_RETRANSMIT, getFrom(p.packet), p.packet->id, p.packet->channel); - // Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived - which - // allows the DSR version to still be able to look at the PendingPacket + // Note: we don't stop retransmission here, instead the Nak packet gets processed in sniffReceived stopRetransmission(it->first); stillValid = false; // just deleted it } else { diff --git a/src/mesh/ReliableRouter.h b/src/mesh/ReliableRouter.h index 3f89dcbd6..ff304cdd7 100644 --- a/src/mesh/ReliableRouter.h +++ b/src/mesh/ReliableRouter.h @@ -38,12 +38,6 @@ struct PendingPacket { /** Starts at NUM_RETRANSMISSIONS -1(normally 3) and counts down. Once zero it will be removed from the list */ uint8_t numRetransmissions = 0; - /** True if we have started trying to find a route - for DSR usage - * While trying to find a route we don't actually send the data packet. We just leave it here pending until - * we have a route or we've failed to find one. - */ - bool wantRoute = false; - PendingPacket() {} explicit PendingPacket(MeshPacket *p); }; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index e0746bdd9..6b6a03a3a 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -21,7 +21,6 @@ extern "C" { * DONE: Implement basic interface and use it elsewhere in app * Add naive flooding mixin (& drop duplicate rx broadcasts), add tools for sending broadcasts with incrementing sequence #s * Add an optional adjacent node only 'send with ack' mixin. If we timeout waiting for the ack, call handleAckTimeout(packet) - * Add DSR mixin * **/ diff --git a/src/sleep.cpp b/src/sleep.cpp index 390ab7f65..39ee43ebc 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -237,7 +237,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t sleepMsec) // FIXME, use a more r // We want RTC peripherals to stay on esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); -#ifdef BUTTON_NEED_PULLUP +#if defined(BUTTON_PIN) && defined(BUTTON_NEED_PULLUP) gpio_pullup_en((gpio_num_t)BUTTON_PIN); #endif From f5120a29ec2d24a6ab30dbd82065bc77e41e75f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 29 Nov 2022 11:22:18 +0100 Subject: [PATCH 03/31] WIP: audio module still does not work, but enabled for all regions where audio is permitted. # Conflicts: # variants/tlora_v2_1_18/platformio.ini --- arch/esp32/esp32.ini | 3 +- arch/esp32/esp32s3.ini | 1 + src/modules/Modules.cpp | 4 - src/modules/esp32/AudioModule.cpp | 221 +++++++++++++++++------------- src/modules/esp32/AudioModule.h | 59 +++++--- 5 files changed, 167 insertions(+), 121 deletions(-) diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 730b78942..70654e8ec 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -33,7 +33,8 @@ lib_deps = ${environmental_base.lib_deps} https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 h2zero/NimBLE-Arduino@^1.4.0 - https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + caveman99/ESP32 Codec2@^1.0.1 lib_ignore = segger_rtt diff --git a/arch/esp32/esp32s3.ini b/arch/esp32/esp32s3.ini index 0c2d7d8f1..b05772344 100644 --- a/arch/esp32/esp32s3.ini +++ b/arch/esp32/esp32s3.ini @@ -34,6 +34,7 @@ lib_deps = https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 h2zero/NimBLE-Arduino@^1.4.0 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + caveman99/ESP32 Codec2@^1.0.1 lib_ignore = segger_rtt diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index d24fa8f26..b97e965e1 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -19,10 +19,8 @@ #ifdef ARCH_ESP32 #include "modules/esp32/RangeTestModule.h" #include "modules/esp32/StoreForwardModule.h" -#ifdef USE_SX1280 #include "modules/esp32/AudioModule.h" #endif -#endif #if defined(ARCH_ESP32) || defined(ARCH_NRF52) #include "modules/ExternalNotificationModule.h" #if !defined(TTGO_T_ECHO) @@ -68,9 +66,7 @@ void setupModules() #endif #ifdef ARCH_ESP32 // Only run on an esp32 based device. -#ifdef USE_SX1280 new AudioModule(); -#endif new ExternalNotificationModule(); storeForwardModule = new StoreForwardModule(); diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index c37a1b2a8..0f95c306a 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -6,6 +6,8 @@ #include "Router.h" #include "FSCommon.h" +#include +#include #include /* @@ -30,7 +32,7 @@ KNOWN PROBLEMS * Until the module is initilized by the startup sequence, the amp_pin pin is in a floating - state. This may produce a bit of "noise". + radio_state. This may produce a bit of "noise". * Will not work on NRF and the Linux device targets. */ @@ -40,12 +42,20 @@ #define AUDIO_MODULE_RX_BUFFER 128 #define AUDIO_MODULE_DATA_MAX Constants_DATA_PAYLOAD_LEN -#define AUDIO_MODULE_MODE 7 // 700B -#define AUDIO_MODULE_ACK 1 +#define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 -#if defined(ARCH_ESP32) && defined(USE_SX1280) +#if defined(ARCH_ESP32) AudioModule *audioModule; +Codec2Thread *codec2Thread; + +FastAudioFIFO audio_fifo; +uint16_t adc_buffer[ADC_BUFFER_SIZE] = {}; +uint16_t adc_buffer_index = 0; +portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; +int16_t speech[ADC_BUFFER_SIZE] = {}; +volatile RadioState radio_state = RadioState::tx; +adc1_channel_t mic_chan = (adc1_channel_t)0; ButterworthFilter hp_filter(240, 8000, ButterworthFilter::ButterworthFilter::Highpass, 1); @@ -55,55 +65,22 @@ int Sine1KHz_index = 0; uint8_t rx_raw_audio_value = 127; -AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { - audio_fifo.init(); +int IRAM_ATTR local_adc1_read(int channel) { + uint16_t adc_value; + SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected + while (SENS.sar_slave_addr1.meas_status != 0); + SENS.sar_meas_start1.meas1_start_sar = 0; + SENS.sar_meas_start1.meas1_start_sar = 1; + while (SENS.sar_meas_start1.meas1_done_sar == 0); + adc_value = SENS.sar_meas_start1.meas1_data_sar; + return adc_value; } -void AudioModule::run_codec2() +IRAM_ATTR void am_onTimer() { - if (state == State::tx) - { - for (int i = 0; i < ADC_BUFFER_SIZE; i++) - speech[i] = (int16_t)hp_filter.Update((float)speech[i]); - - codec2_encode(codec2_state, tx_encode_frame + tx_encode_frame_index, speech); - - //increment the pointer where the encoded frame must be saved - tx_encode_frame_index += 8; - - //If it is the 5th time then we have a ready trasnmission frame - if (tx_encode_frame_index == ENCODE_FRAME_SIZE) - { - tx_encode_frame_index = 0; - //Transmit it - sendPayload(); - } - } - if (state == State::rx) //Receiving - { - //Make a cycle to get each codec2 frame from the received frame - for (int i = 0; i < ENCODE_FRAME_SIZE; i += 8) - { - //Decode the codec2 frame - codec2_decode(codec2_state, output_buffer, rx_encode_frame + i); - - // Add to the audio buffer the 320 samples resulting of the decode of the codec2 frame. - for (int g = 0; g < ADC_BUFFER_SIZE; g++) - audio_fifo.put(output_buffer[g]); - } - } - state = State::standby; -} - -void AudioModule::handleInterrupt() -{ - audioModule->onTimer(); -} - -void AudioModule::onTimer() -{ - if (state == State::tx) { - adc_buffer[adc_buffer_index++] = (16 * adc1_get_raw(mic_chan)) - 32768; + portENTER_CRITICAL_ISR(&timerMux); //Enter crital code without interruptions + if (radio_state == RadioState::tx) { + adc_buffer[adc_buffer_index++] = (16 * local_adc1_read(mic_chan)) - 32768; //If you want to test with a 1KHz tone, comment the line above and descomment the three lines below @@ -113,39 +90,96 @@ void AudioModule::onTimer() if (adc_buffer_index == ADC_BUFFER_SIZE) { adc_buffer_index = 0; + DEBUG_MSG("--- memcpy\n"); memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE); - audioModule->setIntervalFromNow(0); // process buffer immediately + // Notify codec2 task that the buffer is ready. + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + DEBUG_MSG("--- notifyFromISR\n"); + codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::tx, true); + if (xHigherPriorityTaskWoken) + portYIELD_FROM_ISR(); } - } else if (state == State::rx) { + } else if (radio_state == RadioState::rx) { int16_t v; //Get a value from audio_fifo and convert it to 0 - 255 to play it in the ADC - //If none value is available the DAC will play the last one that was read, that's - //why the rx_raw_audio_value variable is a global one. if (audio_fifo.get(&v)) rx_raw_audio_value = (uint8_t)((v + 32768) / 256); - - //Play dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, rx_raw_audio_value); } + portEXIT_CRITICAL_ISR(&timerMux); // exit critical code +} + +Codec2Thread::Codec2Thread() : concurrency::NotifiedWorkerThread("Codec2Thread") { + if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { + DEBUG_MSG("--- Setting up codec2 in mode %u\n", moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE); + codec2_state = codec2_create(moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE); + codec2_set_lpc_post_filter(codec2_state, 1, 0, 0.8, 0.2); + } else { + DEBUG_MSG("--- Codec2 disabled\n"); + } +} + +AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { + audio_fifo.init(); + new Codec2Thread(); +} + +void Codec2Thread::onNotify(uint32_t notification) +{ + switch (notification) { + case RadioState::tx: + for (int i = 0; i < ADC_BUFFER_SIZE; i++) + speech[i] = (int16_t)hp_filter.Update((float)speech[i]); + + codec2_encode(codec2_state, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, speech); + + //increment the pointer where the encoded frame must be saved + audioModule->tx_encode_frame_index += 8; + + //If it is the 5th time then we have a ready trasnmission frame + if (audioModule->tx_encode_frame_index == ENCODE_FRAME_SIZE) + { + audioModule->tx_encode_frame_index = 0; + //Transmit it + audioModule->sendPayload(); + } + break; + case RadioState::rx: + //Make a cycle to get each codec2 frame from the received frame + for (int i = 0; i < ENCODE_FRAME_SIZE; i += ENCODE_CODEC2_SIZE) + { + //Decode the codec2 frame + codec2_decode(codec2_state, output_buffer, audioModule->rx_encode_frame + i); + + // Add to the audio buffer the 320 samples resulting of the decode of the codec2 frame. + for (int g = 0; g < ADC_BUFFER_SIZE; g++) + audio_fifo.put(output_buffer[g]); + } + break; + default: + assert(0); // We expected to receive a valid notification from the ISR + break; + } } int32_t AudioModule::runOnce() { - if (moduleConfig.audio.codec2_enabled) { - + if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { if (firstTime) { - - DEBUG_MSG("Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); + DEBUG_MSG("--- Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); mic_chan = moduleConfig.audio.mic_chan ? (adc1_channel_t)(int)moduleConfig.audio.mic_chan : (adc1_channel_t)AMIC; adc1_config_width(ADC_WIDTH_12Bit); - adc1_config_channel_atten(mic_chan, ADC_ATTEN_DB_6); + adc1_config_channel_atten(mic_chan, ADC_ATTEN_DB_6); + adc1_get_raw(mic_chan); + + radio_state = RadioState::rx; // Start a timer at 8kHz to sample the ADC and play the audio on the DAC. uint32_t cpufreq = getCpuFrequencyMhz(); - switch (cpufreq){ + switch (cpufreq){ case 160: adcTimer = timerBegin(3, 1000, true); // 160 MHz / 1000 = 160KHz break; @@ -160,48 +194,44 @@ int32_t AudioModule::runOnce() adcTimer = timerBegin(3, 500, true); // 80 MHz / 500 = 160KHz break; } - timerAttachInterrupt(adcTimer, &AudioModule::handleInterrupt, true); - timerAlarmWrite(adcTimer, 20, true); // Interrupts when counter == 20, 8.000 times a second - timerAlarmEnable(adcTimer); + DEBUG_MSG("--- Timer CPU Frequency: %u MHz\n", cpufreq); + timerAttachInterrupt(adcTimer, &am_onTimer, false); + timerAlarmWrite(adcTimer, 20, true); // Interrupts when counter == 20, 8.000 times a second + timerAlarmEnable(adcTimer); - DEBUG_MSG("Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); - DEBUG_MSG("Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); + DEBUG_MSG("--- Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); + DEBUG_MSG("--- Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); // Configure PTT input - pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT_PULLUP); + pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT); - state = State::rx; - - DEBUG_MSG("Setting up codec2 in mode %u\n", moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE); - - codec2_state = codec2_create(moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE); - codec2_set_lpc_post_filter(codec2_state, 1, 0, 0.8, 0.2); - - firstTime = 0; + firstTime = false; } else { - // Check if we have a PTT press - if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == LOW) { - // PTT pressed, recording - state = State::tx; - } - if (state != State::standby) { - run_codec2(); + // Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive. + if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) { + if (radio_state == RadioState::rx) { + DEBUG_MSG("--- PTT pressed, switching to TX\n"); + radio_state = RadioState::tx; + } + } else { + if (radio_state == RadioState::tx) { + DEBUG_MSG("--- PTT released, switching to RX\n"); + radio_state = RadioState::rx; + } } + } - return 100; } else { - DEBUG_MSG("Audio Module Disabled\n"); - + DEBUG_MSG("--- Audio Module Disabled\n"); return INT32_MAX; } + } MeshPacket *AudioModule::allocReply() { - auto reply = allocDataPacket(); // Allocate a packet for sending - return reply; } @@ -211,7 +241,8 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies) p->to = dest; p->decoded.want_response = wantReplies; - p->want_ack = AUDIO_MODULE_ACK; + p->want_ack = false; // Audio is shoot&forget. TODO: Is this really suppressing retransmissions? + p->priority = MeshPacket_Priority_MAX; // Audio is important, because realtime p->decoded.payload.size = ENCODE_FRAME_SIZE; memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size); @@ -221,16 +252,18 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies) ProcessMessage AudioModule::handleReceived(const MeshPacket &mp) { - if (moduleConfig.audio.codec2_enabled) { + if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { auto &p = mp.decoded; if (getFrom(&mp) != nodeDB.getNodeNum()) { if (p.payload.size == ENCODE_FRAME_SIZE) { memcpy(rx_encode_frame, p.payload.bytes, p.payload.size); - state = State::rx; - audioModule->setIntervalFromNow(0); - run_codec2(); + radio_state = RadioState::rx; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::rx, true); + if (xHigherPriorityTaskWoken) + portYIELD_FROM_ISR(); } else { - DEBUG_MSG("Invalid payload size %u != %u\n", p.payload.size, ENCODE_FRAME_SIZE); + DEBUG_MSG("--- Invalid payload size %u != %u\n", p.payload.size, ENCODE_FRAME_SIZE); } } } diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index d82af4d43..c83160cfb 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -7,38 +7,45 @@ #include #include #include -#if defined(ARCH_ESP32) && defined(USE_SX1280) +#if defined(ARCH_ESP32) #include #include #include #endif #define ADC_BUFFER_SIZE 320 // 40ms of voice in 8KHz sampling frequency -#define ENCODE_FRAME_SIZE 40 // 5 codec2 frames of 8 bytes each +#define ENCODE_CODEC2_SIZE 8 +#define ENCODE_FRAME_SIZE (ENCODE_CODEC2_SIZE * 5) // 5 codec2 frames of 8 bytes each + +class Codec2Thread : public concurrency::NotifiedWorkerThread +{ +#if defined(ARCH_ESP32) + struct CODEC2* codec2_state = NULL; + int16_t output_buffer[ADC_BUFFER_SIZE] = {}; + + public: + Codec2Thread(); + + protected: + virtual void onNotify(uint32_t notification) override; +#endif +}; class AudioModule : public SinglePortModule, private concurrency::OSThread { -#if defined(ARCH_ESP32) && defined(USE_SX1280) - bool firstTime = 1; +#if defined(ARCH_ESP32) + bool firstTime = true; hw_timer_t* adcTimer = NULL; - uint16_t adc_buffer[ADC_BUFFER_SIZE] = {}; - int16_t speech[ADC_BUFFER_SIZE] = {}; - int16_t output_buffer[ADC_BUFFER_SIZE] = {}; - unsigned char rx_encode_frame[ENCODE_FRAME_SIZE] = {}; - unsigned char tx_encode_frame[ENCODE_FRAME_SIZE] = {}; - int tx_encode_frame_index = 0; + FastAudioFIFO audio_fifo; uint16_t adc_buffer_index = 0; - adc1_channel_t mic_chan = (adc1_channel_t)0; - struct CODEC2* codec2_state = NULL; - enum State - { - standby, rx, tx - }; - volatile State state = State::tx; public: + unsigned char rx_encode_frame[ENCODE_FRAME_SIZE] = {}; + unsigned char tx_encode_frame[ENCODE_FRAME_SIZE] = {}; + int tx_encode_frame_index = 0; + AudioModule(); /** @@ -49,11 +56,7 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread protected: virtual int32_t runOnce() override; - static void handleInterrupt(); - - void onTimer(); - - void run_codec2(); + // void run_codec2(); virtual MeshPacket *allocReply() override; @@ -65,4 +68,16 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread }; extern AudioModule *audioModule; +extern Codec2Thread *codec2Thread; + +extern FastAudioFIFO audio_fifo; +extern uint16_t adc_buffer[ADC_BUFFER_SIZE]; +extern uint16_t adc_buffer_index; +extern portMUX_TYPE timerMux; +extern int16_t speech[ADC_BUFFER_SIZE]; +enum RadioState { standby, rx, tx }; +extern volatile RadioState radio_state; +extern adc1_channel_t mic_chan; + +IRAM_ATTR void am_onTimer(); From 80d0b63c3af409422fa2557a7d30da551a858ae1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 29 Nov 2022 14:35:46 +0100 Subject: [PATCH 04/31] hopefully fix compilation errors --- src/modules/esp32/AudioModule.cpp | 16 ++++++++++++++-- src/modules/esp32/AudioModule.h | 12 ++---------- variants/heltec_v3/platformio.ini | 2 +- variants/heltec_wsl_v3/platformio.ini | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 0f95c306a..959366dec 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -1,4 +1,6 @@ + #include "configuration.h" +#if defined(ARCH_ESP32) #include "AudioModule.h" #include "MeshService.h" #include "NodeDB.h" @@ -44,8 +46,6 @@ #define AUDIO_MODULE_DATA_MAX Constants_DATA_PAYLOAD_LEN #define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 -#if defined(ARCH_ESP32) - AudioModule *audioModule; Codec2Thread *codec2Thread; @@ -67,12 +67,21 @@ uint8_t rx_raw_audio_value = 127; int IRAM_ATTR local_adc1_read(int channel) { uint16_t adc_value; +#if CONFIG_IDF_TARGET_ESP32S3 + SENS.sar_meas1_ctrl2.sar1_en_pad = (1 << channel); // only one channel is selected + while (SENS.sar_slave_addr1.meas_status != 0); + SENS.sar_meas1_ctrl2.meas1_start_sar = 0; + SENS.sar_meas1_ctrl2.meas1_start_sar = 1; + while (SENS.sar_meas1_ctrl2.meas1_done_sar == 0); + adc_value = SENS.sar_meas1_ctrl2.meas1_data_sar; +#else SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected while (SENS.sar_slave_addr1.meas_status != 0); SENS.sar_meas_start1.meas1_start_sar = 0; SENS.sar_meas_start1.meas1_start_sar = 1; while (SENS.sar_meas_start1.meas1_done_sar == 0); adc_value = SENS.sar_meas_start1.meas1_data_sar; +#endif return adc_value; } @@ -106,7 +115,10 @@ IRAM_ATTR void am_onTimer() //Get a value from audio_fifo and convert it to 0 - 255 to play it in the ADC if (audio_fifo.get(&v)) rx_raw_audio_value = (uint8_t)((v + 32768) / 256); + // comment out for now, S3 does not have Hardware-DAC. Consider I2S instead. +#if !CONFIG_IDF_TARGET_ESP32S3 dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, rx_raw_audio_value); +#endif } portEXIT_CRITICAL_ISR(&timerMux); // exit critical code } diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index c83160cfb..16705a141 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -3,15 +3,14 @@ #include "SinglePortModule.h" #include "concurrency/OSThread.h" #include "configuration.h" +#if defined(ARCH_ESP32) #include "NodeDB.h" #include #include #include -#if defined(ARCH_ESP32) #include #include #include -#endif #define ADC_BUFFER_SIZE 320 // 40ms of voice in 8KHz sampling frequency #define ENCODE_CODEC2_SIZE 8 @@ -19,7 +18,6 @@ class Codec2Thread : public concurrency::NotifiedWorkerThread { -#if defined(ARCH_ESP32) struct CODEC2* codec2_state = NULL; int16_t output_buffer[ADC_BUFFER_SIZE] = {}; @@ -28,16 +26,12 @@ class Codec2Thread : public concurrency::NotifiedWorkerThread protected: virtual void onNotify(uint32_t notification) override; -#endif }; class AudioModule : public SinglePortModule, private concurrency::OSThread { -#if defined(ARCH_ESP32) bool firstTime = true; hw_timer_t* adcTimer = NULL; - - FastAudioFIFO audio_fifo; uint16_t adc_buffer_index = 0; @@ -64,13 +58,11 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread * @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it */ virtual ProcessMessage handleReceived(const MeshPacket &mp) override; -#endif }; extern AudioModule *audioModule; extern Codec2Thread *codec2Thread; -extern FastAudioFIFO audio_fifo; extern uint16_t adc_buffer[ADC_BUFFER_SIZE]; extern uint16_t adc_buffer_index; extern portMUX_TYPE timerMux; @@ -80,4 +72,4 @@ extern volatile RadioState radio_state; extern adc1_channel_t mic_chan; IRAM_ATTR void am_onTimer(); - +#endif \ No newline at end of file diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 6cd75bed8..372f42d64 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -1,6 +1,6 @@ [env:heltec-v3] platform = https://github.com/Baptou88/platform-espressif32.git -extends = esp32_base +extends = esp32s3_base board = heltec_wifi_lora_32_V3 # Temporary: https://community.platformio.org/t/heltec-esp32-lora-v3-board-support/30406/2 platform_packages = diff --git a/variants/heltec_wsl_v3/platformio.ini b/variants/heltec_wsl_v3/platformio.ini index 8854b1a44..336e44936 100644 --- a/variants/heltec_wsl_v3/platformio.ini +++ b/variants/heltec_wsl_v3/platformio.ini @@ -1,6 +1,6 @@ [env:heltec-wsl-v3] platform = https://github.com/Baptou88/platform-espressif32.git -extends = esp32_base +extends = esp32s3_base board = heltec_wifi_lora_32_V3 # Temporary: https://community.platformio.org/t/heltec-esp32-lora-v3-board-support/30406/2 platform_packages = From 8cbf292373b51546c2845ce2d2c7dde9009f09b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 29 Nov 2022 17:19:10 +0100 Subject: [PATCH 05/31] WIP: add digital audio. Needs a proto change, so checking in generated files for now. # Conflicts: # src/mesh/generated/localonly.pb.h # src/mesh/generated/module_config.pb.h --- src/mesh/generated/localonly.pb.h | 2 +- src/mesh/generated/module_config.pb.h | 75 +++++++----------- src/modules/esp32/AudioModule.cpp | 110 ++++++++++++++++++-------- src/modules/esp32/AudioModule.h | 1 + 4 files changed, 109 insertions(+), 79 deletions(-) diff --git a/src/mesh/generated/localonly.pb.h b/src/mesh/generated/localonly.pb.h index c154a98db..44d923839 100644 --- a/src/mesh/generated/localonly.pb.h +++ b/src/mesh/generated/localonly.pb.h @@ -151,7 +151,7 @@ extern const pb_msgdesc_t LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define LocalConfig_size 387 -#define LocalModuleConfig_size 376 +#define LocalModuleConfig_size 385 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/module_config.pb.h b/src/mesh/generated/module_config.pb.h index 47f410a5c..3747452c7 100644 --- a/src/mesh/generated/module_config.pb.h +++ b/src/mesh/generated/module_config.pb.h @@ -63,12 +63,14 @@ typedef enum _ModuleConfig_CannedMessageConfig_InputEventChar { /* Struct definitions */ typedef struct _ModuleConfig_AudioConfig { bool codec2_enabled; - uint8_t ptt_pin; + uint32_t mic_chan; + uint32_t amp_pin; + uint32_t ptt_pin; ModuleConfig_AudioConfig_Audio_Baud bitrate; - uint8_t i2s_ws; - uint8_t i2s_sd; - uint8_t i2s_din; - uint8_t i2s_sck; + uint32_t i2s_ws; + uint32_t i2s_sd; + uint32_t i2s_din; + uint32_t i2s_sck; } ModuleConfig_AudioConfig; typedef struct _ModuleConfig_CannedMessageConfig { @@ -93,13 +95,6 @@ typedef struct _ModuleConfig_ExternalNotificationConfig { bool alert_message; bool alert_bell; bool use_pwm; - uint8_t output_vibra; - uint8_t output_buzzer; - bool alert_message_vibra; - bool alert_message_buzzer; - bool alert_bell_vibra; - bool alert_bell_buzzer; - uint16_t nag_timeout; } ModuleConfig_ExternalNotificationConfig; typedef struct _ModuleConfig_MQTTConfig { @@ -192,18 +187,18 @@ extern "C" { /* Initializer values for message structs */ #define ModuleConfig_init_default {0, {ModuleConfig_MQTTConfig_init_default}} #define ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0} -#define ModuleConfig_AudioConfig_init_default {0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} +#define ModuleConfig_AudioConfig_init_default {0, 0, 0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN} -#define ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0} #define ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0} #define ModuleConfig_RangeTestConfig_init_default {0, 0, 0} #define ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0} #define ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define ModuleConfig_init_zero {0, {ModuleConfig_MQTTConfig_init_zero}} #define ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0} -#define ModuleConfig_AudioConfig_init_zero {0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} +#define ModuleConfig_AudioConfig_init_zero {0, 0, 0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN} -#define ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} +#define ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0} #define ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0} #define ModuleConfig_RangeTestConfig_init_zero {0, 0, 0} #define ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0} @@ -211,12 +206,14 @@ extern "C" { /* Field tags (for use in manual encoding/decoding) */ #define ModuleConfig_AudioConfig_codec2_enabled_tag 1 -#define ModuleConfig_AudioConfig_ptt_pin_tag 2 -#define ModuleConfig_AudioConfig_bitrate_tag 3 -#define ModuleConfig_AudioConfig_i2s_ws_tag 4 -#define ModuleConfig_AudioConfig_i2s_sd_tag 5 -#define ModuleConfig_AudioConfig_i2s_din_tag 6 -#define ModuleConfig_AudioConfig_i2s_sck_tag 7 +#define ModuleConfig_AudioConfig_mic_chan_tag 2 +#define ModuleConfig_AudioConfig_amp_pin_tag 3 +#define ModuleConfig_AudioConfig_ptt_pin_tag 4 +#define ModuleConfig_AudioConfig_bitrate_tag 5 +#define ModuleConfig_AudioConfig_i2s_ws_tag 6 +#define ModuleConfig_AudioConfig_i2s_sd_tag 7 +#define ModuleConfig_AudioConfig_i2s_din_tag 8 +#define ModuleConfig_AudioConfig_i2s_sck_tag 9 #define ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1 #define ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2 #define ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3 @@ -235,13 +232,6 @@ extern "C" { #define ModuleConfig_ExternalNotificationConfig_alert_message_tag 5 #define ModuleConfig_ExternalNotificationConfig_alert_bell_tag 6 #define ModuleConfig_ExternalNotificationConfig_use_pwm_tag 7 -#define ModuleConfig_ExternalNotificationConfig_output_vibra_tag 8 -#define ModuleConfig_ExternalNotificationConfig_output_buzzer_tag 9 -#define ModuleConfig_ExternalNotificationConfig_alert_message_vibra_tag 10 -#define ModuleConfig_ExternalNotificationConfig_alert_message_buzzer_tag 11 -#define ModuleConfig_ExternalNotificationConfig_alert_bell_vibra_tag 12 -#define ModuleConfig_ExternalNotificationConfig_alert_bell_buzzer_tag 13 -#define ModuleConfig_ExternalNotificationConfig_nag_timeout_tag 14 #define ModuleConfig_MQTTConfig_enabled_tag 1 #define ModuleConfig_MQTTConfig_address_tag 2 #define ModuleConfig_MQTTConfig_username_tag 3 @@ -310,12 +300,14 @@ X(a, STATIC, SINGULAR, BOOL, json_enabled, 6) #define ModuleConfig_AudioConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, codec2_enabled, 1) \ -X(a, STATIC, SINGULAR, UINT32, ptt_pin, 2) \ -X(a, STATIC, SINGULAR, UENUM, bitrate, 3) \ -X(a, STATIC, SINGULAR, UINT32, i2s_ws, 4) \ -X(a, STATIC, SINGULAR, UINT32, i2s_sd, 5) \ -X(a, STATIC, SINGULAR, UINT32, i2s_din, 6) \ -X(a, STATIC, SINGULAR, UINT32, i2s_sck, 7) +X(a, STATIC, SINGULAR, UINT32, mic_chan, 2) \ +X(a, STATIC, SINGULAR, UINT32, amp_pin, 3) \ +X(a, STATIC, SINGULAR, UINT32, ptt_pin, 4) \ +X(a, STATIC, SINGULAR, UENUM, bitrate, 5) \ +X(a, STATIC, SINGULAR, UINT32, i2s_ws, 6) \ +X(a, STATIC, SINGULAR, UINT32, i2s_sd, 7) \ +X(a, STATIC, SINGULAR, UINT32, i2s_din, 8) \ +X(a, STATIC, SINGULAR, UINT32, i2s_sck, 9) #define ModuleConfig_AudioConfig_CALLBACK NULL #define ModuleConfig_AudioConfig_DEFAULT NULL @@ -337,14 +329,7 @@ X(a, STATIC, SINGULAR, UINT32, output, 3) \ X(a, STATIC, SINGULAR, BOOL, active, 4) \ X(a, STATIC, SINGULAR, BOOL, alert_message, 5) \ X(a, STATIC, SINGULAR, BOOL, alert_bell, 6) \ -X(a, STATIC, SINGULAR, BOOL, use_pwm, 7) \ -X(a, STATIC, SINGULAR, UINT32, output_vibra, 8) \ -X(a, STATIC, SINGULAR, UINT32, output_buzzer, 9) \ -X(a, STATIC, SINGULAR, BOOL, alert_message_vibra, 10) \ -X(a, STATIC, SINGULAR, BOOL, alert_message_buzzer, 11) \ -X(a, STATIC, SINGULAR, BOOL, alert_bell_vibra, 12) \ -X(a, STATIC, SINGULAR, BOOL, alert_bell_buzzer, 13) \ -X(a, STATIC, SINGULAR, UINT32, nag_timeout, 14) +X(a, STATIC, SINGULAR, BOOL, use_pwm, 7) #define ModuleConfig_ExternalNotificationConfig_CALLBACK NULL #define ModuleConfig_ExternalNotificationConfig_DEFAULT NULL @@ -410,9 +395,9 @@ extern const pb_msgdesc_t ModuleConfig_CannedMessageConfig_msg; #define ModuleConfig_CannedMessageConfig_fields &ModuleConfig_CannedMessageConfig_msg /* Maximum encoded size of messages (where known) */ -#define ModuleConfig_AudioConfig_size 19 +#define ModuleConfig_AudioConfig_size 46 #define ModuleConfig_CannedMessageConfig_size 49 -#define ModuleConfig_ExternalNotificationConfig_size 40 +#define ModuleConfig_ExternalNotificationConfig_size 22 #define ModuleConfig_MQTTConfig_size 169 #define ModuleConfig_RangeTestConfig_size 10 #define ModuleConfig_SerialConfig_size 26 diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 959366dec..0c02251b0 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -42,6 +42,14 @@ #define AAMP 14 #define PTT_PIN 39 +// #define I2S_WS 13 +// #define I2S_SD 15 +// #define I2S_SIN 2 +// #define I2S_SCK 14 + +// Use I2S Processor 0 +#define I2S_PORT I2S_NUM_0 + #define AUDIO_MODULE_RX_BUFFER 128 #define AUDIO_MODULE_DATA_MAX Constants_DATA_PAYLOAD_LEN #define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 @@ -88,7 +96,7 @@ int IRAM_ATTR local_adc1_read(int channel) { IRAM_ATTR void am_onTimer() { portENTER_CRITICAL_ISR(&timerMux); //Enter crital code without interruptions - if (radio_state == RadioState::tx) { + if ((radio_state == RadioState::tx) && (!moduleConfig.audio.i2s_sd)) { adc_buffer[adc_buffer_index++] = (16 * local_adc1_read(mic_chan)) - 32768; //If you want to test with a 1KHz tone, comment the line above and descomment the three lines below @@ -108,15 +116,15 @@ IRAM_ATTR void am_onTimer() if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); } - } else if (radio_state == RadioState::rx) { - + } else if ((radio_state == RadioState::rx) && (!moduleConfig.audio.i2s_din) { + // ESP32-S3 does not have DAC support +#if !defined(CONFIG_IDF_TARGET_ESP32S3) int16_t v; //Get a value from audio_fifo and convert it to 0 - 255 to play it in the ADC if (audio_fifo.get(&v)) rx_raw_audio_value = (uint8_t)((v + 32768) / 256); - // comment out for now, S3 does not have Hardware-DAC. Consider I2S instead. -#if !CONFIG_IDF_TARGET_ESP32S3 + dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, rx_raw_audio_value); #endif } @@ -180,41 +188,77 @@ int32_t AudioModule::runOnce() { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { if (firstTime) { - DEBUG_MSG("--- Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); + // if we have I2S_SD defined, take samples from digital mic. I2S_DIN means digital output to amp. + if (moduleConfig.audio.i2s_sd || moduleConfig.audio.i2s_din) { + // Set up I2S Processor configuration. This will produce 16bit samples instead of 12 from the ADC + DEBUG_MSG("--- Initializing I2S for input\n"); + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) | (moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)), + .sample_rate = 8000, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), + .intr_alloc_flags = 0, + .dma_buf_count = 8, + .dma_buf_len = ADC_BUFFER_SIZE, + .use_apll = false, + .tx_desc_auto_clear = true, + .fixed_mclk = 0 + }; + i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); - mic_chan = moduleConfig.audio.mic_chan ? (adc1_channel_t)(int)moduleConfig.audio.mic_chan : (adc1_channel_t)AMIC; - adc1_config_width(ADC_WIDTH_12Bit); - adc1_config_channel_atten(mic_chan, ADC_ATTEN_DB_6); - adc1_get_raw(mic_chan); + const i2s_pin_config_t pin_config = { + .bck_io_num = moduleConfig.audio.i2s_sck, + .ws_io_num = moduleConfig.audio.i2s_ws, + .data_out_num = moduleConfig.audio.i2s_din ? moduleConfig.audio.i2s_din : I2S_PIN_NO_CHANGE, + .data_in_num = moduleConfig.audio.i2s_sd ? moduleConfig.audio.i2s_sd : I2S_PIN_NO_CHANGE + }; + i2s_set_pin(I2S_PORT, &pin_config); + + i2s_start(I2S_PORT); + } + + if (!moduleConfig.audio.i2s_sd) { + DEBUG_MSG("--- Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); + mic_chan = moduleConfig.audio.mic_chan ? (adc1_channel_t)(int)moduleConfig.audio.mic_chan : (adc1_channel_t)AMIC; + adc1_config_width(ADC_WIDTH_12Bit); + adc1_config_channel_atten(mic_chan, ADC_ATTEN_DB_6); + adc1_get_raw(mic_chan); + } radio_state = RadioState::rx; - // Start a timer at 8kHz to sample the ADC and play the audio on the DAC. - uint32_t cpufreq = getCpuFrequencyMhz(); - switch (cpufreq){ - case 160: - adcTimer = timerBegin(3, 1000, true); // 160 MHz / 1000 = 160KHz - break; - case 240: - adcTimer = timerBegin(3, 1500, true); // 240 MHz / 1500 = 160KHz - break; - case 320: - adcTimer = timerBegin(3, 2000, true); // 320 MHz / 2000 = 160KHz - break; - case 80: - default: - adcTimer = timerBegin(3, 500, true); // 80 MHz / 500 = 160KHz - break; + if ((!moduleConfig.audio.i2s_sd) || (!moduleConfig.audio.i2s_din)) { + // Start a timer at 8kHz to sample the ADC and play the audio on the DAC, but only if we have analogue audio to process + uint32_t cpufreq = getCpuFrequencyMhz(); + switch (cpufreq){ + case 160: + adcTimer = timerBegin(3, 1000, true); // 160 MHz / 1000 = 160KHz + break; + case 240: + adcTimer = timerBegin(3, 1500, true); // 240 MHz / 1500 = 160KHz + break; + case 320: + adcTimer = timerBegin(3, 2000, true); // 320 MHz / 2000 = 160KHz + break; + case 80: + default: + adcTimer = timerBegin(3, 500, true); // 80 MHz / 500 = 160KHz + break; + } + DEBUG_MSG("--- Timer CPU Frequency: %u MHz\n", cpufreq); + timerAttachInterrupt(adcTimer, &am_onTimer, false); + timerAlarmWrite(adcTimer, 20, true); // Interrupts when counter == 20, 8.000 times a second + timerAlarmEnable(adcTimer); } - DEBUG_MSG("--- Timer CPU Frequency: %u MHz\n", cpufreq); - timerAttachInterrupt(adcTimer, &am_onTimer, false); - timerAlarmWrite(adcTimer, 20, true); // Interrupts when counter == 20, 8.000 times a second - timerAlarmEnable(adcTimer); - - DEBUG_MSG("--- Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); - DEBUG_MSG("--- Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); + // setup analogue DAC only if we don't use I2S for output. This is not available on ESP32-S3 +#if !defined(CONFIG_IDF_TARGET_ESP32S3) + if (moduleConfig.audio.i2s_din) + DEBUG_MSG("--- Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); +#endif // Configure PTT input + DEBUG_MSG("--- Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT); firstTime = false; diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index 16705a141..b30e82bc2 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -6,6 +6,7 @@ #if defined(ARCH_ESP32) #include "NodeDB.h" #include +#include #include #include #include From 8b58eaac204b658c244b9152dd81077f14d6d57b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 29 Nov 2022 17:45:57 +0100 Subject: [PATCH 06/31] fix compile # Conflicts: # protobufs --- src/mesh/generated/localonly.pb.h | 2 +- src/mesh/generated/module_config.pb.h | 16 ++++++++-------- src/modules/esp32/AudioModule.cpp | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/mesh/generated/localonly.pb.h b/src/mesh/generated/localonly.pb.h index 44d923839..b8c8b27ef 100644 --- a/src/mesh/generated/localonly.pb.h +++ b/src/mesh/generated/localonly.pb.h @@ -151,7 +151,7 @@ extern const pb_msgdesc_t LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define LocalConfig_size 387 -#define LocalModuleConfig_size 385 +#define LocalModuleConfig_size 364 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/module_config.pb.h b/src/mesh/generated/module_config.pb.h index 3747452c7..189eb2ca5 100644 --- a/src/mesh/generated/module_config.pb.h +++ b/src/mesh/generated/module_config.pb.h @@ -63,14 +63,14 @@ typedef enum _ModuleConfig_CannedMessageConfig_InputEventChar { /* Struct definitions */ typedef struct _ModuleConfig_AudioConfig { bool codec2_enabled; - uint32_t mic_chan; - uint32_t amp_pin; - uint32_t ptt_pin; + uint8_t mic_chan; + uint8_t amp_pin; + uint8_t ptt_pin; ModuleConfig_AudioConfig_Audio_Baud bitrate; - uint32_t i2s_ws; - uint32_t i2s_sd; - uint32_t i2s_din; - uint32_t i2s_sck; + uint8_t i2s_ws; + uint8_t i2s_sd; + uint8_t i2s_din; + uint8_t i2s_sck; } ModuleConfig_AudioConfig; typedef struct _ModuleConfig_CannedMessageConfig { @@ -395,7 +395,7 @@ extern const pb_msgdesc_t ModuleConfig_CannedMessageConfig_msg; #define ModuleConfig_CannedMessageConfig_fields &ModuleConfig_CannedMessageConfig_msg /* Maximum encoded size of messages (where known) */ -#define ModuleConfig_AudioConfig_size 46 +#define ModuleConfig_AudioConfig_size 25 #define ModuleConfig_CannedMessageConfig_size 49 #define ModuleConfig_ExternalNotificationConfig_size 22 #define ModuleConfig_MQTTConfig_size 169 diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 0c02251b0..55677c0a9 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -116,7 +116,7 @@ IRAM_ATTR void am_onTimer() if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); } - } else if ((radio_state == RadioState::rx) && (!moduleConfig.audio.i2s_din) { + } else if ((radio_state == RadioState::rx) && (!moduleConfig.audio.i2s_din)) { // ESP32-S3 does not have DAC support #if !defined(CONFIG_IDF_TARGET_ESP32S3) int16_t v; @@ -197,7 +197,7 @@ int32_t AudioModule::runOnce() .sample_rate = 8000, .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, - .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB), + .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), .intr_alloc_flags = 0, .dma_buf_count = 8, .dma_buf_len = ADC_BUFFER_SIZE, From 0e6285edf2d8f981ea36f568df9f793729e52e11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 30 Nov 2022 09:52:28 +0100 Subject: [PATCH 07/31] add temp code for heap debugging. Disable -DDEBUG_HEAP for release builds. DEBUG_MSG output only for now. --- platformio.ini | 1 + src/Power.cpp | 3 +++ 2 files changed, 4 insertions(+) diff --git a/platformio.ini b/platformio.ini index dbddf7535..792f2f8b1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -55,6 +55,7 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_MORSE -DRADIOLIB_EXCLUDE_RTTY -DRADIOLIB_EXCLUDE_SSTV + -DDEBUG_HEAP monitor_speed = 115200 diff --git a/src/Power.cpp b/src/Power.cpp index ec86adc94..83eceeadf 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -283,6 +283,9 @@ void Power::readPowerStatus() DEBUG_MSG("Battery: usbPower=%d, isCharging=%d, batMv=%d, batPct=%d\n", powerStatus2.getHasUSB(), powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); newStatus.notifyObservers(&powerStatus2); +#ifdef DEBUG_HEAP + DEBUG_MSG("Heap status: %d/%d bytes free\n", ESP.getFreeHeap(), ESP.getHeapSize()); +#endif // If we have a battery at all and it is less than 10% full, force deep sleep if we have more than 3 low readings in a row // Supect fluctuating voltage on the RAK4631 to force it to deep sleep even if battery is at 85% after only a few days From ab6b6514cbc7c729c3c57f08140e2e6b16e0cbcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 30 Nov 2022 10:04:54 +0100 Subject: [PATCH 08/31] this define is arch specific --- arch/esp32/esp32.ini | 1 + arch/esp32/esp32s3.ini | 5 +++-- platformio.ini | 1 - 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/esp32/esp32.ini b/arch/esp32/esp32.ini index 70654e8ec..0011cc39f 100644 --- a/arch/esp32/esp32.ini +++ b/arch/esp32/esp32.ini @@ -26,6 +26,7 @@ build_flags = -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 -DCONFIG_BT_NIMBLE_MAX_CCCDS=20 -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING + -DDEBUG_HEAP lib_deps = ${arduino_base.lib_deps} diff --git a/arch/esp32/esp32s3.ini b/arch/esp32/esp32s3.ini index b05772344..72b176999 100644 --- a/arch/esp32/esp32s3.ini +++ b/arch/esp32/esp32s3.ini @@ -23,9 +23,10 @@ build_flags = -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL -DAXP_DEBUG_PORT=Serial -DCONFIG_BT_NIMBLE_ENABLED - -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 + -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 -DCONFIG_BT_NIMBLE_MAX_CCCDS=20 - -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING + -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING + -DDEBUG_HEAP lib_deps = ${arduino_base.lib_deps} diff --git a/platformio.ini b/platformio.ini index 792f2f8b1..dbddf7535 100644 --- a/platformio.ini +++ b/platformio.ini @@ -55,7 +55,6 @@ build_flags = -Wno-missing-field-initializers -DRADIOLIB_EXCLUDE_MORSE -DRADIOLIB_EXCLUDE_RTTY -DRADIOLIB_EXCLUDE_SSTV - -DDEBUG_HEAP monitor_speed = 115200 From 7d1b6f63b53d5253e54bba6119a1f1e8924d5654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 1 Dec 2022 17:47:04 +0100 Subject: [PATCH 09/31] Definition cleanup and AudioModule WIP --- arch/esp32/esp32s3.ini | 2 +- src/mesh/RadioInterface.h | 1 - src/mesh/RadioLibInterface.h | 3 +- src/modules/esp32/AudioModule.cpp | 102 ++++++++++++++++++++---------- src/modules/esp32/AudioModule.h | 16 +++-- src/platform/portduino/SimRadio.h | 1 - 6 files changed, 81 insertions(+), 44 deletions(-) diff --git a/arch/esp32/esp32s3.ini b/arch/esp32/esp32s3.ini index 72b176999..b276ceff9 100644 --- a/arch/esp32/esp32s3.ini +++ b/arch/esp32/esp32s3.ini @@ -35,7 +35,7 @@ lib_deps = https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 h2zero/NimBLE-Arduino@^1.4.0 https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 - caveman99/ESP32 Codec2@^1.0.1 + caveman99/ESP32 Codec2@^1.0.1 lib_ignore = segger_rtt diff --git a/src/mesh/RadioInterface.h b/src/mesh/RadioInterface.h index 437f294a4..f50c0ae77 100644 --- a/src/mesh/RadioInterface.h +++ b/src/mesh/RadioInterface.h @@ -1,6 +1,5 @@ #pragma once -#include "../concurrency/NotifiedWorkerThread.h" #include "MemoryPool.h" #include "MeshTypes.h" #include "Observer.h" diff --git a/src/mesh/RadioLibInterface.h b/src/mesh/RadioLibInterface.h index f368cf83e..16495c2f4 100644 --- a/src/mesh/RadioLibInterface.h +++ b/src/mesh/RadioLibInterface.h @@ -1,10 +1,9 @@ #pragma once -#include "../concurrency/OSThread.h" +#include "concurrency/NotifiedWorkerThread.h" #include "RadioInterface.h" #include "MeshPacketQueue.h" -#define RADIOLIB_EXCLUDE_HTTP #include // ESP32 has special rules about ISR code diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 55677c0a9..7ff46bf10 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -42,6 +42,13 @@ #define AAMP 14 #define PTT_PIN 39 +#ifdef ARCH_ESP32 +// ESP32 doesn't use that flag +#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR() +#else +#define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) +#endif + // #define I2S_WS 13 // #define I2S_SD 15 // #define I2S_SIN 2 @@ -51,7 +58,6 @@ #define I2S_PORT I2S_NUM_0 #define AUDIO_MODULE_RX_BUFFER 128 -#define AUDIO_MODULE_DATA_MAX Constants_DATA_PAYLOAD_LEN #define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 AudioModule *audioModule; @@ -107,11 +113,11 @@ IRAM_ATTR void am_onTimer() if (adc_buffer_index == ADC_BUFFER_SIZE) { adc_buffer_index = 0; - DEBUG_MSG("--- memcpy\n"); + DEBUG_MSG("♪♫♪ memcpy\n"); memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE); // Notify codec2 task that the buffer is ready. BaseType_t xHigherPriorityTaskWoken = pdFALSE; - DEBUG_MSG("--- notifyFromISR\n"); + DEBUG_MSG("♪♫♪ notifyFromISR\n"); codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::tx, true); if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); @@ -133,42 +139,51 @@ IRAM_ATTR void am_onTimer() Codec2Thread::Codec2Thread() : concurrency::NotifiedWorkerThread("Codec2Thread") { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { - DEBUG_MSG("--- Setting up codec2 in mode %u\n", moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE); - codec2_state = codec2_create(moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE); + DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); + codec2_state = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); codec2_set_lpc_post_filter(codec2_state, 1, 0, 0.8, 0.2); + encode_codec_size = (codec2_bits_per_frame(codec2_state) + 7) / 8; + encode_frame_num = Constants_DATA_PAYLOAD_LEN / encode_codec_size; + encode_frame_size = encode_frame_num * encode_codec_size; // max 237 bytes + DEBUG_MSG(" using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, encode_frame_size); } else { - DEBUG_MSG("--- Codec2 disabled\n"); + DEBUG_MSG("♪♫♪ Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted); } } AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { audio_fifo.init(); new Codec2Thread(); + //debug + moduleConfig.audio.i2s_ws = 13; + moduleConfig.audio.i2s_sd = 15; + moduleConfig.audio.i2s_din = 2; + moduleConfig.audio.i2s_sck = 14; } -void Codec2Thread::onNotify(uint32_t notification) +void IRAM_ATTR Codec2Thread::onNotify(uint32_t notification) { switch (notification) { case RadioState::tx: for (int i = 0; i < ADC_BUFFER_SIZE; i++) speech[i] = (int16_t)hp_filter.Update((float)speech[i]); - codec2_encode(codec2_state, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, speech); + codec2_encode(codec2_state, audioModule->tx_encode_frame + tx_encode_frame_index, speech); //increment the pointer where the encoded frame must be saved - audioModule->tx_encode_frame_index += 8; + tx_encode_frame_index += encode_codec_size; - //If it is the 5th time then we have a ready trasnmission frame - if (audioModule->tx_encode_frame_index == ENCODE_FRAME_SIZE) + //If it this is reached we have a ready trasnmission frame + if (tx_encode_frame_index == encode_frame_size) { - audioModule->tx_encode_frame_index = 0; + tx_encode_frame_index = 0; //Transmit it audioModule->sendPayload(); } break; case RadioState::rx: //Make a cycle to get each codec2 frame from the received frame - for (int i = 0; i < ENCODE_FRAME_SIZE; i += ENCODE_CODEC2_SIZE) + for (int i = 0; i < encode_frame_size; i += encode_codec_size) { //Decode the codec2 frame codec2_decode(codec2_state, output_buffer, audioModule->rx_encode_frame + i); @@ -187,25 +202,28 @@ void Codec2Thread::onNotify(uint32_t notification) int32_t AudioModule::runOnce() { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { + esp_err_t res; if (firstTime) { // if we have I2S_SD defined, take samples from digital mic. I2S_DIN means digital output to amp. if (moduleConfig.audio.i2s_sd || moduleConfig.audio.i2s_din) { - // Set up I2S Processor configuration. This will produce 16bit samples instead of 12 from the ADC - DEBUG_MSG("--- Initializing I2S for input\n"); + // Set up I2S Processor configuration. This will produce 16bit samples at 8 kHz instead of 12 from the ADC + DEBUG_MSG("♪♫♪ Initializing I2S SD: %d DIN: %d WS: %d SCK:%d\n", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck); i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) | (moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)), .sample_rate = 8000, .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), - .intr_alloc_flags = 0, + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, .dma_buf_count = 8, - .dma_buf_len = ADC_BUFFER_SIZE, + .dma_buf_len = ADC_BUFFER_SIZE, // 320 * 2 bytes .use_apll = false, .tx_desc_auto_clear = true, .fixed_mclk = 0 }; - i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); + res = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); + if(res != ESP_OK) + DEBUG_MSG("♪♫♪ Failed to install I2S driver: %d\n", res); const i2s_pin_config_t pin_config = { .bck_io_num = moduleConfig.audio.i2s_sck, @@ -213,13 +231,18 @@ int32_t AudioModule::runOnce() .data_out_num = moduleConfig.audio.i2s_din ? moduleConfig.audio.i2s_din : I2S_PIN_NO_CHANGE, .data_in_num = moduleConfig.audio.i2s_sd ? moduleConfig.audio.i2s_sd : I2S_PIN_NO_CHANGE }; - i2s_set_pin(I2S_PORT, &pin_config); + res = i2s_set_pin(I2S_PORT, &pin_config); + if(res != ESP_OK) + DEBUG_MSG("♪♫♪ Failed to set I2S pin config: %d\n", res); - i2s_start(I2S_PORT); + res = i2s_start(I2S_PORT); + if(res != ESP_OK) + DEBUG_MSG("♪♫♪ Failed to start I2S: %d\n", res); } if (!moduleConfig.audio.i2s_sd) { - DEBUG_MSG("--- Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); + // Set up ADC if we don't have a digital microphone. + DEBUG_MSG("♪♫♪ Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); mic_chan = moduleConfig.audio.mic_chan ? (adc1_channel_t)(int)moduleConfig.audio.mic_chan : (adc1_channel_t)AMIC; adc1_config_width(ADC_WIDTH_12Bit); adc1_config_channel_atten(mic_chan, ADC_ATTEN_DB_6); @@ -246,7 +269,7 @@ int32_t AudioModule::runOnce() adcTimer = timerBegin(3, 500, true); // 80 MHz / 500 = 160KHz break; } - DEBUG_MSG("--- Timer CPU Frequency: %u MHz\n", cpufreq); + DEBUG_MSG("♪♫♪ Timer CPU Frequency: %u MHz\n", cpufreq); timerAttachInterrupt(adcTimer, &am_onTimer, false); timerAlarmWrite(adcTimer, 20, true); // Interrupts when counter == 20, 8.000 times a second timerAlarmEnable(adcTimer); @@ -255,10 +278,10 @@ int32_t AudioModule::runOnce() // setup analogue DAC only if we don't use I2S for output. This is not available on ESP32-S3 #if !defined(CONFIG_IDF_TARGET_ESP32S3) if (moduleConfig.audio.i2s_din) - DEBUG_MSG("--- Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); + DEBUG_MSG("♪♫♪ Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); #endif // Configure PTT input - DEBUG_MSG("--- Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); + DEBUG_MSG("♪♫♪ Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT); firstTime = false; @@ -266,20 +289,35 @@ int32_t AudioModule::runOnce() // Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive. if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) { if (radio_state == RadioState::rx) { - DEBUG_MSG("--- PTT pressed, switching to TX\n"); + DEBUG_MSG("♪♫♪ PTT pressed, switching to TX\n"); radio_state = RadioState::tx; } } else { if (radio_state == RadioState::tx) { - DEBUG_MSG("--- PTT released, switching to RX\n"); + DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); radio_state = RadioState::rx; } } - + if ((radio_state == RadioState::tx) && moduleConfig.audio.i2s_sd) { + // Get I2S data from the microphone and place in data buffer + size_t bytesIn = 0; + res = i2s_read(I2S_PORT, &adc_buffer + adc_buffer_index, ADC_BUFFER_SIZE - adc_buffer_index, &bytesIn, pdMS_TO_TICKS(40)); // wait 40ms for audio to arrive. + + if (res == ESP_OK) { + adc_buffer_index += bytesIn; + if (adc_buffer_index == ADC_BUFFER_SIZE) { + adc_buffer_index = 0; + DEBUG_MSG("♪♫♪ We have a full buffer, process it\n"); + memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE); + // Notify codec2 task that the buffer is ready. + codec2Thread->notify(RadioState::tx, true); + } + } + } } return 100; } else { - DEBUG_MSG("--- Audio Module Disabled\n"); + DEBUG_MSG("♪♫♪ Audio Module Disabled\n"); return INT32_MAX; } @@ -300,7 +338,7 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies) p->want_ack = false; // Audio is shoot&forget. TODO: Is this really suppressing retransmissions? p->priority = MeshPacket_Priority_MAX; // Audio is important, because realtime - p->decoded.payload.size = ENCODE_FRAME_SIZE; + p->decoded.payload.size = codec2Thread->get_encode_frame_size(); memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size); service.sendToMesh(p); @@ -311,7 +349,7 @@ ProcessMessage AudioModule::handleReceived(const MeshPacket &mp) if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { auto &p = mp.decoded; if (getFrom(&mp) != nodeDB.getNodeNum()) { - if (p.payload.size == ENCODE_FRAME_SIZE) { + if (p.payload.size == codec2Thread->get_encode_frame_size()) { memcpy(rx_encode_frame, p.payload.bytes, p.payload.size); radio_state = RadioState::rx; BaseType_t xHigherPriorityTaskWoken = pdFALSE; @@ -319,7 +357,7 @@ ProcessMessage AudioModule::handleReceived(const MeshPacket &mp) if (xHigherPriorityTaskWoken) portYIELD_FROM_ISR(); } else { - DEBUG_MSG("--- Invalid payload size %u != %u\n", p.payload.size, ENCODE_FRAME_SIZE); + DEBUG_MSG("♪♫♪ Invalid payload size %u != %u\n", p.payload.size, codec2Thread->get_encode_frame_size()); } } } diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index b30e82bc2..47eba55ee 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -1,7 +1,7 @@ #pragma once #include "SinglePortModule.h" -#include "concurrency/OSThread.h" +#include "concurrency/NotifiedWorkerThread.h" #include "configuration.h" #if defined(ARCH_ESP32) #include "NodeDB.h" @@ -14,8 +14,6 @@ #include #define ADC_BUFFER_SIZE 320 // 40ms of voice in 8KHz sampling frequency -#define ENCODE_CODEC2_SIZE 8 -#define ENCODE_FRAME_SIZE (ENCODE_CODEC2_SIZE * 5) // 5 codec2 frames of 8 bytes each class Codec2Thread : public concurrency::NotifiedWorkerThread { @@ -25,7 +23,13 @@ class Codec2Thread : public concurrency::NotifiedWorkerThread public: Codec2Thread(); + int get_encode_frame_size() { return encode_frame_size; }; + protected: + int tx_encode_frame_index = 0; + int encode_codec_size = 0; + int encode_frame_num = 0; + int encode_frame_size = 0; virtual void onNotify(uint32_t notification) override; }; @@ -35,11 +39,9 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread hw_timer_t* adcTimer = NULL; uint16_t adc_buffer_index = 0; - public: - unsigned char rx_encode_frame[ENCODE_FRAME_SIZE] = {}; - unsigned char tx_encode_frame[ENCODE_FRAME_SIZE] = {}; - int tx_encode_frame_index = 0; + unsigned char rx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; + unsigned char tx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; AudioModule(); diff --git a/src/platform/portduino/SimRadio.h b/src/platform/portduino/SimRadio.h index dad419c62..a71cf22f8 100644 --- a/src/platform/portduino/SimRadio.h +++ b/src/platform/portduino/SimRadio.h @@ -4,7 +4,6 @@ #include "MeshPacketQueue.h" #include "wifi/WiFiServerAPI.h" -#define RADIOLIB_EXCLUDE_HTTP #include class SimRadio : public RadioInterface From a0c1e9cdc6facdc6038cda733f150aa7e1195cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 2 Dec 2022 00:30:31 +0100 Subject: [PATCH 10/31] Still WIP, but first working version of audio. I2S works good, analogue not so much. --- src/modules/Modules.cpp | 2 +- src/modules/esp32/AudioModule.cpp | 262 +++++++++++++++--------------- src/modules/esp32/AudioModule.h | 57 +++---- 3 files changed, 154 insertions(+), 167 deletions(-) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index b97e965e1..b0b923863 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -66,7 +66,7 @@ void setupModules() #endif #ifdef ARCH_ESP32 // Only run on an esp32 based device. - new AudioModule(); + audioModule = new AudioModule(); new ExternalNotificationModule(); storeForwardModule = new StoreForwardModule(); diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 7ff46bf10..a47f47bd8 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -23,25 +23,36 @@ Basic Usage: 1) Enable the module by setting audio.codec2_enabled to 1. - 2) Set the pins (audio.mic_pin / audio.amp_pin) for your preferred microphone and amplifier GPIO pins. - On tbeam, recommend to use: + 2a) Set the pins for the I2S interface if you want to use digital audio. Recommended on TLora is I2S_WS 13/I2S_SD 15/I2S_SIN 2/I2S_SCK 14 + 2b) Set the pins (audio.mic_pin / audio.amp_pin) for your preferred microphone and amplifier GPIO pins if you want to use analog audio. + This is rather heavy on the CPU and not recommended. + On tbeam, best use: audio.mic_chan 6 (GPIO 34) audio.amp_pin 14 audio.ptt_pin 39 - 3) Set audio.timeout to the amount of time to wait before we consider - your voice stream as "done". - 4) Set audio.bitrate to the desired codec2 rate (CODEC2_3200, CODEC2_2400, CODEC2_1600, CODEC2_1400, CODEC2_1300, CODEC2_1200, CODEC2_700, CODEC2_700B) + 3) Set audio.bitrate to the desired codec2 rate (CODEC2_3200, CODEC2_2400, CODEC2_1600, CODEC2_1400, CODEC2_1300, CODEC2_1200, CODEC2_700, CODEC2_700B) KNOWN PROBLEMS - * Until the module is initilized by the startup sequence, the amp_pin pin is in a floating - radio_state. This may produce a bit of "noise". - * Will not work on NRF and the Linux device targets. + * Until the module is initilized by the startup sequence, the amp_pin is in a floating + state. This may produce a bit of "noise". + * Will not work on NRF and the Linux device targets (yet?). */ #define AMIC 6 #define AAMP 14 #define PTT_PIN 39 +ButterworthFilter hp_filter(240, 8000, ButterworthFilter::ButterworthFilter::Highpass, 1); + +// Use I2S Processor 0 +#define I2S_PORT I2S_NUM_0 + +#define AUDIO_MODULE_RX_BUFFER 128 +#define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 + +TaskHandle_t codec2HandlerTask; +AudioModule *audioModule; + #ifdef ARCH_ESP32 // ESP32 doesn't use that flag #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR() @@ -49,36 +60,13 @@ #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) #endif -// #define I2S_WS 13 -// #define I2S_SD 15 -// #define I2S_SIN 2 -// #define I2S_SCK 14 - -// Use I2S Processor 0 -#define I2S_PORT I2S_NUM_0 - -#define AUDIO_MODULE_RX_BUFFER 128 -#define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 - -AudioModule *audioModule; -Codec2Thread *codec2Thread; - -FastAudioFIFO audio_fifo; -uint16_t adc_buffer[ADC_BUFFER_SIZE] = {}; -uint16_t adc_buffer_index = 0; portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; -int16_t speech[ADC_BUFFER_SIZE] = {}; -volatile RadioState radio_state = RadioState::tx; -adc1_channel_t mic_chan = (adc1_channel_t)0; - -ButterworthFilter hp_filter(240, 8000, ButterworthFilter::ButterworthFilter::Highpass, 1); +hw_timer_t* adcTimer = NULL; //int16_t 1KHz sine test tone int16_t Sine1KHz[8] = { -21210 , -30000, -21210, 0 , 21210 , 30000 , 21210, 0 }; int Sine1KHz_index = 0; -uint8_t rx_raw_audio_value = 127; - int IRAM_ATTR local_adc1_read(int channel) { uint16_t adc_value; #if CONFIG_IDF_TARGET_ESP32S3 @@ -102,8 +90,8 @@ int IRAM_ATTR local_adc1_read(int channel) { IRAM_ATTR void am_onTimer() { portENTER_CRITICAL_ISR(&timerMux); //Enter crital code without interruptions - if ((radio_state == RadioState::tx) && (!moduleConfig.audio.i2s_sd)) { - adc_buffer[adc_buffer_index++] = (16 * local_adc1_read(mic_chan)) - 32768; + if ((audioModule->radio_state == RadioState::tx) && (!moduleConfig.audio.i2s_sd)) { + audioModule->adc_buffer[audioModule->adc_buffer_index++] = (16 * local_adc1_read(audioModule->mic_chan)) - 32768; //If you want to test with a 1KHz tone, comment the line above and descomment the three lines below @@ -111,91 +99,90 @@ IRAM_ATTR void am_onTimer() // if (Sine1KHz_index >= 8) // Sine1KHz_index = 0; - if (adc_buffer_index == ADC_BUFFER_SIZE) { - adc_buffer_index = 0; - DEBUG_MSG("♪♫♪ memcpy\n"); - memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE); - // Notify codec2 task that the buffer is ready. + if (audioModule->adc_buffer_index == audioModule->adc_buffer_size) { + audioModule->adc_buffer_index = 0; + memcpy((void*)audioModule->speech, (void*)audioModule->adc_buffer, 2 * audioModule->adc_buffer_size); BaseType_t xHigherPriorityTaskWoken = pdFALSE; - DEBUG_MSG("♪♫♪ notifyFromISR\n"); - codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::tx, true); - if (xHigherPriorityTaskWoken) - portYIELD_FROM_ISR(); + vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) + YIELD_FROM_ISR(xHigherPriorityTaskWoken); } - } else if ((radio_state == RadioState::rx) && (!moduleConfig.audio.i2s_din)) { + } else if ((audioModule->radio_state == RadioState::rx) && (!moduleConfig.audio.i2s_din)) { // ESP32-S3 does not have DAC support #if !defined(CONFIG_IDF_TARGET_ESP32S3) - int16_t v; //Get a value from audio_fifo and convert it to 0 - 255 to play it in the ADC - if (audio_fifo.get(&v)) - rx_raw_audio_value = (uint8_t)((v + 32768) / 256); + if (audioModule->fifo.get(&audioModule->sample)) + audioModule->rx_raw_audio_value = (uint8_t)((audioModule->sample + 32768) / 256); - dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, rx_raw_audio_value); + dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, audioModule->rx_raw_audio_value); #endif } portEXIT_CRITICAL_ISR(&timerMux); // exit critical code } -Codec2Thread::Codec2Thread() : concurrency::NotifiedWorkerThread("Codec2Thread") { - if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { - DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); - codec2_state = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); - codec2_set_lpc_post_filter(codec2_state, 1, 0, 0.8, 0.2); - encode_codec_size = (codec2_bits_per_frame(codec2_state) + 7) / 8; - encode_frame_num = Constants_DATA_PAYLOAD_LEN / encode_codec_size; - encode_frame_size = encode_frame_num * encode_codec_size; // max 237 bytes - DEBUG_MSG(" using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, encode_frame_size); - } else { - DEBUG_MSG("♪♫♪ Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted); +void run_codec2(void* parameter) +{ + while (true) { + uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(10000)); + + if (tcount != 0) { + if (audioModule->radio_state == RadioState::tx) { + + for (int i = 0; i < audioModule->adc_buffer_size; i++) + audioModule->speech[i] = (int16_t)hp_filter.Update((float)audioModule->speech[i]); + + codec2_encode(audioModule->codec2, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, audioModule->speech); + + //increment the pointer where the encoded frame must be saved + audioModule->tx_encode_frame_index += audioModule->encode_codec_size; + + //If it this is reached we have a ready trasnmission frame + if (audioModule->tx_encode_frame_index == audioModule->encode_frame_size) + { + //Transmit it + DEBUG_MSG("♪♫♪ Sending %d bytes\n", audioModule->encode_frame_size); + audioModule->sendPayload(); + audioModule->tx_encode_frame_index = 0; + } + } + if (audioModule->radio_state == RadioState::rx) { + //Make a cycle to get each codec2 frame from the received frame + for (int i = 0; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size) + { + //Decode the codec2 frame + codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); + + if (moduleConfig.audio.i2s_din) { + size_t bytesOut = 0; + + i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(40)); + } else { + //Put the decoded audio in the fifo + for (int j = 0; j < audioModule->adc_buffer_size; j++) + audioModule->fifo.put(audioModule->output_buffer[j]); + } + } + } + } } } -AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { - audio_fifo.init(); - new Codec2Thread(); - //debug - moduleConfig.audio.i2s_ws = 13; - moduleConfig.audio.i2s_sd = 15; - moduleConfig.audio.i2s_din = 2; - moduleConfig.audio.i2s_sck = 14; -} - -void IRAM_ATTR Codec2Thread::onNotify(uint32_t notification) +AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { - switch (notification) { - case RadioState::tx: - for (int i = 0; i < ADC_BUFFER_SIZE; i++) - speech[i] = (int16_t)hp_filter.Update((float)speech[i]); - - codec2_encode(codec2_state, audioModule->tx_encode_frame + tx_encode_frame_index, speech); - - //increment the pointer where the encoded frame must be saved - tx_encode_frame_index += encode_codec_size; - - //If it this is reached we have a ready trasnmission frame - if (tx_encode_frame_index == encode_frame_size) - { - tx_encode_frame_index = 0; - //Transmit it - audioModule->sendPayload(); - } - break; - case RadioState::rx: - //Make a cycle to get each codec2 frame from the received frame - for (int i = 0; i < encode_frame_size; i += encode_codec_size) - { - //Decode the codec2 frame - codec2_decode(codec2_state, output_buffer, audioModule->rx_encode_frame + i); - - // Add to the audio buffer the 320 samples resulting of the decode of the codec2 frame. - for (int g = 0; g < ADC_BUFFER_SIZE; g++) - audio_fifo.put(output_buffer[g]); - } - break; - default: - assert(0); // We expected to receive a valid notification from the ISR - break; + if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { + fifo.init(); + DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); + codec2 = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); + codec2_set_lpc_post_filter(codec2, 1, 0, 0.8, 0.2); + encode_codec_size = (codec2_bits_per_frame(codec2) + 7) / 8; + encode_frame_num = Constants_DATA_PAYLOAD_LEN / encode_codec_size; + encode_frame_size = encode_frame_num * encode_codec_size; // max 237 bytes + adc_buffer_size = codec2_samples_per_frame(codec2); + DEBUG_MSG(" using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, encode_frame_size); + xTaskCreate(&run_codec2, "codec2_task", 30000, NULL, 5, &codec2HandlerTask); + } else { + DEBUG_MSG("♪♫♪ Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted); } } @@ -214,9 +201,9 @@ int32_t AudioModule::runOnce() .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .intr_alloc_flags = 0, .dma_buf_count = 8, - .dma_buf_len = ADC_BUFFER_SIZE, // 320 * 2 bytes + .dma_buf_len = adc_buffer_size, // 320 * 2 bytes .use_apll = false, .tx_desc_auto_clear = true, .fixed_mclk = 0 @@ -239,9 +226,20 @@ int32_t AudioModule::runOnce() if(res != ESP_OK) DEBUG_MSG("♪♫♪ Failed to start I2S: %d\n", res); } + + // setup analogue DAC only if we don't use I2S for output. This is not available on ESP32-S3 +#if !defined(CONFIG_IDF_TARGET_ESP32S3) + if (!moduleConfig.audio.i2s_din) + DEBUG_MSG("♪♫♪ Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); +#else + if (!moduleConfig.audio.i2s_din) { + DEBUG_MSG("♪♫♪ ESP32-S3 does not support DAC. Audio Module Disabled.\n"); + return INT32_MAX; + } +#endif if (!moduleConfig.audio.i2s_sd) { - // Set up ADC if we don't have a digital microphone. + // Set up ADC if we don't have a digital microphone. DEBUG_MSG("♪♫♪ Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); mic_chan = moduleConfig.audio.mic_chan ? (adc1_channel_t)(int)moduleConfig.audio.mic_chan : (adc1_channel_t)AMIC; adc1_config_width(ADC_WIDTH_12Bit); @@ -249,8 +247,6 @@ int32_t AudioModule::runOnce() adc1_get_raw(mic_chan); } - radio_state = RadioState::rx; - if ((!moduleConfig.audio.i2s_sd) || (!moduleConfig.audio.i2s_din)) { // Start a timer at 8kHz to sample the ADC and play the audio on the DAC, but only if we have analogue audio to process uint32_t cpufreq = getCpuFrequencyMhz(); @@ -275,11 +271,8 @@ int32_t AudioModule::runOnce() timerAlarmEnable(adcTimer); } - // setup analogue DAC only if we don't use I2S for output. This is not available on ESP32-S3 -#if !defined(CONFIG_IDF_TARGET_ESP32S3) - if (moduleConfig.audio.i2s_din) - DEBUG_MSG("♪♫♪ Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); -#endif + radio_state = RadioState::rx; + // Configure PTT input DEBUG_MSG("♪♫♪ Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT); @@ -294,23 +287,32 @@ int32_t AudioModule::runOnce() } } else { if (radio_state == RadioState::tx) { - DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); + if (tx_encode_frame_index > 0) { + // Send the incomplete frame + DEBUG_MSG("♪♫♪ Sending %d bytes (incomplete)\n", tx_encode_frame_index); + sendPayload(); + } + DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); + tx_encode_frame_index = 0; radio_state = RadioState::rx; } } if ((radio_state == RadioState::tx) && moduleConfig.audio.i2s_sd) { // Get I2S data from the microphone and place in data buffer size_t bytesIn = 0; - res = i2s_read(I2S_PORT, &adc_buffer + adc_buffer_index, ADC_BUFFER_SIZE - adc_buffer_index, &bytesIn, pdMS_TO_TICKS(40)); // wait 40ms for audio to arrive. + res = i2s_read(I2S_PORT, adc_buffer + adc_buffer_index, adc_buffer_size - adc_buffer_index, &bytesIn, pdMS_TO_TICKS(40)); // wait 40ms for audio to arrive. if (res == ESP_OK) { adc_buffer_index += bytesIn; - if (adc_buffer_index == ADC_BUFFER_SIZE) { + if (adc_buffer_index == adc_buffer_size) { adc_buffer_index = 0; - DEBUG_MSG("♪♫♪ We have a full buffer, process it\n"); - memcpy((void*)speech, (void*)adc_buffer, 2 * ADC_BUFFER_SIZE); - // Notify codec2 task that the buffer is ready. - codec2Thread->notify(RadioState::tx, true); + memcpy((void*)speech, (void*)adc_buffer, 2 * adc_buffer_size); + // Notify run_codec2 task that the buffer is ready. + radio_state = RadioState::tx; + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) + YIELD_FROM_ISR(xHigherPriorityTaskWoken); } } } @@ -338,7 +340,7 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies) p->want_ack = false; // Audio is shoot&forget. TODO: Is this really suppressing retransmissions? p->priority = MeshPacket_Priority_MAX; // Audio is important, because realtime - p->decoded.payload.size = codec2Thread->get_encode_frame_size(); + p->decoded.payload.size = tx_encode_frame_index; memcpy(p->decoded.payload.bytes, tx_encode_frame, p->decoded.payload.size); service.sendToMesh(p); @@ -349,16 +351,14 @@ ProcessMessage AudioModule::handleReceived(const MeshPacket &mp) if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { auto &p = mp.decoded; if (getFrom(&mp) != nodeDB.getNodeNum()) { - if (p.payload.size == codec2Thread->get_encode_frame_size()) { - memcpy(rx_encode_frame, p.payload.bytes, p.payload.size); - radio_state = RadioState::rx; - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - codec2Thread->notifyFromISR(&xHigherPriorityTaskWoken, RadioState::rx, true); - if (xHigherPriorityTaskWoken) - portYIELD_FROM_ISR(); - } else { - DEBUG_MSG("♪♫♪ Invalid payload size %u != %u\n", p.payload.size, codec2Thread->get_encode_frame_size()); - } + memcpy(rx_encode_frame, p.payload.bytes, p.payload.size); + radio_state = RadioState::rx; + rx_encode_frame_index = p.payload.size; + // Notify run_codec2 task that the buffer is ready. + BaseType_t xHigherPriorityTaskWoken = pdFALSE; + vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken); + if (xHigherPriorityTaskWoken == pdTRUE) + YIELD_FROM_ISR(xHigherPriorityTaskWoken); } } diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index 47eba55ee..c81c5c310 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -7,41 +7,36 @@ #include "NodeDB.h" #include #include -#include +// #include #include #include #include #include -#define ADC_BUFFER_SIZE 320 // 40ms of voice in 8KHz sampling frequency +#define ADC_BUFFER_SIZE_MAX 320 -class Codec2Thread : public concurrency::NotifiedWorkerThread -{ - struct CODEC2* codec2_state = NULL; - int16_t output_buffer[ADC_BUFFER_SIZE] = {}; - - public: - Codec2Thread(); - - int get_encode_frame_size() { return encode_frame_size; }; - - protected: - int tx_encode_frame_index = 0; - int encode_codec_size = 0; - int encode_frame_num = 0; - int encode_frame_size = 0; - virtual void onNotify(uint32_t notification) override; -}; +enum RadioState { standby, rx, tx }; class AudioModule : public SinglePortModule, private concurrency::OSThread { - bool firstTime = true; - hw_timer_t* adcTimer = NULL; - uint16_t adc_buffer_index = 0; - public: unsigned char rx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; unsigned char tx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; + int16_t speech[ADC_BUFFER_SIZE_MAX] = {}; + int16_t output_buffer[ADC_BUFFER_SIZE_MAX] = {}; + uint16_t adc_buffer[ADC_BUFFER_SIZE_MAX] = {}; + int adc_buffer_size = 0; + uint16_t adc_buffer_index = 0; + int tx_encode_frame_index = 0; + int rx_encode_frame_index = 0; + int encode_codec_size = 0; + int encode_frame_size = 0; + volatile RadioState radio_state = RadioState::rx; + FastAudioFIFO fifo; + struct CODEC2* codec2 = NULL; + int16_t sample; + adc1_channel_t mic_chan = (adc1_channel_t)0; + uint8_t rx_raw_audio_value = 127; AudioModule(); @@ -51,9 +46,11 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread void sendPayload(NodeNum dest = NODENUM_BROADCAST, bool wantReplies = false); protected: - virtual int32_t runOnce() override; + int encode_frame_num = 0; + bool firstTime = true; - // void run_codec2(); + + virtual int32_t runOnce() override; virtual MeshPacket *allocReply() override; @@ -64,15 +61,5 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread }; extern AudioModule *audioModule; -extern Codec2Thread *codec2Thread; -extern uint16_t adc_buffer[ADC_BUFFER_SIZE]; -extern uint16_t adc_buffer_index; -extern portMUX_TYPE timerMux; -extern int16_t speech[ADC_BUFFER_SIZE]; -enum RadioState { standby, rx, tx }; -extern volatile RadioState radio_state; -extern adc1_channel_t mic_chan; - -IRAM_ATTR void am_onTimer(); #endif \ No newline at end of file From feb7181767927c2c7da6fedb7aaf1e601435cdd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 2 Dec 2022 12:20:21 +0100 Subject: [PATCH 11/31] debug print thread count. max_threads is 32 --- src/Power.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Power.cpp b/src/Power.cpp index 83eceeadf..be44c0d49 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -284,7 +284,7 @@ void Power::readPowerStatus() powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); newStatus.notifyObservers(&powerStatus2); #ifdef DEBUG_HEAP - DEBUG_MSG("Heap status: %d/%d bytes free\n", ESP.getFreeHeap(), ESP.getHeapSize()); + DEBUG_MSG("Heap status: %d/%d bytes free, running %d threads\n", ESP.getFreeHeap(), ESP.getHeapSize(), concurrency::mainController.size(false)); #endif // If we have a battery at all and it is less than 10% full, force deep sleep if we have more than 3 low readings in a row From a0f5e44967bfbd0a436b261a25977c227b6924ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 2 Dec 2022 12:58:26 +0100 Subject: [PATCH 12/31] Audio Module is finished for regression tests. --- src/mesh/generated/localonly.pb.h | 2 +- src/mesh/generated/module_config.pb.h | 36 ++--- src/modules/esp32/AudioModule.cpp | 202 +++++--------------------- src/modules/esp32/AudioModule.h | 9 +- 4 files changed, 58 insertions(+), 191 deletions(-) diff --git a/src/mesh/generated/localonly.pb.h b/src/mesh/generated/localonly.pb.h index b8c8b27ef..596bb7b9a 100644 --- a/src/mesh/generated/localonly.pb.h +++ b/src/mesh/generated/localonly.pb.h @@ -151,7 +151,7 @@ extern const pb_msgdesc_t LocalModuleConfig_msg; /* Maximum encoded size of messages (where known) */ #define LocalConfig_size 387 -#define LocalModuleConfig_size 364 +#define LocalModuleConfig_size 358 #ifdef __cplusplus } /* extern "C" */ diff --git a/src/mesh/generated/module_config.pb.h b/src/mesh/generated/module_config.pb.h index 189eb2ca5..4b2e29f17 100644 --- a/src/mesh/generated/module_config.pb.h +++ b/src/mesh/generated/module_config.pb.h @@ -63,8 +63,6 @@ typedef enum _ModuleConfig_CannedMessageConfig_InputEventChar { /* Struct definitions */ typedef struct _ModuleConfig_AudioConfig { bool codec2_enabled; - uint8_t mic_chan; - uint8_t amp_pin; uint8_t ptt_pin; ModuleConfig_AudioConfig_Audio_Baud bitrate; uint8_t i2s_ws; @@ -187,7 +185,7 @@ extern "C" { /* Initializer values for message structs */ #define ModuleConfig_init_default {0, {ModuleConfig_MQTTConfig_init_default}} #define ModuleConfig_MQTTConfig_init_default {0, "", "", "", 0, 0} -#define ModuleConfig_AudioConfig_init_default {0, 0, 0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} +#define ModuleConfig_AudioConfig_init_default {0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define ModuleConfig_SerialConfig_init_default {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN} #define ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0} #define ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0} @@ -196,7 +194,7 @@ extern "C" { #define ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0} #define ModuleConfig_init_zero {0, {ModuleConfig_MQTTConfig_init_zero}} #define ModuleConfig_MQTTConfig_init_zero {0, "", "", "", 0, 0} -#define ModuleConfig_AudioConfig_init_zero {0, 0, 0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} +#define ModuleConfig_AudioConfig_init_zero {0, 0, _ModuleConfig_AudioConfig_Audio_Baud_MIN, 0, 0, 0, 0} #define ModuleConfig_SerialConfig_init_zero {0, 0, 0, 0, _ModuleConfig_SerialConfig_Serial_Baud_MIN, 0, _ModuleConfig_SerialConfig_Serial_Mode_MIN} #define ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0} #define ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0} @@ -206,14 +204,12 @@ extern "C" { /* Field tags (for use in manual encoding/decoding) */ #define ModuleConfig_AudioConfig_codec2_enabled_tag 1 -#define ModuleConfig_AudioConfig_mic_chan_tag 2 -#define ModuleConfig_AudioConfig_amp_pin_tag 3 -#define ModuleConfig_AudioConfig_ptt_pin_tag 4 -#define ModuleConfig_AudioConfig_bitrate_tag 5 -#define ModuleConfig_AudioConfig_i2s_ws_tag 6 -#define ModuleConfig_AudioConfig_i2s_sd_tag 7 -#define ModuleConfig_AudioConfig_i2s_din_tag 8 -#define ModuleConfig_AudioConfig_i2s_sck_tag 9 +#define ModuleConfig_AudioConfig_ptt_pin_tag 2 +#define ModuleConfig_AudioConfig_bitrate_tag 3 +#define ModuleConfig_AudioConfig_i2s_ws_tag 4 +#define ModuleConfig_AudioConfig_i2s_sd_tag 5 +#define ModuleConfig_AudioConfig_i2s_din_tag 6 +#define ModuleConfig_AudioConfig_i2s_sck_tag 7 #define ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1 #define ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2 #define ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3 @@ -300,14 +296,12 @@ X(a, STATIC, SINGULAR, BOOL, json_enabled, 6) #define ModuleConfig_AudioConfig_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, BOOL, codec2_enabled, 1) \ -X(a, STATIC, SINGULAR, UINT32, mic_chan, 2) \ -X(a, STATIC, SINGULAR, UINT32, amp_pin, 3) \ -X(a, STATIC, SINGULAR, UINT32, ptt_pin, 4) \ -X(a, STATIC, SINGULAR, UENUM, bitrate, 5) \ -X(a, STATIC, SINGULAR, UINT32, i2s_ws, 6) \ -X(a, STATIC, SINGULAR, UINT32, i2s_sd, 7) \ -X(a, STATIC, SINGULAR, UINT32, i2s_din, 8) \ -X(a, STATIC, SINGULAR, UINT32, i2s_sck, 9) +X(a, STATIC, SINGULAR, UINT32, ptt_pin, 2) \ +X(a, STATIC, SINGULAR, UENUM, bitrate, 3) \ +X(a, STATIC, SINGULAR, UINT32, i2s_ws, 4) \ +X(a, STATIC, SINGULAR, UINT32, i2s_sd, 5) \ +X(a, STATIC, SINGULAR, UINT32, i2s_din, 6) \ +X(a, STATIC, SINGULAR, UINT32, i2s_sck, 7) #define ModuleConfig_AudioConfig_CALLBACK NULL #define ModuleConfig_AudioConfig_DEFAULT NULL @@ -395,7 +389,7 @@ extern const pb_msgdesc_t ModuleConfig_CannedMessageConfig_msg; #define ModuleConfig_CannedMessageConfig_fields &ModuleConfig_CannedMessageConfig_msg /* Maximum encoded size of messages (where known) */ -#define ModuleConfig_AudioConfig_size 25 +#define ModuleConfig_AudioConfig_size 19 #define ModuleConfig_CannedMessageConfig_size 49 #define ModuleConfig_ExternalNotificationConfig_size 22 #define ModuleConfig_MQTTConfig_size 169 diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index a47f47bd8..b1433a0ae 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -8,8 +8,6 @@ #include "Router.h" #include "FSCommon.h" -#include -#include #include /* @@ -23,23 +21,14 @@ Basic Usage: 1) Enable the module by setting audio.codec2_enabled to 1. - 2a) Set the pins for the I2S interface if you want to use digital audio. Recommended on TLora is I2S_WS 13/I2S_SD 15/I2S_SIN 2/I2S_SCK 14 - 2b) Set the pins (audio.mic_pin / audio.amp_pin) for your preferred microphone and amplifier GPIO pins if you want to use analog audio. - This is rather heavy on the CPU and not recommended. - On tbeam, best use: - audio.mic_chan 6 (GPIO 34) - audio.amp_pin 14 - audio.ptt_pin 39 + 2) Set the pins for the I2S interface. Recommended on TLora is I2S_WS 13/I2S_SD 15/I2S_SIN 2/I2S_SCK 14 3) Set audio.bitrate to the desired codec2 rate (CODEC2_3200, CODEC2_2400, CODEC2_1600, CODEC2_1400, CODEC2_1300, CODEC2_1200, CODEC2_700, CODEC2_700B) KNOWN PROBLEMS - * Until the module is initilized by the startup sequence, the amp_pin is in a floating - state. This may produce a bit of "noise". + * Half Duplex * Will not work on NRF and the Linux device targets (yet?). */ -#define AMIC 6 -#define AAMP 14 #define PTT_PIN 39 ButterworthFilter hp_filter(240, 8000, ButterworthFilter::ButterworthFilter::Highpass, 1); @@ -60,67 +49,10 @@ AudioModule *audioModule; #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) #endif -portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; -hw_timer_t* adcTimer = NULL; - //int16_t 1KHz sine test tone int16_t Sine1KHz[8] = { -21210 , -30000, -21210, 0 , 21210 , 30000 , 21210, 0 }; int Sine1KHz_index = 0; -int IRAM_ATTR local_adc1_read(int channel) { - uint16_t adc_value; -#if CONFIG_IDF_TARGET_ESP32S3 - SENS.sar_meas1_ctrl2.sar1_en_pad = (1 << channel); // only one channel is selected - while (SENS.sar_slave_addr1.meas_status != 0); - SENS.sar_meas1_ctrl2.meas1_start_sar = 0; - SENS.sar_meas1_ctrl2.meas1_start_sar = 1; - while (SENS.sar_meas1_ctrl2.meas1_done_sar == 0); - adc_value = SENS.sar_meas1_ctrl2.meas1_data_sar; -#else - SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected - while (SENS.sar_slave_addr1.meas_status != 0); - SENS.sar_meas_start1.meas1_start_sar = 0; - SENS.sar_meas_start1.meas1_start_sar = 1; - while (SENS.sar_meas_start1.meas1_done_sar == 0); - adc_value = SENS.sar_meas_start1.meas1_data_sar; -#endif - return adc_value; -} - -IRAM_ATTR void am_onTimer() -{ - portENTER_CRITICAL_ISR(&timerMux); //Enter crital code without interruptions - if ((audioModule->radio_state == RadioState::tx) && (!moduleConfig.audio.i2s_sd)) { - audioModule->adc_buffer[audioModule->adc_buffer_index++] = (16 * local_adc1_read(audioModule->mic_chan)) - 32768; - - //If you want to test with a 1KHz tone, comment the line above and descomment the three lines below - - // adc_buffer[adc_buffer_index++] = Sine1KHz[Sine1KHz_index++]; - // if (Sine1KHz_index >= 8) - // Sine1KHz_index = 0; - - if (audioModule->adc_buffer_index == audioModule->adc_buffer_size) { - audioModule->adc_buffer_index = 0; - memcpy((void*)audioModule->speech, (void*)audioModule->adc_buffer, 2 * audioModule->adc_buffer_size); - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - vTaskNotifyGiveFromISR(codec2HandlerTask, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken == pdTRUE) - YIELD_FROM_ISR(xHigherPriorityTaskWoken); - } - } else if ((audioModule->radio_state == RadioState::rx) && (!moduleConfig.audio.i2s_din)) { - // ESP32-S3 does not have DAC support -#if !defined(CONFIG_IDF_TARGET_ESP32S3) - - //Get a value from audio_fifo and convert it to 0 - 255 to play it in the ADC - if (audioModule->fifo.get(&audioModule->sample)) - audioModule->rx_raw_audio_value = (uint8_t)((audioModule->sample + 32768) / 256); - - dacWrite(moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP, audioModule->rx_raw_audio_value); -#endif - } - portEXIT_CRITICAL_ISR(&timerMux); // exit critical code -} - void run_codec2(void* parameter) { while (true) { @@ -128,10 +60,12 @@ void run_codec2(void* parameter) if (tcount != 0) { if (audioModule->radio_state == RadioState::tx) { - + + // Apply the TX filter for (int i = 0; i < audioModule->adc_buffer_size; i++) audioModule->speech[i] = (int16_t)hp_filter.Update((float)audioModule->speech[i]); + // Encode the audio codec2_encode(audioModule->codec2, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, audioModule->speech); //increment the pointer where the encoded frame must be saved @@ -141,7 +75,7 @@ void run_codec2(void* parameter) if (audioModule->tx_encode_frame_index == audioModule->encode_frame_size) { //Transmit it - DEBUG_MSG("♪♫♪ Sending %d bytes\n", audioModule->encode_frame_size); + DEBUG_MSG("♪♫♪ Sending %d codec2 bytes\n", audioModule->encode_frame_size); audioModule->sendPayload(); audioModule->tx_encode_frame_index = 0; } @@ -152,16 +86,8 @@ void run_codec2(void* parameter) { //Decode the codec2 frame codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); - - if (moduleConfig.audio.i2s_din) { - size_t bytesOut = 0; - - i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(40)); - } else { - //Put the decoded audio in the fifo - for (int j = 0; j < audioModule->adc_buffer_size; j++) - audioModule->fifo.put(audioModule->output_buffer[j]); - } + size_t bytesOut = 0; + i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500)); } } } @@ -171,7 +97,6 @@ void run_codec2(void* parameter) AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { - fifo.init(); DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); codec2 = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); codec2_set_lpc_post_filter(codec2, 1, 0, 0.8, 0.2); @@ -191,86 +116,39 @@ int32_t AudioModule::runOnce() if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { esp_err_t res; if (firstTime) { - // if we have I2S_SD defined, take samples from digital mic. I2S_DIN means digital output to amp. - if (moduleConfig.audio.i2s_sd || moduleConfig.audio.i2s_din) { - // Set up I2S Processor configuration. This will produce 16bit samples at 8 kHz instead of 12 from the ADC - DEBUG_MSG("♪♫♪ Initializing I2S SD: %d DIN: %d WS: %d SCK:%d\n", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck); - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) | (moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)), - .sample_rate = 8000, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, - .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), - .intr_alloc_flags = 0, - .dma_buf_count = 8, - .dma_buf_len = adc_buffer_size, // 320 * 2 bytes - .use_apll = false, - .tx_desc_auto_clear = true, - .fixed_mclk = 0 - }; - res = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); - if(res != ESP_OK) - DEBUG_MSG("♪♫♪ Failed to install I2S driver: %d\n", res); + // Set up I2S Processor configuration. This will produce 16bit samples at 8 kHz instead of 12 from the ADC + DEBUG_MSG("♪♫♪ Initializing I2S SD: %d DIN: %d WS: %d SCK:%d\n", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck); + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) | (moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)), + .sample_rate = 8000, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, + .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_STAND_I2S), + .intr_alloc_flags = 0, + .dma_buf_count = 8, + .dma_buf_len = adc_buffer_size, // 320 * 2 bytes + .use_apll = false, + .tx_desc_auto_clear = true, + .fixed_mclk = 0 + }; + res = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); + if(res != ESP_OK) + DEBUG_MSG("♪♫♪ Failed to install I2S driver: %d\n", res); - const i2s_pin_config_t pin_config = { - .bck_io_num = moduleConfig.audio.i2s_sck, - .ws_io_num = moduleConfig.audio.i2s_ws, - .data_out_num = moduleConfig.audio.i2s_din ? moduleConfig.audio.i2s_din : I2S_PIN_NO_CHANGE, - .data_in_num = moduleConfig.audio.i2s_sd ? moduleConfig.audio.i2s_sd : I2S_PIN_NO_CHANGE - }; - res = i2s_set_pin(I2S_PORT, &pin_config); - if(res != ESP_OK) - DEBUG_MSG("♪♫♪ Failed to set I2S pin config: %d\n", res); + const i2s_pin_config_t pin_config = { + .bck_io_num = moduleConfig.audio.i2s_sck, + .ws_io_num = moduleConfig.audio.i2s_ws, + .data_out_num = moduleConfig.audio.i2s_din ? moduleConfig.audio.i2s_din : I2S_PIN_NO_CHANGE, + .data_in_num = moduleConfig.audio.i2s_sd ? moduleConfig.audio.i2s_sd : I2S_PIN_NO_CHANGE + }; + res = i2s_set_pin(I2S_PORT, &pin_config); + if(res != ESP_OK) + DEBUG_MSG("♪♫♪ Failed to set I2S pin config: %d\n", res); - res = i2s_start(I2S_PORT); - if(res != ESP_OK) - DEBUG_MSG("♪♫♪ Failed to start I2S: %d\n", res); - } - - // setup analogue DAC only if we don't use I2S for output. This is not available on ESP32-S3 -#if !defined(CONFIG_IDF_TARGET_ESP32S3) - if (!moduleConfig.audio.i2s_din) - DEBUG_MSG("♪♫♪ Initializing DAC on Pin %u\n", moduleConfig.audio.amp_pin ? moduleConfig.audio.amp_pin : AAMP); -#else - if (!moduleConfig.audio.i2s_din) { - DEBUG_MSG("♪♫♪ ESP32-S3 does not support DAC. Audio Module Disabled.\n"); - return INT32_MAX; - } -#endif + res = i2s_start(I2S_PORT); + if(res != ESP_OK) + DEBUG_MSG("♪♫♪ Failed to start I2S: %d\n", res); - if (!moduleConfig.audio.i2s_sd) { - // Set up ADC if we don't have a digital microphone. - DEBUG_MSG("♪♫♪ Initializing ADC on Channel %u\n", moduleConfig.audio.mic_chan ? moduleConfig.audio.mic_chan : AMIC); - mic_chan = moduleConfig.audio.mic_chan ? (adc1_channel_t)(int)moduleConfig.audio.mic_chan : (adc1_channel_t)AMIC; - adc1_config_width(ADC_WIDTH_12Bit); - adc1_config_channel_atten(mic_chan, ADC_ATTEN_DB_6); - adc1_get_raw(mic_chan); - } - - if ((!moduleConfig.audio.i2s_sd) || (!moduleConfig.audio.i2s_din)) { - // Start a timer at 8kHz to sample the ADC and play the audio on the DAC, but only if we have analogue audio to process - uint32_t cpufreq = getCpuFrequencyMhz(); - switch (cpufreq){ - case 160: - adcTimer = timerBegin(3, 1000, true); // 160 MHz / 1000 = 160KHz - break; - case 240: - adcTimer = timerBegin(3, 1500, true); // 240 MHz / 1500 = 160KHz - break; - case 320: - adcTimer = timerBegin(3, 2000, true); // 320 MHz / 2000 = 160KHz - break; - case 80: - default: - adcTimer = timerBegin(3, 500, true); // 80 MHz / 500 = 160KHz - break; - } - DEBUG_MSG("♪♫♪ Timer CPU Frequency: %u MHz\n", cpufreq); - timerAttachInterrupt(adcTimer, &am_onTimer, false); - timerAlarmWrite(adcTimer, 20, true); // Interrupts when counter == 20, 8.000 times a second - timerAlarmEnable(adcTimer); - } - radio_state = RadioState::rx; // Configure PTT input @@ -289,7 +167,7 @@ int32_t AudioModule::runOnce() if (radio_state == RadioState::tx) { if (tx_encode_frame_index > 0) { // Send the incomplete frame - DEBUG_MSG("♪♫♪ Sending %d bytes (incomplete)\n", tx_encode_frame_index); + DEBUG_MSG("♪♫♪ Sending %d codec2 bytes (incomplete)\n", tx_encode_frame_index); sendPayload(); } DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); @@ -297,7 +175,7 @@ int32_t AudioModule::runOnce() radio_state = RadioState::rx; } } - if ((radio_state == RadioState::tx) && moduleConfig.audio.i2s_sd) { + if (radio_state == RadioState::tx) { // Get I2S data from the microphone and place in data buffer size_t bytesIn = 0; res = i2s_read(I2S_PORT, adc_buffer + adc_buffer_index, adc_buffer_size - adc_buffer_index, &bytesIn, pdMS_TO_TICKS(40)); // wait 40ms for audio to arrive. diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index c81c5c310..aee09a0b0 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -7,11 +7,9 @@ #include "NodeDB.h" #include #include -// #include #include #include #include -#include #define ADC_BUFFER_SIZE_MAX 320 @@ -32,11 +30,9 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread int encode_codec_size = 0; int encode_frame_size = 0; volatile RadioState radio_state = RadioState::rx; - FastAudioFIFO fifo; + struct CODEC2* codec2 = NULL; - int16_t sample; - adc1_channel_t mic_chan = (adc1_channel_t)0; - uint8_t rx_raw_audio_value = 127; + // int16_t sample; AudioModule(); @@ -49,7 +45,6 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread int encode_frame_num = 0; bool firstTime = true; - virtual int32_t runOnce() override; virtual MeshPacket *allocReply() override; From 8f94463eacc2a63968eaa11a570727206cf84fc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 2 Dec 2022 13:44:36 +0100 Subject: [PATCH 13/31] send a 4 byte magic header including the codec version --- src/modules/esp32/AudioModule.cpp | 25 +++++++++++-------------- src/modules/esp32/AudioModule.h | 20 +++++++++++++++++--- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index b1433a0ae..4e784e1a9 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -29,16 +29,8 @@ * Will not work on NRF and the Linux device targets (yet?). */ -#define PTT_PIN 39 - ButterworthFilter hp_filter(240, 8000, ButterworthFilter::ButterworthFilter::Highpass, 1); -// Use I2S Processor 0 -#define I2S_PORT I2S_NUM_0 - -#define AUDIO_MODULE_RX_BUFFER 128 -#define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 - TaskHandle_t codec2HandlerTask; AudioModule *audioModule; @@ -55,6 +47,9 @@ int Sine1KHz_index = 0; void run_codec2(void* parameter) { + // 4 bytes of header in each frame Kennung hex c0 de c2 plus the bitrate + memcpy(audioModule->tx_encode_frame,&audioModule->tx_header,sizeof(audioModule->tx_header)); + while (true) { uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(10000)); @@ -72,12 +67,12 @@ void run_codec2(void* parameter) audioModule->tx_encode_frame_index += audioModule->encode_codec_size; //If it this is reached we have a ready trasnmission frame - if (audioModule->tx_encode_frame_index == audioModule->encode_frame_size) + if (audioModule->tx_encode_frame_index == (audioModule->encode_frame_size + sizeof(audioModule->tx_header))) { //Transmit it DEBUG_MSG("♪♫♪ Sending %d codec2 bytes\n", audioModule->encode_frame_size); audioModule->sendPayload(); - audioModule->tx_encode_frame_index = 0; + audioModule->tx_encode_frame_index = sizeof(audioModule->tx_header); } } if (audioModule->radio_state == RadioState::rx) { @@ -99,10 +94,12 @@ AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); codec2 = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); + memcpy(tx_header.magic,c2_magic,sizeof(c2_magic)); + tx_header.mode = (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1; codec2_set_lpc_post_filter(codec2, 1, 0, 0.8, 0.2); encode_codec_size = (codec2_bits_per_frame(codec2) + 7) / 8; - encode_frame_num = Constants_DATA_PAYLOAD_LEN / encode_codec_size; - encode_frame_size = encode_frame_num * encode_codec_size; // max 237 bytes + encode_frame_num = (Constants_DATA_PAYLOAD_LEN - sizeof(tx_header)) / encode_codec_size; + encode_frame_size = encode_frame_num * encode_codec_size; // max 233 bytes + 4 header bytes adc_buffer_size = codec2_samples_per_frame(codec2); DEBUG_MSG(" using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, encode_frame_size); xTaskCreate(&run_codec2, "codec2_task", 30000, NULL, 5, &codec2HandlerTask); @@ -165,13 +162,13 @@ int32_t AudioModule::runOnce() } } else { if (radio_state == RadioState::tx) { - if (tx_encode_frame_index > 0) { + if (tx_encode_frame_index > sizeof(tx_header)) { // Send the incomplete frame DEBUG_MSG("♪♫♪ Sending %d codec2 bytes (incomplete)\n", tx_encode_frame_index); sendPayload(); } DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); - tx_encode_frame_index = 0; + tx_encode_frame_index = sizeof(tx_header); radio_state = RadioState::rx; } } diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index aee09a0b0..8e813ad1e 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -11,21 +11,35 @@ #include #include -#define ADC_BUFFER_SIZE_MAX 320 - enum RadioState { standby, rx, tx }; +const char c2_magic[3] = {0xc0, 0xde, 0xc2}; // Magic number for codec2 header + +struct c2_header { + char magic[3]; + char mode; +}; + +#define ADC_BUFFER_SIZE_MAX 320 +#define PTT_PIN 39 + +#define I2S_PORT I2S_NUM_0 + +#define AUDIO_MODULE_RX_BUFFER 128 +#define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 + class AudioModule : public SinglePortModule, private concurrency::OSThread { public: unsigned char rx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; unsigned char tx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; + c2_header tx_header = {}; int16_t speech[ADC_BUFFER_SIZE_MAX] = {}; int16_t output_buffer[ADC_BUFFER_SIZE_MAX] = {}; uint16_t adc_buffer[ADC_BUFFER_SIZE_MAX] = {}; int adc_buffer_size = 0; uint16_t adc_buffer_index = 0; - int tx_encode_frame_index = 0; + int tx_encode_frame_index = sizeof(c2_header); // leave room for header int rx_encode_frame_index = 0; int encode_codec_size = 0; int encode_frame_size = 0; From cea8393a7fe370813fdf5bd44b730f285b9984ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 6 Dec 2022 14:08:04 +0100 Subject: [PATCH 14/31] Merge pull request #2026 from GUVWAF/develop TraceRouteModule --- src/mesh/FloodingRouter.cpp | 5 ++ src/mesh/FloodingRouter.h | 1 + src/modules/Modules.cpp | 2 + src/modules/TraceRouteModule.cpp | 86 ++++++++++++++++++++++++++++++++ src/modules/TraceRouteModule.h | 36 +++++++++++++ 5 files changed, 130 insertions(+) create mode 100644 src/modules/TraceRouteModule.cpp create mode 100644 src/modules/TraceRouteModule.h diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp index 818bacf45..925735903 100644 --- a/src/mesh/FloodingRouter.cpp +++ b/src/mesh/FloodingRouter.cpp @@ -41,6 +41,11 @@ void FloodingRouter::sniffReceived(const MeshPacket *p, const Routing *c) tosend->hop_limit--; // bump down the hop count + // If it is a traceRoute request, update the route that it went via me + if (p->which_payload_variant == MeshPacket_decoded_tag && traceRouteModule->wantPacket(p)) { + traceRouteModule->updateRoute(tosend); + } + printPacket("Rebroadcasting received floodmsg to neighbors", p); // Note: we are careful to resend using the original senders node id // We are careful not to call our hooked version of send() - because we don't want to check this again diff --git a/src/mesh/FloodingRouter.h b/src/mesh/FloodingRouter.h index 387b4576b..7e6271fc0 100644 --- a/src/mesh/FloodingRouter.h +++ b/src/mesh/FloodingRouter.h @@ -2,6 +2,7 @@ #include "PacketHistory.h" #include "Router.h" +#include "modules/TraceRouteModule.h" /** * This is a mixin that extends Router with the ability to do Naive Flooding (in the standard mesh protocol sense) diff --git a/src/modules/Modules.cpp b/src/modules/Modules.cpp index b0b923863..ece160ced 100644 --- a/src/modules/Modules.cpp +++ b/src/modules/Modules.cpp @@ -11,6 +11,7 @@ #include "modules/ReplyModule.h" #include "modules/RoutingModule.h" #include "modules/TextMessageModule.h" +#include "modules/TraceRouteModule.h" #include "modules/WaypointModule.h" #if HAS_TELEMETRY #include "modules/Telemetry/DeviceTelemetry.h" @@ -40,6 +41,7 @@ void setupModules() positionModule = new PositionModule(); waypointModule = new WaypointModule(); textMessageModule = new TextMessageModule(); + traceRouteModule = new TraceRouteModule(); // Note: if the rest of meshtastic doesn't need to explicitly use your module, you do not need to assign the instance // to a global variable. diff --git a/src/modules/TraceRouteModule.cpp b/src/modules/TraceRouteModule.cpp new file mode 100644 index 000000000..1c5fd97d3 --- /dev/null +++ b/src/modules/TraceRouteModule.cpp @@ -0,0 +1,86 @@ +#include "TraceRouteModule.h" +#include "MeshService.h" +#include "FloodingRouter.h" + +TraceRouteModule *traceRouteModule; + + +bool TraceRouteModule::handleReceivedProtobuf(const MeshPacket &mp, RouteDiscovery *r) +{ + // Only handle a response + if (mp.decoded.request_id) { + printRoute(r, mp.to, mp.from); + } + + return false; // let it be handled by RoutingModule +} + + +void TraceRouteModule::updateRoute(MeshPacket* p) +{ + auto &incoming = p->decoded; + // Only append an ID for the request (one way) + if (!incoming.request_id) { + RouteDiscovery scratch; + RouteDiscovery *updated = NULL; + memset(&scratch, 0, sizeof(scratch)); + pb_decode_from_bytes(incoming.payload.bytes, incoming.payload.size, RouteDiscovery_fields, &scratch); + updated = &scratch; + + appendMyID(updated); + printRoute(updated, p->from, NODENUM_BROADCAST); + + // Set updated route to the payload of the to be flooded packet + p->decoded.payload.size = pb_encode_to_bytes(p->decoded.payload.bytes, sizeof(p->decoded.payload.bytes), RouteDiscovery_fields, updated); + } +} + + +void TraceRouteModule::appendMyID(RouteDiscovery* updated) +{ + // Length of route array can normally not be exceeded due to the max. hop_limit of 7 + if (updated->route_count < sizeof(updated->route)/sizeof(updated->route[0])) { + updated->route[updated->route_count] = myNodeInfo.my_node_num; + updated->route_count += 1; + } else { + DEBUG_MSG("WARNING: Route exceeded maximum hop limit, are you bridging networks?\n"); + } +} + + +void TraceRouteModule::printRoute(RouteDiscovery* r, uint32_t origin, uint32_t dest) +{ + DEBUG_MSG("Route traced:\n"); + DEBUG_MSG("0x%x --> ", origin); + for (uint8_t i=0; iroute_count; i++) { + DEBUG_MSG("0x%x --> ", r->route[i]); + } + if (dest != NODENUM_BROADCAST) DEBUG_MSG("0x%x\n", dest); else DEBUG_MSG("...\n"); +} + + +MeshPacket* TraceRouteModule::allocReply() +{ + assert(currentRequest); + + // Copy the payload of the current request + auto req = *currentRequest; + auto &p = req.decoded; + RouteDiscovery scratch; + RouteDiscovery *updated = NULL; + memset(&scratch, 0, sizeof(scratch)); + pb_decode_from_bytes(p.payload.bytes, p.payload.size, RouteDiscovery_fields, &scratch); + updated = &scratch; + + printRoute(updated, req.from, req.to); + + // Create a MeshPacket with this payload and set it as the reply + MeshPacket* reply = allocDataProtobuf(*updated); + + return reply; +} + + +TraceRouteModule::TraceRouteModule() : ProtobufModule("traceroute", PortNum_TRACEROUTE_APP, RouteDiscovery_fields) { + ourPortNum = PortNum_TRACEROUTE_APP; +} diff --git a/src/modules/TraceRouteModule.h b/src/modules/TraceRouteModule.h new file mode 100644 index 000000000..15b7a564d --- /dev/null +++ b/src/modules/TraceRouteModule.h @@ -0,0 +1,36 @@ +#pragma once +#include "ProtobufModule.h" + + +/** + * A module that traces the route to a certain destination node + */ +class TraceRouteModule : public ProtobufModule +{ + public: + TraceRouteModule(); + + // Let FloodingRouter call updateRoute upon rebroadcasting a TraceRoute request + friend class FloodingRouter; + + protected: + bool handleReceivedProtobuf(const MeshPacket &mp, RouteDiscovery *r) override; + + virtual MeshPacket *allocReply() override; + + /* Call before rebroadcasting a RouteDiscovery payload in order to update + the route array containing the IDs of nodes this packet went through */ + void updateRoute(MeshPacket* p); + + private: + // Call to add your ID to the route array of a RouteDiscovery message + void appendMyID(RouteDiscovery *r); + + /* Call to print the route array of a RouteDiscovery message. + Set origin to where the request came from. + Set dest to the ID of its destination, or NODENUM_BROADCAST if it has not yet arrived there. */ + void printRoute(RouteDiscovery* r, uint32_t origin, uint32_t dest); + +}; + +extern TraceRouteModule *traceRouteModule; \ No newline at end of file From aec091e7aaa8b91e23842218dae5b7ed8311f521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 6 Dec 2022 16:56:38 +0100 Subject: [PATCH 15/31] manual master merge # Conflicts: # src/Power.cpp --- src/modules/esp32/StoreForwardModule.cpp | 273 ++++++++++++----------- src/modules/esp32/StoreForwardModule.h | 14 +- 2 files changed, 146 insertions(+), 141 deletions(-) diff --git a/src/modules/esp32/StoreForwardModule.cpp b/src/modules/esp32/StoreForwardModule.cpp index e51296824..9d1c44c18 100644 --- a/src/modules/esp32/StoreForwardModule.cpp +++ b/src/modules/esp32/StoreForwardModule.cpp @@ -16,52 +16,50 @@ StoreForwardModule *storeForwardModule; int32_t StoreForwardModule::runOnce() { - #ifdef ARCH_ESP32 + if (moduleConfig.store_forward.enabled && is_server) { + // Send out the message queue. + if (this->busy) { + // Only send packets if the channel is less than 25% utilized. + if (airTime->channelUtilizationPercent() < polite_channel_util_percent) { - if (moduleConfig.store_forward.enabled) { + // DEBUG_MSG("--- --- --- In busy loop 1 %d\n", this->packetHistoryTXQueue_index); + storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index); - if (config.device.role == Config_DeviceConfig_Role_ROUTER) { - - // Send out the message queue. - if (this->busy) { - - // Only send packets if the channel is less than 25% utilized. - if (airTime->channelUtilizationPercent() < polite_channel_util_percent) { - - // DEBUG_MSG("--- --- --- In busy loop 1 %d\n", this->packetHistoryTXQueue_index); - storeForwardModule->sendPayload(this->busyTo, this->packetHistoryTXQueue_index); - - if (this->packetHistoryTXQueue_index == packetHistoryTXQueue_size) { - strcpy(this->routerMessage, "** S&F - Done"); - storeForwardModule->sendMessage(this->busyTo, this->routerMessage); - - // DEBUG_MSG("--- --- --- In busy loop - Done \n"); - this->packetHistoryTXQueue_index = 0; - this->busy = false; - } else { - this->packetHistoryTXQueue_index++; - } + if (this->packetHistoryTXQueue_index == packetHistoryTXQueue_size) { + strcpy(this->routerMessage, "** S&F - Done"); + storeForwardModule->sendMessage(this->busyTo, this->routerMessage); + // DEBUG_MSG("--- --- --- In busy loop - Done \n"); + this->packetHistoryTXQueue_index = 0; + this->busy = false; } else { - DEBUG_MSG("Channel utilization is too high. Skipping this opportunity to send and will retry later.\n"); + this->packetHistoryTXQueue_index++; } + + } else { + DEBUG_MSG("Channel utilization is too high. Retrying later.\n"); } - DEBUG_MSG("SF myNodeInfo.bitrate = %f bytes / sec\n", myNodeInfo.bitrate); + DEBUG_MSG("SF bitrate = %f bytes / sec\n", myNodeInfo.bitrate); - return (this->packetTimeMax); - } else { - DEBUG_MSG("Store & Forward Module - Disabled (is_router = false)\n"); + } else if (millis() - lastHeartbeat > 300000) { + lastHeartbeat = millis(); + DEBUG_MSG("Sending heartbeat\n"); + + StoreAndForward sf; + sf.rr = StoreAndForward_RequestResponse_ROUTER_HEARTBEAT; + sf.has_heartbeat = true; + sf.heartbeat.period = 300; + sf.heartbeat.secondary = 0; // TODO we always have one primary router for now - return (INT32_MAX); + MeshPacket *p = allocDataProtobuf(sf); + p->to = NODENUM_BROADCAST; + p->decoded.want_response = false; + p->priority = MeshPacket_Priority_MIN; + service.sendToMesh(p); } - - } else { - DEBUG_MSG("Store & Forward Module - Disabled\n"); - - return (INT32_MAX); + return (this->packetTimeMax); } - #endif return (INT32_MAX); } @@ -76,12 +74,7 @@ void StoreForwardModule::populatePSRAM() https://learn.upesy.com/en/programmation/psram.html#psram-tab */ - DEBUG_MSG("Before PSRAM initilization:\n"); - - DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize()); - DEBUG_MSG(" Free heap: %d\n", ESP.getFreeHeap()); - DEBUG_MSG(" Total PSRAM: %d\n", ESP.getPsramSize()); - DEBUG_MSG(" Free PSRAM: %d\n", ESP.getFreePsram()); + DEBUG_MSG("Before PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize()); this->packetHistoryTXQueue = static_cast(ps_calloc(this->historyReturnMax, sizeof(PacketHistoryStruct))); @@ -93,19 +86,12 @@ void StoreForwardModule::populatePSRAM() this->packetHistory = static_cast(ps_calloc(numberOfPackets, sizeof(PacketHistoryStruct))); - DEBUG_MSG("After PSRAM initilization:\n"); - - DEBUG_MSG(" Total heap: %d\n", ESP.getHeapSize()); - DEBUG_MSG(" Free heap: %d\n", ESP.getFreeHeap()); - DEBUG_MSG(" Total PSRAM: %d\n", ESP.getPsramSize()); - DEBUG_MSG(" Free PSRAM: %d\n", ESP.getFreePsram()); - DEBUG_MSG("Store and Forward Stats:\n"); - DEBUG_MSG(" numberOfPackets for packetHistory - %u\n", numberOfPackets); + DEBUG_MSG("After PSRAM initilization: heap %d/%d PSRAM %d/%d\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreePsram(), ESP.getPsramSize()); + DEBUG_MSG("numberOfPackets for packetHistory - %u\n", numberOfPackets); } void StoreForwardModule::historyReport() { - DEBUG_MSG("Iterating through the message history...\n"); DEBUG_MSG("Message history contains %u records\n", this->packetHistoryCurrent); } @@ -246,8 +232,8 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp) DEBUG_MSG("--- S&F Received something\n"); - // The router node should not be sending messages as a client. - if (getFrom(&mp) != nodeDB.getNodeNum()) { + // The router node should not be sending messages as a client. Unless he is a ROUTER_CLIENT + if ((getFrom(&mp) != nodeDB.getNodeNum()) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) { if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) { DEBUG_MSG("Packet came from - PortNum_TEXT_MESSAGE_APP\n"); @@ -264,6 +250,7 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp) } else { storeForwardModule->historySend(1000 * 60, getFrom(&mp)); } + } else if ((p.payload.bytes[0] == 'S') && (p.payload.bytes[1] == 'F') && (p.payload.bytes[2] == 'm') && (p.payload.bytes[3] == 0x00)) { strlcpy(this->routerMessage, @@ -278,14 +265,12 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp) } } else if (mp.decoded.portnum == PortNum_STORE_FORWARD_APP) { + DEBUG_MSG("Packet came from an PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum); } else { DEBUG_MSG("Packet came from an unknown port %u\n", mp.decoded.portnum); } } - - } else { - DEBUG_MSG("Store & Forward Module - Disabled\n"); } #endif @@ -293,92 +278,107 @@ ProcessMessage StoreForwardModule::handleReceived(const MeshPacket &mp) return ProcessMessage::CONTINUE; // Let others look at this message also if they want } -ProcessMessage StoreForwardModule::handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p) +bool StoreForwardModule::handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p) { if (!moduleConfig.store_forward.enabled) { // If this module is not enabled in any capacity, don't handle the packet, and allow other modules to consume - return ProcessMessage::CONTINUE; + return false; } - if (mp.decoded.portnum == PortNum_TEXT_MESSAGE_APP) { - DEBUG_MSG("Packet came from an PortNum_TEXT_MESSAGE_APP port %u\n", mp.decoded.portnum); - return ProcessMessage::CONTINUE; - } else if (mp.decoded.portnum == PortNum_STORE_FORWARD_APP) { - DEBUG_MSG("Packet came from an PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum); - + if (mp.decoded.portnum != PortNum_STORE_FORWARD_APP) { + DEBUG_MSG("Packet came from port %u\n", mp.decoded.portnum); + return false; } else { - DEBUG_MSG("Packet came from an UNKNOWN port %u\n", mp.decoded.portnum); - return ProcessMessage::CONTINUE; - } + DEBUG_MSG("Packet came from PortNum_STORE_FORWARD_APP port %u\n", mp.decoded.portnum); + switch (p->rr) { - case StoreAndForward_RequestResponse_CLIENT_ERROR: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_ERROR\n"); - break; + case StoreAndForward_RequestResponse_CLIENT_ERROR: + if(is_server) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_ERROR\n"); + } + break; - case StoreAndForward_RequestResponse_CLIENT_HISTORY: - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_HISTORY\n"); + case StoreAndForward_RequestResponse_CLIENT_HISTORY: + if(is_server) { + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_HISTORY\n"); + // Send the last 60 minutes of messages. + if (this->busy) { + strcpy(this->routerMessage, "** S&F - Busy. Try again shortly."); + storeForwardModule->sendMessage(getFrom(&mp), this->routerMessage); + } else { + storeForwardModule->historySend(1000 * 60, getFrom(&mp)); + } + } + break; - // Send the last 60 minutes of messages. - if (this->busy) { - strcpy(this->routerMessage, "** S&F - Busy. Try again shortly."); - storeForwardModule->sendMessage(getFrom(&mp), this->routerMessage); - } else { - storeForwardModule->historySend(1000 * 60, getFrom(&mp)); + case StoreAndForward_RequestResponse_CLIENT_PING: + if(is_server) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PING\n"); + } + break; + + case StoreAndForward_RequestResponse_CLIENT_PONG: + if(is_server) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PONG\n"); + } + break; + + case StoreAndForward_RequestResponse_CLIENT_STATS: + if(is_server) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_STATS\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_BUSY: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_BUSY\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_ERROR: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_ERROR\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_HEARTBEAT: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_HEARTBEAT\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_PING: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PING\n"); + } + break; + + case StoreAndForward_RequestResponse_ROUTER_PONG: + if(is_client) { + // Do nothing + DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PONG\n"); + } + break; + + default: + assert(0); // unexpected state - FIXME, make an error code and reboot } - - break; - - case StoreAndForward_RequestResponse_CLIENT_PING: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PING\n"); - break; - - case StoreAndForward_RequestResponse_CLIENT_PONG: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_PONG\n"); - break; - - case StoreAndForward_RequestResponse_CLIENT_STATS: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_CLIENT_STATS\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_BUSY: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_BUSY\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_ERROR: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_ERROR\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_HEARTBEAT: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_HEARTBEAT\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_PING: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PING\n"); - break; - - case StoreAndForward_RequestResponse_ROUTER_PONG: - // Do nothing - DEBUG_MSG("StoreAndForward_RequestResponse_ROUTER_PONG\n"); - break; - - default: - assert(0); // unexpected state - FIXME, make an error code and reboot } - return ProcessMessage::STOP; // There's no need for others to look at this message. + return true; // There's no need for others to look at this message. } StoreForwardModule::StoreForwardModule() - : SinglePortModule("StoreForwardModule", PortNum_TEXT_MESSAGE_APP), concurrency::OSThread("StoreForwardModule") + : concurrency::OSThread("StoreForwardModule"), ProtobufModule("StoreForward", PortNum_STORE_FORWARD_APP, &StoreAndForward_msg) { #ifdef ARCH_ESP32 @@ -397,9 +397,9 @@ StoreForwardModule::StoreForwardModule() if (moduleConfig.store_forward.enabled) { // Router - if (config.device.role == Config_DeviceConfig_Role_ROUTER) { - DEBUG_MSG("Initializing Store & Forward Module - Enabled as Router\n"); - if (ESP.getPsramSize()) { + if ((config.device.role == Config_DeviceConfig_Role_ROUTER) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) { + DEBUG_MSG("Initializing Store & Forward Module in Router mode\n"); + if (ESP.getPsramSize() > 0) { if (ESP.getFreePsram() >= 1024 * 1024) { // Do the startup here @@ -416,26 +416,27 @@ StoreForwardModule::StoreForwardModule() if (moduleConfig.store_forward.records) this->records = moduleConfig.store_forward.records; - // Maximum number of records to store in memory + // send heartbeat advertising? if (moduleConfig.store_forward.heartbeat) this->heartbeat = moduleConfig.store_forward.heartbeat; // Popupate PSRAM with our data structures. this->populatePSRAM(); - + is_server = true; } else { - DEBUG_MSG("Device has less than 1M of PSRAM free. Aborting startup.\n"); - DEBUG_MSG("Store & Forward Module - Aborting Startup.\n"); + DEBUG_MSG("Device has less than 1M of PSRAM free.\n"); + DEBUG_MSG("Store & Forward Module - disabling server.\n"); } - } else { DEBUG_MSG("Device doesn't have PSRAM.\n"); - DEBUG_MSG("Store & Forward Module - Aborting Startup.\n"); + DEBUG_MSG("Store & Forward Module - disabling server.\n"); } // Client - } else { - DEBUG_MSG("Initializing Store & Forward Module - Enabled as Client\n"); + } + if ((config.device.role == Config_DeviceConfig_Role_CLIENT) || (config.device.role == Config_DeviceConfig_Role_ROUTER_CLIENT)) { + is_client = true; + DEBUG_MSG("Initializing Store & Forward Module in Client mode\n"); } } #endif diff --git a/src/modules/esp32/StoreForwardModule.h b/src/modules/esp32/StoreForwardModule.h index 2c6d6ec6e..32d3cddc9 100644 --- a/src/modules/esp32/StoreForwardModule.h +++ b/src/modules/esp32/StoreForwardModule.h @@ -1,6 +1,6 @@ #pragma once -#include "SinglePortModule.h" +#include "ProtobufModule.h" #include "concurrency/OSThread.h" #include "mesh/generated/storeforward.pb.h" @@ -18,9 +18,8 @@ struct PacketHistoryStruct { pb_size_t payload_size; }; -class StoreForwardModule : public SinglePortModule, private concurrency::OSThread +class StoreForwardModule : private concurrency::OSThread, public ProtobufModule { - // bool firstTime = 1; bool busy = 0; uint32_t busyTo = 0; char routerMessage[Constants_DATA_PAYLOAD_LEN] = {0}; @@ -34,7 +33,12 @@ class StoreForwardModule : public SinglePortModule, private concurrency::OSThrea uint32_t packetHistoryTXQueue_size = 0; uint32_t packetHistoryTXQueue_index = 0; - uint32_t packetTimeMax = 2000; + uint32_t packetTimeMax = 5000; + + unsigned long lastHeartbeat = 0; + + bool is_client = false; + bool is_server = false; public: StoreForwardModule(); @@ -78,7 +82,7 @@ class StoreForwardModule : public SinglePortModule, private concurrency::OSThrea it */ virtual ProcessMessage handleReceived(const MeshPacket &mp) override; - virtual ProcessMessage handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p); + virtual bool handleReceivedProtobuf(const MeshPacket &mp, StoreAndForward *p); }; From 706ddf6e95ffd35e91a72af182fc4cda609b59ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 8 Dec 2022 17:17:48 +0100 Subject: [PATCH 16/31] show appropriate message when going into OTA mode --- src/graphics/Screen.cpp | 6 +----- src/modules/AdminModule.cpp | 3 +++ 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index eac78dd64..b5a3ac4d5 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -329,11 +329,7 @@ static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, i display->drawString(64 + x, y, "Updating"); display->setFont(FONT_SMALL); - if ((millis() / 1000) % 2) { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . ."); - } else { - display->drawString(64 + x, FONT_HEIGHT_SMALL + y + 2, "Please wait . . "); - } + display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), "Please be patient and do not power off."); } /// Draw the last text message we received diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 3ce8731e7..2639e0594 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -112,12 +112,15 @@ bool AdminModule::handleReceivedProtobuf(const MeshPacket &mp, AdminMessage *r) #ifdef ARCH_ESP32 if (BleOta::getOtaAppVersion().isEmpty()) { DEBUG_MSG("No OTA firmware available, scheduling regular reboot in %d seconds\n", s); + screen->startRebootScreen(); }else{ + screen->startFirmwareUpdateScreen(); BleOta::switchToOtaApp(); DEBUG_MSG("Rebooting to OTA in %d seconds\n", s); } #else DEBUG_MSG("Not on ESP32, scheduling regular reboot in %d seconds\n", s); + screen->startRebootScreen(); #endif rebootAtMsec = (s < 0) ? 0 : (millis() + s * 1000); break; From c75ea87f6bff7e700d3b2b6738d67c7ff45dc035 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 8 Dec 2022 17:34:14 +0100 Subject: [PATCH 17/31] Format received message screen like sender in canned messages. --- src/graphics/Screen.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index b5a3ac4d5..7919e9891 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -360,6 +360,9 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state { displayedNodeNum = 0; // Not currently showing a node pane + // the max length of this buffer is much longer than we can possibly print + static char tempBuf[237]; + MeshPacket &mp = devicestate.rx_text_message; NodeInfo *node = nodeDB.getNode(getFrom(&mp)); // DEBUG_MSG("drawing text message from 0x%x: %s\n", mp.from, @@ -369,16 +372,13 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state // with the third parameter you can define the width after which words will // be wrapped. Currently only spaces and "-" are allowed for wrapping display->setTextAlignment(TEXT_ALIGN_LEFT); - display->setFont(FONT_MEDIUM); - String sender = (node && node->has_user) ? node->user.short_name : "???"; - display->drawString(0 + x, 0 + y, sender); display->setFont(FONT_SMALL); - - // the max length of this buffer is much longer than we can possibly print - static char tempBuf[96]; - snprintf(tempBuf, sizeof(tempBuf), " %s", mp.decoded.payload.bytes); - - display->drawStringMaxWidth(4 + x, 10 + y, SCREEN_WIDTH - (6 + x), tempBuf); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + display->drawStringf(0 + x, 0 + y, tempBuf, "From: %s", (node && node->has_user) ? node->user.short_name : "???"); + display->setColor(WHITE); + snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); + display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); } /// Draw a series of fields in a column, wrapping to multiple colums if needed From d9cd3dd3e1c976cb7f1826b6ee6674e7fd75529a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Thu, 8 Dec 2022 21:48:01 +0100 Subject: [PATCH 18/31] Change Boot Message format --- src/graphics/Screen.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 7919e9891..abd18d442 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -329,7 +329,8 @@ static void drawFrameFirmware(OLEDDisplay *display, OLEDDisplayUiState *state, i display->drawString(64 + x, y, "Updating"); display->setFont(FONT_SMALL); - display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), "Please be patient and do not power off."); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->drawStringMaxWidth(0 + x, 2 + y + FONT_HEIGHT_SMALL *2, x + display->getWidth(), "Please be patient and do not power off."); } /// Draw the last text message we received From fab08b645171a20f29872323005872a3287b711b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 9 Dec 2022 11:27:12 +0100 Subject: [PATCH 19/31] fix building for screenless devices --- src/graphics/Screen.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 23828b3ee..3988fa1a8 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -21,6 +21,7 @@ class Screen void startBluetoothPinScreen(uint32_t pin) {} void stopBluetoothPinScreen() {} void startRebootScreen() {} + void startFirmwareUpdateScreen() {} }; } From 44a33ed463fee088d1ba3366e8815a26fc5d1093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 11 Dec 2022 21:17:46 +0100 Subject: [PATCH 20/31] add IO7 to RAK pinouts - only comments changed --- variants/rak4631/variant.h | 1 + variants/rak4631_epaper/variant.h | 1 + 2 files changed, 2 insertions(+) diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index 0c1e15f4b..7b07c9aff 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -169,6 +169,7 @@ static const uint8_t SCK = PIN_SPI_SCK; IO4 <-> P0.04 (Arduino GPIO number 4) IO5 <-> P0.09 (Arduino GPIO number 9) IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) SW1 <-> P0.01 (Arduino GPIO number 1) A0 <-> P0.04/AIN2 (Arduino Analog A2 A1 <-> P0.31/AIN7 (Arduino Analog A7 diff --git a/variants/rak4631_epaper/variant.h b/variants/rak4631_epaper/variant.h index 2b626cf91..890f00ddd 100644 --- a/variants/rak4631_epaper/variant.h +++ b/variants/rak4631_epaper/variant.h @@ -169,6 +169,7 @@ static const uint8_t SCK = PIN_SPI_SCK; IO4 <-> P0.04 (Arduino GPIO number 4) IO5 <-> P0.09 (Arduino GPIO number 9) IO6 <-> P0.10 (Arduino GPIO number 10) + IO7 <-> P0.28 (Arduino GPIO number 28) SW1 <-> P0.01 (Arduino GPIO number 1) A0 <-> P0.04/AIN2 (Arduino Analog A2 A1 <-> P0.31/AIN7 (Arduino Analog A7 From ab6a5a5e07963548525afb98d6ac9957e47bde73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sun, 11 Dec 2022 23:12:00 +0100 Subject: [PATCH 21/31] if we get different frames than our own transmission setup, decode and play them anyway --- src/modules/esp32/AudioModule.cpp | 32 +++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 4e784e1a9..f29c11056 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -47,7 +47,7 @@ int Sine1KHz_index = 0; void run_codec2(void* parameter) { - // 4 bytes of header in each frame Kennung hex c0 de c2 plus the bitrate + // 4 bytes of header in each frame hex c0 de c2 plus the bitrate memcpy(audioModule->tx_encode_frame,&audioModule->tx_header,sizeof(audioModule->tx_header)); while (true) { @@ -76,13 +76,29 @@ void run_codec2(void* parameter) } } if (audioModule->radio_state == RadioState::rx) { - //Make a cycle to get each codec2 frame from the received frame - for (int i = 0; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size) - { - //Decode the codec2 frame - codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); - size_t bytesOut = 0; - i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500)); + size_t bytesOut = 0; + if (memcmp(audioModule->rx_encode_frame, &audioModule->tx_header, sizeof(audioModule->tx_header)) == 0) { + // Make a cycle to get each codec2 frame from the received frame + for (int i = 4; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size) + { + //Decode the codec2 frame + codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); + i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500)); + } + } else { + // if the buffer header does not match our own codec, make a temp decoding setup. + CODEC2* tmp_codec2 = codec2_create(audioModule->rx_encode_frame[3]); + codec2_set_lpc_post_filter(tmp_codec2, 1, 0, 0.8, 0.2); + int tmp_encode_codec_size = (codec2_bits_per_frame(tmp_codec2) + 7) / 8; + int tmp_adc_buffer_size = codec2_samples_per_frame(tmp_codec2); + // Make a cycle to get each codec2 frame from the received frame + for (int i = 4; i < audioModule->rx_encode_frame_index; i += tmp_encode_codec_size) + { + //Decode the codec2 frame + codec2_decode(tmp_codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); + i2s_write(I2S_PORT, &audioModule->output_buffer, tmp_adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500)); + } + codec2_destroy(tmp_codec2); } } } From 0f2d0d1f0745135ee9bd71339e0809f3ce59c0ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 13 Dec 2022 12:33:51 +0100 Subject: [PATCH 22/31] change on screen graphics to support bicolor screens --- src/graphics/Screen.cpp | 46 +++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index abd18d442..6f4c1fb58 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -377,6 +377,7 @@ static void drawTextMessageFrame(OLEDDisplay *display, OLEDDisplayUiState *state display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); display->setColor(BLACK); display->drawStringf(0 + x, 0 + y, tempBuf, "From: %s", (node && node->has_user) ? node->user.short_name : "???"); + display->drawStringf(1 + x, 0 + y, tempBuf, "From: %s", (node && node->has_user) ? node->user.short_name : "???"); display->setColor(WHITE); snprintf(tempBuf, sizeof(tempBuf), "%s", mp.decoded.payload.bytes); display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), tempBuf); @@ -392,6 +393,10 @@ static void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char * int xo = x, yo = y; while (*f) { display->drawString(xo, yo, *f); + if (display->getColor() == BLACK) + display->drawString(xo + 1, yo, *f); + + display->setColor(WHITE); yo += FONT_HEIGHT_SMALL; if (yo > SCREEN_HEIGHT - FONT_HEIGHT_SMALL) { xo += SCREEN_WIDTH / 2; @@ -462,6 +467,7 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *no sprintf(usersString, "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); display->drawFastImage(x, y, 8, 8, imgUser); display->drawString(x + 10, y - 2, usersString); + display->drawString(x + 11, y - 2, usersString); } // Draw GPS status summary @@ -470,15 +476,18 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus if (config.position.fixed_position) { // GPS coordinates are currently fixed display->drawString(x - 1, y - 2, "Fixed GPS"); + display->drawString(x, y - 2, "Fixed GPS"); return; } if (!gps->getIsConnected()) { display->drawString(x, y - 2, "No GPS"); + display->drawString(x + 1, y - 2, "No GPS"); return; } display->drawFastImage(x, y, 6, 8, gps->getHasLock() ? imgPositionSolid : imgPositionEmpty); if (!gps->getHasLock()) { display->drawString(x + 8, y - 2, "No sats"); + display->drawString(x + 9, y - 2, "No sats"); return; } else { char satsString[3]; @@ -666,16 +675,16 @@ static uint16_t getCompassDiam(OLEDDisplay *display) { uint16_t diam = 0; // get the smaller of the 2 dimensions and subtract 20 - if(display->getWidth() > display->getHeight()) { - diam = display->getHeight(); + if(display->getWidth() > (display->getHeight() - FONT_HEIGHT_SMALL)) { + diam = display->getHeight() - FONT_HEIGHT_SMALL; // if 2/3 of the other size would be smaller, use that if (diam > (display->getWidth() * 2 / 3)) { diam = display->getWidth() * 2 / 3; } } else { diam = display->getWidth(); - if (diam > (display->getHeight() * 2 / 3)) { - diam = display->getHeight() * 2 / 3; + if (diam > ((display->getHeight() - FONT_HEIGHT_SMALL) * 2 / 3)) { + diam = (display->getHeight() - FONT_HEIGHT_SMALL) * 2 / 3; } } @@ -755,6 +764,8 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + const char *username = node->has_user ? node->user.long_name : "Unknown Name"; static char signalStr[20]; @@ -785,7 +796,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ const char *fields[] = {username, distStr, signalStr, lastStr, NULL}; // coordinates for the center of the compass/circle - int16_t compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5, compassY = y + SCREEN_HEIGHT / 2; + int16_t compassX = x + SCREEN_WIDTH - getCompassDiam(display) / 2 - 5, compassY = y + FONT_HEIGHT_SMALL + (SCREEN_HEIGHT - FONT_HEIGHT_SMALL) / 2; bool hasNodeHeading = false; if (ourNode && hasPosition(ourNode)) { @@ -828,6 +839,7 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ display->drawString(compassX - FONT_HEIGHT_SMALL / 4, compassY - FONT_HEIGHT_SMALL / 2, "?"); display->drawCircle(compassX, compassY, getCompassDiam(display) / 2); + display->setColor(BLACK); // Must be after distStr is populated drawColumns(display, x, y, fields); } @@ -1354,6 +1366,9 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + char channelStr[20]; { concurrency::LockGuard guard(&lock); @@ -1363,14 +1378,15 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Display power status if (powerStatus->getHasBattery()) - drawBattery(display, x, y + 2, imgBattery, powerStatus); + drawBattery(display, x + 1, y + 3, imgBattery, powerStatus); else if (powerStatus->knowsUSB()) - display->drawFastImage(x, y + 2, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); + display->drawFastImage(x + 1, y + 3, 16, 8, powerStatus->getHasUSB() ? imgUSB : imgPower); // Display nodes status - drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 2, nodeStatus); + drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus); // Display GPS status - drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus); + drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus); + display->setColor(WHITE); // Draw the channel name display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); // Draw our hardware ID to assist with bluetooth pairing @@ -1401,6 +1417,10 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + display->setColor(WHITE); + if (WiFi.status() != WL_CONNECTED) { display->drawString(x, y, String("WiFi: Not Connected")); } else { @@ -1518,6 +1538,9 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat // The coordinates define the left starting point of the text display->setTextAlignment(TEXT_ALIGN_LEFT); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + char batStr[20]; if (powerStatus->getHasBattery()) { int batV = powerStatus->getBatteryVoltageMv() / 1000; @@ -1528,9 +1551,11 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat // Line 1 display->drawString(x, y, batStr); + display->drawString(x + 1, y, batStr); } else { // Line 1 display->drawString(x, y, String("USB")); + display->drawString(x + 1, y, String("USB")); } auto mode = ""; @@ -1563,6 +1588,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat } display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode), y, mode); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(mode) - 1, y, mode); // Line 2 uint32_t currentMillis = millis(); @@ -1575,6 +1601,8 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat // minutes %= 60; // hours %= 24; + display->setColor(WHITE); + // Show uptime as days, hours, minutes OR seconds String uptime; if (days >= 2) From 86d7860d8642300c503af6425ac9ce55554d30d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Tue, 13 Dec 2022 17:31:01 +0100 Subject: [PATCH 23/31] made a nice PTT/RECV screen for audio module. And cleaned up screen graphics a bit. --- src/graphics/Screen.cpp | 7 +++- src/graphics/images.h | 30 --------------- src/graphics/img/compass.xbm | 28 -------------- src/graphics/img/pin.xbm | 6 --- src/modules/esp32/AudioModule.cpp | 62 +++++++++++++++++++++++++++++-- src/modules/esp32/AudioModule.h | 15 +++++++- 6 files changed, 78 insertions(+), 70 deletions(-) delete mode 100644 src/graphics/img/compass.xbm delete mode 100644 src/graphics/img/pin.xbm diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 6f4c1fb58..f1c3fdccc 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -1419,17 +1419,22 @@ void DebugInfo::drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, i display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); display->setColor(BLACK); - display->setColor(WHITE); if (WiFi.status() != WL_CONNECTED) { display->drawString(x, y, String("WiFi: Not Connected")); + display->drawString(x + 1, y, String("WiFi: Not Connected")); } else { display->drawString(x, y, String("WiFi: Connected")); + display->drawString(x + 1, y, String("WiFi: Connected")); display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y, "RSSI " + String(WiFi.RSSI())); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y, + "RSSI " + String(WiFi.RSSI())); } + display->setColor(WHITE); + /* - WL_CONNECTED: assigned when connected to a WiFi network; - WL_NO_SSID_AVAIL: assigned when no SSID are available; diff --git a/src/graphics/images.h b/src/graphics/images.h index 41b390eaf..4680b9475 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -15,33 +15,3 @@ const uint8_t imgPositionSolid[] PROGMEM = { 0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF const uint8_t imgInfo[] PROGMEM = { 0xFF, 0x81, 0x81, 0xB5, 0xB5, 0x81, 0x81, 0xFF }; #include "img/icon.xbm" - -// We now programmatically draw our compass -#if 0 -const -#include "img/compass.xbm" -#endif - -#if 0 -const uint8_t activeSymbol[] PROGMEM = { - B00000000, - B00000000, - B00011000, - B00100100, - B01000010, - B01000010, - B00100100, - B00011000 -}; - -const uint8_t inactiveSymbol[] PROGMEM = { - B00000000, - B00000000, - B00000000, - B00000000, - B00011000, - B00011000, - B00000000, - B00000000 -}; -#endif \ No newline at end of file diff --git a/src/graphics/img/compass.xbm b/src/graphics/img/compass.xbm deleted file mode 100644 index 115a7a1d4..000000000 --- a/src/graphics/img/compass.xbm +++ /dev/null @@ -1,28 +0,0 @@ -#define compass_width 48 -#define compass_height 48 -static char compass_bits[] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, - 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, - 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0x01, 0x00, - 0x00, 0xC0, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, - 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0xFC, 0x07, 0xE0, 0x3F, 0x00, - 0x00, 0xFE, 0x01, 0x80, 0x7F, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x7F, 0x00, - 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x00, 0x80, 0x3F, 0x00, 0x00, 0xFC, 0x01, - 0x80, 0x1F, 0x00, 0x00, 0xF8, 0x01, 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x01, - 0xC0, 0x0F, 0x00, 0x00, 0xF0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, - 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, - 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, - 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, 0xFC, 0x07, 0x00, 0x00, 0xE0, 0x3F, - 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, - 0xC0, 0x07, 0x00, 0x00, 0xE0, 0x03, 0xC0, 0x0F, 0x00, 0x00, 0xF0, 0x03, - 0x80, 0x0F, 0x00, 0x00, 0xF0, 0x01, 0x80, 0x1F, 0x00, 0x00, 0xF8, 0x01, - 0x80, 0x3F, 0x00, 0x00, 0xFC, 0x01, 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x00, - 0x00, 0xFE, 0x00, 0x00, 0x7F, 0x00, 0x00, 0xFE, 0x01, 0x80, 0x7F, 0x00, - 0x00, 0xFC, 0x07, 0xE0, 0x3F, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0x1F, 0x00, - 0x00, 0xF0, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0xC0, 0xFF, 0xFF, 0x03, 0x00, - 0x00, 0x80, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0xFC, 0x3F, 0x00, 0x00, - 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, - 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; diff --git a/src/graphics/img/pin.xbm b/src/graphics/img/pin.xbm deleted file mode 100644 index e8c8f8930..000000000 --- a/src/graphics/img/pin.xbm +++ /dev/null @@ -1,6 +0,0 @@ -#define pin_width 13 -#define pin_height 13 -static char pin_bits[] = { - 0x00, 0x00, 0xF0, 0x01, 0xF8, 0x03, 0xFC, 0x07, 0xBC, 0x07, 0xBC, 0x07, - 0xFC, 0x07, 0xF8, 0x03, 0xF8, 0x03, 0xF0, 0x01, 0xE0, 0x00, 0xE0, 0x00, - 0x00, 0x00, }; diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index f29c11056..75e329ce5 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -41,9 +41,26 @@ AudioModule *audioModule; #define YIELD_FROM_ISR(x) portYIELD_FROM_ISR(x) #endif -//int16_t 1KHz sine test tone -int16_t Sine1KHz[8] = { -21210 , -30000, -21210, 0 , 21210 , 30000 , 21210, 0 }; -int Sine1KHz_index = 0; +#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) +// The screen is bigger so use bigger fonts +#define FONT_SMALL ArialMT_Plain_16 +#define FONT_MEDIUM ArialMT_Plain_24 +#define FONT_LARGE ArialMT_Plain_24 +#else +#ifdef OLED_RU +#define FONT_SMALL ArialMT_Plain_10_RU +#else +#define FONT_SMALL ArialMT_Plain_10 +#endif +#define FONT_MEDIUM ArialMT_Plain_16 +#define FONT_LARGE ArialMT_Plain_24 +#endif + +#define fontHeight(font) ((font)[1] + 1) // height is position 1 + +#define FONT_HEIGHT_SMALL fontHeight(FONT_SMALL) +#define FONT_HEIGHT_MEDIUM fontHeight(FONT_MEDIUM) +#define FONT_HEIGHT_LARGE fontHeight(FONT_LARGE) void run_codec2(void* parameter) { @@ -124,6 +141,30 @@ AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), } } +void AudioModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) +{ + displayedNodeNum = 0; // Not currently showing a node pane + + char buffer[50]; + + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FONT_SMALL); + display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); + display->setColor(BLACK); + display->drawStringf(0 + x, 0 + y, buffer, "Codec2 Mode %d Audio", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); + display->setColor(WHITE); + display->setFont(FONT_LARGE); + display->setTextAlignment(TEXT_ALIGN_CENTER); + switch (radio_state) { + case RadioState::tx: + display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "PTT"); + break; + default: + display->drawString(display->getWidth() / 2 + x, (display->getHeight() - FONT_HEIGHT_SMALL) / 2 + y, "Receive"); + break; + } +} + int32_t AudioModule::runOnce() { if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { @@ -170,11 +211,14 @@ int32_t AudioModule::runOnce() firstTime = false; } else { + UIFrameEvent e = {false, true}; // Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive. if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) { if (radio_state == RadioState::rx) { DEBUG_MSG("♪♫♪ PTT pressed, switching to TX\n"); radio_state = RadioState::tx; + e.frameChanged = true; + this->notifyObservers(&e); } } else { if (radio_state == RadioState::tx) { @@ -183,9 +227,11 @@ int32_t AudioModule::runOnce() DEBUG_MSG("♪♫♪ Sending %d codec2 bytes (incomplete)\n", tx_encode_frame_index); sendPayload(); } - DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); + DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); tx_encode_frame_index = sizeof(tx_header); radio_state = RadioState::rx; + e.frameChanged = true; + this->notifyObservers(&e); } } if (radio_state == RadioState::tx) { @@ -222,6 +268,14 @@ MeshPacket *AudioModule::allocReply() return reply; } +bool AudioModule::shouldDraw() +{ + if (!moduleConfig.audio.codec2_enabled) { + return false; + } + return (radio_state == RadioState::tx); +} + void AudioModule::sendPayload(NodeNum dest, bool wantReplies) { MeshPacket *p = allocReply(); diff --git a/src/modules/esp32/AudioModule.h b/src/modules/esp32/AudioModule.h index 8e813ad1e..1a41ab6c4 100644 --- a/src/modules/esp32/AudioModule.h +++ b/src/modules/esp32/AudioModule.h @@ -10,6 +10,8 @@ #include #include #include +#include +#include enum RadioState { standby, rx, tx }; @@ -28,7 +30,7 @@ struct c2_header { #define AUDIO_MODULE_RX_BUFFER 128 #define AUDIO_MODULE_MODE ModuleConfig_AudioConfig_Audio_Baud_CODEC2_700 -class AudioModule : public SinglePortModule, private concurrency::OSThread +class AudioModule : public SinglePortModule, public Observable, private concurrency::OSThread { public: unsigned char rx_encode_frame[Constants_DATA_PAYLOAD_LEN] = {}; @@ -50,6 +52,8 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread AudioModule(); + bool shouldDraw(); + /** * Send our payload into the mesh */ @@ -63,6 +67,15 @@ class AudioModule : public SinglePortModule, private concurrency::OSThread virtual MeshPacket *allocReply() override; + virtual bool wantUIFrame() override { return this->shouldDraw(); } + virtual Observable* getUIFrameObservable() override { return this; } +#if !HAS_SCREEN + void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y); +#else + virtual void drawFrame( + OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) override; +#endif + /** Called to handle a particular incoming message * @return ProcessMessage::STOP if you've guaranteed you've handled this message and no other handlers should be considered for it */ From 055146602abd8531be5ac352cec487caff814902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 14 Dec 2022 13:32:26 +0100 Subject: [PATCH 24/31] support ESP32-S2 CPUs Note: these don't have Bluetooth and only a single physical core. --- arch/esp32/esp32s2.ini | 47 +++++++++++++++++++++++++++++++ src/modules/SerialModule.cpp | 4 +-- src/platform/esp32/main-esp32.cpp | 17 +++++++---- src/sleep.cpp | 2 ++ 4 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 arch/esp32/esp32s2.ini diff --git a/arch/esp32/esp32s2.ini b/arch/esp32/esp32s2.ini new file mode 100644 index 000000000..ca4f576d6 --- /dev/null +++ b/arch/esp32/esp32s2.ini @@ -0,0 +1,47 @@ +[esp32s2_base] +extends = arduino_base +platform = platformio/espressif32@^5.2.0 +build_src_filter = + ${arduino_base.build_src_filter} - - - - - +upload_speed = 961200 +monitor_speed = 115200 +debug_init_break = tbreak setup +monitor_filters = esp32_exception_decoder +board_build.filesystem = littlefs + +# Remove -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL for low level BLE logging. +# See library directory for BLE logging possible values: .pio/libdeps/tbeam/NimBLE-Arduino/src/log_common/log_common.h +# This overrides the BLE logging default of LOG_LEVEL_INFO (1) from: .pio/libdeps/tbeam/NimBLE-Arduino/src/esp_nimble_cfg.h +build_flags = + ${arduino_base.build_flags} + -Wall + -Wextra + -Isrc/platform/esp32 + -std=c++11 + -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG + -DCORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_DEBUG + -DMYNEWT_VAL_BLE_HS_LOG_LVL=LOG_LEVEL_CRITICAL + -DAXP_DEBUG_PORT=Serial + -DCONFIG_BT_NIMBLE_ENABLED + -DCONFIG_NIMBLE_CPP_LOG_LEVEL=2 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=20 + -DESP_OPENSSL_SUPPRESS_LEGACY_WARNING + -DHAS_BLUETOOTH=0 + -DDEBUG_HEAP + +lib_deps = + ${arduino_base.lib_deps} + ${networking_base.lib_deps} + ${environmental_base.lib_deps} + https://github.com/meshtastic/esp32_https_server.git#23665b3adc080a311dcbb586ed5941b5f94d6ea2 + https://github.com/lewisxhe/XPowersLib.git#84b7373faea3118b6c37954d52f98b8a337148d6 + caveman99/ESP32 Codec2@^1.0.1 + +lib_ignore = + segger_rtt + ESP32 BLE Arduino + +; customize the partition table +; http://docs.platformio.org/en/latest/platforms/espressif32.html#partition-tables +board_build.partitions = partition-table.csv + diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 116690d74..c25f69fa4 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -82,7 +82,7 @@ SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio") int32_t SerialModule::runOnce() { -#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2) /* Uncomment the preferences below if you want to use the module without having to configure it from the PythonAPI or WebUI. @@ -239,7 +239,7 @@ void SerialModuleRadio::sendPayload(NodeNum dest, bool wantReplies) ProcessMessage SerialModuleRadio::handleReceived(const MeshPacket &mp) { -#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52)) && !defined(TTGO_T_ECHO) && !defined(CONFIG_IDF_TARGET_ESP32S2) if (moduleConfig.serial.enabled) { auto &p = mp.decoded; diff --git a/src/platform/esp32/main-esp32.cpp b/src/platform/esp32/main-esp32.cpp index 21df018d8..e36a8cfd8 100644 --- a/src/platform/esp32/main-esp32.cpp +++ b/src/platform/esp32/main-esp32.cpp @@ -3,7 +3,9 @@ #include "esp_task_wdt.h" #include "main.h" +#if !defined(CONFIG_IDF_TARGET_ESP32S2) #include "nimble/NimbleBluetooth.h" +#endif #include "BleOta.h" #include "mesh/http/WiFiAPClient.h" @@ -16,13 +18,9 @@ #include #include "soc/rtc.h" +#if !defined(CONFIG_IDF_TARGET_ESP32S2) NimbleBluetooth *nimbleBluetooth; -void getMacAddr(uint8_t *dmac) -{ - assert(esp_efuse_mac_get_default(dmac) == ESP_OK); -} - void setBluetoothEnable(bool on) { if (!isWifiAvailable() && config.bluetooth.enabled == true) { @@ -36,6 +34,15 @@ void setBluetoothEnable(bool on) { } } } +#else +void setBluetoothEnable(bool on) { } +void updateBatteryLevel(uint8_t level) { } +#endif + +void getMacAddr(uint8_t *dmac) +{ + assert(esp_efuse_mac_get_default(dmac) == ESP_OK); +} #ifdef HAS_32768HZ #define CALIBRATE_ONE(cali_clk) calibrate_one(cali_clk, #cali_clk) diff --git a/src/sleep.cpp b/src/sleep.cpp index 39ee43ebc..6625da2a3 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -300,6 +300,8 @@ void enableModemSleep() #if CONFIG_IDF_TARGET_ESP32S3 esp32_config.max_freq_mhz = CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ; +#elif CONFIG_IDF_TARGET_ESP32S2 + esp32_config.max_freq_mhz = CONFIG_ESP32S2_DEFAULT_CPU_FREQ_MHZ; #else esp32_config.max_freq_mhz = CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ; #endif From 9794995d7ad645ee52ca30781dc0e9c0f0945f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 14 Dec 2022 14:33:59 +0100 Subject: [PATCH 25/31] fix building DIY-1 target --- src/modules/esp32/AudioModule.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 75e329ce5..17e5cf4fa 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -10,6 +10,10 @@ #include +#ifdef OLED_RU +#include "graphics/fonts/OLEDDisplayFontsRU.h" +#endif + /* AudioModule A interface to send raw codec2 audio data over the mesh network. Based on the example code from the ESP32_codec2 project. From 4eb620d47b51460bcfd2e371afbd8957573a9842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 16 Dec 2022 20:25:51 +0100 Subject: [PATCH 26/31] Heap Debug: only show if delta occurs --- src/Power.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Power.cpp b/src/Power.cpp index be44c0d49..d5fe312e9 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -284,7 +284,10 @@ void Power::readPowerStatus() powerStatus2.getIsCharging(), powerStatus2.getBatteryVoltageMv(), powerStatus2.getBatteryChargePercent()); newStatus.notifyObservers(&powerStatus2); #ifdef DEBUG_HEAP - DEBUG_MSG("Heap status: %d/%d bytes free, running %d threads\n", ESP.getFreeHeap(), ESP.getHeapSize(), concurrency::mainController.size(false)); + if (lastheap != ESP.getFreeHeap()){ + DEBUG_MSG("Heap status: %d/%d bytes free (%d), running %d threads\n", ESP.getFreeHeap(), ESP.getHeapSize(), ESP.getFreeHeap() - lastheap , concurrency::mainController.size(false)); + lastheap = ESP.getFreeHeap(); + } #endif // If we have a battery at all and it is less than 10% full, force deep sleep if we have more than 3 low readings in a row From 941786669ba0c8ade6b91e031c4bd0a872984e8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Fri, 16 Dec 2022 20:26:22 +0100 Subject: [PATCH 27/31] fix compiler warnings --- src/mesh/NodeDB.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 6b2d5bfb3..2faff2aae 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -214,9 +214,9 @@ void NodeDB::installDefaultModuleConfig() moduleConfig.has_external_notification = true; moduleConfig.has_canned_message = true; - strncpy(moduleConfig.mqtt.address, default_mqtt_address, sizeof(default_mqtt_address)); - strncpy(moduleConfig.mqtt.username, default_mqtt_username, sizeof(default_mqtt_username)); - strncpy(moduleConfig.mqtt.password, default_mqtt_password, sizeof(default_mqtt_password)); + strncpy(moduleConfig.mqtt.address, default_mqtt_address, sizeof(moduleConfig.mqtt.address)); + strncpy(moduleConfig.mqtt.username, default_mqtt_username, sizeof(moduleConfig.mqtt.username)); + strncpy(moduleConfig.mqtt.password, default_mqtt_password, sizeof(moduleConfig.mqtt.password)); initModuleConfigIntervals(); } From 220859d0aaa7ae2efe27d5e560d8b8a6e5667b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Sat, 17 Dec 2022 23:32:20 +0100 Subject: [PATCH 28/31] Merge pull request #2019 from code8buster/gps-toggle-final Adds a flag to turn the GPS power rail off entirely on tbeam --- src/ButtonThread.h | 17 +++++++++++++++-- src/GPSStatus.h | 9 +++++++-- src/gps/GPS.cpp | 24 ++++++++++++++++++++---- src/gps/GPS.h | 3 +++ src/graphics/Screen.cpp | 33 ++++++++++++++++++++++++++++++--- src/sleep.cpp | 32 ++++++++++++++++++++++++++++++++ src/sleep.h | 4 +++- variants/tbeam/platformio.ini | 1 + 8 files changed, 111 insertions(+), 12 deletions(-) diff --git a/src/ButtonThread.h b/src/ButtonThread.h index 088642099..0e9830f3f 100644 --- a/src/ButtonThread.h +++ b/src/ButtonThread.h @@ -51,6 +51,7 @@ class ButtonThread : public concurrency::OSThread pinMode(BUTTON_PIN, INPUT_PULLUP_SENSE); #endif userButton.attachClick(userButtonPressed); + userButton.setClickTicks(300); userButton.attachDuringLongPress(userButtonPressedLong); userButton.attachDoubleClick(userButtonDoublePressed); userButton.attachMultiClick(userButtonMultiPressed); @@ -159,9 +160,21 @@ class ButtonThread : public concurrency::OSThread static void userButtonDoublePressed() { -#if defined(USE_EINK) && defined(PIN_EINK_EN) + #if defined(USE_EINK) && defined(PIN_EINK_EN) digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW); -#endif + #endif + #if defined(GPS_POWER_TOGGLE) + if(config.position.gps_enabled) + { + DEBUG_MSG("Flag set to false for gps power\n"); + } + else + { + DEBUG_MSG("Flag set to true to restore power\n"); + } + config.position.gps_enabled = !(config.position.gps_enabled); + doGPSpowersave(config.position.gps_enabled); + #endif } static void userButtonMultiPressed() diff --git a/src/GPSStatus.h b/src/GPSStatus.h index 35a0b11f2..ef97c59b7 100644 --- a/src/GPSStatus.h +++ b/src/GPSStatus.h @@ -20,16 +20,19 @@ class GPSStatus : public Status bool hasLock = false; // default to false, until we complete our first read bool isConnected = false; // Do we have a GPS we are talking to + bool isPowerSaving = false; //Are we in power saving state + Position p = Position_init_default; public: GPSStatus() { statusType = STATUS_TYPE_GPS; } // preferred method - GPSStatus(bool hasLock, bool isConnected, const Position &pos) : Status() + GPSStatus(bool hasLock, bool isConnected, bool isPowerSaving, const Position &pos) : Status() { this->hasLock = hasLock; this->isConnected = isConnected; + this->isPowerSaving = isPowerSaving; // all-in-one struct copy this->p = pos; @@ -44,6 +47,8 @@ class GPSStatus : public Status bool getIsConnected() const { return isConnected; } + bool getIsPowerSaving() const { return isPowerSaving;} + int32_t getLatitude() const { if (config.position.fixed_position) { @@ -94,7 +99,7 @@ class GPSStatus : public Status #ifdef GPS_EXTRAVERBOSE DEBUG_MSG("GPSStatus.match() new pos@%x to old pos@%x\n", newStatus->p.pos_timestamp, p.pos_timestamp); #endif - return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected || + return (newStatus->hasLock != hasLock || newStatus->isConnected != isConnected || newStatus->isPowerSaving !=isPowerSaving || newStatus->p.latitude_i != p.latitude_i || newStatus->p.longitude_i != p.longitude_i || newStatus->p.altitude != p.altitude || newStatus->p.altitude_hae != p.altitude_hae || newStatus->p.PDOP != p.PDOP || newStatus->p.ground_track != p.ground_track || diff --git a/src/gps/GPS.cpp b/src/gps/GPS.cpp index 54dc59cbb..7e860cdd0 100644 --- a/src/gps/GPS.cpp +++ b/src/gps/GPS.cpp @@ -270,21 +270,30 @@ bool GPS::setup() pinMode(PIN_GPS_EN, OUTPUT); #endif +#ifdef HAS_PMU +if(config.position.gps_enabled){ + setGPSPower(true); +} +#endif + #ifdef PIN_GPS_RESET digitalWrite(PIN_GPS_RESET, 1); // assert for 10ms pinMode(PIN_GPS_RESET, OUTPUT); delay(10); digitalWrite(PIN_GPS_RESET, 0); #endif - setAwake(true); // Wake GPS power before doing any init bool ok = setupGPS(); if (ok) { notifySleepObserver.observe(¬ifySleep); notifyDeepSleepObserver.observe(¬ifyDeepSleep); + notifyGPSSleepObserver.observe(¬ifyGPSSleep); + } + if (config.position.gps_enabled==false) { + setAwake(false); + doGPSpowersave(false); } - return ok; } @@ -293,6 +302,7 @@ GPS::~GPS() // we really should unregister our sleep observer notifySleepObserver.unobserve(¬ifySleep); notifyDeepSleepObserver.unobserve(¬ifyDeepSleep); + notifyGPSSleepObserver.observe(¬ifyGPSSleep); } bool GPS::hasLock() @@ -405,7 +415,7 @@ void GPS::publishUpdate() DEBUG_MSG("publishing pos@%x:2, hasVal=%d, GPSlock=%d\n", p.timestamp, hasValidLocation, hasLock()); // Notify any status instances that are observing us - const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasValidLocation, isConnected(), p); + const meshtastic::GPSStatus status = meshtastic::GPSStatus(hasValidLocation, isConnected(), isPowerSaving(), p); newStatus.notifyObservers(&status); } } @@ -416,7 +426,7 @@ int32_t GPS::runOnce() // if we have received valid NMEA claim we are connected setConnected(); } else { - if(gnssModel == GNSS_MODEL_UBLOX){ + if((config.position.gps_enabled == 1) && (gnssModel == GNSS_MODEL_UBLOX)){ // reset the GPS on next bootup if(devicestate.did_gps_reset && (millis() > 60000) && !hasFlow()) { DEBUG_MSG("GPS is not communicating, trying factory reset on next bootup.\n"); @@ -518,6 +528,7 @@ int GPS::prepareDeepSleep(void *unused) DEBUG_MSG("GPS deep sleep!\n"); // For deep sleep we also want abandon any lock attempts (because we want minimum power) + getSleepTime(); setAwake(false); return 0; @@ -653,6 +664,11 @@ GPS *createGps() return new_gps; } } + else{ + GPS *new_gps = new NMEAGPS(); + new_gps->setup(); + return new_gps; + } return nullptr; #endif } diff --git a/src/gps/GPS.h b/src/gps/GPS.h index 8f04b6793..5d24268d7 100644 --- a/src/gps/GPS.h +++ b/src/gps/GPS.h @@ -49,6 +49,7 @@ class GPS : private concurrency::OSThread CallbackObserver notifySleepObserver = CallbackObserver(this, &GPS::prepareSleep); CallbackObserver notifyDeepSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); + CallbackObserver notifyGPSSleepObserver = CallbackObserver(this, &GPS::prepareDeepSleep); public: /** If !NULL we will use this serial port to construct our GPS */ @@ -77,6 +78,8 @@ class GPS : private concurrency::OSThread /// Return true if we are connected to a GPS bool isConnected() const { return hasGPS; } + bool isPowerSaving() const { return !config.position.gps_enabled;} + /** * Restart our lock attempt - try to get and broadcast a GPS reading ASAP * called after the CPU wakes from light-sleep state diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index f1c3fdccc..3ea4463ea 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -512,6 +512,22 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus } } +//Draw status when gps is disabled by PMU +static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps){ +String displayLine = ""; +displayLine = "GPS disabled"; +int16_t xPos = display->getStringWidth(displayLine); + #ifdef HAS_PMU + if (!config.position.gps_enabled){ + display->drawString(x + xPos, y, displayLine); + #ifdef GPS_POWER_TOGGLE + display->drawString(x + xPos, y - 2 + FONT_HEIGHT_SMALL, " by button"); + #endif + //display->drawString(x + xPos, y + 2, displayLine); + } + #endif +} + static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) { String displayLine = ""; @@ -1384,7 +1400,16 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 // Display nodes status drawNodes(display, x + (SCREEN_WIDTH * 0.25), y + 3, nodeStatus); // Display GPS status - drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus); + if (!config.position.gps_enabled){ + int16_t yPos = y + 2; +#ifdef GPS_POWER_TOGGLE + yPos = (y + 10 + FONT_HEIGHT_SMALL); +#endif + drawGPSpowerstat(display, x, yPos, gpsStatus); + } else { + drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 2, gpsStatus); + drawGPS(display, x + (SCREEN_WIDTH * 0.63), y + 3, gpsStatus); + } display->setColor(WHITE); // Draw the channel name @@ -1643,7 +1668,7 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat char chUtil[13]; sprintf(chUtil, "ChUtil %2.0f%%", airTime->channelUtilizationPercent()); display->drawString(x + SCREEN_WIDTH - display->getStringWidth(chUtil), y + FONT_HEIGHT_SMALL * 1, chUtil); - +if (config.position.gps_enabled) { // Line 3 if (config.display.gps_format != Config_DisplayConfig_GpsCoordinateFormat_DMS) // if DMS then don't draw altitude @@ -1651,7 +1676,9 @@ void DebugInfo::drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *stat // Line 4 drawGPScoordinates(display, x, y + FONT_HEIGHT_SMALL * 3, gpsStatus); - +} else { + drawGPSpowerstat(display, x - (SCREEN_WIDTH / 4), y + FONT_HEIGHT_SMALL * 2, gpsStatus); +} /* Display a heartbeat pixel that blinks every time the frame is redrawn */ #ifdef SHOW_REDRAWS if (heartbeat) diff --git a/src/sleep.cpp b/src/sleep.cpp index 6625da2a3..40426a777 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -29,7 +29,9 @@ Observable preflightSleep; /// Called to tell observers we are now entering sleep and you should prepare. Must return 0 /// notifySleep will be called for light or deep sleep, notifyDeepSleep is only called for deep sleep +/// notifyGPSSleep will be called when config.position.gps_enabled is set to 0 or from buttonthread when GPS_POWER_TOGGLE is enabled. Observable notifySleep, notifyDeepSleep; +Observable notifyGPSSleep; // deep sleep support RTC_DATA_ATTR int bootCount = 0; @@ -167,6 +169,36 @@ static void waitEnterSleep() notifySleep.notifyObservers(NULL); } +void doGPSpowersave(bool on) +{ + #ifdef HAS_PMU + if (on) + { + DEBUG_MSG("Turning GPS back on\n"); + gps->forceWake(1); + setGPSPower(1); + } + else + { + DEBUG_MSG("Turning off GPS chip\n"); + notifyGPSSleep.notifyObservers(NULL); + setGPSPower(0); + } + #endif + #ifdef PIN_GPS_WAKE + if (on) + { + DEBUG_MSG("Waking GPS"); + gps->forceWake(1); + } + else + { + DEBUG_MSG("GPS entering sleep"); + notifyGPSSleep.notifyObservers(NULL); + } + #endif +} + void doDeepSleep(uint64_t msecToWake) { DEBUG_MSG("Entering deep sleep for %lu seconds\n", msecToWake / 1000); diff --git a/src/sleep.h b/src/sleep.h index 7f0592af4..af59a8dad 100644 --- a/src/sleep.h +++ b/src/sleep.h @@ -13,7 +13,7 @@ esp_sleep_wakeup_cause_t doLightSleep(uint64_t msecToWake); extern esp_sleep_source_t wakeCause; #endif void setGPSPower(bool on); - +void doGPSpowersave(bool on); // Perform power on init that we do on each wake from deep sleep void initDeepSleep(); @@ -37,4 +37,6 @@ extern Observable notifySleep; /// Called to tell observers we are now entering (deep) sleep and you should prepare. Must return 0 extern Observable notifyDeepSleep; +/// Called to tell GPS thread to enter deep sleep independently of LoRa/MCU sleep, prior to full poweroff. Must return 0 +extern Observable notifyGPSSleep; void enableModemSleep(); \ No newline at end of file diff --git a/variants/tbeam/platformio.ini b/variants/tbeam/platformio.ini index 578a74f7b..76a03d126 100644 --- a/variants/tbeam/platformio.ini +++ b/variants/tbeam/platformio.ini @@ -6,4 +6,5 @@ lib_deps = ${esp32_base.lib_deps} build_flags = ${esp32_base.build_flags} -D TBEAM_V10 -I variants/tbeam + -DGPS_POWER_TOGGLE ; comment this line to disable double press function on the user button to turn off gps entirely. upload_speed = 921600 From 7396d0f2419b916b65dbc9664dc402ae53ec5590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 21 Dec 2022 13:36:38 +0100 Subject: [PATCH 29/31] Cherry Picking Stuff from develop... --- bin/regen-protos.bat | 2 +- bin/regen-protos.sh | 6 +- src/Power.cpp | 3 + src/airtime.cpp | 14 +++ src/airtime.h | 2 + src/graphics/Screen.cpp | 89 +++++++++--------- src/graphics/images.h | 14 ++- src/mesh/NodeDB.cpp | 1 + src/mesh/Router.cpp | 13 +++ src/mesh/http/WiFiAPClient.cpp | 134 +++++++++++++++++----------- src/mesh/http/WiFiAPClient.h | 4 + src/modules/AdminModule.cpp | 5 +- src/modules/CannedMessageModule.cpp | 4 + src/modules/esp32/AudioModule.cpp | 48 +++++----- src/power.h | 3 + 15 files changed, 220 insertions(+), 122 deletions(-) diff --git a/bin/regen-protos.bat b/bin/regen-protos.bat index 12ee7d7ab..5c576c5b2 100644 --- a/bin/regen-protos.bat +++ b/bin/regen-protos.bat @@ -1 +1 @@ -cd protobufs && ..\nanopb-0.4.6\generator-bin\protoc.exe --nanopb_out=-v:..\src\mesh\generated -I=..\protobufs *.proto +cd protobufs && ..\nanopb-0.4.7\generator-bin\protoc.exe --nanopb_out=-v:..\src\mesh\generated -I=..\protobufs *.proto diff --git a/bin/regen-protos.sh b/bin/regen-protos.sh index 2734c213b..133d587a3 100755 --- a/bin/regen-protos.sh +++ b/bin/regen-protos.sh @@ -2,13 +2,13 @@ set -e -echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.6 to be located in the" +echo "This script requires https://jpa.kapsi.fi/nanopb/download/ version 0.4.7 to be located in the" echo "firmware root directory if the following step fails, you should download the correct" -echo "prebuilt binaries for your computer into nanopb-0.4.6" +echo "prebuilt binaries for your computer into nanopb-0.4.7" # the nanopb tool seems to require that the .options file be in the current directory! cd protobufs -../nanopb-0.4.6/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated -I=../protobufs *.proto +../nanopb-0.4.7/generator-bin/protoc --nanopb_out=-v:../src/mesh/generated -I=../protobufs *.proto #echo "Regenerating protobuf documentation - if you see an error message" #echo "you can ignore it unless doing a new protobuf release to github." diff --git a/src/Power.cpp b/src/Power.cpp index d5fe312e9..d2c5b01a0 100644 --- a/src/Power.cpp +++ b/src/Power.cpp @@ -182,6 +182,9 @@ Power::Power() : OSThread("Power") { statusHandler = {}; low_voltage_counter = 0; +#ifdef DEBUG_HEAP + lastheap = ESP.getFreeHeap(); +#endif } bool Power::analogInit() diff --git a/src/airtime.cpp b/src/airtime.cpp index fed4ef8aa..1c2fb3233 100644 --- a/src/airtime.cpp +++ b/src/airtime.cpp @@ -117,6 +117,20 @@ float AirTime::utilizationTXPercent() return (float(sum) / float(MS_IN_HOUR)) * 100; } +// Get the amount of minutes we have to be silent before we can send again +uint8_t AirTime::getSilentMinutes(float txPercent, float dutyCycle) +{ + float newTxPercent = txPercent; + for (int8_t i = MINUTES_IN_HOUR-1; i >= 0; --i) { + newTxPercent -= ((float)this->utilizationTX[i] / (MS_IN_MINUTE * MINUTES_IN_HOUR / 100)); + if (newTxPercent < dutyCycle) + return MINUTES_IN_HOUR-1-i; + } + + return MINUTES_IN_HOUR; +} + + AirTime::AirTime() : concurrency::OSThread("AirTime"),airtimes({}) { } diff --git a/src/airtime.h b/src/airtime.h index f6b9bdcb5..3f38f39f8 100644 --- a/src/airtime.h +++ b/src/airtime.h @@ -29,6 +29,7 @@ #define PERIODS_TO_LOG 8 #define MINUTES_IN_HOUR 60 #define SECONDS_IN_MINUTE 60 +#define MS_IN_MINUTE (SECONDS_IN_MINUTE * 1000) #define MS_IN_HOUR (MINUTES_IN_HOUR * SECONDS_IN_MINUTE * 1000) @@ -57,6 +58,7 @@ class AirTime : private concurrency::OSThread uint32_t getSecondsPerPeriod(); uint32_t getSecondsSinceBoot(); uint32_t *airtimeReport(reportTypes reportType); + uint8_t getSilentMinutes(float txPercent, float dutyCycle); private: bool firstTime = true; diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 3ea4463ea..30fc396a9 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -35,6 +35,7 @@ along with this program. If not, see . #include "mesh/Channels.h" #include "mesh/generated/deviceonly.pb.h" #include "modules/TextMessageModule.h" +#include "modules/esp32/StoreForwardModule.h" #include "sleep.h" #include "target_specific.h" #include "utils.h" @@ -95,17 +96,17 @@ static uint16_t displayWidth, displayHeight; #if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) // The screen is bigger so use bigger fonts -#define FONT_SMALL ArialMT_Plain_16 -#define FONT_MEDIUM ArialMT_Plain_24 -#define FONT_LARGE ArialMT_Plain_24 +#define FONT_SMALL ArialMT_Plain_16 // Height: 19 +#define FONT_MEDIUM ArialMT_Plain_24 // Height: 28 +#define FONT_LARGE ArialMT_Plain_24 // Height: 28 #else #ifdef OLED_RU #define FONT_SMALL ArialMT_Plain_10_RU #else -#define FONT_SMALL ArialMT_Plain_10 +#define FONT_SMALL ArialMT_Plain_10 // Height: 13 #endif -#define FONT_MEDIUM ArialMT_Plain_16 -#define FONT_LARGE ArialMT_Plain_24 +#define FONT_MEDIUM ArialMT_Plain_16 // Height: 19 +#define FONT_LARGE ArialMT_Plain_24 // Height: 28 #endif #define fontHeight(font) ((font)[1] + 1) // height is position 1 @@ -465,7 +466,11 @@ static void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, NodeStatus *no { char usersString[20]; sprintf(usersString, "%d/%d", nodeStatus->getNumOnline(), nodeStatus->getNumTotal()); +#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) + display->drawFastImage(x, y + 3, 8, 8, imgUser); +#else display->drawFastImage(x, y, 8, 8, imgUser); +#endif display->drawString(x + 10, y - 2, usersString); display->drawString(x + 11, y - 2, usersString); } @@ -513,19 +518,20 @@ static void drawGPS(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus } //Draw status when gps is disabled by PMU -static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps){ -String displayLine = ""; -displayLine = "GPS disabled"; -int16_t xPos = display->getStringWidth(displayLine); - #ifdef HAS_PMU +static void drawGPSpowerstat(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) +{ +#ifdef HAS_PMU + String displayLine = "GPS disabled"; + int16_t xPos = display->getStringWidth(displayLine); + if (!config.position.gps_enabled){ - display->drawString(x + xPos, y, displayLine); - #ifdef GPS_POWER_TOGGLE - display->drawString(x + xPos, y - 2 + FONT_HEIGHT_SMALL, " by button"); - #endif + display->drawString(x + xPos, y, displayLine); +#ifdef GPS_POWER_TOGGLE + display->drawString(x + xPos, y - 2 + FONT_HEIGHT_SMALL, " by button"); +#endif //display->drawString(x + xPos, y + 2, displayLine); } - #endif +#endif } static void drawGPSAltitude(OLEDDisplay *display, int16_t x, int16_t y, const GPSStatus *gps) @@ -860,29 +866,6 @@ static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_ drawColumns(display, x, y, fields); } -#if 0 -void _screen_header() -{ - if (!disp) - return; - - // Message count - //snprintf(buffer, sizeof(buffer), "#%03d", ttn_get_count() % 1000); - //display->setTextAlignment(TEXT_ALIGN_LEFT); - //display->drawString(0, 2, buffer); - - // Datetime - display->setTextAlignment(TEXT_ALIGN_CENTER); - display->drawString(display->getWidth()/2, 2, gps.getTimeStr()); - - // Satellite count - display->setTextAlignment(TEXT_ALIGN_RIGHT); - char buffer[10]; - display->drawString(display->getWidth() - SATELLITE_IMAGE_WIDTH - 4, 2, itoa(gps.satellites.value(), buffer, 10)); - display->drawXbm(display->getWidth() - SATELLITE_IMAGE_WIDTH, 0, SATELLITE_IMAGE_WIDTH, SATELLITE_IMAGE_HEIGHT, SATELLITE_IMAGE); -} -#endif - // #ifdef RAK4630 // Screen::Screen(uint8_t address, int sda, int scl) : OSThread("Screen"), cmdQueue(32), dispdev(address, sda, scl), // dispdev_oled(address, sda, scl), ui(&dispdev) @@ -1414,8 +1397,32 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 display->setColor(WHITE); // Draw the channel name display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); - // Draw our hardware ID to assist with bluetooth pairing - display->drawFastImage(x + SCREEN_WIDTH - (10) - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo); + // Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo + if (moduleConfig.store_forward.enabled) { + if (millis() - storeForwardModule->lastHeartbeat > (storeForwardModule->heartbeatInterval * 1200)) { //no heartbeat, overlap a bit +#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL2); +#else + display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgQuestion); +#endif + } else { +#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) + display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 16, 8, imgSFL1); + display->drawFastImage(x + SCREEN_WIDTH - 18 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 16, 8, imgSFL2); +#else + display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 11, 8, imgSF); +#endif + } + } else { +#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1); + display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 11 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL2); +#else + display->drawFastImage(x + SCREEN_WIDTH - 10 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 8, 8, imgInfo); +#endif + } + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(ourId), y + FONT_HEIGHT_SMALL, ourId); // Draw any log messages diff --git a/src/graphics/images.h b/src/graphics/images.h index 4680b9475..9bf66a3a3 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -12,6 +12,18 @@ const uint8_t imgPower[] PROGMEM = { 0x40, 0x40, 0x40, 0x58, 0x48, 0x08, const uint8_t imgUser[] PROGMEM = { 0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3C }; const uint8_t imgPositionEmpty[] PROGMEM = { 0x20, 0x30, 0x28, 0x24, 0x42, 0xFF }; const uint8_t imgPositionSolid[] PROGMEM = { 0x20, 0x30, 0x38, 0x3C, 0x7E, 0xFF }; -const uint8_t imgInfo[] PROGMEM = { 0xFF, 0x81, 0x81, 0xB5, 0xB5, 0x81, 0x81, 0xFF }; + +#if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) +const uint8_t imgQuestionL1[] PROGMEM = { 0xff, 0x01, 0x01, 0x32, 0x7b, 0x49, 0x49, 0x6f, 0x26, 0x01, 0x01, 0xff }; +const uint8_t imgQuestionL2[] PROGMEM = { 0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f }; +const uint8_t imgInfoL1[] PROGMEM = { 0xff, 0x01, 0x01, 0x01, 0x1e, 0x7f, 0x1e, 0x01, 0x01, 0x01, 0x01, 0xff }; +const uint8_t imgInfoL2[] PROGMEM = { 0x0f, 0x08, 0x08, 0x08, 0x06, 0x0f, 0x0f, 0x06, 0x08, 0x08, 0x08, 0x0f }; +const uint8_t imgSFL1[] PROGMEM = { 0xb6, 0x8f, 0x19, 0x11, 0x31, 0xe3, 0xc2, 0x01, 0x01, 0xf9, 0xf9, 0x89, 0x89, 0x89, 0x09, 0xeb}; +const uint8_t imgSFL2[] PROGMEM = { 0x0e, 0x09, 0x09, 0x09, 0x09, 0x09, 0x08, 0x08, 0x00, 0x0f, 0x0f, 0x00, 0x08, 0x08, 0x08, 0x0f}; +#else +const uint8_t imgInfo[] PROGMEM = { 0xff, 0x81, 0x00, 0xfb, 0xfb, 0x00, 0x81, 0xff }; +const uint8_t imgQuestion[] PROGMEM = { 0xbf, 0x41, 0xc0, 0x8b, 0xdb, 0x70, 0xa1, 0xdf }; +const uint8_t imgSF[] PROGMEM = { 0xd2, 0xb7, 0xad, 0xbb, 0x92, 0x01, 0xfd, 0xfd, 0x15, 0x85, 0xf5}; +#endif #include "img/icon.xbm" diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index 2faff2aae..1e40b8d92 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -162,6 +162,7 @@ void NodeDB::installDefaultConfig() config.has_network = true; config.has_bluetooth = true; config.lora.tx_enabled = true; // FIXME: maybe false in the future, and setting region to enable it. (unset region forces it off) + config.lora.override_duty_cycle = false; config.lora.region = Config_LoRaConfig_RegionCode_UNSET; config.lora.modem_preset = Config_LoRaConfig_ModemPreset_LONG_FAST; config.lora.hop_limit = HOP_RELIABLE; diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp index 6b6a03a3a..5ce26f49a 100644 --- a/src/mesh/Router.cpp +++ b/src/mesh/Router.cpp @@ -2,6 +2,7 @@ #include "Channels.h" #include "CryptoEngine.h" #include "NodeDB.h" +#include "MeshRadio.h" #include "RTC.h" #include "configuration.h" #include "main.h" @@ -187,6 +188,18 @@ ErrorCode Router::send(MeshPacket *p) { assert(p->to != nodeDB.getNodeNum()); // should have already been handled by sendLocal + // Abort sending if we are violating the duty cycle + if (!config.lora.override_duty_cycle && myRegion->dutyCycle != 100) { + float hourlyTxPercent = airTime->utilizationTXPercent(); + if (hourlyTxPercent > myRegion->dutyCycle) { + uint8_t silentMinutes = airTime->getSilentMinutes(hourlyTxPercent, myRegion->dutyCycle); + DEBUG_MSG("WARNING: Duty cycle limit exceeded. Aborting send for now, you can send again in %d minutes.\n", silentMinutes); + Routing_Error err = Routing_Error_DUTY_CYCLE_LIMIT; + abortSendAndNak(err, p); + return err; + } + } + // PacketId nakId = p->decoded.which_ackVariant == SubPacket_fail_id_tag ? p->decoded.ackVariant.fail_id : 0; // assert(!nakId); // I don't think we ever send 0hop naks over the wire (other than to the phone), test that assumption with // assert diff --git a/src/mesh/http/WiFiAPClient.cpp b/src/mesh/http/WiFiAPClient.cpp index bb4542468..731197e60 100644 --- a/src/mesh/http/WiFiAPClient.cpp +++ b/src/mesh/http/WiFiAPClient.cpp @@ -1,7 +1,7 @@ -#include "mesh/http/WiFiAPClient.h" #include "NodeDB.h" #include "RTC.h" #include "concurrency/Periodic.h" +#include "mesh/http/WiFiAPClient.h" #include "configuration.h" #include "main.h" #include "mesh/http/WebServer.h" @@ -37,9 +37,9 @@ bool APStartupComplete = 0; unsigned long lastrun_ntp = 0; -static bool needReconnect = true; // If we create our reconnector, run it once at the beginning +bool needReconnect = true; // If we create our reconnector, run it once at the beginning -static Periodic *wifiReconnect; +Periodic *wifiReconnect; static int32_t reconnectWiFi() { @@ -56,29 +56,15 @@ static int32_t reconnectWiFi() // Make sure we clear old connection credentials WiFi.disconnect(false, true); - DEBUG_MSG("... Reconnecting to WiFi access point %s\n",wifiName); - - int n = WiFi.scanNetworks(); - - if (n > 0) { - for (int i = 0; i < n; ++i) { - DEBUG_MSG("Found WiFi network %s, signal strength %d\n", WiFi.SSID(i).c_str(), WiFi.RSSI(i)); - yield(); - } - WiFi.mode(WIFI_MODE_STA); - WiFi.begin(wifiName, wifiPsw); - } else { - DEBUG_MSG("No networks found during site survey. Rebooting MCU...\n"); - screen->startRebootScreen(); - rebootAtMsec = millis() + 5000; - } - + DEBUG_MSG("Reconnecting to WiFi access point %s\n",wifiName); + WiFi.mode(WIFI_MODE_STA); + WiFi.begin(wifiName, wifiPsw); } #ifndef DISABLE_NTP if (WiFi.isConnected() && (((millis() - lastrun_ntp) > 43200000) || (lastrun_ntp == 0))) { // every 12 hours - DEBUG_MSG("Updating NTP time\n"); + DEBUG_MSG("Updating NTP time from %s\n",config.network.ntp_server); if (timeClient.update()) { DEBUG_MSG("NTP Request Success - Setting RTCQualityNTP if needed\n"); @@ -129,7 +115,7 @@ static void onNetworkConnected() { if (!APStartupComplete) { // Start web server - DEBUG_MSG("... Starting network services\n"); + DEBUG_MSG("Starting network services\n"); // start mdns if (!MDNS.begin("Meshtastic")) { @@ -168,6 +154,8 @@ bool initWifi() createSSLCert(); + esp_wifi_set_storage(WIFI_STORAGE_RAM); // Disable flash storage for WiFi credentials + if (!*wifiPsw) // Treat empty password as no password wifiPsw = NULL; @@ -194,7 +182,7 @@ bool initWifi() WiFi.onEvent( [](WiFiEvent_t event, WiFiEventInfo_t info) { - Serial.print("\nWiFi lost connection. Reason: "); + Serial.print("WiFi lost connection. Reason: "); Serial.println(info.wifi_sta_disconnected.reason); /* @@ -221,91 +209,137 @@ bool initWifi() // Called by the Espressif SDK to static void WiFiEvent(WiFiEvent_t event) { - DEBUG_MSG("************ [WiFi-event] event: %d ************\n", event); + DEBUG_MSG("WiFi-Event %d: ", event); switch (event) { - case SYSTEM_EVENT_WIFI_READY: + case ARDUINO_EVENT_WIFI_READY: DEBUG_MSG("WiFi interface ready\n"); break; - case SYSTEM_EVENT_SCAN_DONE: + case ARDUINO_EVENT_WIFI_SCAN_DONE: DEBUG_MSG("Completed scan for access points\n"); break; - case SYSTEM_EVENT_STA_START: + case ARDUINO_EVENT_WIFI_STA_START: DEBUG_MSG("WiFi station started\n"); break; - case SYSTEM_EVENT_STA_STOP: + case ARDUINO_EVENT_WIFI_STA_STOP: DEBUG_MSG("WiFi station stopped\n"); break; - case SYSTEM_EVENT_STA_CONNECTED: + case ARDUINO_EVENT_WIFI_STA_CONNECTED: DEBUG_MSG("Connected to access point\n"); break; - case SYSTEM_EVENT_STA_DISCONNECTED: + case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: DEBUG_MSG("Disconnected from WiFi access point\n"); WiFi.disconnect(false, true); needReconnect = true; wifiReconnect->setIntervalFromNow(1000); break; - case SYSTEM_EVENT_STA_AUTHMODE_CHANGE: + case ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE: DEBUG_MSG("Authentication mode of access point has changed\n"); break; - case SYSTEM_EVENT_STA_GOT_IP: + case ARDUINO_EVENT_WIFI_STA_GOT_IP: DEBUG_MSG("Obtained IP address: "); Serial.println(WiFi.localIP()); onNetworkConnected(); break; - case SYSTEM_EVENT_STA_LOST_IP: + case ARDUINO_EVENT_WIFI_STA_GOT_IP6: + DEBUG_MSG("Obtained IP6 address: "); + Serial.println(WiFi.localIPv6()); + break; + case ARDUINO_EVENT_WIFI_STA_LOST_IP: DEBUG_MSG("Lost IP address and IP address is reset to 0\n"); WiFi.disconnect(false, true); needReconnect = true; wifiReconnect->setIntervalFromNow(1000); break; - case SYSTEM_EVENT_STA_WPS_ER_SUCCESS: + case ARDUINO_EVENT_WPS_ER_SUCCESS: DEBUG_MSG("WiFi Protected Setup (WPS): succeeded in enrollee mode\n"); break; - case SYSTEM_EVENT_STA_WPS_ER_FAILED: + case ARDUINO_EVENT_WPS_ER_FAILED: DEBUG_MSG("WiFi Protected Setup (WPS): failed in enrollee mode\n"); break; - case SYSTEM_EVENT_STA_WPS_ER_TIMEOUT: + case ARDUINO_EVENT_WPS_ER_TIMEOUT: DEBUG_MSG("WiFi Protected Setup (WPS): timeout in enrollee mode\n"); break; - case SYSTEM_EVENT_STA_WPS_ER_PIN: + case ARDUINO_EVENT_WPS_ER_PIN: DEBUG_MSG("WiFi Protected Setup (WPS): pin code in enrollee mode\n"); break; - case SYSTEM_EVENT_AP_START: + case ARDUINO_EVENT_WPS_ER_PBC_OVERLAP: + DEBUG_MSG("WiFi Protected Setup (WPS): push button overlap in enrollee mode\n"); + break; + case ARDUINO_EVENT_WIFI_AP_START: DEBUG_MSG("WiFi access point started\n"); break; - case SYSTEM_EVENT_AP_STOP: + case ARDUINO_EVENT_WIFI_AP_STOP: DEBUG_MSG("WiFi access point stopped\n"); break; - case SYSTEM_EVENT_AP_STACONNECTED: + case ARDUINO_EVENT_WIFI_AP_STACONNECTED: DEBUG_MSG("Client connected\n"); break; - case SYSTEM_EVENT_AP_STADISCONNECTED: + case ARDUINO_EVENT_WIFI_AP_STADISCONNECTED: DEBUG_MSG("Client disconnected\n"); break; - case SYSTEM_EVENT_AP_STAIPASSIGNED: + case ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED: DEBUG_MSG("Assigned IP address to client\n"); break; - case SYSTEM_EVENT_AP_PROBEREQRECVED: + case ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED: DEBUG_MSG("Received probe request\n"); break; - case SYSTEM_EVENT_GOT_IP6: + case ARDUINO_EVENT_WIFI_AP_GOT_IP6: DEBUG_MSG("IPv6 is preferred\n"); break; - case SYSTEM_EVENT_ETH_START: + case ARDUINO_EVENT_WIFI_FTM_REPORT: + DEBUG_MSG("Fast Transition Management report\n"); + break; + case ARDUINO_EVENT_ETH_START: DEBUG_MSG("Ethernet started\n"); break; - case SYSTEM_EVENT_ETH_STOP: + case ARDUINO_EVENT_ETH_STOP: DEBUG_MSG("Ethernet stopped\n"); break; - case SYSTEM_EVENT_ETH_CONNECTED: + case ARDUINO_EVENT_ETH_CONNECTED: DEBUG_MSG("Ethernet connected\n"); break; - case SYSTEM_EVENT_ETH_DISCONNECTED: + case ARDUINO_EVENT_ETH_DISCONNECTED: DEBUG_MSG("Ethernet disconnected\n"); break; - case SYSTEM_EVENT_ETH_GOT_IP: - DEBUG_MSG("Obtained IP address (SYSTEM_EVENT_ETH_GOT_IP)\n"); + case ARDUINO_EVENT_ETH_GOT_IP: + DEBUG_MSG("Obtained IP address (ARDUINO_EVENT_ETH_GOT_IP)\n"); + break; + case ARDUINO_EVENT_ETH_GOT_IP6: + DEBUG_MSG("Obtained IP6 address (ARDUINO_EVENT_ETH_GOT_IP6)\n"); + break; + case ARDUINO_EVENT_SC_SCAN_DONE: + DEBUG_MSG("SmartConfig: Scan done\n"); + break; + case ARDUINO_EVENT_SC_FOUND_CHANNEL: + DEBUG_MSG("SmartConfig: Found channel\n"); + break; + case ARDUINO_EVENT_SC_GOT_SSID_PSWD: + DEBUG_MSG("SmartConfig: Got SSID and password\n"); + break; + case ARDUINO_EVENT_SC_SEND_ACK_DONE: + DEBUG_MSG("SmartConfig: Send ACK done\n"); + break; + case ARDUINO_EVENT_PROV_INIT: + DEBUG_MSG("Provisioning: Init\n"); + break; + case ARDUINO_EVENT_PROV_DEINIT: + DEBUG_MSG("Provisioning: Stopped\n"); + break; + case ARDUINO_EVENT_PROV_START: + DEBUG_MSG("Provisioning: Started\n"); + break; + case ARDUINO_EVENT_PROV_END: + DEBUG_MSG("Provisioning: End\n"); + break; + case ARDUINO_EVENT_PROV_CRED_RECV: + DEBUG_MSG("Provisioning: Credentials received\n"); + break; + case ARDUINO_EVENT_PROV_CRED_FAIL: + DEBUG_MSG("Provisioning: Credentials failed\n"); + break; + case ARDUINO_EVENT_PROV_CRED_SUCCESS: + DEBUG_MSG("Provisioning: Credentials success\n"); break; default: break; diff --git a/src/mesh/http/WiFiAPClient.h b/src/mesh/http/WiFiAPClient.h index 9729c24b5..a11330ad0 100644 --- a/src/mesh/http/WiFiAPClient.h +++ b/src/mesh/http/WiFiAPClient.h @@ -1,6 +1,7 @@ #pragma once #include "configuration.h" +#include "concurrency/Periodic.h" #include #include @@ -8,6 +9,9 @@ #include #endif +extern bool needReconnect; +extern concurrency::Periodic *wifiReconnect; + /// @return true if wifi is now in use bool initWifi(); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 2639e0594..0b617adea 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -195,6 +195,7 @@ bool AdminModule::handleReceivedProtobuf(const MeshPacket &mp, AdminMessage *r) void AdminModule::handleSetOwner(const User &o) { int changed = 0; + bool licensed_changed = false; if (*o.long_name) { changed |= strcmp(owner.long_name, o.long_name); @@ -210,12 +211,14 @@ void AdminModule::handleSetOwner(const User &o) } if (owner.is_licensed != o.is_licensed) { changed = 1; + licensed_changed = true; owner.is_licensed = o.is_licensed; + config.lora.override_duty_cycle = owner.is_licensed; // override duty cycle for licensed operators } if (changed) { // If nothing really changed, don't broadcast on the network or write to flash service.reloadOwner(!hasOpenEditTransaction); - saveChanges(SEGMENT_DEVICESTATE); + licensed_changed ? saveChanges(SEGMENT_CONFIG | SEGMENT_DEVICESTATE) : saveChanges(SEGMENT_DEVICESTATE); } } diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index e7b34b9a7..d148768ad 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -451,11 +451,15 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st if (this->destSelect) { display->fillRect(0 + x, 0 + y, x + display->getWidth(), y + FONT_HEIGHT_SMALL); display->setColor(BLACK); + display->drawStringf(1 + x, 0 + y, buffer, "To: %s", cannedMessageModule->getNodeName(this->dest)); } display->drawStringf(0 + x, 0 + y, buffer, "To: %s", cannedMessageModule->getNodeName(this->dest)); // used chars right aligned sprintf(buffer, "%d left", Constants_DATA_PAYLOAD_LEN - this->freetext.length()); display->drawString(x + display->getWidth() - display->getStringWidth(buffer), y + 0, buffer); + if (this->destSelect) { + display->drawString(x + display->getWidth() - display->getStringWidth(buffer) - 1, y + 0, buffer); + } display->setColor(WHITE); display->drawStringMaxWidth(0 + x, 0 + y + FONT_HEIGHT_SMALL, x + display->getWidth(), cannedMessageModule->drawWithCursor(cannedMessageModule->freetext, cannedMessageModule->cursor)); } else { diff --git a/src/modules/esp32/AudioModule.cpp b/src/modules/esp32/AudioModule.cpp index 17e5cf4fa..e7132fcc4 100644 --- a/src/modules/esp32/AudioModule.cpp +++ b/src/modules/esp32/AudioModule.cpp @@ -71,27 +71,22 @@ void run_codec2(void* parameter) // 4 bytes of header in each frame hex c0 de c2 plus the bitrate memcpy(audioModule->tx_encode_frame,&audioModule->tx_header,sizeof(audioModule->tx_header)); + DEBUG_MSG("Starting codec2 task\n"); + while (true) { uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(10000)); if (tcount != 0) { if (audioModule->radio_state == RadioState::tx) { - - // Apply the TX filter for (int i = 0; i < audioModule->adc_buffer_size; i++) audioModule->speech[i] = (int16_t)hp_filter.Update((float)audioModule->speech[i]); - // Encode the audio codec2_encode(audioModule->codec2, audioModule->tx_encode_frame + audioModule->tx_encode_frame_index, audioModule->speech); - - //increment the pointer where the encoded frame must be saved audioModule->tx_encode_frame_index += audioModule->encode_codec_size; - //If it this is reached we have a ready trasnmission frame if (audioModule->tx_encode_frame_index == (audioModule->encode_frame_size + sizeof(audioModule->tx_header))) { - //Transmit it - DEBUG_MSG("♪♫♪ Sending %d codec2 bytes\n", audioModule->encode_frame_size); + DEBUG_MSG("Sending %d codec2 bytes\n", audioModule->encode_frame_size); audioModule->sendPayload(); audioModule->tx_encode_frame_index = sizeof(audioModule->tx_header); } @@ -99,10 +94,8 @@ void run_codec2(void* parameter) if (audioModule->radio_state == RadioState::rx) { size_t bytesOut = 0; if (memcmp(audioModule->rx_encode_frame, &audioModule->tx_header, sizeof(audioModule->tx_header)) == 0) { - // Make a cycle to get each codec2 frame from the received frame for (int i = 4; i < audioModule->rx_encode_frame_index; i += audioModule->encode_codec_size) { - //Decode the codec2 frame codec2_decode(audioModule->codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); i2s_write(I2S_PORT, &audioModule->output_buffer, audioModule->adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500)); } @@ -112,10 +105,8 @@ void run_codec2(void* parameter) codec2_set_lpc_post_filter(tmp_codec2, 1, 0, 0.8, 0.2); int tmp_encode_codec_size = (codec2_bits_per_frame(tmp_codec2) + 7) / 8; int tmp_adc_buffer_size = codec2_samples_per_frame(tmp_codec2); - // Make a cycle to get each codec2 frame from the received frame for (int i = 4; i < audioModule->rx_encode_frame_index; i += tmp_encode_codec_size) { - //Decode the codec2 frame codec2_decode(tmp_codec2, audioModule->output_buffer, audioModule->rx_encode_frame + i); i2s_write(I2S_PORT, &audioModule->output_buffer, tmp_adc_buffer_size, &bytesOut, pdMS_TO_TICKS(500)); } @@ -128,8 +119,15 @@ void run_codec2(void* parameter) AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), concurrency::OSThread("AudioModule") { + // moduleConfig.audio.codec2_enabled = true; + // moduleConfig.audio.i2s_ws = 13; + // moduleConfig.audio.i2s_sd = 15; + // moduleConfig.audio.i2s_din = 22; + // moduleConfig.audio.i2s_sck = 14; + // moduleConfig.audio.ptt_pin = 39; + if ((moduleConfig.audio.codec2_enabled) && (myRegion->audioPermitted)) { - DEBUG_MSG("♪♫♪ Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); + DEBUG_MSG("Setting up codec2 in mode %u", (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); codec2 = codec2_create((moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1); memcpy(tx_header.magic,c2_magic,sizeof(c2_magic)); tx_header.mode = (moduleConfig.audio.bitrate ? moduleConfig.audio.bitrate : AUDIO_MODULE_MODE) - 1; @@ -141,7 +139,7 @@ AudioModule::AudioModule() : SinglePortModule("AudioModule", PortNum_AUDIO_APP), DEBUG_MSG(" using %d frames of %d bytes for a total payload length of %d bytes\n", encode_frame_num, encode_codec_size, encode_frame_size); xTaskCreate(&run_codec2, "codec2_task", 30000, NULL, 5, &codec2HandlerTask); } else { - DEBUG_MSG("♪♫♪ Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted); + DEBUG_MSG("Codec2 disabled (AudioModule %d, Region %s, permitted %d)\n", moduleConfig.audio.codec2_enabled, myRegion->name, myRegion->audioPermitted); } } @@ -175,7 +173,7 @@ int32_t AudioModule::runOnce() esp_err_t res; if (firstTime) { // Set up I2S Processor configuration. This will produce 16bit samples at 8 kHz instead of 12 from the ADC - DEBUG_MSG("♪♫♪ Initializing I2S SD: %d DIN: %d WS: %d SCK:%d\n", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck); + DEBUG_MSG("Initializing I2S SD: %d DIN: %d WS: %d SCK: %d\n", moduleConfig.audio.i2s_sd, moduleConfig.audio.i2s_din, moduleConfig.audio.i2s_ws, moduleConfig.audio.i2s_sck); i2s_config_t i2s_config = { .mode = (i2s_mode_t)(I2S_MODE_MASTER | (moduleConfig.audio.i2s_sd ? I2S_MODE_RX : 0) | (moduleConfig.audio.i2s_din ? I2S_MODE_TX : 0)), .sample_rate = 8000, @@ -191,7 +189,7 @@ int32_t AudioModule::runOnce() }; res = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); if(res != ESP_OK) - DEBUG_MSG("♪♫♪ Failed to install I2S driver: %d\n", res); + DEBUG_MSG("Failed to install I2S driver: %d\n", res); const i2s_pin_config_t pin_config = { .bck_io_num = moduleConfig.audio.i2s_sck, @@ -201,16 +199,16 @@ int32_t AudioModule::runOnce() }; res = i2s_set_pin(I2S_PORT, &pin_config); if(res != ESP_OK) - DEBUG_MSG("♪♫♪ Failed to set I2S pin config: %d\n", res); + DEBUG_MSG("Failed to set I2S pin config: %d\n", res); res = i2s_start(I2S_PORT); if(res != ESP_OK) - DEBUG_MSG("♪♫♪ Failed to start I2S: %d\n", res); + DEBUG_MSG("Failed to start I2S: %d\n", res); radio_state = RadioState::rx; // Configure PTT input - DEBUG_MSG("♪♫♪ Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); + DEBUG_MSG("Initializing PTT on Pin %u\n", moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN); pinMode(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN, INPUT); firstTime = false; @@ -219,19 +217,19 @@ int32_t AudioModule::runOnce() // Check if PTT is pressed. TODO hook that into Onebutton/Interrupt drive. if (digitalRead(moduleConfig.audio.ptt_pin ? moduleConfig.audio.ptt_pin : PTT_PIN) == HIGH) { if (radio_state == RadioState::rx) { - DEBUG_MSG("♪♫♪ PTT pressed, switching to TX\n"); + DEBUG_MSG("PTT pressed, switching to TX\n"); radio_state = RadioState::tx; e.frameChanged = true; this->notifyObservers(&e); } } else { if (radio_state == RadioState::tx) { + DEBUG_MSG("PTT released, switching to RX\n"); if (tx_encode_frame_index > sizeof(tx_header)) { // Send the incomplete frame - DEBUG_MSG("♪♫♪ Sending %d codec2 bytes (incomplete)\n", tx_encode_frame_index); + DEBUG_MSG("Sending %d codec2 bytes (incomplete)\n", tx_encode_frame_index); sendPayload(); } - DEBUG_MSG("♪♫♪ PTT released, switching to RX\n"); tx_encode_frame_index = sizeof(tx_header); radio_state = RadioState::rx; e.frameChanged = true; @@ -260,7 +258,7 @@ int32_t AudioModule::runOnce() } return 100; } else { - DEBUG_MSG("♪♫♪ Audio Module Disabled\n"); + DEBUG_MSG("Audio Module Disabled\n"); return INT32_MAX; } @@ -268,7 +266,7 @@ int32_t AudioModule::runOnce() MeshPacket *AudioModule::allocReply() { - auto reply = allocDataPacket(); // Allocate a packet for sending + auto reply = allocDataPacket(); return reply; } @@ -286,7 +284,7 @@ void AudioModule::sendPayload(NodeNum dest, bool wantReplies) p->to = dest; p->decoded.want_response = wantReplies; - p->want_ack = false; // Audio is shoot&forget. TODO: Is this really suppressing retransmissions? + p->want_ack = false; // Audio is shoot&forget. No need to wait for ACKs. p->priority = MeshPacket_Priority_MAX; // Audio is important, because realtime p->decoded.payload.size = tx_encode_frame_index; diff --git a/src/power.h b/src/power.h index 64043eb86..b370e0248 100644 --- a/src/power.h +++ b/src/power.h @@ -40,6 +40,9 @@ class Power : private concurrency::OSThread private: uint8_t low_voltage_counter; +#ifdef DEBUG_HEAP + uint32_t lastheap; +#endif }; extern Power *power; From 755c0b7008c7dd6bb278e0447e0ca8578cb53368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 21 Dec 2022 13:37:38 +0100 Subject: [PATCH 30/31] use nanopb 0.4.7 --- .github/workflows/update_protobufs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/update_protobufs.yml b/.github/workflows/update_protobufs.yml index 3b32c578c..6944d827e 100644 --- a/.github/workflows/update_protobufs.yml +++ b/.github/workflows/update_protobufs.yml @@ -17,9 +17,9 @@ jobs: - name: Download nanopb run: | - wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.6-linux-x86.tar.gz - tar xvzf nanopb-0.4.6-linux-x86.tar.gz - mv nanopb-0.4.6-linux-x86 nanopb-0.4.6 + wget https://jpa.kapsi.fi/nanopb/download/nanopb-0.4.7-linux-x86.tar.gz + tar xvzf nanopb-0.4.7-linux-x86.tar.gz + mv nanopb-0.4.7-linux-x86 nanopb-0.4.7 - name: Re-generate protocol buffers run: | From 201b786f777f4588e2998e206651fe631247b84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20G=C3=B6ttgens?= Date: Wed, 21 Dec 2022 14:06:02 +0100 Subject: [PATCH 31/31] fix RAK build --- src/PowerFSM.cpp | 5 +---- src/graphics/Screen.cpp | 5 ++++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PowerFSM.cpp b/src/PowerFSM.cpp index 647167242..e60056ccd 100644 --- a/src/PowerFSM.cpp +++ b/src/PowerFSM.cpp @@ -326,10 +326,10 @@ void PowerFSM_setup() powerFSM.add_timed_transition(&stateON, &stateDARK, getConfiguredOrDefaultMs(config.display.screen_on_secs, default_screen_on_secs), NULL, "Screen-on timeout"); +#ifdef ARCH_ESP32 // On most boards we use light-sleep to be our main state, but on NRF52 we just stay in DARK State *lowPowerState = &stateLS; -#ifdef ARCH_ESP32 // We never enter light-sleep or NB states on NRF52 (because the CPU uses so little power normally) // See: https://github.com/meshtastic/firmware/issues/1071 @@ -340,9 +340,6 @@ void PowerFSM_setup() if (config.power.sds_secs != UINT32_MAX) powerFSM.add_timed_transition(lowPowerState, &stateSDS, getConfiguredOrDefaultMs(config.power.sds_secs), NULL, "mesh timeout"); - -#elif defined (ARCH_NRF52) - lowPowerState = &stateDARK; #endif diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 30fc396a9..8d3429454 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -35,7 +35,7 @@ along with this program. If not, see . #include "mesh/Channels.h" #include "mesh/generated/deviceonly.pb.h" #include "modules/TextMessageModule.h" -#include "modules/esp32/StoreForwardModule.h" + #include "sleep.h" #include "target_specific.h" #include "utils.h" @@ -43,6 +43,7 @@ along with this program. If not, see . #ifdef ARCH_ESP32 #include "esp_task_wdt.h" #include "mesh/http/WiFiAPClient.h" +#include "modules/esp32/StoreForwardModule.h" #endif #ifdef OLED_RU @@ -1399,6 +1400,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 display->drawString(x, y + FONT_HEIGHT_SMALL, channelStr); // Draw our hardware ID to assist with bluetooth pairing. Either prefix with Info or S&F Logo if (moduleConfig.store_forward.enabled) { +#if 0 if (millis() - storeForwardModule->lastHeartbeat > (storeForwardModule->heartbeatInterval * 1200)) { //no heartbeat, overlap a bit #if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgQuestionL1); @@ -1414,6 +1416,7 @@ void DebugInfo::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16 display->drawFastImage(x + SCREEN_WIDTH - 13 - display->getStringWidth(ourId), y + 2 + FONT_HEIGHT_SMALL, 11, 8, imgSF); #endif } +#endif } else { #if defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ST7735_CS) display->drawFastImage(x + SCREEN_WIDTH - 14 - display->getStringWidth(ourId), y + 3 + FONT_HEIGHT_SMALL, 12, 8, imgInfoL1);