From 6c3b7b41a4fa36d201081c174f0e3912f96dd216 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 13 Nov 2023 12:23:39 +0000 Subject: [PATCH 01/11] Add SARA-R5_Example17_AssistNow_MQTT --- .../SARA-R5_Example17_AssistNow_MQTT/CONFIG.h | 110 +++ .../SARA-R5_Example17_AssistNow_MQTT/HW.h | 120 +++ .../SARA-R5_Example17_AssistNow_MQTT/LTE.h | 929 ++++++++++++++++++ .../SARA-R5_Example17_AssistNow_MQTT.ino | 139 +++ .../UBXFILE.h | 127 +++ .../secrets.h | 35 + 6 files changed, 1460 insertions(+) create mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h create mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/HW.h create mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h create mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/SARA-R5_Example17_AssistNow_MQTT.ino create mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/UBXFILE.h create mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h b/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h new file mode 100644 index 0000000..35062c3 --- /dev/null +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h @@ -0,0 +1,110 @@ +/* + * Copyright 2022 by Michael Ammann (@mazgch) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +#include +#include + +#include "HW.h" +#include "secrets.h" + +// ----------------------------------------------------------------------- +// MQTT / PointPerfect settings +// ----------------------------------------------------------------------- + +const unsigned short MQTT_BROKER_PORT = 8883; //!< MQTTS port +const int MQTT_MAX_MSG_SIZE = 9*1024; //!< the max size of a MQTT pointperfect topic + +#define MQTT_TOPIC_MGA "/pp/ubx/mga" //!< GNSS assistance topic + +const char MQTT_TOPIC_MGA_GPS[] = MQTT_TOPIC_MGA "/gps"; //!< GPS (US) +const char MQTT_TOPIC_MGA_GLO[] = MQTT_TOPIC_MGA "/glo"; //!< Glonass (RU) +const char MQTT_TOPIC_MGA_GAL[] = MQTT_TOPIC_MGA "/gal"; //!< Galileo (EU) +const char MQTT_TOPIC_MGA_BDS[] = MQTT_TOPIC_MGA "/bds"; //!< Beidou (CN) + +// ----------------------------------------------------------------------- +// CONFIGURATION keys +// ----------------------------------------------------------------------- + +#define CONFIG_DEVICE_TITLE "HPG solution" //!< a used friendly name +#define CONFIG_DEVICE_NAMEPREFIX "hpg" //!< a hostname compatible prefix, only a-z, 0-9 and - + +// PointPerfect configuration +const char CONFIG_VALUE_BROKERHOST[] = "brokerHost"; //!< config key for brocker host +const char CONFIG_VALUE_STREAM[] = "stream"; //!< config key for stream +const char CONFIG_VALUE_ROOTCA[] = "rootCa"; //!< config key for root certificate +const char CONFIG_VALUE_CLIENTCERT[] = "clientCert"; //!< config key for client certificate +const char CONFIG_VALUE_CLIENTKEY[] = "clientKey"; //!< config key for client keys +const char CONFIG_VALUE_CLIENTID[] = "clientId"; //!< config key for client id + +/** This class encapsulates all WLAN functions. +*/ +class CONFIG { + +public: + + /** constructor + */ + CONFIG() { + // create a unique name from the mac + uint64_t mac = ESP.getEfuseMac(); + const char* p = (const char*)&mac; + char str[64]; + sprintf(str, CONFIG_DEVICE_TITLE " - %02x%02x%02x", p[3], p[4], p[5]); + title = str; + sprintf(str, CONFIG_DEVICE_NAMEPREFIX "-%02x%02x%02x", p[3], p[4], p[5]); + name = str; + } + + /** get a name of the device + * \return the device name + */ + String getDeviceName(void) { + return name; + } + + /** get a friendly name of the device + * \return the friendly device title + */ + String getDeviceTitle(void) { + return title; + } + + /** get the topics to subscribe + * \return a vector with all the topics + */ + std::vector getTopics(void) + { + std::vector topics; + topics.push_back(MQTT_TOPIC_MGA); + //topics.push_back(MQTT_TOPIC_MGA_GPS); + //topics.push_back(MQTT_TOPIC_MGA_GLO); + //topics.push_back(MQTT_TOPIC_MGA_GAL); + //topics.push_back(MQTT_TOPIC_MGA_BDS); + return topics; + } + +protected: + + String title; //!< the title of the device + String name; //!< the name of the device +}; + +CONFIG Config; //!< The global CONFIG object + +#endif // __CONFIG_H__ diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/HW.h b/examples/SARA-R5_Example17_AssistNow_MQTT/HW.h new file mode 100644 index 0000000..a5fd708 --- /dev/null +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/HW.h @@ -0,0 +1,120 @@ +/* + * Copyright 2022 by Michael Ammann (@mazgch) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __HW_H__ +#define __HW_H__ + +/** The target is used to enable conditional code thoughout this application + */ + +#define SPARKFUN_MICROMOD_ASSET_TRACKER 41 //!< Choose Sparkfun ESP32 Arduino / Sparkfun ESP32 MicroMod + +#define SPARKFUN_RTK_EVERYWHERE 51 //!< Select ESP32 / ESP32 Wrover Module + +#define HW_TARGET SPARKFUN_MICROMOD_ASSET_TRACKER + +/** the pins are defined here for each hardware target + */ +enum HW_PINS { + // Standard pins + BOOT = 0, + CDC_RX = RX, CDC_TX = TX, +#if (HW_TARGET == SPARKFUN_RTK_EVERYWHERE) + LED = 2, + CAN_RX = -1, CAN_TX = -1, + I2C_SDA = 21, I2C_SCL = 22, +#elif (HW_TARGET == SPARKFUN_MICROMOD_ASSET_TRACKER) + LED = 2, + CAN_RX = -1, CAN_TX = -1, + I2C_SDA = 21, I2C_SCL = 22, +#else + #error unknown board target +#endif + +#if (HW_TARGET == SPARKFUN_MICROMOD_ASSET_TRACKER) + // LTE (DCE) + LTE_RESET = -1, LTE_PWR_ON = G2, LTE_ON = G6, LTE_INT = G5, + LTE_TXI = TX1, LTE_RXO = RX1, LTE_RTS = -1, LTE_CTS = -1, + LTE_RI = G4, LTE_DSR = -1, LTE_DCD = -1, LTE_DTR = -1, + LTE_PWR_ON_ACTIVE = HIGH, LTE_ON_ACTIVE = LOW, + + // Power supply + VIN = 39, V33_EN = -1, V33_EN_ACTIVE = HIGH, + + // Micro SD card + MICROSD_SCK = SCK, MICROSD_SDI = MISO, MICROSD_SDO = MOSI, + MICROSD_DET = -1, MICROSD_PWR_EN = G1, + MICROSD_CS = G0, + MICROSD_DET_REMOVED = HIGH, MICROSD_PWR_EN_ACTIVE = LOW, + + REQUIRED_GPIO_PIN = -1, REQUIRED_GPIO_PIN_ACTIVE = HIGH, + +#elif (HW_TARGET == SPARKFUN_RTK_EVERYWHERE) + // LTE (DCE) + LTE_RESET = -1, LTE_PWR_ON = 26, LTE_ON = 5, LTE_INT = -1, + LTE_TXI = 13, LTE_RXO = 14, LTE_RTS = -1, LTE_CTS = -1, + LTE_RI = -1, LTE_DSR = -1, LTE_DCD = -1, LTE_DTR = -1, + LTE_NI = 34, + LTE_PWR_ON_ACTIVE = HIGH, LTE_ON_ACTIVE = HIGH, + + // Power supply + VIN = -1, V33_EN = 32, V33_EN_ACTIVE = HIGH, + + // Micro SD card + MICROSD_SCK = SCK, MICROSD_SDI = MISO, MICROSD_SDO = MOSI, + MICROSD_DET = 36, MICROSD_PWR_EN = -1, + MICROSD_CS = 4, + MICROSD_DET_REMOVED = LOW, MICROSD_PWR_EN_ACTIVE = LOW, + + // Required GPIO pin - on SPARKFUN_RTK_EVERYWHERE this is the WizNet W5500 CS + REQUIRED_GPIO_PIN = 27, REQUIRED_GPIO_PIN_ACTIVE = HIGH, + +#endif + PIN_INVALID = -1 +}; + +class HW { + +public: + + /** constructor + */ + HW(){ + hwInit(); + } + + void hwInit(void) { + // Do any top-level hardware initialization here: + // Initialize any required GPIO pins + if (PIN_INVALID != REQUIRED_GPIO_PIN) { + digitalWrite(REQUIRED_GPIO_PIN, REQUIRED_GPIO_PIN_ACTIVE); + pinMode(REQUIRED_GPIO_PIN, OUTPUT); + digitalWrite(REQUIRED_GPIO_PIN, REQUIRED_GPIO_PIN_ACTIVE); + } + // Turn on the 3.3V regulator - if present + if (PIN_INVALID != V33_EN) { + digitalWrite(V33_EN, V33_EN_ACTIVE); + pinMode(V33_EN, OUTPUT); + digitalWrite(V33_EN, V33_EN_ACTIVE); + } + log_i("Hardware initialized"); + } + +}; + +HW Hardware; //!< The global HW object + +#endif // __HW_H__ diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h b/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h new file mode 100644 index 0000000..04ba667 --- /dev/null +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h @@ -0,0 +1,929 @@ +/* + * Copyright 2022 by Michael Ammann (@mazgch) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __LTE_H__ +#define __LTE_H__ + +#include +#include + +#include "HW.h" +#include "CONFIG.h" +#include "UBXFILE.h" + +// MNO_GLOBAL is the factory-programmed default +// If you are in Europe, you may find no operators unless you choose MNO_STD_EUROPE +const mobile_network_operator_t MOBILE_NETWORK_OPERATOR = MNO_GLOBAL; + +String CONFIG_VALUE_SIMPIN = ""; // Set the SIM PIN here if needed + +String CONFIG_VALUE_LTEAPN = ""; // Define the APN here if needed + +// Set this to (e.g.) SARA_R5_PSD_PROTOCOL_IPV4V6_V4_PREF if needed. O2 (UK) returns strPdpType "IP" but expects "IPV4V6_V4_PREF" +SARA_R5_pdp_protocol_type_t preferredPdpProtocol = SARA_R5_PSD_PROTOCOL_IPV4; + +const int LTE_1S_RETRY = 1000; //!< standard 1s retry +const int LTE_DETECT_RETRY = 5000; //!< delay between detect attempts +const int LTE_CHECKSIM_RETRY = 60000; //!< delay between SIM Card check attempts, SIM detection may be disables and you need to restart +const int LTE_ACTIVATION_RETRY = 10000; //!< delay between activation attempts +const int LTE_PROVISION_RETRY = 60000; //!< delay between provisioning attempts, provisioning may consume data +const int LTE_CONNECT_RETRY = 10000; //!< delay between server connection attempts to correction severs +const int LTE_MQTTCMD_DELAY = 100; //!< the client is not happy if multiple commands are sent too fast + +const int LTE_POWER_ON_PULSE = 2000; //!< Power on pulse width (2s works for for SARA, LARA and LENA) +const int LTE_POWER_ON_WAITTIME = 4000; //!< Dont't do anything duing this time after the power on pulse +const int LTE_POWER_ON_WAITTIME_MAX = 10000; //!< If the device does not respond until this time, it has failed +const int LTE_POWER_ON_WAITSIMREADY = 4000; //!< It usually takes a few seconds to detect the SIM card. + +const int LTE_PSD_PROFILE = 0; //!< The packet switched data profile used +const int LTE_HTTP_PROFILE = 0; //!< The HTTP profile used duing provisioning +const int LTE_SEC_PROFILE_HTTP = 1; //!< The security profile used for the HTTP connections druing provisioning +const int LTE_SEC_PROFILE_MQTT = 0; //!< The security profile used for the MQTT connections +const char* FILE_REQUEST = "req.json"; //!< Temporarly file name used for the request during HTTP GET transations (ZTP) +const char* FILE_RESP = "resp.json"; //!< Temporarly file name used for the response during HTTP POST and GET transations (AWS/ZTP) +const char* SEC_ROOT_CA ="aws-rootCA"; //!< Temporarly file name used when injecting the ROOT CA +const char* SEC_CLIENT_CERT = "pp-cert"; //!< Temporarly file name used when injecting the client certificate +const char* SEC_CLIENT_KEY = "pp-key"; //!< Temporarly file name used when injecting the client keys + +const uint16_t HTTPS_PORT = 443; //!< The HTTPS default port + +const int LTE_BAUDRATE = 115200; //!< baudrates 230400, 460800 or 921600 cause issues even when CTS/RTS is enabled + +const char* LTE_TASK_NAME = "Lte"; //!< Lte task name +const int LTE_STACK_SIZE = 4*1024; //!< Lte task stack size +const int LTE_TASK_PRIO = 1; //!< Lte task priority +const int LTE_TASK_CORE = 1; //!< Lte task MCU code + +// helper macros to handle the AT interface errors +#define LTE_CHECK_INIT int _step = 0; (void)_step; SARA_R5_error_t _err = SARA_R5_SUCCESS //!< init variable +#define LTE_CHECK_OK (SARA_R5_SUCCESS == _err) //!< record the return result +#define LTE_CHECK(x) if (SARA_R5_SUCCESS == _err) _step = x, _err //!< interim evaluate +#define LTE_CHECK_EVAL(txt) if (SARA_R5_SUCCESS != _err) log_e(txt ", AT sequence failed at step %d with error %d", _step, _err) //!< final verdict and log_e report + +extern class LTE Lte; //!< Forward declaration of class + +/** This class encapsulates all LTE functions. +*/ +class LTE : public SARA_R5 { + +public: + + /** constructor + * \note we do not pass the pins to the class as we prefer to be in control of them + */ + LTE() : SARA_R5{ PIN_INVALID/*LTE_PWR_ON*/, PIN_INVALID/*LTE_RESET*/, 3/*retries*/ } { + state = INIT; + hwInit(); + } + + /** initialize the object, this spins of a worker task. + */ + void init(void) { + xTaskCreatePinnedToCore(task, LTE_TASK_NAME, LTE_STACK_SIZE, this, LTE_TASK_PRIO, NULL, LTE_TASK_CORE); + } + + // Inject MGA (AssistNow) data directly to the internal GNSS using Send of UBX string +UGUBX + SARA_R5_error_t injectMGA(uint8_t *buf, size_t len) { + if (module.startsWith("SARA-R510M8S")) { + + size_t dataPtr = 0; // Pointer into buf + size_t bytesPushed = 0; // Keep count + + while (dataPtr < len) // Keep going until we have processed all the bytes + { + // Start by checking the validity of the packet being pointed to + bool dataIsOK = true; + + dataIsOK &= (*(buf + dataPtr + 0) == 0xB5); // Check for 0xB5 + dataIsOK &= (*(buf + dataPtr + 1) == 0x62); // Check for 0x62 + dataIsOK &= (*(buf + dataPtr + 2) == 0x13); // Check for class UBX-MGA + + size_t packetLength = ((size_t) * (buf + dataPtr + 4)) | (((size_t) * (buf + dataPtr + 5)) << 8); // Extract the length + + uint8_t checksumA = 0; + uint8_t checksumB = 0; + // Calculate the checksum bytes + // Keep going until the end of the packet is reached (payloadPtr == (dataPtr + packetLength)) + // or we reach the end of the AssistNow data (payloadPtr == len) + for (size_t payloadPtr = dataPtr + ((size_t)2); (payloadPtr < (dataPtr + packetLength + ((size_t)6))) && (payloadPtr < len); payloadPtr++) + { + checksumA += *(buf + payloadPtr); + checksumB += checksumA; + } + // Check the checksum bytes + dataIsOK &= (checksumA == *(buf + dataPtr + packetLength + ((size_t)6))); + dataIsOK &= (checksumB == *(buf + dataPtr + packetLength + ((size_t)7))); + + dataIsOK &= ((dataPtr + packetLength + ((size_t)8)) <= len); // Check we haven't overrun + + // If the data is valid, push it + if (dataIsOK) + { + SARA_R5_error_t err; + + char *command; + const char ugubx[] = "+UGUBX=\""; // Include the first quote + command = (char *)calloc(strlen(ugubx) + 3 + ((packetLength + 8) * 2), sizeof(char)); + if (command == nullptr) + return SARA_R5_ERROR_OUT_OF_MEMORY; + sprintf(command, "%s", ugubx); + + size_t ptr = strlen(ugubx); + for (size_t i = 0; i < packetLength + 8; i++) + sprintf(&command[ptr++], "%02X", *(buf + dataPtr + i)); + sprintf(&command[ptr], "\""); + + err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, + nullptr, SARA_R5_STANDARD_RESPONSE_TIMEOUT); + free(command); + + if (err == SARA_R5_ERROR_SUCCESS) { + bytesPushed += packetLength + ((size_t)8); // Increment bytesPushed if the push was successful + log_i("packet ID 0x%02X length %d", *(buf + dataPtr + 3), packetLength); + } + else { + log_e("send UBX fail!"); + } + + dataPtr += packetLength + ((size_t)8); // Point to the next message + } + else + { + // The data was invalid. Send a debug message and then try to find the next 0xB5 + log_w("bad data - ignored! dataPtr is %d", dataPtr); + + while ((dataPtr < len) && (*(buf + ++dataPtr) != 0xB5)) + { + ; // Increment dataPtr until we are pointing at the next 0xB5 - or we reach the end of the data + } + } + } + + if (bytesPushed == len) + return (SARA_R5_ERROR_SUCCESS); + } + return (SARA_R5_ERROR_ERROR); + } + +protected: + + // ----------------------------------------------------------------------- + // MQTT / PointPerfect + // ----------------------------------------------------------------------- + + std::vector topics; //!< vector with current subscribed topics + String subTopic; //!< requested topic to be subscribed (needed by the callback) + String unsubTopic; //!< requested topic to be un-subscribed (needed by the callback) + int mqttMsgs; //!< remember the number of messages pending indicated by the URC + + //! this helper deals with some AT commands that are not yet implemted in LENA-R8 and throw a warning + SARA_R5_error_t LTE_IGNORE_LENA(SARA_R5_error_t err) { + if ((err != SARA_R5_SUCCESS) && module.startsWith("LENA-R8")) { + log_w("AT command error ignored due to LENA-R8 IP Status"); + err = SARA_R5_SUCCESS; + } + return err; + } + + /** Connect to the Thingstream PointPerfect server using the credentials from ZTP process + * \param id the client ID for this device + */ + void mqttConnect(String id) { + String rootCa = AWS_CERT_CA; + String broker = AWS_IOT_ENDPOINT; + String cert = AWS_CERT_CRT; + String key = AWS_CERT_PRIVATE; + // disconncect must fail here, so that we can connect + setMQTTCommandCallback(mqttCallbackStatic); // callback will advance state + // make sure the client is disconnected here + if (SARA_R5_SUCCESS == disconnectMQTT()) { + log_i("forced disconnect"); // if this sucessful it means were were still connected. + } else { + log_i("connect to \"%s:%d\" as client \"%s\"", broker.c_str(), MQTT_BROKER_PORT, id.c_str()); + LTE_CHECK_INIT; + LTE_CHECK(1) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_ROOTCA, SEC_ROOT_CA, rootCa); + LTE_CHECK(2) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_CLIENT_CERT, SEC_CLIENT_CERT, cert); + LTE_CHECK(3) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_CLIENT_KEY, SEC_CLIENT_KEY, key); + LTE_CHECK(4) = LTE_IGNORE_LENA( resetSecurityProfile(LTE_SEC_PROFILE_MQTT) ); + LTE_CHECK(5) = configSecurityProfile(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CERT_VAL_LEVEL, SARA_R5_SEC_PROFILE_CERTVAL_OPCODE_YESNOURL); + LTE_CHECK(6) = configSecurityProfile(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_TLS_VER, SARA_R5_SEC_PROFILE_TLS_OPCODE_VER1_2); + LTE_CHECK(7) = configSecurityProfile(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CYPHER_SUITE, SARA_R5_SEC_PROFILE_SUITE_OPCODE_PROPOSEDDEFAULT); + LTE_CHECK(8) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_ROOT_CA, SEC_ROOT_CA); + LTE_CHECK(9) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CLIENT_CERT, SEC_CLIENT_CERT); + LTE_CHECK(10) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CLIENT_KEY, SEC_CLIENT_KEY); + LTE_CHECK(11) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_SNI, broker); + LTE_CHECK(12) = nvMQTT(SARA_R5_MQTT_NV_RESTORE); + LTE_CHECK(13) = setMQTTclientId(id); + LTE_CHECK(14) = setMQTTserver(broker, MQTT_BROKER_PORT); + LTE_CHECK(15) = setMQTTsecure(true, LTE_SEC_PROFILE_MQTT); + LTE_CHECK(16) = connectMQTT(); + LTE_CHECK_EVAL("setup and connect"); + mqttMsgs = 0; + topics.clear(); + subTopic = ""; + unsubTopic = ""; + } + } + + /** Disconnect and cleanup the MQTT connection + * \return true if already disconnected (no need to wait for the callback) + */ + bool mqttStop(void) { + SARA_R5_error_t err = disconnectMQTT(); + if (SARA_R5_SUCCESS == err) { + log_i("disconnect"); + } else { + log_e("disconnect, failed with error %d", err); + } + return SARA_R5_SUCCESS != err; + } + + /** The MQTT task is responsible for: + * 1) subscribing to topics + * 2) unsubscribing from topics + * 3) read MQTT data from the modem and inject it into the GNSS receiver + */ + void mqttTask(void) { + /* The LTE modem has difficulties subscribing/unsubscribing more than one topic at the same time + * We can only start one operation at a time wait for the URC and add a extra delay before we can + * do the next operation. + */ + bool busy = (0 < subTopic.length()) || (0 < unsubTopic.length()); + if (!busy) { + std::vector newTopics = Config.getTopics(); + // loop through new topics and subscribe to the first topic that is not in our curent topics list. + for (auto it = newTopics.begin(); (it != newTopics.end()) && !busy; it = std::next(it)) { + String topic = *it; + std::vector::iterator pos = std::find(topics.begin(), topics.end(), topic); + if (pos == topics.end()) { + SARA_R5_error_t err = subscribeMQTTtopic(0,topic); + if (SARA_R5_SUCCESS == err) { + log_d("subscribe requested topic \"%s\" qos %d", topic.c_str(), 0); + subTopic = topic; + } else { + log_e("subscribe request topic \"%s\" qos %d, failed with error %d", topic.c_str(), 0, err); + } + busy = true; + } + } + // loop through current topics and unsubscribe to the first topic that is not in the new topics list. + for (auto it = topics.begin(); (it != topics.end()) && !busy; it = std::next(it)) { + String topic = *it; + std::vector::iterator pos = std::find(newTopics.begin(), newTopics.end(), topic); + if (pos == newTopics.end()) { + SARA_R5_error_t err = unsubscribeMQTTtopic(topic); + if (SARA_R5_SUCCESS == err) { + log_d("unsubscribe requested topic \"%s\"", topic.c_str()); + unsubTopic = topic; + } else { + log_e("unsubscribe request topic \"%s\", failed with error %d", topic.c_str(), err); + } + busy = true; + } + } + if (!busy && (0 < mqttMsgs)) { + // at this point we are properly subscribed to the needed topics and can now read data + log_d("read request %d msg", mqttMsgs); + // The MQTT API does not allow getting the size before actually reading the data. So we + // have to allocate a big enough buffer. PointPerfect may send upto 9kB on the MGA topic. + uint8_t *buf = new uint8_t[MQTT_MAX_MSG_SIZE]; + if (buf != NULL) { + String topic; + int len = -1; + int qos = -1; + SARA_R5_error_t err = readMQTT(&qos, &topic, buf, MQTT_MAX_MSG_SIZE, &len); + if (SARA_R5_SUCCESS == err) { + mqttMsgs = 0; // expect a URC afterwards + const char* strTopic = topic.c_str(); + log_i("topic \"%s\" read %d bytes", strTopic, len); + // if we detect data from a topic, then why not unsubscribe from it. + std::vector::iterator pos = std::find(topics.begin(), topics.end(), topic); + if (pos == Lte.topics.end()) { + log_e("getting data from an unexpected topic \"%s\"", strTopic); + if (!busy) { + err = unsubscribeMQTTtopic(topic); + if (SARA_R5_SUCCESS == err) { + log_d("unsubscribe requested for unexpected topic \"%s\"", strTopic); + unsubTopic = topic; + } else { + log_e("unsubscribe request for unexpected topic \"%s\", failed with error %d", topic.c_str(), err); + } + busy = true; + } + } else { + // anything else can be sent to the GNSS as is + len = injectMGA(buf, (size_t)len); + } + } else { + log_e("read failed with error %d", err); + } + // we need to free the buffer, as the inject function took a copy with only the required size + delete [] buf; + } + } + } + } + + /** The MQTT callback processes the URC and is responsible for advancing the state. + * \param command the MQTT command + * \param request the response code 1 = sucess, 0 = error + */ + void mqttCallback(int command, int result) { + log_d("%d command %d result %d", command, result); + if (result == 0) { + int code, code2; + SARA_R5_error_t err = getMQTTprotocolError(&code, &code2); + if (SARA_R5_SUCCESS == err) { + log_e("command %d protocol error code %d code2 %d", command, code, code2); + } else { + log_e("command %d protocol error failed with error", command, err); + } + } else { + switch (command) { + case SARA_R5_MQTT_COMMAND_LOGIN: + if (state != ONLINE) { + log_e("login wrong state"); + } else { + log_i("login"); + setState(MQTT, LTE_MQTTCMD_DELAY); + } + break; + case SARA_R5_MQTT_COMMAND_LOGOUT: + if ((state != MQTT) && (state != ONLINE)) { + log_e("logout wrong state"); + } else { + log_i("logout"); + mqttMsgs = 0; + topics.clear(); + subTopic = ""; + unsubTopic = ""; + setState(ONLINE, LTE_MQTTCMD_DELAY); + } + break; + case SARA_R5_MQTT_COMMAND_SUBSCRIBE: + if (state != MQTT) { + log_e("subscribe wrong state"); + } else if (!subTopic.length()) { + log_e("subscribe result %d but no topic", result); + } else { + log_i("subscribe result %d topic \"%s\"", result, subTopic.c_str()); + topics.push_back(subTopic); + subTopic = ""; + setState(MQTT, LTE_MQTTCMD_DELAY); + } + break; + case SARA_R5_MQTT_COMMAND_UNSUBSCRIBE: + if (state != MQTT) { + log_e("unsubscribe wrong state"); + } else if (!unsubTopic.length()) { + log_e("unsubscribe result %d but no topic", result); + } else { + std::vector::iterator pos = std::find(topics.begin(), topics.end(), unsubTopic); + if (pos == topics.end()) { + topics.erase(pos); + log_i("unsubscribe result %d topic \"%s\"", result, unsubTopic.c_str()); + unsubTopic = ""; + setState(MQTT, LTE_MQTTCMD_DELAY); + } else { + log_e("unsubscribe result %d topic \"%s\" but topic not in list", result, unsubTopic.c_str()); + } + } + break; + case SARA_R5_MQTT_COMMAND_READ: + if (state != MQTT) { + log_e("read wrong state"); + } else { + log_d("read result %d", result); + mqttMsgs = result; + setState(MQTT, LTE_MQTTCMD_DELAY); + } + break; + default: + break; + } + } + } + //! static callback helper, regCallback will do the real work + static void mqttCallbackStatic(int command, int result) { + Lte.mqttCallback(command, result); + } + + + // ----------------------------------------------------------------------- + // LTE + // ----------------------------------------------------------------------- + + String module; //!< a string holding the module name (SARA-R5, LARA-R6 or LENA-R8) + + /** detect the LTE modem + * \return the detection status + */ + bool lteDetect(void) { + bool ok = hwReady(); + if (ok) { + module = getModelID(); + String version = getFirmwareVersion(); + log_i("config manufacturer \"%s\" model=\"%s\" version=\"%s\"", + getManufacturerID().c_str(), module.c_str(), version.c_str()); + if ((version.toDouble() < 0.13) && module.startsWith("LARA-R6")) { + log_e("LARA-R6 firmware %s has MQTT limitations, please update firmware", version.c_str()); + } + else if ((version.toDouble() < 2.00) && module.startsWith("LENA-R8")) { + log_e("LENA-R8 firmware %s has limitations, please update firmware", version.c_str()); + } +#if ((HW_TARGET == UBLOX_XPLR_HPG2_C214) || (HW_TARGET == MAZGCH_HPG_SOLUTION_V09)) + // enableSIMDetectAndHotswap(); +#endif + // wait for the SIM to get ready ... this can take a while (<4s) + SARA_R5_error_t err = SARA_R5_ERROR_ERROR; + for (int i = 0; i < LTE_POWER_ON_WAITSIMREADY/100; i ++) { + err = getSimStatus(NULL); + if (SARA_R5_ERROR_ERROR != err) + break; + delay(100); + } + if (SARA_R5_ERROR_ERROR == err) { + log_e("SIM card not found, err %d", err); + } + } + return ok; + } + + /** initialize the LTE modem and report useful information + * \return initialisation process sucess + */ + bool lteInit(void) { + String code; + LTE_CHECK_INIT; + LTE_CHECK(1) = getSimStatus(&code); + if (LTE_CHECK_OK && code.equals("SIM PIN")) { + if (CONFIG_VALUE_SIMPIN.length()) { + LTE_CHECK(2) = setSimPin(CONFIG_VALUE_SIMPIN.c_str()); + LTE_CHECK(3) = getSimStatus(&code); // retry get the SIM status + LTE_CHECK_EVAL("SIM card initialisation"); + } + } + if (LTE_CHECK_OK) { + if (code.equals("READY")) { + log_i("SIM card status \"%s\" CCID=\"%s\"", code.c_str(), getCCID().c_str()); + String subNo = getSubscriberNo(); + int from = subNo.indexOf(",\""); + int to = (-1 != from) ? subNo.indexOf("\"", from + 2) : -1; + subNo = ((-1 != from) && (-1 != to)) ? subNo.substring(from + 2, to) : ""; + log_i("IMEI=\"%s\" IMSI=\"%s\" subscriber=\"%s\"", getIMEI().c_str(), getIMSI().c_str(), subNo.c_str()); + // configure the MNO profile + if (!module.startsWith("LENA-R8")) { + if (!setNetworkProfile(MOBILE_NETWORK_OPERATOR)) { + log_e("detect setting network profile for MNO %d failed", MOBILE_NETWORK_OPERATOR); + } + } + // register the callbacks + LTE_CHECK_INIT; + LTE_CHECK(1) = setEpsRegistrationCallback(epsRegCallbackStatic); + LTE_CHECK(2) = setRegistrationCallback(regCallbackStatic); + // set the APn + if (CONFIG_VALUE_LTEAPN.length()) { + LTE_CHECK(3) = setAPN(CONFIG_VALUE_LTEAPN); + } + LTE_CHECK_EVAL("callback and apn config"); + return true; + } + else { + log_w("SIM card status \"%s\"", code.c_str()); + } + } + return false; + } + + //! helper look up table for string conversion of registration status + const char *REG_STATUS_LUT[11] = { + "not registered", + "home", + "searching", + "denied", + "unknown", + "roaming", + "home sms only", + "roaming sms only", + "emergency service only", + "home cfsb not preferred", + "roaming cfsb not preferred" + }; + + //! helper look up table for string conversion of network activation + const char *REG_ACT_LUT[10] = { + "GSM", + "GSM COMPACT", + "UTRAN", + "GSM/GPRS + EDGE", + "UTRAN + HSDPA", + "UTRAN + HSUPA", + "UTRAN + HSDPA + HSUPA", + "E-UTRAN", + "EC-GSM-IoT (A/Gb mode)", + "E-UTRAN (NB-S1 mode)" + }; + + //! helper to safely convert the string with above tables + #define REG_LUT(l, x) ((((unsigned int)x) <= (sizeof(l)/sizeof(*l))) ? l[x] : "unknown") + + /** check if we are registered + * \return registration success + */ + bool lteRegistered(void) { + SARA_R5_registration_status_t status = registration(true); // EPS + const char* statusText = REG_LUT(REG_STATUS_LUT, status); + if ((status == SARA_R5_REGISTRATION_HOME) || (status == SARA_R5_REGISTRATION_ROAMING)) { + String op = ""; + getOperator(&op); + log_i("registered %d(%s) operator \"%s\" rssi %d clock \"%s\"", + status, statusText, op.c_str(), rssi(), clock().c_str()); + // AT+UPSV? + // AT+CLCK="SC",2 + return true; + } + else { + log_d("EPS registration status %d(%s), waiting ...", status, statusText); + } + return false; + } + + /** evaluate registration status and report it + * \param status the registration status + * \param tacLac the tac or lac + * \param ci the ci + * \param Act the network activation + * \param strTacLac a string indicating if tacLac is either tac or lac. + */ + void regCallback(SARA_R5_registration_status_t status, unsigned int tacLac, unsigned int ci, int Act, const char* strTacLac) + { + const char* actText = REG_LUT(REG_ACT_LUT, Act); + const char* statusText = REG_LUT(REG_STATUS_LUT, status); + log_d("status %d(%s) %s \"%04X\" ci \"%08X\" Act %d(%s)", status, statusText, strTacLac, tacLac, ci, Act, actText); /*unused*/ (void)actText; (void)statusText; + if (((status == SARA_R5_REGISTRATION_HOME) || (status == SARA_R5_REGISTRATION_ROAMING)) && (state < REGISTERED)) { + setState(REGISTERED); + } else if ((status == SARA_R5_REGISTRATION_SEARCHING) && (state >= REGISTERED)) { + setState(WAITREGISTER); + } + } + //! static callback helper, regCallback will do the real work + static void epsRegCallbackStatic(SARA_R5_registration_status_t status, unsigned int tac, unsigned int ci, int Act) { + Lte.regCallback(status, tac, ci, Act, "tac"); + } + //! static callback helper, regCallback will do the real work + static void regCallbackStatic(SARA_R5_registration_status_t status, unsigned int lac, unsigned int ci, int Act) { + Lte.regCallback(status, lac, ci, Act, "lac"); + } + + /** make sure the modem is activated properly. Some modem do this automatically other need some help. + * \return activaion sucess + */ + bool lteActivate(void) { + if (module.startsWith("LARA-R6")) { + return true; + } else if (module.startsWith("LENA-R8")) { + String apn; + IPAddress ip(0, 0, 0, 0); + SARA_R5_pdp_type pdpType = PDP_TYPE_INVALID; + LTE_CHECK_INIT; + LTE_CHECK(1) = getAPN(0, &apn, &ip, &pdpType); + // on LENA-R8 we need to move a IP context 0 to a different context id other than 0 + // ok if these commands fail as the context could be already active ... + if (LTE_CHECK_OK && (0 < apn.length()) && (pdpType != PDP_TYPE_NONIP)) { + setAPN(apn, 1 /* LENA only has contexts 0 to 7, do not use SARA_R5_NUM_PDP_CONTEXT_IDENTIFIERS-1 */ , pdpType); + } + activatePDPcontext(true); + LTE_CHECK_EVAL("LTE activate context"); + if (LTE_CHECK_OK) { + return true; + } + } else /* SARA-R5 */ { + performPDPaction(LTE_PSD_PROFILE, SARA_R5_PSD_ACTION_DEACTIVATE); + for (int cid = 0; cid < SARA_R5_NUM_PDP_CONTEXT_IDENTIFIERS; cid++) + { + String apn; + IPAddress ip(0, 0, 0, 0); + SARA_R5_pdp_type pdpType = PDP_TYPE_INVALID; + LTE_CHECK_INIT; + LTE_CHECK(1) = getAPN(cid, &apn, &ip, &pdpType); + if (SARA_R5_PSD_PROTOCOL_IPV4 != preferredPdpProtocol) + pdpType = (SARA_R5_pdp_type)preferredPdpProtocol; // Override the PDP Protocol if desired + if ((LTE_CHECK_OK) && (0 < apn.length()) && (PDP_TYPE_INVALID != pdpType)) + { + // Activate the profile + log_i("activate profile for apn \"%s\" with IP %s pdp %d", apn.c_str(), ip.toString().c_str(), pdpType); + setPSDActionCallback(psdCallbackStatic); + LTE_CHECK(2) = setPDPconfiguration(LTE_PSD_PROFILE, SARA_R5_PSD_CONFIG_PARAM_PROTOCOL, pdpType); + LTE_CHECK(3) = setPDPconfiguration(LTE_PSD_PROFILE, SARA_R5_PSD_CONFIG_PARAM_MAP_TO_CID, cid); + LTE_CHECK(4) = performPDPaction(LTE_PSD_PROFILE, SARA_R5_PSD_ACTION_ACTIVATE); + LTE_CHECK_EVAL("profile activation"); + if (LTE_CHECK_OK) { + return true; // abort the loop as we found a good profile + } + } + } + } + return false; + } + + /** the packet switched data activation callback + * \param profile the psd profile + * \param ip the ip address for this profile + */ + static void psdCallbackStatic(int profile, IPAddress ip) { + log_d("psdCallback profile %d IP %s", profile, ip.toString().c_str()); + if (profile == LTE_PSD_PROFILE) { + Lte.setState(ONLINE); + } + } + + // ----------------------------------------------------------------------- + // STATEMACHINE + // ----------------------------------------------------------------------- + + //! states of the statemachine + typedef enum { + INIT = 0, + CHECKSIM, + SIMREADY, + WAITREGISTER, + REGISTERED, + ONLINE, + MQTT, + NTRIP, + NUM + } STATE; + //! string conversion helper table, must be aligned and match with STATE + const char* STATE_LUT[STATE::NUM] = { + "init", + "check sim", + "sim ready", + "wait register", + "registered", + "online", + "mqtt", + "ntrip" + }; + STATE state; //!< the current state + int32_t ttagNextTry; //!< time tag when to call the state machine again + + /** advance the state and report transitions + * \param newState the new state + * \param delay schedule delay + */ + void setState(STATE newState, int32_t delay = 0) { + if (state != newState) { + log_i("state change %d(%s)", newState, STATE_LUT[newState]); + state = newState; + } + ttagNextTry = millis() + delay; + } + + /* FreeRTOS static task function, will just call the objects task function + * \param pvParameters the Lte object (this) + */ + static void task(void * pvParameters) { + ((LTE*) pvParameters)->task(); + } + + /** This task handling the whole LTE state machine, here is where different activities are scheduled + * and where the code decides what actions to perform. + */ + void task(void) { + if (!lteDetect()) { + log_w("LARA-R6/SARA-R5/LENA-R8 not detected, check wiring"); + } else { + setState(CHECKSIM); + } + while(true) { + if ((PIN_INVALID != LTE_ON) && (state != INIT)) { + // detect if LTE was turned off + if (LTE_ON_ACTIVE != digitalRead(LTE_ON)) { + UbxSerial.end(); + setState(INIT, LTE_DETECT_RETRY); + } + } + + if (state != INIT) { + SARA_R5::poll(); + } + + int32_t now = millis(); + if (0 >= (ttagNextTry - now)) { + ttagNextTry = now + LTE_1S_RETRY; + String id = MQTT_CLIENT_ID; + bool useMqtt = true; + switch (state) { + case INIT: + ttagNextTry = now + LTE_DETECT_RETRY; + if (lteDetect()) { + setState(CHECKSIM); + } + break; + case CHECKSIM: + ttagNextTry = now + LTE_CHECKSIM_RETRY; + if (lteInit()) { + setState(WAITREGISTER); + } + break; + case WAITREGISTER: + if (lteRegistered()) { + setState(REGISTERED); + } + break; + case REGISTERED: + ttagNextTry = now + LTE_ACTIVATION_RETRY; + if (lteActivate()) { + setState(ONLINE); + } + break; + case ONLINE: + if (useMqtt) { + { + ttagNextTry = now + LTE_CONNECT_RETRY; + mqttConnect(id); // callback will advance the state + } + } + break; + case MQTT: + if (!useMqtt || (0 == id.length())) { + if (mqttStop()) { + setState(ONLINE); + } + } else { + mqttTask(); + } + break; + default: + setState(INIT); + break; + } + } + vTaskDelay(30); + } + } + + // ----------------------------------------------------------------------- + // HARDWARE + // ----------------------------------------------------------------------- + + /** The modem is connected using different GPIOs and full 8 wire Serial port. + * this function makes sure that at boot all the pins are properly configured. + * The pins are defined in the HW.h file and depend on the board compiled for. + */ + void hwInit(void) { + // The current SARA-R5 library is setting the LTE_RESET and LTE_PWR_ON pins to input after using them, + // This is not ideal for the v0.8/v0.9 hardware. Therefore it is prefered to control the LTE_RESET + // and LTE_PWR_ON externally in our code here. We also like to have better control of the pins to + // support different variants of LTE modems. + + // Module Power On time Power Off time Reset + // SARA-R5 0.1/1-2s 23s + 1.5sReset 0.1s + // LARA-R6 0.15-3.2s >1.5s 0.05-6s (10s emergency) + // LENA-R8 >2s >3.1s 0.05s + + // The LTE_RESET pin is active LOW, HIGH = idle, LOW = in reset + // The LTE_RESET pin unfortunately does not have a pull up resistor on the v0.8/v0.9 hardware + if (PIN_INVALID != LTE_RESET) { + digitalWrite(LTE_RESET, HIGH); + pinMode(LTE_RESET, OUTPUT); + digitalWrite(LTE_RESET, HIGH); + } + // The LTE_PWR_ON pin is usually active HIGH, LOW = idle, defined by LTE_PWR_ON_ACTIVE, active timings see table above + // The LTE_PWR_ON pin has a external pull low resistor on the board. + if (PIN_INVALID != LTE_PWR_ON) { + digitalWrite(LTE_PWR_ON, !LTE_PWR_ON_ACTIVE); + pinMode(LTE_PWR_ON, OUTPUT); + digitalWrite(LTE_PWR_ON, !LTE_PWR_ON_ACTIVE); + } + if (PIN_INVALID != LTE_TXI) { + digitalWrite(LTE_TXI, HIGH); + pinMode(LTE_TXI, OUTPUT); + digitalWrite(LTE_TXI, HIGH); + } + if (PIN_INVALID != LTE_RTS) { + digitalWrite(LTE_RTS, LOW); + pinMode(LTE_RTS, OUTPUT); + digitalWrite(LTE_RTS, LOW); + } + if (PIN_INVALID != LTE_DTR) { + digitalWrite(LTE_DTR, LOW); + pinMode(LTE_DTR, OUTPUT); + digitalWrite(LTE_DTR, LOW); + } + // init all other pins here + if (PIN_INVALID != LTE_ON) pinMode(LTE_ON, INPUT); + if (PIN_INVALID != LTE_RXO) pinMode(LTE_RXO, INPUT); + if (PIN_INVALID != LTE_CTS) pinMode(LTE_CTS, INPUT); + if (PIN_INVALID != LTE_DSR) pinMode(LTE_DSR, INPUT); + if (PIN_INVALID != LTE_DCD) pinMode(LTE_DCD, INPUT); + if (PIN_INVALID != LTE_RI) pinMode(LTE_RI, INPUT); + if (PIN_INVALID != LTE_INT) pinMode(LTE_INT, INPUT); + } + + /** The modem is started using a PWR_ON pin. This process takes quite some time and is a + * critical phase. Duing the process it is a bad idea to try to talk to the modem too early. + * \return true if hardware is ready. + */ + bool hwReady(void) { + // turn on the module + #define DETECT_DELAY 100 + int pwrOnTime = -1; // will never trigger + if (PIN_INVALID != LTE_PWR_ON) { + if ((PIN_INVALID != LTE_ON) ? LTE_ON_ACTIVE != digitalRead(LTE_ON) : true) { + log_i("LTE power on"); + digitalWrite(LTE_PWR_ON, LTE_PWR_ON_ACTIVE); + pwrOnTime = LTE_POWER_ON_PULSE / DETECT_DELAY; + } + } + bool ready = true; + char lastCts = -1; + char lastOn = -1; + char lastRxo = -1; + for (int i = 0; i < LTE_POWER_ON_WAITTIME_MAX / DETECT_DELAY; i ++) { + ready = (pwrOnTime < 0); + if (i == pwrOnTime) { + digitalWrite(LTE_PWR_ON, !LTE_PWR_ON_ACTIVE); + log_d("LTE pin PWR_ON off(idle)"); + pwrOnTime = -1; // no more + i = 0; // restart timer + } + if (PIN_INVALID != LTE_RXO) { + char rxo = digitalRead(LTE_RXO); + if (lastRxo != rxo) { + log_d("LTE pin RXO %s", (rxo == LOW) ? "LOW(active)" : "HIGH(idle)"); + lastRxo = rxo; + } + ready = ready && (rxo == HIGH); + } + if (PIN_INVALID != LTE_ON) { + char on = digitalRead(LTE_ON); + if (on != lastOn) { + log_d("LTE pin ON %s", (on == LTE_ON_ACTIVE) ? "on(active)" : "off(idle)"); + lastOn = on; + } + ready = ready && (on == LTE_ON_ACTIVE); + } + if (PIN_INVALID != LTE_CTS) { + char cts = digitalRead(LTE_CTS); + if (lastCts != cts) { + log_d("LTE pin CTS %s", (cts == LOW) ? "LOW(idle)" : "HIGH(wait)"); + lastCts = cts; + } + ready = ready && (cts == LOW); + } + if (ready && (i > LTE_POWER_ON_WAITTIME / DETECT_DELAY)) { + break; + } + delay(DETECT_DELAY); + } + if (ready) { + log_i("LTE ready"); + } else { + log_w("not ready RXO PWRON CTS : %d %d %d != 1 0 0", lastRxo, lastOn, lastCts); + } + if (ready) { + #define PIN_TXT(pin) (PIN_INVALID == pin) ? "" : digitalRead(pin) == LOW ? " LOW" : " HIGH" + log_d("baudrate %d pins RXo %d%s TXi %d%s CTSo %d%s RTSi %d%s", LTE_BAUDRATE, + LTE_RXO, PIN_TXT(LTE_RXO), LTE_TXI, PIN_TXT(LTE_TXI), + LTE_CTS, PIN_TXT(LTE_CTS), LTE_RTS, PIN_TXT(LTE_RTS)); + ready = begin(UbxSerial, LTE_BAUDRATE); + } + return ready; + } + + /** We need to sub class the Sparkfun class, and override this function as we are + * using a four wire serial port with flow control that needs to be preserved. + * \param baud the desired baud rate + */ + void beginSerial(unsigned long baud) override + { + delay(100); + UbxSerial.end(); + delay(10); + log_d("LTE baudrate %d pins RXo %d TXi %d CTSo %d RTSi %d", baud, LTE_RXO, LTE_TXI, LTE_CTS, LTE_RTS); + UbxSerial.begin(baud, SERIAL_8N1, LTE_RXO, LTE_TXI); + if ((PIN_INVALID != LTE_RTS) && (PIN_INVALID != LTE_CTS)) { + UbxSerial.setPins(LTE_RXO, LTE_TXI, LTE_CTS, LTE_RTS); + UbxSerial.setHwFlowCtrlMode(HW_FLOWCTRL_CTS_RTS, 64); + } + delay(100); + } +}; + +LTE Lte; //!< The global LTE peripherial object + +#endif // __LTE_H__ diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/SARA-R5_Example17_AssistNow_MQTT.ino b/examples/SARA-R5_Example17_AssistNow_MQTT/SARA-R5_Example17_AssistNow_MQTT.ino new file mode 100644 index 0000000..3e40462 --- /dev/null +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/SARA-R5_Example17_AssistNow_MQTT.ino @@ -0,0 +1,139 @@ +/* + + SARA-R5 Example + =============== + + Assist Now via MQTT + + This example demonstrates how to request u-blox PointPerfect AssistNow Online data from Thingstream + using MQTT, and push it to the SARA-R510M8S internal GNSS. It also demonstrates how quickly the internal + GNSS can achieve a 3D fix from a cold start with help from AssistNow. + + AssistNow Online data is available via HTTP GET using a PointPerfect AssistNow Token. See Example12 for + more details. But here we are using MQTT, using the SARA-R5's built-in MQTT commands. This means we need + a Client Certificate and Client Key, plus the Amazon root Server Certificate to be able to connect and + subscribe to the /pp/ubx/mga AssistNow topic. See: + https://developer.thingstream.io/guides/location-services/pointperfect-getting-started + Specifically: + https://developer.thingstream.io/guides/location-services/pointperfect-getting-started#h.vn9iuugch1ah + A free PointPerfect Developer plan will provide capped access to the IP stream - and allow you to + download the MQTT credentials: + https://portal.thingstream.io/pricing/laas/laaspointperfect + + Log in to ThingStream, create a Location Thing, select the PointPerfect Developer plan, activate the + Location Thing, navigate to the Credentials tab, download the Client Key, Client Certificate and + Amazon Root Certificate, paste all three into secrets.h. + + The PDP profile is read from NVM. Please make sure you have run examples 4 & 7 previously to set up the profile. + + This example is written for the SparkFun Asset Tracker Carrier Board with ESP32 MicroMod Processor Board: + https://www.sparkfun.com/products/17272 + https://www.sparkfun.com/products/16781 + + This example contains a stripped down version of Michael Ammann (@mazgch)'s HPG Solution: + https://github.com/mazgch/hpg + Specifically, LTE.h: + https://github.com/mazgch/hpg/blob/main/software/LTE.h + + We have written this example for the ESP32 Processor Board because Michael's code: is written using + ESP32 mbed tasks; and uses ESP32 log_ for diagnostic messages, instead of Serial.print. + This means you can limit the messages to Error, Warn, Debug, Info by selecting the appropriate + Core Debug Level in the board options. + + ************************************************************************************************** + * Important Note: * + * * + * This example pulls kBytes of correction data from Thingstream. * + * Depending on your location and service provider, the data rate may exceed the allowable * + * rates for LTE-M or NB-IoT. * + * Worst case, your service provider may throttle or block the connection - now or in the future. * + ************************************************************************************************** + + Feel like supporting open source hardware? + Buy a board from SparkFun! + + Adapted by: Paul Clark + Date: November 10th 2023 + + Licence: + + The code written by Michael Ammann is: + + * Copyright 2022 by Michael Ammann (@mazgch) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. +*/ + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_SARA-R5_Arduino_Library + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// Michael Ammann (@mazgch)'s HPG Solution: +// https://github.com/mazgch/hpg + +#include "HW.h" +#include "CONFIG.h" +#include "UBXFILE.h" +#include "LTE.h" + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void setup() +{ + + delay(1000); // Wait for the ESP32 to start up + + Serial.begin(115200); // Start the serial console + + Serial.println(F("SARA-R5 Example")); + + log_i("-------------------------------------------------------------------"); + + String hwName = Config.getDeviceName(); + log_i("SARA-R5_Example17_AssistNow_MQTT %s (%s)", Config.getDeviceTitle().c_str(), hwName.c_str()); + espVersion(); + + //Lte.enableDebugging(UbxSerial); + //Lte.enableAtDebugging(Serial); // we use UbxSerial for data logging instead + Lte.init(); // LTE runs in a task +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +void loop() +{ + // Nothing to do here! Everything is performed by tasks... +} + +//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + +// ==================================================================================== +// Helpers +// ==================================================================================== + +#ifndef ESP_ARDUINO_VERSION + #include +#endif + +/** Print the version number of the Arduino and ESP core. + */ +void espVersion(void) { +#ifndef ESP_ARDUINO_VERSION + log_i("Version IDF %s Arduino_esp32 %s", esp_get_idf_version(), ARDUINO_ESP32_RELEASE); +#else + log_i("Version IDF %s Arduino_esp32 %d.%d.%d", esp_get_idf_version(), + ESP_ARDUINO_VERSION_MAJOR,ESP_ARDUINO_VERSION_MINOR,ESP_ARDUINO_VERSION_PATCH); +#endif +} diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/UBXFILE.h b/examples/SARA-R5_Example17_AssistNow_MQTT/UBXFILE.h new file mode 100644 index 0000000..5c8697b --- /dev/null +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/UBXFILE.h @@ -0,0 +1,127 @@ +/* + * Copyright 2022 by Michael Ammann (@mazgch) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __UBXFILE_H__ +#define __UBXFILE_H__ + +#include + +const int UBXSERIAL_BUFFER_SIZE = 0*1024; //!< Size of circular buffer, typically AT modem gets bursts upto 9kB of MQTT data, but 2kB is also fine + +/** older versions of ESP32_Arduino do not yet support flow control, but we need this for the modem. + * The following flag will make sure code is added for this, + */ +#include "driver/uart.h" // for flow control +#if !defined(HW_FLOWCTRL_CTS_RTS) || !defined(ESP_ARDUINO_VERSION) || !defined(ESP_ARDUINO_VERSION_VAL) + #define UBXSERIAL_OVERRIDE_FLOWCONTROL +#elif (ESP_ARDUINO_VERSION <= ESP_ARDUINO_VERSION_VAL(2, 0, 3)) + #define UBXSERIAL_OVERRIDE_FLOWCONTROL +#endif + +/** This class encapsulates all UBXSERIAL functions. the class can be used as alternative to a + * normal Serial port, but add full RX and TX logging capability. +*/ +class UBXSERIAL : public HardwareSerial { +public: + + /** constructor + * \param size the circular buffer size + * \param uart_num the hardware uart number + */ + UBXSERIAL(size_t size, uint8_t uart_num) + : HardwareSerial{uart_num}, buffer{size} { + mutex = xSemaphoreCreateMutex(); // Previously, this was inherited from class UBXFILE + setRxBufferSize(256); + } + + // -------------------------------------------------------------------------------------- + // STREAM interface: https://github.com/arduino/ArduinoCore-API/blob/master/api/Stream.h + // -------------------------------------------------------------------------------------- + + /** The character written is also passed into the circular buffer + * \param ch character to write + * \return the bytes written + */ + size_t write(uint8_t ch) override { + if (pdTRUE == xSemaphoreTake(mutex, portMAX_DELAY)) { + if (buffer.size() > 1) { + buffer.write((const char*)&ch, 1); + } + xSemaphoreGive(mutex); + } + return HardwareSerial::write(ch); + } + + /** All data written is also passed into the circular buffer + * \param ptr pointer to buffer to write + * \param size number of bytes in ptr to write + * \return the bytes written + */ + size_t write(const uint8_t *ptr, size_t size) override { + if (pdTRUE == xSemaphoreTake(mutex, portMAX_DELAY)) { + if (buffer.size() > 1) { + buffer.write((const char*)ptr, size); + } + xSemaphoreGive(mutex); + } + return HardwareSerial::write(ptr, size); + } + + /** The character read is also passed in also passed into the circular buffer. + * \return the character read + */ + int read(void) override { + int ch = HardwareSerial::read(); + if (-1 != ch) { + if (pdTRUE == xSemaphoreTake(mutex, portMAX_DELAY)) { + if (buffer.size() > 1) { + buffer.write((const char*)&ch, 1); + } + xSemaphoreGive(mutex); + } + } + return ch; + } + +#ifdef UBXSERIAL_OVERRIDE_FLOWCONTROL + // The arduino_esp32 core has a bug that some pins are swapped in the setPins function. + // PR https://github.com/espressif/arduino-esp32/pull/6816#pullrequestreview-987757446 was issued + // We will override as we cannot rely on that bugfix being applied in the users environment. + + // extend the flow control API while on older arduino_ESP32 revisions + // we keep the API forward compatible so that when the new platform is released it just works + void setPins(int8_t rxPin, int8_t txPin, int8_t ctsPin, int8_t rtsPin) { + uart_set_pin((uart_port_t)_uart_nr, txPin, rxPin, rtsPin, ctsPin); + } + + void setHwFlowCtrlMode(uint8_t mode, uint8_t threshold) { + uart_set_hw_flow_ctrl((uart_port_t)_uart_nr, (uart_hw_flowcontrol_t) mode, threshold); + } + + #ifndef HW_FLOWCTRL_CTS_RTS + #define HW_FLOWCTRL_CTS_RTS UART_HW_FLOWCTRL_CTS_RTS + #endif +#endif + +protected: + + SemaphoreHandle_t mutex; //!< protects cbuf from concurnet access by tasks. + cbuf buffer; //!< the circular local buffer +}; + +UBXSERIAL UbxSerial(UBXSERIAL_BUFFER_SIZE, UART_NUM_1); //!< The global UBXSERIAL peripherial object (replaces Serial1) + +#endif // __UBXFILE_H__ diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h b/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h new file mode 100644 index 0000000..028310a --- /dev/null +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h @@ -0,0 +1,35 @@ +// Below infomation you can set after signing up with u-blox Thingstream portal +// and after add a new New PointPerfect Thing +// https://portal.thingstream.io/app/location-services/things +// in the new PointPerfect Thing you go to the credentials page and copy paste the values and certificate into this. + +// -> Credentials -> Hostname +const char AWS_IOT_ENDPOINT[] = "pp.services.u-blox.com"; +const unsigned short AWS_IOT_PORT = 8883; +// -> Credentials -> AssistNow (MGA) topic +const char MQTT_TOPIC_ASSISTNOW[] = "/pp/ubx/mga"; + +// -> Credentials -> Client Id +static const char MQTT_CLIENT_ID[] = ""; + +// -> Credentials -> Amazon Root Certificate +static const char AWS_CERT_CA[] PROGMEM = R"EOF( +-----BEGIN CERTIFICATE----- + +-----END CERTIFICATE----- +)EOF"; + +// -> Credentials -> Client Certificate +static const char AWS_CERT_CRT[] PROGMEM = R"KEY( +-----BEGIN CERTIFICATE----- + +-----END CERTIFICATE----- +)KEY"; + +// Get this from Thingstream Portal +// -> Credentials -> Client Key +static const char AWS_CERT_PRIVATE[] PROGMEM = R"KEY( +-----BEGIN RSA PRIVATE KEY----- + +-----END RSA PRIVATE KEY----- +)KEY"; From 8cab36a1a943e018cef9c1c31ad331cf13143b8f Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 13 Nov 2023 16:20:27 +0000 Subject: [PATCH 02/11] Update CONFIG.h --- examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h b/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h index 35062c3..1760d9e 100644 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h @@ -44,14 +44,6 @@ const char MQTT_TOPIC_MGA_BDS[] = MQTT_TOPIC_MGA "/bds"; //!< Beidou (CN) #define CONFIG_DEVICE_TITLE "HPG solution" //!< a used friendly name #define CONFIG_DEVICE_NAMEPREFIX "hpg" //!< a hostname compatible prefix, only a-z, 0-9 and - -// PointPerfect configuration -const char CONFIG_VALUE_BROKERHOST[] = "brokerHost"; //!< config key for brocker host -const char CONFIG_VALUE_STREAM[] = "stream"; //!< config key for stream -const char CONFIG_VALUE_ROOTCA[] = "rootCa"; //!< config key for root certificate -const char CONFIG_VALUE_CLIENTCERT[] = "clientCert"; //!< config key for client certificate -const char CONFIG_VALUE_CLIENTKEY[] = "clientKey"; //!< config key for client keys -const char CONFIG_VALUE_CLIENTID[] = "clientId"; //!< config key for client id - /** This class encapsulates all WLAN functions. */ class CONFIG { From f06bb2fbea8363d9471603f3e31c7dc063096775 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 13 Nov 2023 16:20:44 +0000 Subject: [PATCH 03/11] Update LTE.h --- .../SARA-R5_Example17_AssistNow_MQTT/LTE.h | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h b/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h index 04ba667..affc2e7 100644 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h @@ -33,7 +33,7 @@ String CONFIG_VALUE_SIMPIN = ""; // Set the SIM PIN here if needed String CONFIG_VALUE_LTEAPN = ""; // Define the APN here if needed // Set this to (e.g.) SARA_R5_PSD_PROTOCOL_IPV4V6_V4_PREF if needed. O2 (UK) returns strPdpType "IP" but expects "IPV4V6_V4_PREF" -SARA_R5_pdp_protocol_type_t preferredPdpProtocol = SARA_R5_PSD_PROTOCOL_IPV4; +SARA_R5_pdp_protocol_type_t preferredPdpProtocol = SARA_R5_PSD_PROTOCOL_IPV4; // SARA_R5_PSD_PROTOCOL_IPV4V6_V4_PREF; const int LTE_1S_RETRY = 1000; //!< standard 1s retry const int LTE_DETECT_RETRY = 5000; //!< delay between detect attempts @@ -63,7 +63,7 @@ const uint16_t HTTPS_PORT = 443; //!< The HTTPS default port const int LTE_BAUDRATE = 115200; //!< baudrates 230400, 460800 or 921600 cause issues even when CTS/RTS is enabled const char* LTE_TASK_NAME = "Lte"; //!< Lte task name -const int LTE_STACK_SIZE = 4*1024; //!< Lte task stack size +const int LTE_STACK_SIZE = 8*1024; //!< Lte task stack size const int LTE_TASK_PRIO = 1; //!< Lte task priority const int LTE_TASK_CORE = 1; //!< Lte task MCU code @@ -202,21 +202,17 @@ class LTE : public SARA_R5 { * \param id the client ID for this device */ void mqttConnect(String id) { - String rootCa = AWS_CERT_CA; - String broker = AWS_IOT_ENDPOINT; - String cert = AWS_CERT_CRT; - String key = AWS_CERT_PRIVATE; // disconncect must fail here, so that we can connect setMQTTCommandCallback(mqttCallbackStatic); // callback will advance state // make sure the client is disconnected here if (SARA_R5_SUCCESS == disconnectMQTT()) { log_i("forced disconnect"); // if this sucessful it means were were still connected. } else { - log_i("connect to \"%s:%d\" as client \"%s\"", broker.c_str(), MQTT_BROKER_PORT, id.c_str()); + log_i("connect to \"%s:%d\" as client \"%s\"", AWS_IOT_ENDPOINT, MQTT_BROKER_PORT, id.c_str()); LTE_CHECK_INIT; - LTE_CHECK(1) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_ROOTCA, SEC_ROOT_CA, rootCa); - LTE_CHECK(2) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_CLIENT_CERT, SEC_CLIENT_CERT, cert); - LTE_CHECK(3) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_CLIENT_KEY, SEC_CLIENT_KEY, key); + LTE_CHECK(1) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_ROOTCA, SEC_ROOT_CA, AWS_CERT_CA); + LTE_CHECK(2) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_CLIENT_CERT, SEC_CLIENT_CERT, AWS_CERT_CRT); + LTE_CHECK(3) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_CLIENT_KEY, SEC_CLIENT_KEY, AWS_CERT_PRIVATE); LTE_CHECK(4) = LTE_IGNORE_LENA( resetSecurityProfile(LTE_SEC_PROFILE_MQTT) ); LTE_CHECK(5) = configSecurityProfile(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CERT_VAL_LEVEL, SARA_R5_SEC_PROFILE_CERTVAL_OPCODE_YESNOURL); LTE_CHECK(6) = configSecurityProfile(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_TLS_VER, SARA_R5_SEC_PROFILE_TLS_OPCODE_VER1_2); @@ -224,10 +220,10 @@ class LTE : public SARA_R5 { LTE_CHECK(8) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_ROOT_CA, SEC_ROOT_CA); LTE_CHECK(9) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CLIENT_CERT, SEC_CLIENT_CERT); LTE_CHECK(10) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CLIENT_KEY, SEC_CLIENT_KEY); - LTE_CHECK(11) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_SNI, broker); + LTE_CHECK(11) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_SNI, AWS_IOT_ENDPOINT); LTE_CHECK(12) = nvMQTT(SARA_R5_MQTT_NV_RESTORE); LTE_CHECK(13) = setMQTTclientId(id); - LTE_CHECK(14) = setMQTTserver(broker, MQTT_BROKER_PORT); + LTE_CHECK(14) = setMQTTserver(AWS_IOT_ENDPOINT, MQTT_BROKER_PORT); LTE_CHECK(15) = setMQTTsecure(true, LTE_SEC_PROFILE_MQTT); LTE_CHECK(16) = connectMQTT(); LTE_CHECK_EVAL("setup and connect"); From a6a8cab5985d2f2b787a602ecb8c5247886672a0 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 13 Nov 2023 16:30:49 +0000 Subject: [PATCH 04/11] Update secrets.h --- .../secrets.h | 30 ++++++------------- 1 file changed, 9 insertions(+), 21 deletions(-) diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h b/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h index 028310a..fcc14f7 100644 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h @@ -3,33 +3,21 @@ // https://portal.thingstream.io/app/location-services/things // in the new PointPerfect Thing you go to the credentials page and copy paste the values and certificate into this. -// -> Credentials -> Hostname -const char AWS_IOT_ENDPOINT[] = "pp.services.u-blox.com"; -const unsigned short AWS_IOT_PORT = 8883; -// -> Credentials -> AssistNow (MGA) topic -const char MQTT_TOPIC_ASSISTNOW[] = "/pp/ubx/mga"; - // -> Credentials -> Client Id static const char MQTT_CLIENT_ID[] = ""; // -> Credentials -> Amazon Root Certificate -static const char AWS_CERT_CA[] PROGMEM = R"EOF( ------BEGIN CERTIFICATE----- - ------END CERTIFICATE----- -)EOF"; +static const char AWS_CERT_CA[] PROGMEM = R"(-----BEGIN CERTIFICATE----- + +-----END CERTIFICATE-----)"; // -> Credentials -> Client Certificate -static const char AWS_CERT_CRT[] PROGMEM = R"KEY( ------BEGIN CERTIFICATE----- - ------END CERTIFICATE----- -)KEY"; +static const char AWS_CERT_CRT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- + +-----END CERTIFICATE-----)"; // Get this from Thingstream Portal // -> Credentials -> Client Key -static const char AWS_CERT_PRIVATE[] PROGMEM = R"KEY( ------BEGIN RSA PRIVATE KEY----- - ------END RSA PRIVATE KEY----- -)KEY"; +static const char AWS_CERT_PRIVATE[] PROGMEM = R"(-----BEGIN RSA PRIVATE KEY----- + +-----END RSA PRIVATE KEY-----)"; From 7b45c03c712f70d6fadb47588a037c7498321a1d Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 13 Nov 2023 16:31:39 +0000 Subject: [PATCH 05/11] Update CONFIG.h --- examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h b/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h index 1760d9e..8f22991 100644 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h @@ -27,6 +27,7 @@ // MQTT / PointPerfect settings // ----------------------------------------------------------------------- +const char AWS_IOT_ENDPOINT[] = "pp.services.u-blox.com"; const unsigned short MQTT_BROKER_PORT = 8883; //!< MQTTS port const int MQTT_MAX_MSG_SIZE = 9*1024; //!< the max size of a MQTT pointperfect topic From 358a603eb234e7d82bf61b3b91f7787253f02f94 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 13 Nov 2023 19:21:40 +0000 Subject: [PATCH 06/11] Update secrets.h --- examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h b/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h index fcc14f7..88ac5bb 100644 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h +++ b/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h @@ -16,7 +16,6 @@ static const char AWS_CERT_CRT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- -----END CERTIFICATE-----)"; -// Get this from Thingstream Portal // -> Credentials -> Client Key static const char AWS_CERT_PRIVATE[] PROGMEM = R"(-----BEGIN RSA PRIVATE KEY----- From dff3cec430b83a0f858e81d16c78566fd3d3c1ea Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 14 Nov 2023 17:28:15 +0000 Subject: [PATCH 07/11] Add simpler MQTT example for Thingspeak --- .../SARA-R5_Example17_AssistNow_MQTT/CONFIG.h | 103 -- .../SARA-R5_Example17_AssistNow_MQTT/HW.h | 120 --- .../SARA-R5_Example17_AssistNow_MQTT/LTE.h | 925 ------------------ .../SARA-R5_Example17_AssistNow_MQTT.ino | 139 --- .../UBXFILE.h | 127 --- .../secrets.h | 22 - ...A-R5_Example17_ThingSpeak_MQTT_Publish.ino | 215 ++++ 7 files changed, 215 insertions(+), 1436 deletions(-) delete mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h delete mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/HW.h delete mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h delete mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/SARA-R5_Example17_AssistNow_MQTT.ino delete mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/UBXFILE.h delete mode 100644 examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h create mode 100644 examples/SARA-R5_Example17_ThingSpeak_MQTT_Publish/SARA-R5_Example17_ThingSpeak_MQTT_Publish.ino diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h b/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h deleted file mode 100644 index 8f22991..0000000 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/CONFIG.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2022 by Michael Ammann (@mazgch) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __CONFIG_H__ -#define __CONFIG_H__ - -#include -#include - -#include "HW.h" -#include "secrets.h" - -// ----------------------------------------------------------------------- -// MQTT / PointPerfect settings -// ----------------------------------------------------------------------- - -const char AWS_IOT_ENDPOINT[] = "pp.services.u-blox.com"; -const unsigned short MQTT_BROKER_PORT = 8883; //!< MQTTS port -const int MQTT_MAX_MSG_SIZE = 9*1024; //!< the max size of a MQTT pointperfect topic - -#define MQTT_TOPIC_MGA "/pp/ubx/mga" //!< GNSS assistance topic - -const char MQTT_TOPIC_MGA_GPS[] = MQTT_TOPIC_MGA "/gps"; //!< GPS (US) -const char MQTT_TOPIC_MGA_GLO[] = MQTT_TOPIC_MGA "/glo"; //!< Glonass (RU) -const char MQTT_TOPIC_MGA_GAL[] = MQTT_TOPIC_MGA "/gal"; //!< Galileo (EU) -const char MQTT_TOPIC_MGA_BDS[] = MQTT_TOPIC_MGA "/bds"; //!< Beidou (CN) - -// ----------------------------------------------------------------------- -// CONFIGURATION keys -// ----------------------------------------------------------------------- - -#define CONFIG_DEVICE_TITLE "HPG solution" //!< a used friendly name -#define CONFIG_DEVICE_NAMEPREFIX "hpg" //!< a hostname compatible prefix, only a-z, 0-9 and - - -/** This class encapsulates all WLAN functions. -*/ -class CONFIG { - -public: - - /** constructor - */ - CONFIG() { - // create a unique name from the mac - uint64_t mac = ESP.getEfuseMac(); - const char* p = (const char*)&mac; - char str[64]; - sprintf(str, CONFIG_DEVICE_TITLE " - %02x%02x%02x", p[3], p[4], p[5]); - title = str; - sprintf(str, CONFIG_DEVICE_NAMEPREFIX "-%02x%02x%02x", p[3], p[4], p[5]); - name = str; - } - - /** get a name of the device - * \return the device name - */ - String getDeviceName(void) { - return name; - } - - /** get a friendly name of the device - * \return the friendly device title - */ - String getDeviceTitle(void) { - return title; - } - - /** get the topics to subscribe - * \return a vector with all the topics - */ - std::vector getTopics(void) - { - std::vector topics; - topics.push_back(MQTT_TOPIC_MGA); - //topics.push_back(MQTT_TOPIC_MGA_GPS); - //topics.push_back(MQTT_TOPIC_MGA_GLO); - //topics.push_back(MQTT_TOPIC_MGA_GAL); - //topics.push_back(MQTT_TOPIC_MGA_BDS); - return topics; - } - -protected: - - String title; //!< the title of the device - String name; //!< the name of the device -}; - -CONFIG Config; //!< The global CONFIG object - -#endif // __CONFIG_H__ diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/HW.h b/examples/SARA-R5_Example17_AssistNow_MQTT/HW.h deleted file mode 100644 index a5fd708..0000000 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/HW.h +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright 2022 by Michael Ammann (@mazgch) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __HW_H__ -#define __HW_H__ - -/** The target is used to enable conditional code thoughout this application - */ - -#define SPARKFUN_MICROMOD_ASSET_TRACKER 41 //!< Choose Sparkfun ESP32 Arduino / Sparkfun ESP32 MicroMod - -#define SPARKFUN_RTK_EVERYWHERE 51 //!< Select ESP32 / ESP32 Wrover Module - -#define HW_TARGET SPARKFUN_MICROMOD_ASSET_TRACKER - -/** the pins are defined here for each hardware target - */ -enum HW_PINS { - // Standard pins - BOOT = 0, - CDC_RX = RX, CDC_TX = TX, -#if (HW_TARGET == SPARKFUN_RTK_EVERYWHERE) - LED = 2, - CAN_RX = -1, CAN_TX = -1, - I2C_SDA = 21, I2C_SCL = 22, -#elif (HW_TARGET == SPARKFUN_MICROMOD_ASSET_TRACKER) - LED = 2, - CAN_RX = -1, CAN_TX = -1, - I2C_SDA = 21, I2C_SCL = 22, -#else - #error unknown board target -#endif - -#if (HW_TARGET == SPARKFUN_MICROMOD_ASSET_TRACKER) - // LTE (DCE) - LTE_RESET = -1, LTE_PWR_ON = G2, LTE_ON = G6, LTE_INT = G5, - LTE_TXI = TX1, LTE_RXO = RX1, LTE_RTS = -1, LTE_CTS = -1, - LTE_RI = G4, LTE_DSR = -1, LTE_DCD = -1, LTE_DTR = -1, - LTE_PWR_ON_ACTIVE = HIGH, LTE_ON_ACTIVE = LOW, - - // Power supply - VIN = 39, V33_EN = -1, V33_EN_ACTIVE = HIGH, - - // Micro SD card - MICROSD_SCK = SCK, MICROSD_SDI = MISO, MICROSD_SDO = MOSI, - MICROSD_DET = -1, MICROSD_PWR_EN = G1, - MICROSD_CS = G0, - MICROSD_DET_REMOVED = HIGH, MICROSD_PWR_EN_ACTIVE = LOW, - - REQUIRED_GPIO_PIN = -1, REQUIRED_GPIO_PIN_ACTIVE = HIGH, - -#elif (HW_TARGET == SPARKFUN_RTK_EVERYWHERE) - // LTE (DCE) - LTE_RESET = -1, LTE_PWR_ON = 26, LTE_ON = 5, LTE_INT = -1, - LTE_TXI = 13, LTE_RXO = 14, LTE_RTS = -1, LTE_CTS = -1, - LTE_RI = -1, LTE_DSR = -1, LTE_DCD = -1, LTE_DTR = -1, - LTE_NI = 34, - LTE_PWR_ON_ACTIVE = HIGH, LTE_ON_ACTIVE = HIGH, - - // Power supply - VIN = -1, V33_EN = 32, V33_EN_ACTIVE = HIGH, - - // Micro SD card - MICROSD_SCK = SCK, MICROSD_SDI = MISO, MICROSD_SDO = MOSI, - MICROSD_DET = 36, MICROSD_PWR_EN = -1, - MICROSD_CS = 4, - MICROSD_DET_REMOVED = LOW, MICROSD_PWR_EN_ACTIVE = LOW, - - // Required GPIO pin - on SPARKFUN_RTK_EVERYWHERE this is the WizNet W5500 CS - REQUIRED_GPIO_PIN = 27, REQUIRED_GPIO_PIN_ACTIVE = HIGH, - -#endif - PIN_INVALID = -1 -}; - -class HW { - -public: - - /** constructor - */ - HW(){ - hwInit(); - } - - void hwInit(void) { - // Do any top-level hardware initialization here: - // Initialize any required GPIO pins - if (PIN_INVALID != REQUIRED_GPIO_PIN) { - digitalWrite(REQUIRED_GPIO_PIN, REQUIRED_GPIO_PIN_ACTIVE); - pinMode(REQUIRED_GPIO_PIN, OUTPUT); - digitalWrite(REQUIRED_GPIO_PIN, REQUIRED_GPIO_PIN_ACTIVE); - } - // Turn on the 3.3V regulator - if present - if (PIN_INVALID != V33_EN) { - digitalWrite(V33_EN, V33_EN_ACTIVE); - pinMode(V33_EN, OUTPUT); - digitalWrite(V33_EN, V33_EN_ACTIVE); - } - log_i("Hardware initialized"); - } - -}; - -HW Hardware; //!< The global HW object - -#endif // __HW_H__ diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h b/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h deleted file mode 100644 index affc2e7..0000000 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/LTE.h +++ /dev/null @@ -1,925 +0,0 @@ -/* - * Copyright 2022 by Michael Ammann (@mazgch) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __LTE_H__ -#define __LTE_H__ - -#include -#include - -#include "HW.h" -#include "CONFIG.h" -#include "UBXFILE.h" - -// MNO_GLOBAL is the factory-programmed default -// If you are in Europe, you may find no operators unless you choose MNO_STD_EUROPE -const mobile_network_operator_t MOBILE_NETWORK_OPERATOR = MNO_GLOBAL; - -String CONFIG_VALUE_SIMPIN = ""; // Set the SIM PIN here if needed - -String CONFIG_VALUE_LTEAPN = ""; // Define the APN here if needed - -// Set this to (e.g.) SARA_R5_PSD_PROTOCOL_IPV4V6_V4_PREF if needed. O2 (UK) returns strPdpType "IP" but expects "IPV4V6_V4_PREF" -SARA_R5_pdp_protocol_type_t preferredPdpProtocol = SARA_R5_PSD_PROTOCOL_IPV4; // SARA_R5_PSD_PROTOCOL_IPV4V6_V4_PREF; - -const int LTE_1S_RETRY = 1000; //!< standard 1s retry -const int LTE_DETECT_RETRY = 5000; //!< delay between detect attempts -const int LTE_CHECKSIM_RETRY = 60000; //!< delay between SIM Card check attempts, SIM detection may be disables and you need to restart -const int LTE_ACTIVATION_RETRY = 10000; //!< delay between activation attempts -const int LTE_PROVISION_RETRY = 60000; //!< delay between provisioning attempts, provisioning may consume data -const int LTE_CONNECT_RETRY = 10000; //!< delay between server connection attempts to correction severs -const int LTE_MQTTCMD_DELAY = 100; //!< the client is not happy if multiple commands are sent too fast - -const int LTE_POWER_ON_PULSE = 2000; //!< Power on pulse width (2s works for for SARA, LARA and LENA) -const int LTE_POWER_ON_WAITTIME = 4000; //!< Dont't do anything duing this time after the power on pulse -const int LTE_POWER_ON_WAITTIME_MAX = 10000; //!< If the device does not respond until this time, it has failed -const int LTE_POWER_ON_WAITSIMREADY = 4000; //!< It usually takes a few seconds to detect the SIM card. - -const int LTE_PSD_PROFILE = 0; //!< The packet switched data profile used -const int LTE_HTTP_PROFILE = 0; //!< The HTTP profile used duing provisioning -const int LTE_SEC_PROFILE_HTTP = 1; //!< The security profile used for the HTTP connections druing provisioning -const int LTE_SEC_PROFILE_MQTT = 0; //!< The security profile used for the MQTT connections -const char* FILE_REQUEST = "req.json"; //!< Temporarly file name used for the request during HTTP GET transations (ZTP) -const char* FILE_RESP = "resp.json"; //!< Temporarly file name used for the response during HTTP POST and GET transations (AWS/ZTP) -const char* SEC_ROOT_CA ="aws-rootCA"; //!< Temporarly file name used when injecting the ROOT CA -const char* SEC_CLIENT_CERT = "pp-cert"; //!< Temporarly file name used when injecting the client certificate -const char* SEC_CLIENT_KEY = "pp-key"; //!< Temporarly file name used when injecting the client keys - -const uint16_t HTTPS_PORT = 443; //!< The HTTPS default port - -const int LTE_BAUDRATE = 115200; //!< baudrates 230400, 460800 or 921600 cause issues even when CTS/RTS is enabled - -const char* LTE_TASK_NAME = "Lte"; //!< Lte task name -const int LTE_STACK_SIZE = 8*1024; //!< Lte task stack size -const int LTE_TASK_PRIO = 1; //!< Lte task priority -const int LTE_TASK_CORE = 1; //!< Lte task MCU code - -// helper macros to handle the AT interface errors -#define LTE_CHECK_INIT int _step = 0; (void)_step; SARA_R5_error_t _err = SARA_R5_SUCCESS //!< init variable -#define LTE_CHECK_OK (SARA_R5_SUCCESS == _err) //!< record the return result -#define LTE_CHECK(x) if (SARA_R5_SUCCESS == _err) _step = x, _err //!< interim evaluate -#define LTE_CHECK_EVAL(txt) if (SARA_R5_SUCCESS != _err) log_e(txt ", AT sequence failed at step %d with error %d", _step, _err) //!< final verdict and log_e report - -extern class LTE Lte; //!< Forward declaration of class - -/** This class encapsulates all LTE functions. -*/ -class LTE : public SARA_R5 { - -public: - - /** constructor - * \note we do not pass the pins to the class as we prefer to be in control of them - */ - LTE() : SARA_R5{ PIN_INVALID/*LTE_PWR_ON*/, PIN_INVALID/*LTE_RESET*/, 3/*retries*/ } { - state = INIT; - hwInit(); - } - - /** initialize the object, this spins of a worker task. - */ - void init(void) { - xTaskCreatePinnedToCore(task, LTE_TASK_NAME, LTE_STACK_SIZE, this, LTE_TASK_PRIO, NULL, LTE_TASK_CORE); - } - - // Inject MGA (AssistNow) data directly to the internal GNSS using Send of UBX string +UGUBX - SARA_R5_error_t injectMGA(uint8_t *buf, size_t len) { - if (module.startsWith("SARA-R510M8S")) { - - size_t dataPtr = 0; // Pointer into buf - size_t bytesPushed = 0; // Keep count - - while (dataPtr < len) // Keep going until we have processed all the bytes - { - // Start by checking the validity of the packet being pointed to - bool dataIsOK = true; - - dataIsOK &= (*(buf + dataPtr + 0) == 0xB5); // Check for 0xB5 - dataIsOK &= (*(buf + dataPtr + 1) == 0x62); // Check for 0x62 - dataIsOK &= (*(buf + dataPtr + 2) == 0x13); // Check for class UBX-MGA - - size_t packetLength = ((size_t) * (buf + dataPtr + 4)) | (((size_t) * (buf + dataPtr + 5)) << 8); // Extract the length - - uint8_t checksumA = 0; - uint8_t checksumB = 0; - // Calculate the checksum bytes - // Keep going until the end of the packet is reached (payloadPtr == (dataPtr + packetLength)) - // or we reach the end of the AssistNow data (payloadPtr == len) - for (size_t payloadPtr = dataPtr + ((size_t)2); (payloadPtr < (dataPtr + packetLength + ((size_t)6))) && (payloadPtr < len); payloadPtr++) - { - checksumA += *(buf + payloadPtr); - checksumB += checksumA; - } - // Check the checksum bytes - dataIsOK &= (checksumA == *(buf + dataPtr + packetLength + ((size_t)6))); - dataIsOK &= (checksumB == *(buf + dataPtr + packetLength + ((size_t)7))); - - dataIsOK &= ((dataPtr + packetLength + ((size_t)8)) <= len); // Check we haven't overrun - - // If the data is valid, push it - if (dataIsOK) - { - SARA_R5_error_t err; - - char *command; - const char ugubx[] = "+UGUBX=\""; // Include the first quote - command = (char *)calloc(strlen(ugubx) + 3 + ((packetLength + 8) * 2), sizeof(char)); - if (command == nullptr) - return SARA_R5_ERROR_OUT_OF_MEMORY; - sprintf(command, "%s", ugubx); - - size_t ptr = strlen(ugubx); - for (size_t i = 0; i < packetLength + 8; i++) - sprintf(&command[ptr++], "%02X", *(buf + dataPtr + i)); - sprintf(&command[ptr], "\""); - - err = sendCommandWithResponse(command, SARA_R5_RESPONSE_OK, - nullptr, SARA_R5_STANDARD_RESPONSE_TIMEOUT); - free(command); - - if (err == SARA_R5_ERROR_SUCCESS) { - bytesPushed += packetLength + ((size_t)8); // Increment bytesPushed if the push was successful - log_i("packet ID 0x%02X length %d", *(buf + dataPtr + 3), packetLength); - } - else { - log_e("send UBX fail!"); - } - - dataPtr += packetLength + ((size_t)8); // Point to the next message - } - else - { - // The data was invalid. Send a debug message and then try to find the next 0xB5 - log_w("bad data - ignored! dataPtr is %d", dataPtr); - - while ((dataPtr < len) && (*(buf + ++dataPtr) != 0xB5)) - { - ; // Increment dataPtr until we are pointing at the next 0xB5 - or we reach the end of the data - } - } - } - - if (bytesPushed == len) - return (SARA_R5_ERROR_SUCCESS); - } - return (SARA_R5_ERROR_ERROR); - } - -protected: - - // ----------------------------------------------------------------------- - // MQTT / PointPerfect - // ----------------------------------------------------------------------- - - std::vector topics; //!< vector with current subscribed topics - String subTopic; //!< requested topic to be subscribed (needed by the callback) - String unsubTopic; //!< requested topic to be un-subscribed (needed by the callback) - int mqttMsgs; //!< remember the number of messages pending indicated by the URC - - //! this helper deals with some AT commands that are not yet implemted in LENA-R8 and throw a warning - SARA_R5_error_t LTE_IGNORE_LENA(SARA_R5_error_t err) { - if ((err != SARA_R5_SUCCESS) && module.startsWith("LENA-R8")) { - log_w("AT command error ignored due to LENA-R8 IP Status"); - err = SARA_R5_SUCCESS; - } - return err; - } - - /** Connect to the Thingstream PointPerfect server using the credentials from ZTP process - * \param id the client ID for this device - */ - void mqttConnect(String id) { - // disconncect must fail here, so that we can connect - setMQTTCommandCallback(mqttCallbackStatic); // callback will advance state - // make sure the client is disconnected here - if (SARA_R5_SUCCESS == disconnectMQTT()) { - log_i("forced disconnect"); // if this sucessful it means were were still connected. - } else { - log_i("connect to \"%s:%d\" as client \"%s\"", AWS_IOT_ENDPOINT, MQTT_BROKER_PORT, id.c_str()); - LTE_CHECK_INIT; - LTE_CHECK(1) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_ROOTCA, SEC_ROOT_CA, AWS_CERT_CA); - LTE_CHECK(2) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_CLIENT_CERT, SEC_CLIENT_CERT, AWS_CERT_CRT); - LTE_CHECK(3) = setSecurityManager(SARA_R5_SEC_MANAGER_OPCODE_IMPORT, SARA_R5_SEC_MANAGER_CLIENT_KEY, SEC_CLIENT_KEY, AWS_CERT_PRIVATE); - LTE_CHECK(4) = LTE_IGNORE_LENA( resetSecurityProfile(LTE_SEC_PROFILE_MQTT) ); - LTE_CHECK(5) = configSecurityProfile(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CERT_VAL_LEVEL, SARA_R5_SEC_PROFILE_CERTVAL_OPCODE_YESNOURL); - LTE_CHECK(6) = configSecurityProfile(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_TLS_VER, SARA_R5_SEC_PROFILE_TLS_OPCODE_VER1_2); - LTE_CHECK(7) = configSecurityProfile(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CYPHER_SUITE, SARA_R5_SEC_PROFILE_SUITE_OPCODE_PROPOSEDDEFAULT); - LTE_CHECK(8) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_ROOT_CA, SEC_ROOT_CA); - LTE_CHECK(9) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CLIENT_CERT, SEC_CLIENT_CERT); - LTE_CHECK(10) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_CLIENT_KEY, SEC_CLIENT_KEY); - LTE_CHECK(11) = configSecurityProfileString(LTE_SEC_PROFILE_MQTT, SARA_R5_SEC_PROFILE_PARAM_SNI, AWS_IOT_ENDPOINT); - LTE_CHECK(12) = nvMQTT(SARA_R5_MQTT_NV_RESTORE); - LTE_CHECK(13) = setMQTTclientId(id); - LTE_CHECK(14) = setMQTTserver(AWS_IOT_ENDPOINT, MQTT_BROKER_PORT); - LTE_CHECK(15) = setMQTTsecure(true, LTE_SEC_PROFILE_MQTT); - LTE_CHECK(16) = connectMQTT(); - LTE_CHECK_EVAL("setup and connect"); - mqttMsgs = 0; - topics.clear(); - subTopic = ""; - unsubTopic = ""; - } - } - - /** Disconnect and cleanup the MQTT connection - * \return true if already disconnected (no need to wait for the callback) - */ - bool mqttStop(void) { - SARA_R5_error_t err = disconnectMQTT(); - if (SARA_R5_SUCCESS == err) { - log_i("disconnect"); - } else { - log_e("disconnect, failed with error %d", err); - } - return SARA_R5_SUCCESS != err; - } - - /** The MQTT task is responsible for: - * 1) subscribing to topics - * 2) unsubscribing from topics - * 3) read MQTT data from the modem and inject it into the GNSS receiver - */ - void mqttTask(void) { - /* The LTE modem has difficulties subscribing/unsubscribing more than one topic at the same time - * We can only start one operation at a time wait for the URC and add a extra delay before we can - * do the next operation. - */ - bool busy = (0 < subTopic.length()) || (0 < unsubTopic.length()); - if (!busy) { - std::vector newTopics = Config.getTopics(); - // loop through new topics and subscribe to the first topic that is not in our curent topics list. - for (auto it = newTopics.begin(); (it != newTopics.end()) && !busy; it = std::next(it)) { - String topic = *it; - std::vector::iterator pos = std::find(topics.begin(), topics.end(), topic); - if (pos == topics.end()) { - SARA_R5_error_t err = subscribeMQTTtopic(0,topic); - if (SARA_R5_SUCCESS == err) { - log_d("subscribe requested topic \"%s\" qos %d", topic.c_str(), 0); - subTopic = topic; - } else { - log_e("subscribe request topic \"%s\" qos %d, failed with error %d", topic.c_str(), 0, err); - } - busy = true; - } - } - // loop through current topics and unsubscribe to the first topic that is not in the new topics list. - for (auto it = topics.begin(); (it != topics.end()) && !busy; it = std::next(it)) { - String topic = *it; - std::vector::iterator pos = std::find(newTopics.begin(), newTopics.end(), topic); - if (pos == newTopics.end()) { - SARA_R5_error_t err = unsubscribeMQTTtopic(topic); - if (SARA_R5_SUCCESS == err) { - log_d("unsubscribe requested topic \"%s\"", topic.c_str()); - unsubTopic = topic; - } else { - log_e("unsubscribe request topic \"%s\", failed with error %d", topic.c_str(), err); - } - busy = true; - } - } - if (!busy && (0 < mqttMsgs)) { - // at this point we are properly subscribed to the needed topics and can now read data - log_d("read request %d msg", mqttMsgs); - // The MQTT API does not allow getting the size before actually reading the data. So we - // have to allocate a big enough buffer. PointPerfect may send upto 9kB on the MGA topic. - uint8_t *buf = new uint8_t[MQTT_MAX_MSG_SIZE]; - if (buf != NULL) { - String topic; - int len = -1; - int qos = -1; - SARA_R5_error_t err = readMQTT(&qos, &topic, buf, MQTT_MAX_MSG_SIZE, &len); - if (SARA_R5_SUCCESS == err) { - mqttMsgs = 0; // expect a URC afterwards - const char* strTopic = topic.c_str(); - log_i("topic \"%s\" read %d bytes", strTopic, len); - // if we detect data from a topic, then why not unsubscribe from it. - std::vector::iterator pos = std::find(topics.begin(), topics.end(), topic); - if (pos == Lte.topics.end()) { - log_e("getting data from an unexpected topic \"%s\"", strTopic); - if (!busy) { - err = unsubscribeMQTTtopic(topic); - if (SARA_R5_SUCCESS == err) { - log_d("unsubscribe requested for unexpected topic \"%s\"", strTopic); - unsubTopic = topic; - } else { - log_e("unsubscribe request for unexpected topic \"%s\", failed with error %d", topic.c_str(), err); - } - busy = true; - } - } else { - // anything else can be sent to the GNSS as is - len = injectMGA(buf, (size_t)len); - } - } else { - log_e("read failed with error %d", err); - } - // we need to free the buffer, as the inject function took a copy with only the required size - delete [] buf; - } - } - } - } - - /** The MQTT callback processes the URC and is responsible for advancing the state. - * \param command the MQTT command - * \param request the response code 1 = sucess, 0 = error - */ - void mqttCallback(int command, int result) { - log_d("%d command %d result %d", command, result); - if (result == 0) { - int code, code2; - SARA_R5_error_t err = getMQTTprotocolError(&code, &code2); - if (SARA_R5_SUCCESS == err) { - log_e("command %d protocol error code %d code2 %d", command, code, code2); - } else { - log_e("command %d protocol error failed with error", command, err); - } - } else { - switch (command) { - case SARA_R5_MQTT_COMMAND_LOGIN: - if (state != ONLINE) { - log_e("login wrong state"); - } else { - log_i("login"); - setState(MQTT, LTE_MQTTCMD_DELAY); - } - break; - case SARA_R5_MQTT_COMMAND_LOGOUT: - if ((state != MQTT) && (state != ONLINE)) { - log_e("logout wrong state"); - } else { - log_i("logout"); - mqttMsgs = 0; - topics.clear(); - subTopic = ""; - unsubTopic = ""; - setState(ONLINE, LTE_MQTTCMD_DELAY); - } - break; - case SARA_R5_MQTT_COMMAND_SUBSCRIBE: - if (state != MQTT) { - log_e("subscribe wrong state"); - } else if (!subTopic.length()) { - log_e("subscribe result %d but no topic", result); - } else { - log_i("subscribe result %d topic \"%s\"", result, subTopic.c_str()); - topics.push_back(subTopic); - subTopic = ""; - setState(MQTT, LTE_MQTTCMD_DELAY); - } - break; - case SARA_R5_MQTT_COMMAND_UNSUBSCRIBE: - if (state != MQTT) { - log_e("unsubscribe wrong state"); - } else if (!unsubTopic.length()) { - log_e("unsubscribe result %d but no topic", result); - } else { - std::vector::iterator pos = std::find(topics.begin(), topics.end(), unsubTopic); - if (pos == topics.end()) { - topics.erase(pos); - log_i("unsubscribe result %d topic \"%s\"", result, unsubTopic.c_str()); - unsubTopic = ""; - setState(MQTT, LTE_MQTTCMD_DELAY); - } else { - log_e("unsubscribe result %d topic \"%s\" but topic not in list", result, unsubTopic.c_str()); - } - } - break; - case SARA_R5_MQTT_COMMAND_READ: - if (state != MQTT) { - log_e("read wrong state"); - } else { - log_d("read result %d", result); - mqttMsgs = result; - setState(MQTT, LTE_MQTTCMD_DELAY); - } - break; - default: - break; - } - } - } - //! static callback helper, regCallback will do the real work - static void mqttCallbackStatic(int command, int result) { - Lte.mqttCallback(command, result); - } - - - // ----------------------------------------------------------------------- - // LTE - // ----------------------------------------------------------------------- - - String module; //!< a string holding the module name (SARA-R5, LARA-R6 or LENA-R8) - - /** detect the LTE modem - * \return the detection status - */ - bool lteDetect(void) { - bool ok = hwReady(); - if (ok) { - module = getModelID(); - String version = getFirmwareVersion(); - log_i("config manufacturer \"%s\" model=\"%s\" version=\"%s\"", - getManufacturerID().c_str(), module.c_str(), version.c_str()); - if ((version.toDouble() < 0.13) && module.startsWith("LARA-R6")) { - log_e("LARA-R6 firmware %s has MQTT limitations, please update firmware", version.c_str()); - } - else if ((version.toDouble() < 2.00) && module.startsWith("LENA-R8")) { - log_e("LENA-R8 firmware %s has limitations, please update firmware", version.c_str()); - } -#if ((HW_TARGET == UBLOX_XPLR_HPG2_C214) || (HW_TARGET == MAZGCH_HPG_SOLUTION_V09)) - // enableSIMDetectAndHotswap(); -#endif - // wait for the SIM to get ready ... this can take a while (<4s) - SARA_R5_error_t err = SARA_R5_ERROR_ERROR; - for (int i = 0; i < LTE_POWER_ON_WAITSIMREADY/100; i ++) { - err = getSimStatus(NULL); - if (SARA_R5_ERROR_ERROR != err) - break; - delay(100); - } - if (SARA_R5_ERROR_ERROR == err) { - log_e("SIM card not found, err %d", err); - } - } - return ok; - } - - /** initialize the LTE modem and report useful information - * \return initialisation process sucess - */ - bool lteInit(void) { - String code; - LTE_CHECK_INIT; - LTE_CHECK(1) = getSimStatus(&code); - if (LTE_CHECK_OK && code.equals("SIM PIN")) { - if (CONFIG_VALUE_SIMPIN.length()) { - LTE_CHECK(2) = setSimPin(CONFIG_VALUE_SIMPIN.c_str()); - LTE_CHECK(3) = getSimStatus(&code); // retry get the SIM status - LTE_CHECK_EVAL("SIM card initialisation"); - } - } - if (LTE_CHECK_OK) { - if (code.equals("READY")) { - log_i("SIM card status \"%s\" CCID=\"%s\"", code.c_str(), getCCID().c_str()); - String subNo = getSubscriberNo(); - int from = subNo.indexOf(",\""); - int to = (-1 != from) ? subNo.indexOf("\"", from + 2) : -1; - subNo = ((-1 != from) && (-1 != to)) ? subNo.substring(from + 2, to) : ""; - log_i("IMEI=\"%s\" IMSI=\"%s\" subscriber=\"%s\"", getIMEI().c_str(), getIMSI().c_str(), subNo.c_str()); - // configure the MNO profile - if (!module.startsWith("LENA-R8")) { - if (!setNetworkProfile(MOBILE_NETWORK_OPERATOR)) { - log_e("detect setting network profile for MNO %d failed", MOBILE_NETWORK_OPERATOR); - } - } - // register the callbacks - LTE_CHECK_INIT; - LTE_CHECK(1) = setEpsRegistrationCallback(epsRegCallbackStatic); - LTE_CHECK(2) = setRegistrationCallback(regCallbackStatic); - // set the APn - if (CONFIG_VALUE_LTEAPN.length()) { - LTE_CHECK(3) = setAPN(CONFIG_VALUE_LTEAPN); - } - LTE_CHECK_EVAL("callback and apn config"); - return true; - } - else { - log_w("SIM card status \"%s\"", code.c_str()); - } - } - return false; - } - - //! helper look up table for string conversion of registration status - const char *REG_STATUS_LUT[11] = { - "not registered", - "home", - "searching", - "denied", - "unknown", - "roaming", - "home sms only", - "roaming sms only", - "emergency service only", - "home cfsb not preferred", - "roaming cfsb not preferred" - }; - - //! helper look up table for string conversion of network activation - const char *REG_ACT_LUT[10] = { - "GSM", - "GSM COMPACT", - "UTRAN", - "GSM/GPRS + EDGE", - "UTRAN + HSDPA", - "UTRAN + HSUPA", - "UTRAN + HSDPA + HSUPA", - "E-UTRAN", - "EC-GSM-IoT (A/Gb mode)", - "E-UTRAN (NB-S1 mode)" - }; - - //! helper to safely convert the string with above tables - #define REG_LUT(l, x) ((((unsigned int)x) <= (sizeof(l)/sizeof(*l))) ? l[x] : "unknown") - - /** check if we are registered - * \return registration success - */ - bool lteRegistered(void) { - SARA_R5_registration_status_t status = registration(true); // EPS - const char* statusText = REG_LUT(REG_STATUS_LUT, status); - if ((status == SARA_R5_REGISTRATION_HOME) || (status == SARA_R5_REGISTRATION_ROAMING)) { - String op = ""; - getOperator(&op); - log_i("registered %d(%s) operator \"%s\" rssi %d clock \"%s\"", - status, statusText, op.c_str(), rssi(), clock().c_str()); - // AT+UPSV? - // AT+CLCK="SC",2 - return true; - } - else { - log_d("EPS registration status %d(%s), waiting ...", status, statusText); - } - return false; - } - - /** evaluate registration status and report it - * \param status the registration status - * \param tacLac the tac or lac - * \param ci the ci - * \param Act the network activation - * \param strTacLac a string indicating if tacLac is either tac or lac. - */ - void regCallback(SARA_R5_registration_status_t status, unsigned int tacLac, unsigned int ci, int Act, const char* strTacLac) - { - const char* actText = REG_LUT(REG_ACT_LUT, Act); - const char* statusText = REG_LUT(REG_STATUS_LUT, status); - log_d("status %d(%s) %s \"%04X\" ci \"%08X\" Act %d(%s)", status, statusText, strTacLac, tacLac, ci, Act, actText); /*unused*/ (void)actText; (void)statusText; - if (((status == SARA_R5_REGISTRATION_HOME) || (status == SARA_R5_REGISTRATION_ROAMING)) && (state < REGISTERED)) { - setState(REGISTERED); - } else if ((status == SARA_R5_REGISTRATION_SEARCHING) && (state >= REGISTERED)) { - setState(WAITREGISTER); - } - } - //! static callback helper, regCallback will do the real work - static void epsRegCallbackStatic(SARA_R5_registration_status_t status, unsigned int tac, unsigned int ci, int Act) { - Lte.regCallback(status, tac, ci, Act, "tac"); - } - //! static callback helper, regCallback will do the real work - static void regCallbackStatic(SARA_R5_registration_status_t status, unsigned int lac, unsigned int ci, int Act) { - Lte.regCallback(status, lac, ci, Act, "lac"); - } - - /** make sure the modem is activated properly. Some modem do this automatically other need some help. - * \return activaion sucess - */ - bool lteActivate(void) { - if (module.startsWith("LARA-R6")) { - return true; - } else if (module.startsWith("LENA-R8")) { - String apn; - IPAddress ip(0, 0, 0, 0); - SARA_R5_pdp_type pdpType = PDP_TYPE_INVALID; - LTE_CHECK_INIT; - LTE_CHECK(1) = getAPN(0, &apn, &ip, &pdpType); - // on LENA-R8 we need to move a IP context 0 to a different context id other than 0 - // ok if these commands fail as the context could be already active ... - if (LTE_CHECK_OK && (0 < apn.length()) && (pdpType != PDP_TYPE_NONIP)) { - setAPN(apn, 1 /* LENA only has contexts 0 to 7, do not use SARA_R5_NUM_PDP_CONTEXT_IDENTIFIERS-1 */ , pdpType); - } - activatePDPcontext(true); - LTE_CHECK_EVAL("LTE activate context"); - if (LTE_CHECK_OK) { - return true; - } - } else /* SARA-R5 */ { - performPDPaction(LTE_PSD_PROFILE, SARA_R5_PSD_ACTION_DEACTIVATE); - for (int cid = 0; cid < SARA_R5_NUM_PDP_CONTEXT_IDENTIFIERS; cid++) - { - String apn; - IPAddress ip(0, 0, 0, 0); - SARA_R5_pdp_type pdpType = PDP_TYPE_INVALID; - LTE_CHECK_INIT; - LTE_CHECK(1) = getAPN(cid, &apn, &ip, &pdpType); - if (SARA_R5_PSD_PROTOCOL_IPV4 != preferredPdpProtocol) - pdpType = (SARA_R5_pdp_type)preferredPdpProtocol; // Override the PDP Protocol if desired - if ((LTE_CHECK_OK) && (0 < apn.length()) && (PDP_TYPE_INVALID != pdpType)) - { - // Activate the profile - log_i("activate profile for apn \"%s\" with IP %s pdp %d", apn.c_str(), ip.toString().c_str(), pdpType); - setPSDActionCallback(psdCallbackStatic); - LTE_CHECK(2) = setPDPconfiguration(LTE_PSD_PROFILE, SARA_R5_PSD_CONFIG_PARAM_PROTOCOL, pdpType); - LTE_CHECK(3) = setPDPconfiguration(LTE_PSD_PROFILE, SARA_R5_PSD_CONFIG_PARAM_MAP_TO_CID, cid); - LTE_CHECK(4) = performPDPaction(LTE_PSD_PROFILE, SARA_R5_PSD_ACTION_ACTIVATE); - LTE_CHECK_EVAL("profile activation"); - if (LTE_CHECK_OK) { - return true; // abort the loop as we found a good profile - } - } - } - } - return false; - } - - /** the packet switched data activation callback - * \param profile the psd profile - * \param ip the ip address for this profile - */ - static void psdCallbackStatic(int profile, IPAddress ip) { - log_d("psdCallback profile %d IP %s", profile, ip.toString().c_str()); - if (profile == LTE_PSD_PROFILE) { - Lte.setState(ONLINE); - } - } - - // ----------------------------------------------------------------------- - // STATEMACHINE - // ----------------------------------------------------------------------- - - //! states of the statemachine - typedef enum { - INIT = 0, - CHECKSIM, - SIMREADY, - WAITREGISTER, - REGISTERED, - ONLINE, - MQTT, - NTRIP, - NUM - } STATE; - //! string conversion helper table, must be aligned and match with STATE - const char* STATE_LUT[STATE::NUM] = { - "init", - "check sim", - "sim ready", - "wait register", - "registered", - "online", - "mqtt", - "ntrip" - }; - STATE state; //!< the current state - int32_t ttagNextTry; //!< time tag when to call the state machine again - - /** advance the state and report transitions - * \param newState the new state - * \param delay schedule delay - */ - void setState(STATE newState, int32_t delay = 0) { - if (state != newState) { - log_i("state change %d(%s)", newState, STATE_LUT[newState]); - state = newState; - } - ttagNextTry = millis() + delay; - } - - /* FreeRTOS static task function, will just call the objects task function - * \param pvParameters the Lte object (this) - */ - static void task(void * pvParameters) { - ((LTE*) pvParameters)->task(); - } - - /** This task handling the whole LTE state machine, here is where different activities are scheduled - * and where the code decides what actions to perform. - */ - void task(void) { - if (!lteDetect()) { - log_w("LARA-R6/SARA-R5/LENA-R8 not detected, check wiring"); - } else { - setState(CHECKSIM); - } - while(true) { - if ((PIN_INVALID != LTE_ON) && (state != INIT)) { - // detect if LTE was turned off - if (LTE_ON_ACTIVE != digitalRead(LTE_ON)) { - UbxSerial.end(); - setState(INIT, LTE_DETECT_RETRY); - } - } - - if (state != INIT) { - SARA_R5::poll(); - } - - int32_t now = millis(); - if (0 >= (ttagNextTry - now)) { - ttagNextTry = now + LTE_1S_RETRY; - String id = MQTT_CLIENT_ID; - bool useMqtt = true; - switch (state) { - case INIT: - ttagNextTry = now + LTE_DETECT_RETRY; - if (lteDetect()) { - setState(CHECKSIM); - } - break; - case CHECKSIM: - ttagNextTry = now + LTE_CHECKSIM_RETRY; - if (lteInit()) { - setState(WAITREGISTER); - } - break; - case WAITREGISTER: - if (lteRegistered()) { - setState(REGISTERED); - } - break; - case REGISTERED: - ttagNextTry = now + LTE_ACTIVATION_RETRY; - if (lteActivate()) { - setState(ONLINE); - } - break; - case ONLINE: - if (useMqtt) { - { - ttagNextTry = now + LTE_CONNECT_RETRY; - mqttConnect(id); // callback will advance the state - } - } - break; - case MQTT: - if (!useMqtt || (0 == id.length())) { - if (mqttStop()) { - setState(ONLINE); - } - } else { - mqttTask(); - } - break; - default: - setState(INIT); - break; - } - } - vTaskDelay(30); - } - } - - // ----------------------------------------------------------------------- - // HARDWARE - // ----------------------------------------------------------------------- - - /** The modem is connected using different GPIOs and full 8 wire Serial port. - * this function makes sure that at boot all the pins are properly configured. - * The pins are defined in the HW.h file and depend on the board compiled for. - */ - void hwInit(void) { - // The current SARA-R5 library is setting the LTE_RESET and LTE_PWR_ON pins to input after using them, - // This is not ideal for the v0.8/v0.9 hardware. Therefore it is prefered to control the LTE_RESET - // and LTE_PWR_ON externally in our code here. We also like to have better control of the pins to - // support different variants of LTE modems. - - // Module Power On time Power Off time Reset - // SARA-R5 0.1/1-2s 23s + 1.5sReset 0.1s - // LARA-R6 0.15-3.2s >1.5s 0.05-6s (10s emergency) - // LENA-R8 >2s >3.1s 0.05s - - // The LTE_RESET pin is active LOW, HIGH = idle, LOW = in reset - // The LTE_RESET pin unfortunately does not have a pull up resistor on the v0.8/v0.9 hardware - if (PIN_INVALID != LTE_RESET) { - digitalWrite(LTE_RESET, HIGH); - pinMode(LTE_RESET, OUTPUT); - digitalWrite(LTE_RESET, HIGH); - } - // The LTE_PWR_ON pin is usually active HIGH, LOW = idle, defined by LTE_PWR_ON_ACTIVE, active timings see table above - // The LTE_PWR_ON pin has a external pull low resistor on the board. - if (PIN_INVALID != LTE_PWR_ON) { - digitalWrite(LTE_PWR_ON, !LTE_PWR_ON_ACTIVE); - pinMode(LTE_PWR_ON, OUTPUT); - digitalWrite(LTE_PWR_ON, !LTE_PWR_ON_ACTIVE); - } - if (PIN_INVALID != LTE_TXI) { - digitalWrite(LTE_TXI, HIGH); - pinMode(LTE_TXI, OUTPUT); - digitalWrite(LTE_TXI, HIGH); - } - if (PIN_INVALID != LTE_RTS) { - digitalWrite(LTE_RTS, LOW); - pinMode(LTE_RTS, OUTPUT); - digitalWrite(LTE_RTS, LOW); - } - if (PIN_INVALID != LTE_DTR) { - digitalWrite(LTE_DTR, LOW); - pinMode(LTE_DTR, OUTPUT); - digitalWrite(LTE_DTR, LOW); - } - // init all other pins here - if (PIN_INVALID != LTE_ON) pinMode(LTE_ON, INPUT); - if (PIN_INVALID != LTE_RXO) pinMode(LTE_RXO, INPUT); - if (PIN_INVALID != LTE_CTS) pinMode(LTE_CTS, INPUT); - if (PIN_INVALID != LTE_DSR) pinMode(LTE_DSR, INPUT); - if (PIN_INVALID != LTE_DCD) pinMode(LTE_DCD, INPUT); - if (PIN_INVALID != LTE_RI) pinMode(LTE_RI, INPUT); - if (PIN_INVALID != LTE_INT) pinMode(LTE_INT, INPUT); - } - - /** The modem is started using a PWR_ON pin. This process takes quite some time and is a - * critical phase. Duing the process it is a bad idea to try to talk to the modem too early. - * \return true if hardware is ready. - */ - bool hwReady(void) { - // turn on the module - #define DETECT_DELAY 100 - int pwrOnTime = -1; // will never trigger - if (PIN_INVALID != LTE_PWR_ON) { - if ((PIN_INVALID != LTE_ON) ? LTE_ON_ACTIVE != digitalRead(LTE_ON) : true) { - log_i("LTE power on"); - digitalWrite(LTE_PWR_ON, LTE_PWR_ON_ACTIVE); - pwrOnTime = LTE_POWER_ON_PULSE / DETECT_DELAY; - } - } - bool ready = true; - char lastCts = -1; - char lastOn = -1; - char lastRxo = -1; - for (int i = 0; i < LTE_POWER_ON_WAITTIME_MAX / DETECT_DELAY; i ++) { - ready = (pwrOnTime < 0); - if (i == pwrOnTime) { - digitalWrite(LTE_PWR_ON, !LTE_PWR_ON_ACTIVE); - log_d("LTE pin PWR_ON off(idle)"); - pwrOnTime = -1; // no more - i = 0; // restart timer - } - if (PIN_INVALID != LTE_RXO) { - char rxo = digitalRead(LTE_RXO); - if (lastRxo != rxo) { - log_d("LTE pin RXO %s", (rxo == LOW) ? "LOW(active)" : "HIGH(idle)"); - lastRxo = rxo; - } - ready = ready && (rxo == HIGH); - } - if (PIN_INVALID != LTE_ON) { - char on = digitalRead(LTE_ON); - if (on != lastOn) { - log_d("LTE pin ON %s", (on == LTE_ON_ACTIVE) ? "on(active)" : "off(idle)"); - lastOn = on; - } - ready = ready && (on == LTE_ON_ACTIVE); - } - if (PIN_INVALID != LTE_CTS) { - char cts = digitalRead(LTE_CTS); - if (lastCts != cts) { - log_d("LTE pin CTS %s", (cts == LOW) ? "LOW(idle)" : "HIGH(wait)"); - lastCts = cts; - } - ready = ready && (cts == LOW); - } - if (ready && (i > LTE_POWER_ON_WAITTIME / DETECT_DELAY)) { - break; - } - delay(DETECT_DELAY); - } - if (ready) { - log_i("LTE ready"); - } else { - log_w("not ready RXO PWRON CTS : %d %d %d != 1 0 0", lastRxo, lastOn, lastCts); - } - if (ready) { - #define PIN_TXT(pin) (PIN_INVALID == pin) ? "" : digitalRead(pin) == LOW ? " LOW" : " HIGH" - log_d("baudrate %d pins RXo %d%s TXi %d%s CTSo %d%s RTSi %d%s", LTE_BAUDRATE, - LTE_RXO, PIN_TXT(LTE_RXO), LTE_TXI, PIN_TXT(LTE_TXI), - LTE_CTS, PIN_TXT(LTE_CTS), LTE_RTS, PIN_TXT(LTE_RTS)); - ready = begin(UbxSerial, LTE_BAUDRATE); - } - return ready; - } - - /** We need to sub class the Sparkfun class, and override this function as we are - * using a four wire serial port with flow control that needs to be preserved. - * \param baud the desired baud rate - */ - void beginSerial(unsigned long baud) override - { - delay(100); - UbxSerial.end(); - delay(10); - log_d("LTE baudrate %d pins RXo %d TXi %d CTSo %d RTSi %d", baud, LTE_RXO, LTE_TXI, LTE_CTS, LTE_RTS); - UbxSerial.begin(baud, SERIAL_8N1, LTE_RXO, LTE_TXI); - if ((PIN_INVALID != LTE_RTS) && (PIN_INVALID != LTE_CTS)) { - UbxSerial.setPins(LTE_RXO, LTE_TXI, LTE_CTS, LTE_RTS); - UbxSerial.setHwFlowCtrlMode(HW_FLOWCTRL_CTS_RTS, 64); - } - delay(100); - } -}; - -LTE Lte; //!< The global LTE peripherial object - -#endif // __LTE_H__ diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/SARA-R5_Example17_AssistNow_MQTT.ino b/examples/SARA-R5_Example17_AssistNow_MQTT/SARA-R5_Example17_AssistNow_MQTT.ino deleted file mode 100644 index 3e40462..0000000 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/SARA-R5_Example17_AssistNow_MQTT.ino +++ /dev/null @@ -1,139 +0,0 @@ -/* - - SARA-R5 Example - =============== - - Assist Now via MQTT - - This example demonstrates how to request u-blox PointPerfect AssistNow Online data from Thingstream - using MQTT, and push it to the SARA-R510M8S internal GNSS. It also demonstrates how quickly the internal - GNSS can achieve a 3D fix from a cold start with help from AssistNow. - - AssistNow Online data is available via HTTP GET using a PointPerfect AssistNow Token. See Example12 for - more details. But here we are using MQTT, using the SARA-R5's built-in MQTT commands. This means we need - a Client Certificate and Client Key, plus the Amazon root Server Certificate to be able to connect and - subscribe to the /pp/ubx/mga AssistNow topic. See: - https://developer.thingstream.io/guides/location-services/pointperfect-getting-started - Specifically: - https://developer.thingstream.io/guides/location-services/pointperfect-getting-started#h.vn9iuugch1ah - A free PointPerfect Developer plan will provide capped access to the IP stream - and allow you to - download the MQTT credentials: - https://portal.thingstream.io/pricing/laas/laaspointperfect - - Log in to ThingStream, create a Location Thing, select the PointPerfect Developer plan, activate the - Location Thing, navigate to the Credentials tab, download the Client Key, Client Certificate and - Amazon Root Certificate, paste all three into secrets.h. - - The PDP profile is read from NVM. Please make sure you have run examples 4 & 7 previously to set up the profile. - - This example is written for the SparkFun Asset Tracker Carrier Board with ESP32 MicroMod Processor Board: - https://www.sparkfun.com/products/17272 - https://www.sparkfun.com/products/16781 - - This example contains a stripped down version of Michael Ammann (@mazgch)'s HPG Solution: - https://github.com/mazgch/hpg - Specifically, LTE.h: - https://github.com/mazgch/hpg/blob/main/software/LTE.h - - We have written this example for the ESP32 Processor Board because Michael's code: is written using - ESP32 mbed tasks; and uses ESP32 log_ for diagnostic messages, instead of Serial.print. - This means you can limit the messages to Error, Warn, Debug, Info by selecting the appropriate - Core Debug Level in the board options. - - ************************************************************************************************** - * Important Note: * - * * - * This example pulls kBytes of correction data from Thingstream. * - * Depending on your location and service provider, the data rate may exceed the allowable * - * rates for LTE-M or NB-IoT. * - * Worst case, your service provider may throttle or block the connection - now or in the future. * - ************************************************************************************************** - - Feel like supporting open source hardware? - Buy a board from SparkFun! - - Adapted by: Paul Clark - Date: November 10th 2023 - - Licence: - - The code written by Michael Ammann is: - - * Copyright 2022 by Michael Ammann (@mazgch) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. -*/ - -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - -#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_SARA-R5_Arduino_Library - -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - -// Michael Ammann (@mazgch)'s HPG Solution: -// https://github.com/mazgch/hpg - -#include "HW.h" -#include "CONFIG.h" -#include "UBXFILE.h" -#include "LTE.h" - -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - -void setup() -{ - - delay(1000); // Wait for the ESP32 to start up - - Serial.begin(115200); // Start the serial console - - Serial.println(F("SARA-R5 Example")); - - log_i("-------------------------------------------------------------------"); - - String hwName = Config.getDeviceName(); - log_i("SARA-R5_Example17_AssistNow_MQTT %s (%s)", Config.getDeviceTitle().c_str(), hwName.c_str()); - espVersion(); - - //Lte.enableDebugging(UbxSerial); - //Lte.enableAtDebugging(Serial); // we use UbxSerial for data logging instead - Lte.init(); // LTE runs in a task -} - -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - -void loop() -{ - // Nothing to do here! Everything is performed by tasks... -} - -//=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - -// ==================================================================================== -// Helpers -// ==================================================================================== - -#ifndef ESP_ARDUINO_VERSION - #include -#endif - -/** Print the version number of the Arduino and ESP core. - */ -void espVersion(void) { -#ifndef ESP_ARDUINO_VERSION - log_i("Version IDF %s Arduino_esp32 %s", esp_get_idf_version(), ARDUINO_ESP32_RELEASE); -#else - log_i("Version IDF %s Arduino_esp32 %d.%d.%d", esp_get_idf_version(), - ESP_ARDUINO_VERSION_MAJOR,ESP_ARDUINO_VERSION_MINOR,ESP_ARDUINO_VERSION_PATCH); -#endif -} diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/UBXFILE.h b/examples/SARA-R5_Example17_AssistNow_MQTT/UBXFILE.h deleted file mode 100644 index 5c8697b..0000000 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/UBXFILE.h +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2022 by Michael Ammann (@mazgch) - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef __UBXFILE_H__ -#define __UBXFILE_H__ - -#include - -const int UBXSERIAL_BUFFER_SIZE = 0*1024; //!< Size of circular buffer, typically AT modem gets bursts upto 9kB of MQTT data, but 2kB is also fine - -/** older versions of ESP32_Arduino do not yet support flow control, but we need this for the modem. - * The following flag will make sure code is added for this, - */ -#include "driver/uart.h" // for flow control -#if !defined(HW_FLOWCTRL_CTS_RTS) || !defined(ESP_ARDUINO_VERSION) || !defined(ESP_ARDUINO_VERSION_VAL) - #define UBXSERIAL_OVERRIDE_FLOWCONTROL -#elif (ESP_ARDUINO_VERSION <= ESP_ARDUINO_VERSION_VAL(2, 0, 3)) - #define UBXSERIAL_OVERRIDE_FLOWCONTROL -#endif - -/** This class encapsulates all UBXSERIAL functions. the class can be used as alternative to a - * normal Serial port, but add full RX and TX logging capability. -*/ -class UBXSERIAL : public HardwareSerial { -public: - - /** constructor - * \param size the circular buffer size - * \param uart_num the hardware uart number - */ - UBXSERIAL(size_t size, uint8_t uart_num) - : HardwareSerial{uart_num}, buffer{size} { - mutex = xSemaphoreCreateMutex(); // Previously, this was inherited from class UBXFILE - setRxBufferSize(256); - } - - // -------------------------------------------------------------------------------------- - // STREAM interface: https://github.com/arduino/ArduinoCore-API/blob/master/api/Stream.h - // -------------------------------------------------------------------------------------- - - /** The character written is also passed into the circular buffer - * \param ch character to write - * \return the bytes written - */ - size_t write(uint8_t ch) override { - if (pdTRUE == xSemaphoreTake(mutex, portMAX_DELAY)) { - if (buffer.size() > 1) { - buffer.write((const char*)&ch, 1); - } - xSemaphoreGive(mutex); - } - return HardwareSerial::write(ch); - } - - /** All data written is also passed into the circular buffer - * \param ptr pointer to buffer to write - * \param size number of bytes in ptr to write - * \return the bytes written - */ - size_t write(const uint8_t *ptr, size_t size) override { - if (pdTRUE == xSemaphoreTake(mutex, portMAX_DELAY)) { - if (buffer.size() > 1) { - buffer.write((const char*)ptr, size); - } - xSemaphoreGive(mutex); - } - return HardwareSerial::write(ptr, size); - } - - /** The character read is also passed in also passed into the circular buffer. - * \return the character read - */ - int read(void) override { - int ch = HardwareSerial::read(); - if (-1 != ch) { - if (pdTRUE == xSemaphoreTake(mutex, portMAX_DELAY)) { - if (buffer.size() > 1) { - buffer.write((const char*)&ch, 1); - } - xSemaphoreGive(mutex); - } - } - return ch; - } - -#ifdef UBXSERIAL_OVERRIDE_FLOWCONTROL - // The arduino_esp32 core has a bug that some pins are swapped in the setPins function. - // PR https://github.com/espressif/arduino-esp32/pull/6816#pullrequestreview-987757446 was issued - // We will override as we cannot rely on that bugfix being applied in the users environment. - - // extend the flow control API while on older arduino_ESP32 revisions - // we keep the API forward compatible so that when the new platform is released it just works - void setPins(int8_t rxPin, int8_t txPin, int8_t ctsPin, int8_t rtsPin) { - uart_set_pin((uart_port_t)_uart_nr, txPin, rxPin, rtsPin, ctsPin); - } - - void setHwFlowCtrlMode(uint8_t mode, uint8_t threshold) { - uart_set_hw_flow_ctrl((uart_port_t)_uart_nr, (uart_hw_flowcontrol_t) mode, threshold); - } - - #ifndef HW_FLOWCTRL_CTS_RTS - #define HW_FLOWCTRL_CTS_RTS UART_HW_FLOWCTRL_CTS_RTS - #endif -#endif - -protected: - - SemaphoreHandle_t mutex; //!< protects cbuf from concurnet access by tasks. - cbuf buffer; //!< the circular local buffer -}; - -UBXSERIAL UbxSerial(UBXSERIAL_BUFFER_SIZE, UART_NUM_1); //!< The global UBXSERIAL peripherial object (replaces Serial1) - -#endif // __UBXFILE_H__ diff --git a/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h b/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h deleted file mode 100644 index 88ac5bb..0000000 --- a/examples/SARA-R5_Example17_AssistNow_MQTT/secrets.h +++ /dev/null @@ -1,22 +0,0 @@ -// Below infomation you can set after signing up with u-blox Thingstream portal -// and after add a new New PointPerfect Thing -// https://portal.thingstream.io/app/location-services/things -// in the new PointPerfect Thing you go to the credentials page and copy paste the values and certificate into this. - -// -> Credentials -> Client Id -static const char MQTT_CLIENT_ID[] = ""; - -// -> Credentials -> Amazon Root Certificate -static const char AWS_CERT_CA[] PROGMEM = R"(-----BEGIN CERTIFICATE----- - ------END CERTIFICATE-----)"; - -// -> Credentials -> Client Certificate -static const char AWS_CERT_CRT[] PROGMEM = R"(-----BEGIN CERTIFICATE----- - ------END CERTIFICATE-----)"; - -// -> Credentials -> Client Key -static const char AWS_CERT_PRIVATE[] PROGMEM = R"(-----BEGIN RSA PRIVATE KEY----- - ------END RSA PRIVATE KEY-----)"; diff --git a/examples/SARA-R5_Example17_ThingSpeak_MQTT_Publish/SARA-R5_Example17_ThingSpeak_MQTT_Publish.ino b/examples/SARA-R5_Example17_ThingSpeak_MQTT_Publish/SARA-R5_Example17_ThingSpeak_MQTT_Publish.ino new file mode 100644 index 0000000..44a2470 --- /dev/null +++ b/examples/SARA-R5_Example17_ThingSpeak_MQTT_Publish/SARA-R5_Example17_ThingSpeak_MQTT_Publish.ino @@ -0,0 +1,215 @@ +/* + + SARA-R5 Example + =============== + + ThingSpeak (MQTT) + + Written by: Paul Clark + Date: November 14th 2023 + + This example uses the SARA's mobile data connection to publish random temperatures on ThingSpeak using MQTT. + https://thingspeak.com/ + + See: https://uk.mathworks.com/help/thingspeak/mqtt-basics.html#responsive_offcanvas + + You will need to: + Create a ThingSpeak User Account – https://thingspeak.com/login + Create a new Channel by selecting Channels, My Channels, and then New Channel + Note the Channel ID and copy&paste it into myChannelID below + Click on the Devices drop-down at the top of the screen and select MQTT + Create a new MQTT Device using "Add a new device". Give it a name + Authorize the New Channel you created above, then Add Channel, then Add Device + Copy the Username, Client ID and password into myUsername, myClientID and myPassword below + The random temperature reading will be added to the channel as "Field 1" + + Feel like supporting open source hardware? + Buy a board from SparkFun! + + Licence: MIT + Please see LICENSE.md for full details + +*/ + +#include + +// ThingSpeak via MQTT Publish + +String brokerName = "mqtt3.thingspeak.com"; // MQTT Broker + +const int brokerPort = 1883; // MQTT port (TCP, no encryption) + +String myUsername = "OAAxOjYHIwooJykfCiYoEx0"; + +String myClientID = "OAAxOjYHIwooJykfCiYoEx0"; + +String myPassword = "RqY/6L246tULLVWUzCqJBX/V"; + +String myChannelID = "1225363"; + +// SARA-R5 + +#include //Click here to get the library: http://librarymanager/All#SparkFun_u-blox_SARA-R5_Arduino_Library + +// Uncomment the next line to connect to the SARA-R5 using hardware Serial1 +#define saraSerial Serial1 + +// Uncomment the next line to create a SoftwareSerial object to pass to the SARA-R5 library instead +//SoftwareSerial saraSerial(8, 9); + +// Create a SARA_R5 object to use throughout the sketch +// Usually we would tell the library which GPIO pin to use to control the SARA power (see below), +// but we can start the SARA without a power pin. It just means we need to manually +// turn the power on if required! ;-D +SARA_R5 mySARA; + +// Create a SARA_R5 object to use throughout the sketch +// We need to tell the library what GPIO pin is connected to the SARA power pin. +// If you're using the MicroMod Asset Tracker and the MicroMod Artemis Processor Board, +// the pin name is G2 which is connected to pin AD34. +// Change the pin number if required. +//SARA_R5 mySARA(34); + +// processMQTTcommandResult is provided to the SARA-R5 library via a +// callback setter -- setMQTTCommandCallback. (See the end of setup()) +void processMQTTcommandResult(int command, int result) +{ + Serial.println(); + Serial.print(F("MQTT Command Result: command: ")); + Serial.print(command); + Serial.print(F(" result: ")); + Serial.print(result); + if (result == 0) + Serial.print(F(" (fail)")); + if (result == 1) + Serial.print(F(" (success)")); + Serial.println(); + + // Get and print the most recent MQTT protocol error + int error_class; + int error_code; + mySARA.getMQTTprotocolError(&error_class, &error_code); + Serial.print(F("Most recent MQTT protocol error: class: ")); + Serial.print(error_class); + Serial.print(F(" code: ")); + Serial.print(error_code); + if (error_code == 0) + Serial.print(F(" (no error)")); + Serial.println(); +} + +void setup() +{ + delay(1000); + + String currentOperator = ""; + + Serial.begin(115200); // Start the serial console + + // Wait for user to press key to begin + Serial.println(F("SARA-R5 Example")); + Serial.println(F("Wait for the SARA NI LED to light up - then press any key to begin")); + + while (!Serial.available()) // Wait for the user to press a key (send any serial character) + ; + while (Serial.available()) // Empty the serial RX buffer + Serial.read(); + + mySARA.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + + // For the MicroMod Asset Tracker, we need to invert the power pin so it pulls high instead of low + // Comment the next line if required + mySARA.invertPowerPin(true); + + // Initialize the SARA + if (mySARA.begin(saraSerial, 115200) ) + { + Serial.println(F("SARA-R5 connected!")); + } + else + { + Serial.println(F("Unable to communicate with the SARA.")); + Serial.println(F("Manually power-on (hold the SARA On button for 3 seconds) on and try again.")); + while (1) ; // Loop forever on fail + } + Serial.println(); + + // First check to see if we're connected to an operator: + if (mySARA.getOperator(¤tOperator) == SARA_R5_SUCCESS) + { + Serial.print(F("Connected to: ")); + Serial.println(currentOperator); + } + else + { + Serial.print(F("The SARA is not yet connected to an operator. Please use the previous examples to connect. Or wait and retry. Freezing...")); + while (1) + ; // Do nothing more + } + + // Deactivate the PSD profile - in case one is already active + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_DEACTIVATE) != SARA_R5_SUCCESS) + { + Serial.println(F("Warning: performPDPaction (deactivate profile) failed. Probably because no profile was active.")); + } + + // Load the PSD profile from NVM - these were saved by a previous example + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_LOAD) != SARA_R5_SUCCESS) + { + Serial.println(F("performPDPaction (load from NVM) failed! Freezing...")); + while (1) + ; // Do nothing more + } + + // Activate the PSD profile + if (mySARA.performPDPaction(0, SARA_R5_PSD_ACTION_ACTIVATE) != SARA_R5_SUCCESS) + { + Serial.println(F("performPDPaction (activate profile) failed! Freezing...")); + while (1) + ; // Do nothing more + } + + // Set up the MQTT command callback + mySARA.setMQTTCommandCallback(processMQTTcommandResult); + + // Disable the security profile + mySARA.setMQTTsecure(false); + + // Set Client ID + mySARA.setMQTTclientId(myClientID); + + // Set the broker name and port + mySARA.setMQTTserver(brokerName, brokerPort); + + // Set the user name and password + mySARA.setMQTTcredentials(myUsername, myPassword); + + // Connect + if (mySARA.connectMQTT() == SARA_R5_SUCCESS) + Serial.println(F("MQTT connected")); + else + Serial.println(F("MQTT failed to connect!")); +} + +void loop() +{ + float temperature = ((float)random(2000,3000)) / 100.0; // Create a random temperature between 20 and 30 + + // Send data using MQTT Publish + String Topic = "channels/" + myChannelID + "/publish"; + String DataField = "field1=" + String(temperature) + "&status=MQTTPUBLISH"; + + Serial.print(F("Publishing a temperature of ")); + Serial.print(String(temperature)); + Serial.println(F(" to ThingSpeak")); + + // Publish the text message + mySARA.mqttPublishTextMsg(Topic, DataField.c_str(), 0, true); // This defaults to QoS = 0, and retain = false + + // Wait for 20 seconds + for (int i = 0; i < 20000; i++) + { + mySARA.poll(); // Keep processing data from the SARA + delay(1); + } +} From 2d3291b5a1c4529732d1222c8881adfa541638fe Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 14 Nov 2023 17:28:23 +0000 Subject: [PATCH 08/11] Update keywords.txt --- keywords.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/keywords.txt b/keywords.txt index 53ce387..1b23534 100644 --- a/keywords.txt +++ b/keywords.txt @@ -164,12 +164,16 @@ sendHTTPPOSTfile KEYWORD2 nvMQTT KEYWORD2 setMQTTclientId KEYWORD2 setMQTTserver KEYWORD2 +setMQTTcredentials KEYWORD2 setMQTTsecure KEYWORD2 connectMQTT KEYWORD2 disconnectMQTT KEYWORD2 subscribeMQTTtopic KEYWORD2 unsubscribeMQTTtopic KEYWORD2 readMQTT KEYWORD2 +mqttPublishTextMsg KEYWORD2 +mqttPublishBinaryMsg KEYWORD2 +mqttPublishFromFile KEYWORD2 getMQTTprotocolError KEYWORD2 resetSecurityProfile KEYWORD2 configSecurityProfileString KEYWORD2 From 44232513eca30f8e5389b8685d7ad36130a79e08 Mon Sep 17 00:00:00 2001 From: Paul Date: Tue, 14 Nov 2023 17:28:59 +0000 Subject: [PATCH 09/11] Add SARA_R5_SECURITY_RESPONSE_TIMEOUT (10s) --- src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp | 2 +- src/SparkFun_u-blox_SARA-R5_Arduino_Library.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp index 800dcef..490a161 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.cpp @@ -4814,7 +4814,7 @@ SARA_R5_error_t SARA_R5::setSecurityManager(SARA_R5_sec_manager_opcode_t opcode, _debugPort->println(F(" bytes")); } hwWriteData(data.c_str(), dataLen); - err = waitForResponse(SARA_R5_RESPONSE_OK, SARA_R5_RESPONSE_ERROR, SARA_R5_STANDARD_RESPONSE_TIMEOUT*3); + err = waitForResponse(SARA_R5_RESPONSE_OK, SARA_R5_RESPONSE_ERROR, SARA_R5_SECURITY_RESPONSE_TIMEOUT); } diff --git a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h index b743cde..ea1a274 100644 --- a/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h +++ b/src/SparkFun_u-blox_SARA-R5_Arduino_Library.h @@ -89,6 +89,7 @@ #define SARA_R5_IP_CONNECT_TIMEOUT 130000 #define SARA_R5_POLL_DELAY 1 #define SARA_R5_SOCKET_WRITE_TIMEOUT 10000 +#define SARA_R5_SECURITY_RESPONSE_TIMEOUT 10000 // ## Suported AT Commands // ### General From 3455093efca0f43d345cd7bb0a5b07ae2a2a8f76 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 15 Nov 2023 13:39:07 +0000 Subject: [PATCH 10/11] Add combined ThingSpeak MQTT Publish and Subscribe example --- .../SARA-R5_Example17_ThingSpeak_MQTT.ino} | 77 ++++++++++++++++--- 1 file changed, 65 insertions(+), 12 deletions(-) rename examples/{SARA-R5_Example17_ThingSpeak_MQTT_Publish/SARA-R5_Example17_ThingSpeak_MQTT_Publish.ino => SARA-R5_Example17_ThingSpeak_MQTT/SARA-R5_Example17_ThingSpeak_MQTT.ino} (73%) diff --git a/examples/SARA-R5_Example17_ThingSpeak_MQTT_Publish/SARA-R5_Example17_ThingSpeak_MQTT_Publish.ino b/examples/SARA-R5_Example17_ThingSpeak_MQTT/SARA-R5_Example17_ThingSpeak_MQTT.ino similarity index 73% rename from examples/SARA-R5_Example17_ThingSpeak_MQTT_Publish/SARA-R5_Example17_ThingSpeak_MQTT_Publish.ino rename to examples/SARA-R5_Example17_ThingSpeak_MQTT/SARA-R5_Example17_ThingSpeak_MQTT.ino index 44a2470..a48183f 100644 --- a/examples/SARA-R5_Example17_ThingSpeak_MQTT_Publish/SARA-R5_Example17_ThingSpeak_MQTT_Publish.ino +++ b/examples/SARA-R5_Example17_ThingSpeak_MQTT/SARA-R5_Example17_ThingSpeak_MQTT.ino @@ -8,10 +8,12 @@ Written by: Paul Clark Date: November 14th 2023 - This example uses the SARA's mobile data connection to publish random temperatures on ThingSpeak using MQTT. - https://thingspeak.com/ + This example uses the SARA's mobile data connection and MQTT to publish random temperatures on ThingSpeak. + It also subscribes to the same topic (channel) so you can read the data back again! + + See: https://thingspeak.com/ - See: https://uk.mathworks.com/help/thingspeak/mqtt-basics.html#responsive_offcanvas + And: https://uk.mathworks.com/help/thingspeak/mqtt-basics.html#responsive_offcanvas You will need to: Create a ThingSpeak User Account – https://thingspeak.com/login @@ -45,7 +47,7 @@ String myClientID = "OAAxOjYHIwooJykfCiYoEx0"; String myPassword = "RqY/6L246tULLVWUzCqJBX/V"; -String myChannelID = "1225363"; +String myChannelID = "1225363"; // Public View: https://thingspeak.com/channels/1225363 // SARA-R5 @@ -115,7 +117,7 @@ void setup() while (Serial.available()) // Empty the serial RX buffer Serial.read(); - mySARA.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial + //mySARA.enableDebugging(); // Uncomment this line to enable helpful debug messages on Serial // For the MicroMod Asset Tracker, we need to invert the power pin so it pulls high instead of low // Comment the next line if required @@ -186,9 +188,33 @@ void setup() // Connect if (mySARA.connectMQTT() == SARA_R5_SUCCESS) - Serial.println(F("MQTT connected")); + Serial.println(F("MQTT connect: success")); + else + Serial.println(F("MQTT connect: failed!")); + + // The LTE modem has difficulties subscribing/unsubscribing more than one topic at the same time. + // We can only start one operation at a time wait for the URC and add a extra delay before we can + // do the next operation. + // Wait for ~2 seconds + for (int i = 0; i < 200; i++) + { + mySARA.bufferedPoll(); // Keep processing data from the SARA + delay(10); + } + + // Subscribe to the channel topic, so we can read the data back again + String subscribeTopic = "channels/" + myChannelID + "/subscribe/fields/field1"; + if (mySARA.subscribeMQTTtopic(0, subscribeTopic) == SARA_R5_SUCCESS) // QoS = 0 + Serial.println(F("MQTT subscribe: success")); else - Serial.println(F("MQTT failed to connect!")); + Serial.println(F("MQTT subscribe: failed!")); + + // Wait for ~2 seconds + for (int i = 0; i < 200; i++) + { + mySARA.bufferedPoll(); // Keep processing data from the SARA + delay(10); + } } void loop() @@ -199,17 +225,44 @@ void loop() String Topic = "channels/" + myChannelID + "/publish"; String DataField = "field1=" + String(temperature) + "&status=MQTTPUBLISH"; + Serial.println(); Serial.print(F("Publishing a temperature of ")); Serial.print(String(temperature)); Serial.println(F(" to ThingSpeak")); // Publish the text message - mySARA.mqttPublishTextMsg(Topic, DataField.c_str(), 0, true); // This defaults to QoS = 0, and retain = false + mySARA.mqttPublishTextMsg(Topic, DataField.c_str(), 0, true); // QoS = 0, retain = true + + // Wait for ~10 seconds + for (int i = 0; i < 1000; i++) + { + mySARA.bufferedPoll(); // Keep processing data from the SARA + delay(10); + } + + // Check for any received data + // The MQTT API does not allow getting the size before actually reading the data. + // So we have to allocate a big enough buffer. + const int MQTT_MAX_MSG_SIZE = 1024; + static uint8_t buf[MQTT_MAX_MSG_SIZE]; + String topic; + int len = -1; + int qos = -1; + if (mySARA.readMQTT(&qos, &topic, buf, MQTT_MAX_MSG_SIZE, &len) == SARA_R5_SUCCESS) + { + if (len > 0) + { + Serial.println(); + Serial.print(F("Subscribed MQTT data: ")); + Serial.write((const char *)buf, len); + Serial.println(); + } + } - // Wait for 20 seconds - for (int i = 0; i < 20000; i++) + // Wait for ~10 seconds + for (int i = 0; i < 1000; i++) { - mySARA.poll(); // Keep processing data from the SARA - delay(1); + mySARA.bufferedPoll(); // Keep processing data from the SARA + delay(10); } } From 8815d3883e45ff2b14021f115b1bb2fa98b6ae21 Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 15 Nov 2023 13:42:38 +0000 Subject: [PATCH 11/11] v1.1.9 --- library.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library.properties b/library.properties index 80b33a0..767ddfd 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun u-blox SARA-R5 Arduino Library -version=1.1.8 +version=1.1.9 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=Library for the u-blox SARA-R5 LTE-M / NB-IoT modules with secure cloud