diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 0000000..45faf75
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,18 @@
+name: Publish Release
+
+on:
+ push:
+ tags:
+ - 'v*.*.*'
+
+jobs:
+ publish:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v3
+
+ - name: Publish release
+ uses: softprops/action-gh-release@v1
+ with:
+ generate_release_notes: true
diff --git a/hardware/BOM.csv b/hardware/BOM.csv
new file mode 100644
index 0000000..6e4d1ab
--- /dev/null
+++ b/hardware/BOM.csv
@@ -0,0 +1,8 @@
+Part Name, Total Quantity Required, Note
+LilyGo TTGO T-Beam v1.1 SX1262 + GPS NEO-M8N + ESP32 + BMS, 1,
+GPS + GLONASS Antenna, 1,
+LoRa Antenna, 1,
+u.FL to Waterproof SMA Adapter Cable, 2,
+SMA Connector Cap, 2,
+NCR 18650B Li-Ion Battery 3400mAh, 1,
+LED Diode 3mm, 1,
diff --git a/hardware/STL/case-bottom.stl b/hardware/STL/case-bottom.stl
new file mode 100644
index 0000000..becff97
Binary files /dev/null and b/hardware/STL/case-bottom.stl differ
diff --git a/hardware/STL/case-top.stl b/hardware/STL/case-top.stl
new file mode 100644
index 0000000..1204e5a
Binary files /dev/null and b/hardware/STL/case-top.stl differ
diff --git a/hardware/STL/gasket.stl b/hardware/STL/gasket.stl
new file mode 100644
index 0000000..4ee4a51
Binary files /dev/null and b/hardware/STL/gasket.stl differ
diff --git a/hardware/wiring-diagram.svg b/hardware/wiring-diagram.svg
new file mode 100644
index 0000000..1df55d3
--- /dev/null
+++ b/hardware/wiring-diagram.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/lib/E32-868T20D/E32-868T20D.cpp b/lib/E32-868T20D/E32-868T20D.cpp
new file mode 100644
index 0000000..71d420d
--- /dev/null
+++ b/lib/E32-868T20D/E32-868T20D.cpp
@@ -0,0 +1,45 @@
+#include "E32-868T20D.h"
+
+uint8_t E32_868T20D::checksum(uint8_t * data, size_t len) {
+ uint8_t cs = 0;
+ for (int i = 0; i < len; i++)
+ cs += data[i];
+ return ~cs + 1;
+}
+
+size_t E32_868T20D::encode(uint16_t address, const char * message, uint8_t * output, size_t * outputSize) {
+ size_t consumed = strnlen(message, 58);
+ *outputSize = consumed + 5;
+ output[0] = consumed;
+ output[1] = random(0xFF);
+ output[2] = address >> 8;
+ output[3] = address;
+ for (int i = 0; i < consumed; i++)
+ output[i+4] = message[i] ^ keys[output[1]];
+ output[*outputSize-1] = checksum(output, *outputSize-1);
+ return consumed;
+}
+
+uint16_t E32_868T20D::getAddress(uint8_t * packet) {
+ return (uint16_t)packet[2] << 8 | packet[3];
+}
+
+size_t E32_868T20D::decode(uint8_t * packet, char * output) {
+ size_t len = packet[0];
+ if (checksum(packet, len+5)) return 0;
+ uint8_t index = packet[1];
+ for (int i = 0; i < len; i++)
+ output[i] = packet[i+4] ^ keys[index];
+ output[len] = 0;
+ return len;
+}
+
+void E32_868T20D::generateKeyDumpPacket(uint16_t address, uint8_t keyIndex, uint8_t * output, size_t * outputSize) {
+ *outputSize = 6;
+ output[0] = 0x01;
+ output[1] = keyIndex;
+ output[2] = address >> 8;
+ output[3] = address;
+ output[4] = 0x00;
+ output[5] = checksum(output, *outputSize-1);
+}
diff --git a/lib/E32-868T20D/E32-868T20D.h b/lib/E32-868T20D/E32-868T20D.h
new file mode 100644
index 0000000..37813c0
--- /dev/null
+++ b/lib/E32-868T20D/E32-868T20D.h
@@ -0,0 +1,34 @@
+#ifndef E32_868T20D_H
+#define E32_868T20D_H
+
+#include
+
+class E32_868T20D {
+ const uint8_t keys[256] = {
+ 0x9A, 0x99, 0x98, 0x9F, 0x9E, 0x9D, 0x9C, 0xA3, 0xA2, 0xA1, 0xA0, 0xA7, 0xA6, 0xA5, 0xA4, 0xAB,
+ 0xAA, 0xA9, 0xA8, 0xAF, 0xAE, 0xAD, 0xAC, 0xB3, 0xB2, 0xB1, 0xB0, 0xB7, 0xB6, 0xB5, 0xB4, 0xBB,
+ 0xBA, 0xB9, 0xB8, 0xBF, 0xBE, 0xBD, 0xBC, 0xC3, 0xC2, 0xC1, 0xC0, 0xC7, 0xC6, 0xC5, 0xC4, 0xCB,
+ 0xCA, 0xC9, 0xC8, 0xCF, 0xCE, 0xCD, 0xCC, 0xD3, 0xD2, 0xD1, 0xD0, 0xD7, 0xD6, 0xD5, 0xD4, 0xDB,
+ 0xDA, 0xD9, 0xD8, 0xDF, 0xDE, 0xDD, 0xDC, 0xE3, 0xE2, 0xE1, 0xE0, 0xE7, 0xE6, 0xE5, 0xE4, 0xEB,
+ 0xEA, 0xE9, 0xE8, 0xEF, 0xEE, 0xED, 0xEC, 0xF3, 0xF2, 0xF1, 0xF0, 0xF7, 0xF6, 0xF5, 0xF4, 0xFB,
+ 0xFA, 0xF9, 0xF8, 0xFF, 0xFE, 0xFD, 0xFC, 0x03, 0x02, 0x01, 0x00, 0x07, 0x06, 0x05, 0x04, 0x0B,
+ 0x0A, 0x09, 0x08, 0x0F, 0x0E, 0x0D, 0x0C, 0x13, 0x12, 0x11, 0x10, 0x17, 0x16, 0x15, 0x14, 0x1B,
+ 0x1A, 0x19, 0x18, 0x1F, 0x1E, 0x1D, 0x1C, 0x23, 0x22, 0x21, 0x20, 0x27, 0x26, 0x25, 0x24, 0x2B,
+ 0x2A, 0x29, 0x28, 0x2F, 0x2E, 0x2D, 0x2C, 0x33, 0x32, 0x31, 0x30, 0x37, 0x36, 0x35, 0x34, 0x3B,
+ 0x3A, 0x39, 0x38, 0x3F, 0x3E, 0x3D, 0x3C, 0x43, 0x42, 0x41, 0x40, 0x47, 0x46, 0x45, 0x44, 0x4B,
+ 0x4A, 0x49, 0x48, 0x4F, 0x4E, 0x4D, 0x4C, 0x53, 0x52, 0x51, 0x50, 0x57, 0x56, 0x55, 0x54, 0x5B,
+ 0x5A, 0x59, 0x58, 0x5F, 0x5E, 0x5D, 0x5C, 0x63, 0x62, 0x61, 0x60, 0x67, 0x66, 0x65, 0x64, 0x6B,
+ 0x6A, 0x69, 0x68, 0x6F, 0x6E, 0x6D, 0x6C, 0x73, 0x72, 0x71, 0x70, 0x77, 0x76, 0x75, 0x74, 0x7B,
+ 0x7A, 0x79, 0x78, 0x7F, 0x7E, 0x7D, 0x7C, 0x83, 0x82, 0x81, 0x80, 0x87, 0x86, 0x85, 0x84, 0x8B,
+ 0x8A, 0x89, 0x88, 0x8F, 0x8E, 0x8D, 0x8C, 0x93, 0x92, 0x91, 0x90, 0x97, 0x96, 0x95, 0x94, 0x9B,
+ };
+
+ public:
+ uint8_t checksum(uint8_t * data, size_t len);
+ size_t encode(uint16_t address, const char * message, uint8_t * output, size_t * outputSize);
+ uint16_t getAddress(uint8_t * packet);
+ size_t decode(uint8_t * packet, char * output);
+ void generateKeyDumpPacket(uint16_t address, uint8_t keyIndex, uint8_t * output, size_t * outputSize);
+};
+
+#endif
diff --git a/lib/README b/lib/README
new file mode 100644
index 0000000..58415f5
--- /dev/null
+++ b/lib/README
@@ -0,0 +1,45 @@
+This directory is intended for project specific (private) libraries.
+PlatformIO will compile them to static libraries and link into executable file.
+
+The source code of each library should be placed in a an own separate directory
+("lib/your_library_name/[here are source files]").
+
+For example, see a structure of the following two libraries `Foo` and `Bar`:
+
+|--lib
+| |
+| |--Bar
+| | |--docs
+| | |--examples
+| | |--src
+| | |- Bar.c
+| | |- Bar.h
+| | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
+| |
+| |--Foo
+| | |- Foo.c
+| | |- Foo.h
+| |
+| |- README --> THIS FILE
+|
+|- platformio.ini
+|--src
+ |- main.c
+
+and a contents of `src/main.c`:
+```
+#include
+#include
+
+int main (void)
+{
+ ...
+}
+
+```
+
+PlatformIO Library Dependency Finder will find automatically dependent
+libraries scanning project source files.
+
+More information about PlatformIO Library Dependency Finder
+- https://docs.platformio.org/page/librarymanager/ldf.html
diff --git a/platformio.ini b/platformio.ini
index ffe8504..361ee82 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -8,18 +8,28 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
-[env:LilyGo TTGO T-Beam]
+[env:TTGO T-Beam]
platform = espressif32
board = ttgo-t-beam
framework = arduino
+monitor_speed = 115200
+src_filter = - +
lib_deps =
- bblanchon/ArduinoJson@^6.18.2
+ metisvela/SailtrackModule@^1.6.0
lewisxhe/AXP202X_Library@^1.1.3
sparkfun/SparkFun u-blox GNSS Arduino Library@^2.0.9
- metisvela/SailTrack Module@^1.1.3
-monitor_speed = 115200
+ jgromes/RadioLib@^5.1.2
+build_flags =
+ -D STM_NOTIFICATION_LED_PIN=4
+ -D STM_NOTIFICATION_LED_ON_STATE=LOW
+
+; Uncomment to use OTA
+; upload_protocol = espota
+; upload_port = 192.168.42.101
-[env:LilyGo TTGO T-Beam OTA]
-extends = env:LilyGo TTGO T-Beam
-upload_protocol = espota
-upload_port = sailtrack-radio.local
+[env: TTGO T-Beam (KeyDump)]
+extends = env:TTGO T-Beam
+src_filter = + -
+lib_deps =
+ lewisxhe/AXP202X_Library@^1.1.3
+ jgromes/RadioLib@^5.1.2
diff --git a/src/keydump.cpp b/src/keydump.cpp
new file mode 100644
index 0000000..8d84017
--- /dev/null
+++ b/src/keydump.cpp
@@ -0,0 +1,61 @@
+#include
+#include
+#include
+#include
+
+// -------------------------- Configuration -------------------------- //
+
+#define LORA_CS_PIN 18
+#define LORA_DIO1_PIN 33
+#define LORA_RST_PIN 23
+#define LORA_BUSY_PIN 32
+
+// EBYTE E32-868T20D parameters
+#define E32_CHANNEL 0x09
+#define E32_ADDRESS 0x1310
+#define E32_BASE_FREQUENCY_MHZ 862
+#define E32_BANDWIDTH_KHZ 500
+#define E32_SPREADING_FACTOR 11
+#define E32_CODING_RATE_DENOM 5
+
+// ------------------------------------------------------------------- //
+
+AXP20X_Class pmu;
+SX1262 lora = new Module(LORA_CS_PIN, LORA_DIO1_PIN, LORA_RST_PIN, LORA_BUSY_PIN);
+E32_868T20D e32;
+
+void beginPMU() {
+ Wire.begin();
+ pmu.begin(Wire, AXP192_SLAVE_ADDRESS);
+ pmu.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); // GPIO Pins Power Source
+ pmu.setPowerOutPut(AXP192_DCDC2, AXP202_OFF); // Unused
+ pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LoRa Power Source
+ pmu.setPowerOutPut(AXP192_LDO3, AXP202_OFF); // GPS Power Source
+ pmu.setPowerOutPut(AXP192_EXTEN, AXP202_OFF); // External Connector Power Source
+}
+
+void beginLora() {
+ pmu.setLDO2Voltage(3300);
+ pmu.setPowerOutPut(AXP192_LDO2, AXP202_ON);
+ lora.begin(E32_BASE_FREQUENCY_MHZ + E32_CHANNEL, E32_BANDWIDTH_KHZ, E32_SPREADING_FACTOR, E32_CODING_RATE_DENOM);
+}
+
+void setup() {
+ Serial.begin(115200);
+ beginPMU();
+ beginLora();
+
+ Serial.println("Dumping keys...");
+ uint8_t packet[64];
+ size_t len;
+ for (int i = 0; i <= 0xFF; i++) {
+ e32.generateKeyDumpPacket(E32_ADDRESS, (uint8_t)i, packet, &len);
+ lora.transmit(packet, len);
+ Serial.print("Completed: ");
+ Serial.print(i+1); Serial.print("/"); Serial.println(256);
+ delay(100);
+ }
+ Serial.println("Done!");
+}
+
+void loop() {}
diff --git a/src/main.cpp b/src/main.cpp
index 53e2836..d925769 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,87 +1,172 @@
#include
-#include
+#include
#include
#include
-#include
+#include
+#include
+
+// -------------------------- Configuration -------------------------- //
+
+#define MQTT_PUBLISH_FREQ_HZ 5
+#define LORA_SEND_FREQ_HZ 1
+
+#define GPS_BAUD_RATE 9600
+#define GPS_SERIAL_CONFIG SERIAL_8N1
+#define GPS_RX_PIN 34
+#define GPS_TX_PIN 12
+#define GPS_NAVIGATION_FREQ_HZ MQTT_PUBLISH_FREQ_HZ
+
+#define LORA_CS_PIN 18
+#define LORA_DIO1_PIN 33
+#define LORA_RST_PIN 23
+#define LORA_BUSY_PIN 32
+#define LORA_MESSAGE_BUFFER_SIZE 512
+#define LORA_PACKET_SIZE 64
+
+// EBYTE E32-868T20D parameters
+#define E32_CHANNEL 0x09
+#define E32_ADDRESS 0x1310
+#define E32_BASE_FREQUENCY_MHZ 862
+#define E32_BANDWIDTH_KHZ 500
+#define E32_SPREADING_FACTOR 11
+#define E32_CODING_RATE_DENOM 5
-#define I2C_SDA 21
-#define I2C_SCL 22
+#define LOOP_TASK_INTERVAL_MS 1000 / (2 * GPS_NAVIGATION_FREQ_HZ)
+#define LORA_TASK_INTERVAL_MS 1000 / LORA_SEND_FREQ_HZ
-#define GPS_RX_PIN 34
-#define GPS_TX_PIN 12
-#define GPS_BAND_RATE 9600
+struct LoraMetric {
+ char value[32];
+ char topic[32];
+ char name[32];
+} loraMetrics[] = {
+ { "0", "sensor/gps0", "fixType" },
+ { "0", "sensor/gps0", "epoch" },
+ { "0", "sensor/gps0", "lon" },
+ { "0", "sensor/gps0", "lat" },
+ { "0", "sensor/gps0", "gSpeed" },
+ { "0", "sensor/gps0", "headMot" },
+ { "0", "sensor/imu0", "euler.x"},
+ { "0", "sensor/imu0", "euler.y" },
+ { "0", "sensor/imu0", "euler.z" }
+};
+
+// ------------------------------------------------------------------- //
-SFE_UBLOX_GNSS GPS;
-AXP20X_Class PMU;
+SailtrackModule stm;
+SFE_UBLOX_GNSS gps;
+AXP20X_Class pmu;
+SX1262 lora = new Module(LORA_CS_PIN, LORA_DIO1_PIN, LORA_RST_PIN, LORA_BUSY_PIN);
+E32_868T20D e32;
+
+size_t loraSentBytes = 0;
class ModuleCallbacks: public SailtrackModuleCallbacks {
- void onWifiConnectionBegin() {
- // TODO: Notify user
- }
-
- void onWifiConnectionResult(wl_status_t status) {
- // TODO: Notify user
+ void onStatusPublish(JsonObject status) {
+ JsonObject battery = status.createNestedObject("battery");
+ battery["voltage"] = pmu.getBattVoltage() / 1000;
+ JsonObject lora = status.createNestedObject("lora");
+ lora["bitrate"] = loraSentBytes * 8 * STM_STATUS_PUBLISH_FREQ_HZ / 1000;
+ loraSentBytes = 0;
}
- DynamicJsonDocument getStatus() {
- DynamicJsonDocument payload(300);
- JsonObject battery = payload.createNestedObject("battery");
- JsonObject cpu = payload.createNestedObject("cpu");
- battery["voltage"] = PMU.getBattVoltage() / 1000;
- battery["charging"] = PMU.isChargeing();
- cpu["temperature"] = temperatureRead();
- return payload;
+ void onMqttMessage(const char * topic, JsonObjectConst message) {
+ for (int i = 0; i < sizeof(loraMetrics)/sizeof(*loraMetrics); i++) {
+ LoraMetric & metric = loraMetrics[i];
+ if (!strcmp(topic, metric.topic)) {
+ char metricName[strlen(metric.name)+1];
+ strcpy(metricName, metric.name);
+ char * token = strtok(metricName, ".");
+ JsonVariantConst tmpVal = message;
+ while (token) {
+ if (!tmpVal.containsKey(token)) break;
+ tmpVal = tmpVal[token];
+ token = strtok(NULL, ".");
+ }
+ if (!token) serializeJson(tmpVal, metric.value);
+ }
+ }
}
};
-void onGPSData(UBX_NAV_PVT_data_t ubxDataStruct) {
- DynamicJsonDocument payload(300);
- payload["latitude"] = ubxDataStruct.lat;
- payload["longitude"] = ubxDataStruct.lon;
- payload["speed"] = ubxDataStruct.gSpeed;
- payload["heading"] = ubxDataStruct.headMot;
- payload["vacc"] = ubxDataStruct.vAcc;
- payload["hacc"] = ubxDataStruct.hAcc;
- payload["sacc"] = ubxDataStruct.sAcc;
- payload["headacc"] = ubxDataStruct.headAcc;
- STModule.publish("sensor/gps0", "gps0", payload);
+void loraTask(void * pvArguments) {
+ TickType_t lastWakeTime = xTaskGetTickCount();
+ while (true) {
+ char message[LORA_MESSAGE_BUFFER_SIZE];
+ strcpy(message, loraMetrics[0].value);
+ for (int i = 1; i < sizeof(loraMetrics)/sizeof(*loraMetrics); i++) {
+ strcat(message, " ");
+ strcat(message, loraMetrics[i].value);
+ }
+ strcat(message, "\n");
+
+ uint8_t packet[LORA_PACKET_SIZE];
+ size_t len;
+ size_t consumed = 0;
+ size_t toConsume = strlen(message);
+ while (consumed < toConsume) {
+ consumed += e32.encode(E32_ADDRESS, message + consumed, packet, &len);
+ lora.transmit(packet, len);
+ loraSentBytes += len;
+ }
+ vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(LORA_TASK_INTERVAL_MS));
+ }
}
void beginPMU() {
- Wire.begin(I2C_SDA, I2C_SCL);
- PMU.begin(Wire, AXP192_SLAVE_ADDRESS);
- PMU.setPowerOutPut(AXP192_DCDC2, AXP202_OFF);
- PMU.setPowerOutPut(AXP192_LDO2, AXP202_OFF);
- PMU.setPowerOutPut(AXP192_LDO3, AXP202_OFF);
- PMU.setPowerOutPut(AXP192_EXTEN, AXP202_OFF);
+ Wire.begin();
+ pmu.begin(Wire, AXP192_SLAVE_ADDRESS);
+ pmu.setPowerOutPut(AXP192_DCDC1, AXP202_OFF); // GPIO Pins Power Source
+ pmu.setPowerOutPut(AXP192_DCDC2, AXP202_OFF); // Unused
+ pmu.setPowerOutPut(AXP192_LDO2, AXP202_OFF); // LoRa Power Source
+ pmu.setPowerOutPut(AXP192_LDO3, AXP202_OFF); // GPS Power Source
+ pmu.setPowerOutPut(AXP192_EXTEN, AXP202_OFF); // External Connector Power Source
}
void beginGPS() {
- PMU.setLDO3Voltage(3300);
- PMU.setPowerOutPut(AXP192_LDO3, AXP202_ON);
- Serial1.begin(GPS_BAND_RATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
- GPS.begin(Serial1);
- GPS.setUART1Output(COM_TYPE_UBX);
- GPS.setMeasurementRate(200);
- GPS.setAutoPVTcallback(&onGPSData);
+ pmu.setLDO3Voltage(3300);
+ pmu.setPowerOutPut(AXP192_LDO3, AXP202_ON);
+ Serial1.begin(GPS_BAUD_RATE, GPS_SERIAL_CONFIG, GPS_RX_PIN, GPS_TX_PIN);
+ gps.begin(Serial1);
+ gps.setUART1Output(COM_TYPE_UBX);
+ gps.setDynamicModel(DYN_MODEL_SEA);
+ gps.setNavigationFrequency(GPS_NAVIGATION_FREQ_HZ);
+ gps.setAutoPVT(true);
}
void beginLora() {
- PMU.setLDO2Voltage(3300);
- PMU.setPowerOutPut(AXP192_LDO2, AXP202_ON);
- // TODO: Init LoRa
+ pmu.setLDO2Voltage(3300);
+ pmu.setPowerOutPut(AXP192_LDO2, AXP202_ON);
+ lora.begin(E32_BASE_FREQUENCY_MHZ + E32_CHANNEL, E32_BANDWIDTH_KHZ, E32_SPREADING_FACTOR, E32_CODING_RATE_DENOM);
+ for (auto metric : loraMetrics) stm.subscribe(metric.topic);
+ xTaskCreate(loraTask, "loraTask", STM_TASK_MEDIUM_STACK_SIZE, NULL, STM_TASK_MEDIUM_PRIORITY, NULL);
}
void setup() {
beginPMU();
- STModule.begin("radio", "sailtrack-radio", IPAddress(192, 168, 42, 101));
- STModule.setCallbacks(new ModuleCallbacks());
+ stm.begin("radio", IPAddress(192, 168, 42, 101), new ModuleCallbacks());
beginGPS();
- //beginLora();
+ beginLora();
}
void loop() {
- GPS.checkUblox();
- GPS.checkCallbacks();
- delay(50);
+ TickType_t lastWakeTime = xTaskGetTickCount();
+ if (gps.getPVT() && gps.getTimeValid()) {
+ StaticJsonDocument doc;
+ doc["fixType"] = gps.getFixType(); // GNSSfix Type: 0 = no fix, 1 = dead reckoning only, 2 = 2D-fix, 3 = 3D-fix
+ doc["epoch"] = gps.getUnixEpoch(); // Get the current Unix epoch time rounded to the nearest second
+ doc["lon"] = gps.getLongitude(); // Longitude: deg * 1e-7
+ doc["lat"] = gps.getLatitude(); // Latitude: deg * 1e-7
+ doc["hMSL"] = gps.getAltitudeMSL(); // Height above mean sea level: mm
+ doc["hAcc"] = gps.getHorizontalAccEst(); // Horizontal accuracy estimate: mm
+ doc["vAcc"] = gps.getVerticalAccEst(); // Vertical accuracy estimate: mm
+ doc["velN"] = gps.getNedNorthVel(); // NED north velocity: mm/s
+ doc["velE"] = gps.getNedEastVel(); // NED east velocity: mm/s
+ doc["velD"] = gps.getNedDownVel(); // NED down velocity: mm/s
+ doc["gSpeed"] = gps.getGroundSpeed(); // Ground Speed (2-D): mm/s
+ doc["headMot"] = gps.getHeading(); // Heading of motion (2-D): deg * 1e-5
+ doc["sAcc"] = gps.getSpeedAccEst(); // Speed accuracy estimate: mm/s
+ doc["headAcc"] = gps.getHeadingAccEst(); // Heading accuracy estimate (both motion and vehicle): deg * 1e-5
+ stm.publish("sensor/gps0", doc.as());
+ }
+ vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(LOOP_TASK_INTERVAL_MS));
}