diff --git a/README.md b/README.md index 0550da3..f57bad3 100644 --- a/README.md +++ b/README.md @@ -44,15 +44,15 @@ - [Network management](#network-management) - [NTP time synchronization](#ntp-time-synchronization) - [Start/stop services](#startstop-services) - - [Services callbacks](#services-callbacks) + - [Callbacks](#callbacks) - [TCPClient features](#tcpclient-features) - [Connecting to a TCP server](#connecting-to-a-tcp-server) - [Transferring data](#transferring-data) - - [Callbacks](#callbacks) + - [Callbacks](#callbacks-1) - [MQTTClient features](#mqttclient-features) - [Connecting to an MQTT broker](#connecting-to-an-mqtt-broker) - [Publishing and subscribing to topics](#publishing-and-subscribing-to-topics) - - [Callbacks](#callbacks-1) + - [Callbacks](#callbacks-2) - [Troubleshooting](#troubleshooting) - [Module doesn't respond](#module-doesnt-respond) - [Can't register on network](#cant-register-on-network) @@ -486,6 +486,11 @@ void setup() { } else { Serial.println("Modem is not registered on the network."); } + + // Get last known registration status without querying the modem + Serial.printf("Currently registered on network: %s, Status: %u\r\n", + modem.isCurrentlyRegisteredOnNetwork() ? "Yes" : "No", + static_cast(modem.getCurrentRegistrationStatus())); } ``` @@ -555,7 +560,7 @@ void setup() { } ``` -### Services callbacks +### Callbacks The `SIM7600::Modem` class allows you to set callbacks for various services events, such as TCP/IP and MQTT. You need to define your callback functions with the appropriate signatures and then set them using the @@ -569,6 +574,13 @@ and MQTT. You need to define your callback functions with the appropriate signat using namespace SIM7600; Modem modem(&Serial1); // Use Serial1 for communication +// Network changed event callback +void networkChangedCB(const bool registered, const RegStatus reg_status) { + Serial.printf("Event: Network changed! Registered: %s, Status: %u\r\n", + registered ? "Yes" : "No", + static_cast(reg_status)); +} + // TCP/IP closed event callback // IMPORTANT: If this event occurs, you need to start the TCP/IP service again before using it. void tcpNetworkClosedCB() { Serial.println("Event: TCP network connection closed!"); } @@ -580,6 +592,9 @@ void mqttNetworkClosedCB() { Serial.println("Event: MQTT network connection clos void setup() { // ... Initialize modem as shown in the previous example + // Set network changed event callback + modem.setNetworkChangedCallback(networkChangedCB); + // Set TCP/IP closed event callback modem.setTCPNetworkClosedCallback(tcpNetworkClosedCB); diff --git a/examples/ModemTest/ModemTest.ino b/examples/ModemTest/ModemTest.ino index 34a98f9..9cdbd55 100644 --- a/examples/ModemTest/ModemTest.ino +++ b/examples/ModemTest/ModemTest.ino @@ -49,6 +49,13 @@ SIM7600::MQTTClient mqtt(&modem); void handleSerial(); /* Callback functions definitions */ +// Modem +void networkChangedCB(const bool registered, const SIM7600::RegStatus reg_status) { + Serial.printf("Event: Network changed! Registered: %s, Status: %u\r\n", + registered ? "Yes" : "No", + static_cast(reg_status)); +} + // TCP void tcpNetworkClosedCB() { Serial.println("Event: TCP network connection closed!"); } @@ -124,6 +131,7 @@ void setup() { digitalWrite(GSM_GPIO_ENABLE, HIGH); // Modem callbacks + modem.setNetworkChangedCallback(networkChangedCB); modem.setTCPNetworkClosedCallback(tcpNetworkClosedCB); modem.setMQTTNetworkClosedCallback(mqttNetworkClosedCB); @@ -227,6 +235,13 @@ void handleSerial() { } break; case '4': + { + Serial.printf("Currently registered on network: %s, Status: %u\r\n", + modem.isCurrentlyRegisteredOnNetwork() ? "Yes" : "No", + static_cast(modem.getCurrentRegistrationStatus())); + } break; + + case '5': { SIM7600::NTPSyncStatus ntp_status; status = modem.setNTPServer(ntp_status, "pool.ntp.org", 0); @@ -239,7 +254,7 @@ void handleSerial() { } } break; - case '5': + case '6': { SIM7600::NTPTimeData time_data; @@ -259,7 +274,7 @@ void handleSerial() { } } break; - case '6': + case '7': { status = modem.setGPSAntennaVoltage(3050); @@ -271,7 +286,7 @@ void handleSerial() { } } break; - case '7': + case '8': { uint16_t voltage_mv; status = modem.getGPSAntennaVoltage(voltage_mv); @@ -284,7 +299,7 @@ void handleSerial() { } } break; - case '8': + case '9': { status = modem.enableGPSAntennaVoltage(); @@ -296,7 +311,7 @@ void handleSerial() { } } break; - case '9': + case '0': { status = modem.disableGPSAntennaVoltage(); @@ -308,7 +323,7 @@ void handleSerial() { } } break; - case '0': + case 'q': { bool enabled; status = modem.isGPSAntennaVoltageEnabled(enabled); @@ -321,7 +336,7 @@ void handleSerial() { } } break; - case 'q': + case 'w': { status = modem.enableGPS(); @@ -332,7 +347,7 @@ void handleSerial() { } } break; - case 'w': + case 'e': { status = modem.disableGPS(); @@ -343,7 +358,7 @@ void handleSerial() { } } break; - case 'e': + case 'r': { bool enabled; status = modem.isGPSEnabled(enabled); @@ -355,7 +370,7 @@ void handleSerial() { } } break; - case 'r': + case 't': { status = modem.enableGPSAutoStart(true); @@ -366,7 +381,7 @@ void handleSerial() { } } break; - case 't': + case 'y': { status = modem.enableGPSAutoStart(false); @@ -377,7 +392,7 @@ void handleSerial() { } } break; - case 'y': + case 'u': { bool enabled; status = modem.getGPSAutoStart(enabled); @@ -390,7 +405,7 @@ void handleSerial() { } } break; - case 'u': + case 'i': { static SIM7600::GPSData gps_data; status = modem.getGPSData(gps_data); @@ -427,7 +442,7 @@ void handleSerial() { } break; // TCP Client tests - case 'i': + case 'o': { status = modem.startTCPIPService(); @@ -438,7 +453,7 @@ void handleSerial() { } } break; - case 'o': + case 'p': { status = modem.stopTCPIPService(); @@ -449,7 +464,7 @@ void handleSerial() { } } break; - case 'p': + case 'a': { status = tcp.connectToHost(TCP_SERVER, TCP_PORT); @@ -460,7 +475,7 @@ void handleSerial() { } } break; - case 'a': + case 's': { status = tcp.disconnect(); @@ -471,7 +486,7 @@ void handleSerial() { } } break; - case 's': + case 'd': { bool connected; status = tcp.isConnected(connected); @@ -483,7 +498,7 @@ void handleSerial() { } } break; - case 'd': + case 'f': { // Don't send the null terminator static const size_t request_size = sizeof(TCP_HTTP_GET_REQ) - 1; @@ -500,7 +515,7 @@ void handleSerial() { } } break; - case 'f': + case 'g': { size_t available = 0; status = tcp.getAvailableBytes(available); @@ -513,7 +528,7 @@ void handleSerial() { } } break; - case 'g': + case 'h': { static char rx_buffer[512]; size_t bytes_read = 0; @@ -540,7 +555,7 @@ void handleSerial() { } break; // MQTT Client tests - case 'h': + case 'j': { status = modem.startMQTTService(); @@ -551,7 +566,7 @@ void handleSerial() { } } break; - case 'j': + case 'k': { status = modem.stopMQTTService(); @@ -562,7 +577,7 @@ void handleSerial() { } } break; - case 'k': + case 'l': { status = mqtt.acquireClient("sim7600", false, SIM7600::MQTTVersion::V3_1_1); @@ -573,7 +588,7 @@ void handleSerial() { } } break; - case 'l': + case 'z': { status = mqtt.releaseClient(); @@ -584,7 +599,7 @@ void handleSerial() { } } break; - case 'z': + case 'x': { status = mqtt.setLastWillMessage(MQTT_WILL_TOPIC, MQTT_WILL_MESSAGE, SIM7600::MQTTQoS::AtLeastOnce); @@ -596,7 +611,7 @@ void handleSerial() { } } break; - case 'x': + case 'c': { status = mqtt.connect(MQTT_SERVER); @@ -607,7 +622,7 @@ void handleSerial() { } } break; - case 'c': + case 'v': { status = mqtt.disconnect(); @@ -618,7 +633,7 @@ void handleSerial() { } } break; - case 'v': + case 'b': { bool connected; status = mqtt.isConnected(connected); @@ -630,7 +645,7 @@ void handleSerial() { } } break; - case 'b': + case 'n': { status = mqtt.subscribe(MQTT_TOPIC, SIM7600::MQTTQoS::AtLeastOnce); @@ -642,7 +657,7 @@ void handleSerial() { } } break; - case 'n': + case 'm': { status = mqtt.unsubscribe(MQTT_TOPIC); @@ -655,7 +670,7 @@ void handleSerial() { } } break; - case 'm': + case ',': { static char mqtt_payload[16]; diff --git a/platformio.ini b/platformio.ini index a36411b..768e74d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -50,3 +50,6 @@ build_flags = ; Enable USB CDC on boot -DARDUINO_USB_CDC_ON_BOOT=1 + + ; Enable library debug (for ESP32) + -DSIM7600_LOG_LEVEL=5 \ No newline at end of file diff --git a/src/SIM7600Modem.cpp b/src/SIM7600Modem.cpp index 7d49331..18787ed 100644 --- a/src/SIM7600Modem.cpp +++ b/src/SIM7600Modem.cpp @@ -13,8 +13,11 @@ namespace SIM7600 { Modem::Modem() : _serial(nullptr) + , _cb_network_changed(nullptr) , _cb_tcp_network_closed(nullptr) , _cb_mqtt_network_closed(nullptr) + , _registered_on_network(false) + , _current_reg_status(RegStatus::NotRegisteredAndNotSearching) , _urc_handler_count(0) { memset(_tx_buf, 0, SIM7600_MODEM_TX_BUFFER_SIZE_B); memset(_rx_buf, 0, SIM7600_MODEM_RX_BUFFER_SIZE_B); @@ -23,8 +26,11 @@ Modem::Modem() Modem::Modem(Stream* serial) : _serial(serial) + , _cb_network_changed(nullptr) , _cb_tcp_network_closed(nullptr) , _cb_mqtt_network_closed(nullptr) + , _registered_on_network(false) + , _current_reg_status(RegStatus::NotRegisteredAndNotSearching) , _urc_handler_count(0) { memset(_tx_buf, 0, SIM7600_MODEM_TX_BUFFER_SIZE_B); memset(_rx_buf, 0, SIM7600_MODEM_RX_BUFFER_SIZE_B); @@ -33,6 +39,12 @@ Modem::Modem(Stream* serial) void Modem::setSerialPort(Stream* serial) { _serial = serial; } +Status Modem::setNetworkChangedCallback(NetworkChangedCB callback) { + if (callback == nullptr) return Status::InvalidCallback; + _cb_network_changed = callback; + return Status::Success; +} + Status Modem::setTCPNetworkClosedCallback(TCPNetworkClosedCB callback) { if (callback == nullptr) return Status::InvalidCallback; _cb_tcp_network_closed = callback; @@ -62,6 +74,11 @@ Status Modem::init(const char* pin, const uint32_t timeout_ms) { status = sendATCmdAndWaitResp("AT+CMEE=0", AT_OK); if (status != Status::Success) return status; + // Enable CGREG URCs + SIM7600_LOGD(tag, "Enabling CGREG URCs"); + status = sendATCmdAndWaitResp("AT+CGREG=1", AT_OK); + if (status != Status::Success) return status; + // TCPIP receive mode set to manual SIM7600_LOGD(tag, "Setting TCPIP receive mode to manual"); status = sendATCmdAndWaitResp("AT+CIPRXGET=1", AT_OK); @@ -323,7 +340,6 @@ Status Modem::parseLine(char* const buffer, const uint8_t parameters, const char va_end(args); if (parsed < parameters) { - SIM7600_LOGE(tag, "Parsed parameters less than expected: %d/%d", parsed, parameters); return Status::InvalidResponse; } @@ -769,6 +785,11 @@ Status Modem::getNetworkRegistrationStatus(RegStatus& reg_status, const uint32_t reg_status = static_cast(stat); + // Update internal state + _current_reg_status = reg_status; + _registered_on_network = + (reg_status == RegStatus::RegisteredHomeNetwork || reg_status == RegStatus::RegisteredRoaming); + return waitForResponse(AT_OK); } @@ -787,6 +808,10 @@ Status Modem::isRegisteredOnNetwork(bool& registered, const uint32_t timeout_ms) return Status::Success; } +bool Modem::isCurrentlyRegisteredOnNetwork() const { return (_registered_on_network); } + +RegStatus Modem::getCurrentRegistrationStatus() const { return _current_reg_status; } + Status Modem::configureAPN(const char* apn, const char* user, const char* password) { SIM7600_LOGI(tag, "Configuring APN: %s, USER: %s, PASS: %s", @@ -1111,6 +1136,43 @@ bool Modem::_handleURCs() { // Then, check modem-specific URCs + // Network changed + if (strncmp(_rx_buf, "+CGREG:", 7) == 0) { + uint8_t n; + uint8_t stat; + Status status = parseLine(_rx_buf, 2, "+CGREG: %hhu,%hhu", &n, &stat); + + // If the parameter is present, this is not a URC but a response to AT+CGREG? + // Exit without further processing + if (status == Status::Success) { + SIM7600_LOGD(tag, "CGREG response detected, not a URC"); + return false; + } + + status = parseLine(_rx_buf, 1, "+CGREG: %hhu", &stat); + if (status != Status::Success) { + SIM7600_LOGE(tag, "Failed to parse network registration URC"); + return false; + } + + RegStatus reg_status = static_cast(stat); + bool registered = (reg_status == RegStatus::RegisteredHomeNetwork || + reg_status == RegStatus::RegisteredRoaming); + + SIM7600_LOGD(tag, + "URC: Network reg status changed: Registered: %s, status: %u", + registered ? "YES" : "NO", + static_cast(reg_status)); + + // Update internal state + _current_reg_status = reg_status; + _registered_on_network = registered; + + if (_cb_network_changed != nullptr) _cb_network_changed(registered, reg_status); + + return true; + } + // Modem ready if (strcmp(_rx_buf, "RDY") == 0) { SIM7600_LOGD(tag, "URC: Modem is ready"); diff --git a/src/SIM7600Modem.h b/src/SIM7600Modem.h index dfd3d45..9a66e87 100644 --- a/src/SIM7600Modem.h +++ b/src/SIM7600Modem.h @@ -104,9 +104,11 @@ class Modem { public: // Callback function types. Use std::function if available. #if SIM7600_HAS_STD_FUNCTION + using NetworkChangedCB = std::function; using TCPNetworkClosedCB = std::function; using MQTTNetworkClosedCB = std::function; #else + using NetworkChangedCB = void (*)(const bool registered, const RegStatus status); using TCPNetworkClosedCB = void (*)(); using MQTTNetworkClosedCB = void (*)(); #endif @@ -129,6 +131,13 @@ class Modem { */ void setSerialPort(Stream* serial); + /** + * @brief Set the network changed callback. + * @param callback Callback function to be called when the network registration status changes. + * @return Status::Success on success, error code otherwise. + */ + Status setNetworkChangedCallback(NetworkChangedCB callback); + /** * @brief Set the TCP network closed callback. * @param callback Callback function to be called when the TCP network is closed. @@ -408,6 +417,21 @@ class Modem { Status isRegisteredOnNetwork(bool& registered, const uint32_t timeout_ms = SIM7600_MODEM_DEFAULT_TIMEOUT_MS); + /** + * @brief Check if the modem is currently registered on the network. This method returns the last + * known registration status without querying the modem. It is updated when receiving the +CGREG + * URC and when querying the registration status. + * @return true if registered on the network, false otherwise. + */ + bool isCurrentlyRegisteredOnNetwork() const; + + /** + * @brief Get the last known registration status without querying the modem. It is updated when + * receiving the +CGREG URC and when querying the registration status. + * @return RegStatus Registration status. + */ + RegStatus getCurrentRegistrationStatus() const; + /** * @brief Configure the APN settings for GPRS connection. * @param apn Access Point Name. @@ -483,8 +507,11 @@ class Modem { private: Stream* _serial; + NetworkChangedCB _cb_network_changed; TCPNetworkClosedCB _cb_tcp_network_closed; MQTTNetworkClosedCB _cb_mqtt_network_closed; + bool _registered_on_network; + RegStatus _current_reg_status; char _tx_buf[SIM7600_MODEM_TX_BUFFER_SIZE_B]; char _rx_buf[SIM7600_MODEM_RX_BUFFER_SIZE_B];