#include #include #include #include #include #include #include #define WB_IO2 21 #define PIN_GPS_TX 15 #define PIN_GPS_RX 16 #define TELEM_RAK12500_ADDRESS 0x42 #define RAK_UART_GPS_PROBE_TIMEOUT_MS 1200UL #define TELEM_WIRE (&Wire) #define MESH_DEBUG_PRINTLN(...) ((void)0) #define POWERSAVING_DEBUG_PRINTLN(...) ((void)0) #define COM_TYPE_UBX 1 #define SFE_UBLOX_GNSS_ID_GPS 0 #define SFE_UBLOX_GNSS_ID_GALILEO 1 #define SFE_UBLOX_GNSS_ID_GLONASS 2 #define SFE_UBLOX_GNSS_ID_SBAS 3 #define SFE_UBLOX_GNSS_ID_BEIDOU 4 #define SFE_UBLOX_GNSS_ID_IMES 5 #define SFE_UBLOX_GNSS_ID_QZSS 6 #define VAL_CFG_SUBSEC_IOPORT 1 void LocationProvider::sendSentence(const char*) {} static uint32_t gpsResetPin = static_cast(-1); static bool i2cGPSFlag = false, serialGPSFlag = false; static const char meshcore_capability_rak_wisblock_gps[] = "linked-provider"; struct SerialMock : Stream { unsigned begins=0, ends=0, polls=0; bool installed=false; std::deque incoming; void setPins(int tx,int rx) { event("uartPins",tx,rx); } void begin(unsigned baud) { installed=true; ++begins; event("uartBegin",baud); } void end() { assert(installed&&"pinned UARTE end must not be called twice"); installed=false; ++ends; event("uartEnd"); } int available() override { ++polls; return installed ? int(incoming.size()) : 0; } int read() override { if(incoming.empty()) return -1; int c=incoming.front(); incoming.pop_front(); return c; } void nmea() { const char* body="GPRMC,valid"; uint8_t checksum=0; for(const char* c=body;*c;++c) checksum^=uint8_t(*c); char sentence[64]; std::snprintf(sentence,sizeof(sentence),"$%s*%02X\r\n",body,checksum); for(const char* c=sentence;*c;++c) incoming.push_back(uint8_t(*c)); } } Serial1; namespace mesh { namespace ota { struct OtaStoreQspiNrf52 { inline static uint8_t selected=0; inline static uint32_t rak_id=0,w25_id=0; inline static bool header_present=false; inline static bool cached=false; inline static unsigned probes=0; static bool headerW25Detected() { autoDetect(); event("headerProbe"); return header_present; } static uint8_t autoDetect() { if(!cached) { cached=true; ++probes; event("norProbe"); assert(digitalRead(WB_IO2)==HIGH); } return selected; } static void autoProbeIds(uint32_t& rak,uint32_t& w25) { autoDetect(); rak=rak_id; w25=w25_id; } }; }} static bool bus_available=true; static bool ensureI2cBusReleased(TwoWire*) { event("i2cBus"); return bus_available; } struct GnssMock { bool present=false, begin_ok=true, output_ok=true, rate_ok=true, psm_ok=true, valid=false; unsigned begins=0, polls=0, flushes=0, output_calls=0, rate_calls=0, nv_writes=0; std::vector psm; bool begin(TwoWire&,uint8_t address) { ++begins; event("gnssBegin",address); return present&&begin_ok; } bool powerSaveMode(bool enabled) { psm.push_back(enabled); event("gnssPsm",enabled); return psm_ok; } bool setI2COutput(unsigned output) { ++output_calls; event("gnssOutput",output); return output_ok; } void enableGNSS(bool,unsigned) {} bool setMeasurementRate(unsigned rate) { ++rate_calls; event("gnssRate",rate); return rate_ok; } void saveConfigSelective(unsigned) { ++nv_writes; } void flushPVT() { ++flushes; } bool getPVT(unsigned) { ++polls; return valid; } bool getGnssFixOk(unsigned) { ++polls; return valid; } long getLatitude(unsigned) { return 476100000; } long getLongitude(unsigned) { return -1223300000; } long getAltitude(unsigned) { return 125000; } int getSIV(unsigned) { return valid?8:0; } bool getDateValid(unsigned) { return valid; } bool getTimeValid(unsigned) { return valid; } long getUnixEpoch(unsigned) { return 1790942400; } } ublox_GNSS; @HELPERS@ @PROVIDER@ static RAK12500LocationProvider RAK12500_provider; struct SensorDef { uint32_t address; const char* name; uint8_t (*init)(TwoWire*,uint8_t); void (*query)(uint8_t,uint8_t,CayenneLPP&); bool (*query_voltage)(uint8_t,float&); }; static const SensorDef SENSOR_TABLE[1] = {}; static const size_t SENSOR_TABLE_SIZE=0; bool EnvironmentSensorManager::i2c_probe(TwoWire&,uint8_t) { return false; } @METHODS@ struct TestLocation : LocationProvider { unsigned loops=0,resets=0,clears=0; int pin=-1; mesh::RTCClock clock; bool valid=false; void reset() override { ++resets; valid=false; } void begin() override {} void stop() override {} void loop() override { ++loops; } bool isEnabled() override { return pin<0 || digitalRead(pin)==HIGH; } bool clearPositionFix() override { valid=false; ++clears; return true; } void setPinEn(int value) override { pin=value; } int getPinEn() override { return pin; } mesh::RTCClock* getRTCClock() override { return &clock; } long getLatitude() override { return 47610000; } long getLongitude() override { return -122330000; } long getAltitude() override { return 125000; } long satellitesCount() override { return valid?8:0; } bool isValid() override { return valid; } long getTimestamp() override { return 1790942400; } void acceptedClock() { markTimeSyncApplied(); stopTimeSync(); } }; struct TestEnvironment : EnvironmentSensorManager { explicit TestEnvironment(TestLocation& location):EnvironmentSensorManager(location) {} bool active() const { return gps_active; } bool userEnabled() const { return isGpsTelemetryUserEnabled(); } bool transportAvailable() const { return isGpsTelemetryTransportAvailable(); } using EnvironmentSensorManager::start_gps; using EnvironmentSensorManager::stop_gps; using EnvironmentSensorManager::rakGPSInit; using EnvironmentSensorManager::_wisblock_power; }; static constexpr bool compile_held = mesh::gps::wisBlockCompileRailHolds()!=0; static void fresh() { now_ms=100; delay_count=0; hardware_events.clear(); std::memset(pin_values,0,sizeof(pin_values)); pin_values[WB_IO2]=HIGH; Serial1=SerialMock{}; ublox_GNSS=GnssMock{}; RAK12500_provider=RAK12500LocationProvider{}; mesh::ota::OtaStoreQspiNrf52::selected=0; mesh::ota::OtaStoreQspiNrf52::rak_id=mesh::ota::OtaStoreQspiNrf52::w25_id=0; mesh::ota::OtaStoreQspiNrf52::header_present=false; mesh::ota::OtaStoreQspiNrf52::cached=false; mesh::ota::OtaStoreQspiNrf52::probes=0; i2cGPSFlag=serialGPSFlag=false; gpsResetPin=uint32_t(-1); bus_available=true; } static size_t firstEvent(const char* name,int a=-999,int b=-999,size_t after=0) { for(size_t i=after;i=started+(held?0:500)); assert(location.clears==1&&location.pin<0); sensors.setPowerSavingEnabled(false); now_ms+=600001; sensors.loop(); assert(sensors.active()); // Manual on plus power-saving off is continuous. hardware_events.clear(); assert(sensors.setSettingValue("gps","0")); assert(!sensors.active()&&!Serial1.installed&&sensors.isGpsPowered()==held); if(!held) requireUartReleasedBeforePowerOff(); else requireNoLow(); sensors.setPowerSavingEnabled(true); now_ms+=1000; sensors.loop(); sensors.setPowerSavingEnabled(false); assert(!sensors.active()); const unsigned saved_begins=Serial1.begins; sensors.configureSharedPeripheralMode(!shared); // Boot-only mode cannot revoke/reconfigure owners. assert(sensors.isGpsPowered()==held&&Serial1.begins==saved_begins); } static void hourly() { fresh(); TestLocation location; TestEnvironment sensors(location); bootUart(sensors,location); sensors.applyGpsTimeSyncInterval(1); sensors.setPowerSavingEnabled(true); assert(sensors.setSettingValue("gps","1")); assert(sensors.active()&&location.getGPSPowerSaving()); assert(location.getNextGPSOff()!=0); now_ms+=100; location.acceptedClock(); sensors.loop(); assert(!sensors.active()&&!sensors.isGpsPowered()); const uint32_t deadline=uint32_t(location.getNextGPSOn()); assert(deadline==millis()+3600000U); now_ms=deadline-1; sensors.loop(); assert(!sensors.active()); now_ms=deadline; hardware_events.clear(); sensors.loop(); assert(sensors.active()&&Serial1.installed); assert(firstEvent("write",WB_IO2,HIGH)0); } } } } static void i2c(bool shared,unsigned wake_failure=0) { fresh(); TestLocation location; TestEnvironment sensors(location); ublox_GNSS.present=true; assert(sensors.begin()); assert(sensors.isGPSDetected()&&sensors.active()&&!sensors.userEnabled()); assert(i2cGPSFlag&&!serialGPSFlag&&sensors.getLocationProvider()==&RAK12500_provider); assert(RAK12500_provider.getPinEn()<0); sensors.configureSharedPeripheralMode(shared); assert(sensors.setSettingValue("gps","0")); const bool held=shared||compile_held; assert(!sensors.active()&&sensors.isGpsPowered()==held); if(held) { requireNoLow(); assert(ublox_GNSS.psm.back()); } else assert(ublox_GNSS.psm.empty()); const unsigned begins=ublox_GNSS.begins,polls=ublox_GNSS.polls; now_ms+=1000; sensors.loop(); assert(ublox_GNSS.polls==polls); hardware_events.clear(); const uint32_t started=millis(); const unsigned writes=ublox_GNSS.nv_writes; if(wake_failure==1) ublox_GNSS.begin_ok=false; if(wake_failure==2) ublox_GNSS.output_ok=false; if(wake_failure==3) ublox_GNSS.rate_ok=false; if(wake_failure==4) bus_available=false; assert(sensors.setSettingValue("gps","1")); if(wake_failure) { assert(!held&&!sensors.active()&&!sensors.isGPSDetected()&&!sensors.isGpsPowered()); assert(sensors.userEnabled()); // Persisted intent is not silently rewritten after failure. const unsigned failed_begins=ublox_GNSS.begins; for(unsigned i=0;i<10;++i) { now_ms+=1000; sensors.loop(); } assert(ublox_GNSS.begins==failed_begins&&ublox_GNSS.polls==polls); assert(!sensors.setSettingValue("gps","1")); return; } assert(ublox_GNSS.nv_writes==writes); // Cold duty wake must not write receiver flash/BBR again. assert(sensors.active()&&sensors.isGpsPowered()); if(held) { assert(ublox_GNSS.begins==begins&&!ublox_GNSS.psm.back()); requireNoLow(); } else { assert(ublox_GNSS.begins==begins+1); assert(firstEvent("write",WB_IO2,HIGH)=started+500); } assert(ublox_GNSS.flushes>0); // A cold wake never reuses a previous position as fresh. assert(sensors.setSettingValue("gps","0")); assert(!sensors.active()&&sensors.isGpsPowered()==held); } static void initialConfigFailure() { for(unsigned failed_step:{1U,2U}) { fresh(); TestLocation location; TestEnvironment sensors(location); ublox_GNSS.present=true; if(failed_step==1) ublox_GNSS.output_ok=false; else ublox_GNSS.rate_ok=false; assert(sensors.begin()); assert(!sensors.isGPSDetected()&&!sensors.active()&&!i2cGPSFlag&&!serialGPSFlag); assert(sensors.getLocationProvider()==&location&&!Serial1.installed); assert(ublox_GNSS.output_calls==1&&ublox_GNSS.rate_calls==(failed_step==1?0U:1U)); assert(ublox_GNSS.nv_writes==0&&!sensors.userEnabled()); requireNoLow(); sensors.configureSharedPeripheralMode(false); assert(!sensors.isGpsPowered()&&digitalRead(WB_IO2)==LOW); const unsigned begins=ublox_GNSS.begins,polls=ublox_GNSS.polls; for(unsigned i=0;i<10;++i) { now_ms+=1000; sensors.loop(); } assert(ublox_GNSS.begins==begins&&ublox_GNSS.polls==polls); assert(!sensors.setSettingValue("gps","1")); } } static void dedicated() { #ifdef PIN_GPS_EN fresh(); TestLocation location; TestEnvironment sensors(location); Serial1.nmea(); assert(sensors.begin()&&sensors.isGPSDetected()&&location.pin==PIN_GPS_EN); sensors.configureSharedPeripheralMode(true); assert(sensors.setSettingValue("gps","0")); requireUartReleasedBeforePowerOff(PIN_GPS_EN,!PIN_GPS_EN_ACTIVE); assert(!sensors.active()&&!sensors.isGpsPowered()); assert(digitalRead(PIN_GPS_EN)==!PIN_GPS_EN_ACTIVE); assert(digitalRead(WB_IO2)==HIGH); requireNoLow(); assert(sensors.gpsSerialTransportCanYield(1)); const unsigned off_ends=Serial1.ends; assert(sensors.setGpsSerialTransportBlocked(1,true)); assert(Serial1.ends==off_ends); // Already-off GPS must not close UARTE twice. assert(!sensors.transportAvailable()&&!sensors.isGpsPowered()); assert(sensors.setSettingValue("gps","1")); assert(sensors.userEnabled()&&!sensors.active()&&!sensors.isGpsPowered()); hardware_events.clear(); assert(sensors.setGpsSerialTransportBlocked(1,false)); assert(sensors.active()&&Serial1.installed&&sensors.isGpsPowered()); assert(firstEvent("write",PIN_GPS_EN,PIN_GPS_EN_ACTIVE)