diff --git a/cellular-registration-flow.mmd b/cellular-registration-flow.mmd new file mode 100644 index 0000000..728fc7d --- /dev/null +++ b/cellular-registration-flow.mmd @@ -0,0 +1,137 @@ +--- +title: Cellular Network Registration State Machine +--- +stateDiagram-v2 + direction TB + + [*] --> CHECK_MODULE_READY + + %% ─── CHECK_MODULE_READY ─── + state CHECK_MODULE_READY { + direction LR + cmr1: Test AT + cmr2: Check SIM ready + cmr1 --> cmr2 + } + CHECK_MODULE_READY --> PREPARE_MODULE : AT ok, SIM ready + CHECK_MODULE_READY --> FAILED : AT or SIM not ready + + %% ─── PREPARE_MODULE ─── + state PREPARE_MODULE { + direction LR + pm1: Disable URC + pm2: Apply cell technology + pm3: Apply APN + pm4: Check operator list + pm1 --> pm2 + pm2 --> pm3 + pm3 --> pm4 + } + PREPARE_MODULE --> SCAN_OPERATOR : No cached operator list + PREPARE_MODULE --> CONFIGURE_MANUAL_NETWORK : Has cached operator list + PREPARE_MODULE --> CHECK_MODULE_READY : Timeout + + %% ─── SCAN_OPERATOR ─── + state SCAN_OPERATOR { + direction LR + so1: AT+COPS=? + so2: Parse operator list + so1 --> so2 + } + SCAN_OPERATOR --> CONFIGURE_MANUAL_NETWORK : Operators found + SCAN_OPERATOR --> CHECK_MODULE_READY : Scan failed / timeout + + %% ─── CONFIGURE_MANUAL_NETWORK ─── + state CONFIGURE_MANUAL_NETWORK { + direction TB + mn1: Find saved operator (by currentOperatorId_) + mn2: Check index >= list size? + mn3: Select operator via AT+COPS + mn1 --> mn2 + mn2 --> mn3 : Has operators left + } + CONFIGURE_MANUAL_NETWORK --> OPERATOR_LIST_EXHAUSTED : All operators tried (index >= size) + CONFIGURE_MANUAL_NETWORK --> CHECK_NETWORK_REGISTRATION : Operator selected OK + CONFIGURE_MANUAL_NETWORK --> CONFIGURE_MANUAL_NETWORK : Selection error, try next + CONFIGURE_MANUAL_NETWORK --> CHECK_MODULE_READY : Selection timeout, try next + + %% ─── CHECK_NETWORK_REGISTRATION ─── + state CHECK_NETWORK_REGISTRATION { + direction TB + nr1: Query registration status + nr2: Query signal strength + nr1 --> nr2 + } + CHECK_NETWORK_REGISTRATION --> CHECK_SERVICE_STATUS : Registered (stat=1,5) with good signal + CHECK_NETWORK_REGISTRATION --> CONFIGURE_MANUAL_NETWORK : Low signal (< 10 csq), try next + CHECK_NETWORK_REGISTRATION --> CONFIGURE_MANUAL_NETWORK : Denied/emergency (stat=3,11) after 10s confirm + CHECK_NETWORK_REGISTRATION --> CONFIGURE_MANUAL_NETWORK : Not registered after 60s, try next + CHECK_NETWORK_REGISTRATION --> CHECK_NETWORK_REGISTRATION : Still searching, retry (3s) + CHECK_NETWORK_REGISTRATION --> CHECK_MODULE_READY : Status check timeout + + %% ─── CHECK_SERVICE_STATUS ─── + state CHECK_SERVICE_STATUS { + direction TB + ss1: Check service available (CNSMOD) + ss2: Activate PDP context + ss3: Ensure packet domain attached + ss1 --> ss2 + ss2 --> ss3 + } + CHECK_SERVICE_STATUS --> NETWORK_READY : Service ready, PDP active + CHECK_SERVICE_STATUS --> CHECK_SERVICE_STATUS : Service/PDP not ready, retry + CHECK_SERVICE_STATUS --> CHECK_NETWORK_REGISTRATION : Service check timeout (30s) + CHECK_SERVICE_STATUS --> CHECK_MODULE_READY : AT timeout + + %% ─── NETWORK_READY ─── + state NETWORK_READY { + direction LR + ready1: Validate signal + ready2: Retrieve IP address + ready1 --> ready2 + } + NETWORK_READY --> SUCCESS : Signal valid, IP obtained + NETWORK_READY --> CHECK_SERVICE_STATUS : Invalid signal / no IP + + %% ─── OPERATOR_LIST_EXHAUSTED ─── + state OPERATOR_LIST_EXHAUSTED { + direction TB + ex1: registrationFailCount_++ + ex2: Check >= MAX_REGISTRATION_FAILURES (3)? + ex1 --> ex2 + } + OPERATOR_LIST_EXHAUSTED --> CHECK_MODULE_READY : Under threshold:\nreset module, retry from index 0 + OPERATOR_LIST_EXHAUSTED --> FAILED : Threshold reached:\nclear operator list,\nreset counter + + %% ─── Terminal states ─── + state SUCCESS { + direction LR + s1: Reset registrationFailCount_ = 0 + s2: Save currentOperatorId_ + s1 --> s2 + } + SUCCESS --> [*] + + state FAILED { + direction LR + f1: Persisted state read back\nby application for NVS save + } + FAILED --> [*] + + %% ─── Operation timeout (outer while loop) ─── + note right of FAILED + Any state can exit to FAILED + when operationTimeoutMs expires. + currentOperatorId_ = last attempted operator + registrationFailCount_ preserved as-is. + Both persisted to NVS by application. + end note + + note right of CONFIGURE_MANUAL_NETWORK + On wake cycle resume: + setOperators() restores list, + currentOperatorId_, and + registrationFailCount_ from NVS. + Registration resumes from the + last attempted operator position. + end note diff --git a/src/airgradientClient.cpp b/src/airgradientClient.cpp index 359f3f6..39db282 100644 --- a/src/airgradientClient.cpp +++ b/src/airgradientClient.cpp @@ -23,6 +23,10 @@ void AirgradientClient::setHttpDomain(const std::string &target) { httpDomain = void AirgradientClient::setHttpDomainDefault() { httpDomain = AIRGRADIENT_HTTP_DOMAIN; } +void AirgradientClient::setCoapDomain(const std::string &target) { coapHostTarget = target; } + +void AirgradientClient::setCoapDomainDefault() { coapHostTarget = AIRGRADIENT_COAP_IP; } + void AirgradientClient::setExtendedPmMeasures(bool enable) {} bool AirgradientClient::isClientReady() { return clientReady; } diff --git a/src/airgradientClient.h b/src/airgradientClient.h index a7500a6..c68dad7 100644 --- a/src/airgradientClient.h +++ b/src/airgradientClient.h @@ -105,6 +105,8 @@ class AirgradientClient { */ void setHttpDomain(const std::string &target); void setHttpDomainDefault(); + void setCoapDomain(const std::string &target); + void setCoapDomainDefault(); bool isClientReady(); void setClientReady(bool isReady); void resetFetchConfigurationStatus(); diff --git a/src/cellularModule.cpp b/src/cellularModule.cpp index 19e9584..1d2cc70 100644 --- a/src/cellularModule.cpp +++ b/src/cellularModule.cpp @@ -35,7 +35,8 @@ CellResult CellularModule::resolveDNS(const std::string &hostname) return CellResult(); } -bool CellularModule::setOperators(const std::string &serialized, uint32_t operatorId) { +bool CellularModule::setOperators(const std::string &serialized, uint32_t operatorId, + uint32_t registrationFailCount) { return false; } @@ -43,6 +44,8 @@ std::string CellularModule::getSerializedOperators() const { return std::string( uint32_t CellularModule::getCurrentOperatorId() const { return 0; } +uint32_t CellularModule::getRegistrationFailCount() const { return 0; } + CellReturnStatus CellularModule::isNetworkRegistered(CellTechnology ct) { return CellReturnStatus(); } diff --git a/src/cellularModule.h b/src/cellularModule.h index 6f9d990..b960f06 100644 --- a/src/cellularModule.h +++ b/src/cellularModule.h @@ -57,9 +57,11 @@ class CellularModule { virtual CellResult retrieveSignal(); virtual CellResult retrieveIPAddr(); virtual CellResult resolveDNS(const std::string &hostname); - virtual bool setOperators(const std::string &serialized, uint32_t operatorId); + virtual bool setOperators(const std::string &serialized, uint32_t operatorId, + uint32_t registrationFailCount = 0); virtual std::string getSerializedOperators() const; virtual uint32_t getCurrentOperatorId() const; + virtual uint32_t getRegistrationFailCount() const; virtual CellReturnStatus isNetworkRegistered(CellTechnology ct); virtual CellResult startNetworkRegistration(CellTechnology ct, const std::string &apn, diff --git a/src/cellularModuleA7672xx.cpp b/src/cellularModuleA7672xx.cpp index cf8021f..5640582 100644 --- a/src/cellularModuleA7672xx.cpp +++ b/src/cellularModuleA7672xx.cpp @@ -286,9 +286,6 @@ CellularModuleA7672XX::startNetworkRegistration(CellTechnology ct, const std::st uint32_t manualOperatorStartTime = 0; // Track time per operator in manual mode (60 sec timeout) uint32_t serviceStatusStartTime = 0; // Track time in CHECK_SERVICE_STATUS (30 sec timeout) - // Track operator list exhaustion (full iterations through all operators) - uint32_t operatorListExhaustedCount = 0; - const uint32_t MAX_OPERATOR_LIST_EXHAUSTION = 3; const uint32_t SERVICE_STATUS_TIMEOUT = 30000; // 30 seconds NetworkRegistrationState state = CHECK_MODULE_READY; @@ -326,18 +323,19 @@ CellularModuleA7672XX::startNetworkRegistration(CellTechnology ct, const std::st case OPERATOR_LIST_EXHAUSTED: { // All operators exhausted, increment exhaustion counter - operatorListExhaustedCount++; + registrationFailCount_++; AG_LOGW(TAG, "Operator list exhausted (attempt %" PRIu32 " of %" PRIu32 ")", - operatorListExhaustedCount, MAX_OPERATOR_LIST_EXHAUSTION); + registrationFailCount_, MAX_REGISTRATION_FAILURES); - if (operatorListExhaustedCount >= MAX_OPERATOR_LIST_EXHAUSTION) { + if (registrationFailCount_ >= MAX_REGISTRATION_FAILURES) { // Reached maximum exhaustion attempts, fail registration AG_LOGE(TAG, "Failed after %" PRIu32 " full iterations through operator list", - MAX_OPERATOR_LIST_EXHAUSTION); - // Clear operator list and saved operator + registrationFailCount_); + // Clear operator list and saved operator, reset fail counter availableOperators_.clear(); currentOperatorId_ = 0; currentOperatorIndex_ = 0; + registrationFailCount_ = 0; finish = true; continue; } @@ -397,10 +395,14 @@ CellularModuleA7672XX::startNetworkRegistration(CellTechnology ct, const std::st } if (state != NETWORK_READY) { - AG_LOGW(TAG, "Network registration failed! Final state: %d", state); + AG_LOGW(TAG, "Network registration failed! Final state: %d (fail count: %" PRIu32 " of %" PRIu32 ")", + state, registrationFailCount_, MAX_REGISTRATION_FAILURES); return result; } + // Registration succeeded, reset fail counter + registrationFailCount_ = 0; + AG_LOGI(TAG, "Warming up for %" PRIu32 "ms...", _warmUpTimeMs); DELAY_MS(_warmUpTimeMs); @@ -1146,7 +1148,6 @@ CellularModuleA7672XX::_implCheckNetworkRegistration(CellTechnology ct, AG_LOGW(TAG, "This operator %" PRIu32 " has really low signal %d (csq), moving on..", currentOperatorId_, signal); - currentOperatorId_ = 0; // Clear saved operator currentOperatorIndex_++; REGIS_RETRY_DELAY(); return CONFIGURE_MANUAL_NETWORK; @@ -1181,7 +1182,6 @@ CellularModuleA7672XX::_implCheckNetworkRegistration(CellTechnology ct, // Still denied/emergency after confirmation period if (stat == 3 || stat == 11) { AG_LOGW(TAG, "Registration still denied/emergency (status=%d) after 10s, trying next operator", stat); - currentOperatorId_ = 0; // Clear saved operator currentOperatorIndex_++; return CONFIGURE_MANUAL_NETWORK; } @@ -1190,7 +1190,6 @@ CellularModuleA7672XX::_implCheckNetworkRegistration(CellTechnology ct, // Not registered, check timeout if ((MILLIS() - manualOperatorStartTime) > TIMEOUT_WAIT_REGISTERED) { AG_LOGW(TAG, "Not registered with current operator after 60 seconds, trying next"); - currentOperatorId_ = 0; // Clear saved operator currentOperatorIndex_++; return CONFIGURE_MANUAL_NETWORK; } @@ -1285,6 +1284,7 @@ CellularModuleA7672XX::_implConfigureManualNetwork() { } OperatorInfo opInfo = availableOperators_[currentOperatorIndex_]; + currentOperatorId_ = opInfo.operatorId; // Track last attempted operator for persistence AG_LOGI(TAG, "Configuring manual operator: %" PRIu32 " with AcT: %d (index %zu of %zu)", opInfo.operatorId, opInfo.accessTech, currentOperatorIndex_ + 1, availableOperators_.size()); DELAY_MS(5000); @@ -2182,9 +2182,12 @@ int CellularModuleA7672XX::_calculateResponseTimeout(int connectionTimeout, int return waitActionTimeout; } -bool CellularModuleA7672XX::setOperators(const std::string &serialized, uint32_t operatorId) { - AG_LOGI(TAG, "Setting operators from serialized string: %s, current operatorId: %" PRIu32, - serialized.c_str(), operatorId); +bool CellularModuleA7672XX::setOperators(const std::string &serialized, uint32_t operatorId, + uint32_t registrationFailCount) { + AG_LOGI(TAG, "Setting operators from serialized string: %s, current operatorId: %" PRIu32 + ", failCount: %" PRIu32, + serialized.c_str(), operatorId, registrationFailCount); + registrationFailCount_ = registrationFailCount; // Clear existing operators availableOperators_.clear(); @@ -2283,4 +2286,8 @@ uint32_t CellularModuleA7672XX::getCurrentOperatorId() const { return currentOperatorId_; } +uint32_t CellularModuleA7672XX::getRegistrationFailCount() const { + return registrationFailCount_; +} + #endif // ESP8266 diff --git a/src/cellularModuleA7672xx.h b/src/cellularModuleA7672xx.h index 88653e2..1a555f9 100644 --- a/src/cellularModuleA7672xx.h +++ b/src/cellularModuleA7672xx.h @@ -51,10 +51,13 @@ class CellularModuleA7672XX : public CellularModule { int accessTech; // Access technology: 0=GSM, 2=UTRAN, 7=E-UTRAN(LTE) }; + static constexpr uint32_t MAX_REGISTRATION_FAILURES = 3; + // Operator selection for manual network registration std::vector availableOperators_; // Persisted operator list with IDs and access tech size_t currentOperatorIndex_ = 0; // Track position in manual mode uint32_t currentOperatorId_ = 0; // Current operator PLMN ID (saved successful operator) + uint32_t registrationFailCount_ = 0; // Consecutive registration failures (persisted via setOperators) public: // Structure to hold detailed registration status @@ -121,9 +124,11 @@ class CellularModuleA7672XX : public CellularModule { CellResult udpReceive(uint32_t timeout); CellResult resolveDNS(const std::string &hostname); // Operator serialization/deserialization - bool setOperators(const std::string &serialized, uint32_t operatorId); + bool setOperators(const std::string &serialized, uint32_t operatorId, + uint32_t registrationFailCount = 0); std::string getSerializedOperators() const; uint32_t getCurrentOperatorId() const; + uint32_t getRegistrationFailCount() const; private: const int DEFAULT_HTTP_CONNECT_TIMEOUT = 120; // seconds