Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 137 additions & 0 deletions cellular-registration-flow.mmd
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions src/airgradientClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
2 changes: 2 additions & 0 deletions src/airgradientClient.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
5 changes: 4 additions & 1 deletion src/cellularModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,17 @@ CellResult<std::string> CellularModule::resolveDNS(const std::string &hostname)
return CellResult<std::string>();
}

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;
}

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();
}
Expand Down
4 changes: 3 additions & 1 deletion src/cellularModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ class CellularModule {
virtual CellResult<int> retrieveSignal();
virtual CellResult<std::string> retrieveIPAddr();
virtual CellResult<std::string> 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<std::string> startNetworkRegistration(CellTechnology ct,
const std::string &apn,
Expand Down
37 changes: 22 additions & 15 deletions src/cellularModuleA7672xx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -2283,4 +2286,8 @@ uint32_t CellularModuleA7672XX::getCurrentOperatorId() const {
return currentOperatorId_;
}

uint32_t CellularModuleA7672XX::getRegistrationFailCount() const {
return registrationFailCount_;
}

#endif // ESP8266
7 changes: 6 additions & 1 deletion src/cellularModuleA7672xx.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<OperatorInfo> 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
Expand Down Expand Up @@ -121,9 +124,11 @@ class CellularModuleA7672XX : public CellularModule {
CellResult<CellularModule::UdpPacket> udpReceive(uint32_t timeout);
CellResult<std::string> 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
Expand Down
Loading