From d133abefecbe11627b44c45c0686154c11c41b7e Mon Sep 17 00:00:00 2001 From: WorldDrknss Date: Mon, 19 Aug 2024 09:29:50 -0700 Subject: [PATCH] Initial Commit --- .gitignore | 5 + .vscode/extensions.json | 10 ++ include/astm.h | 247 +++++++++++++++++++++++++++++++++++++ include/display.h | 38 ++++++ include/enc.h | 246 +++++++++++++++++++++++++++++++++++++ include/gps.h | 42 +++++++ include/init.h | 54 ++++++++ include/nema.h | 108 ++++++++++++++++ include/wifiBeacon.h | 71 +++++++++++ lib/README | 46 +++++++ platformio.ini | 18 +++ src/display.cpp | 210 ++++++++++++++++++++++++++++++++ src/gps.cpp | 127 +++++++++++++++++++ src/init.cpp | 81 ++++++++++++ src/main.cpp | 131 ++++++++++++++++++++ src/nema.cpp | 264 ++++++++++++++++++++++++++++++++++++++++ src/wifiBeacon.cpp | 203 ++++++++++++++++++++++++++++++ test/README | 11 ++ 18 files changed, 1912 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/extensions.json create mode 100644 include/astm.h create mode 100644 include/display.h create mode 100644 include/enc.h create mode 100644 include/gps.h create mode 100644 include/init.h create mode 100644 include/nema.h create mode 100644 include/wifiBeacon.h create mode 100644 lib/README create mode 100644 platformio.ini create mode 100644 src/display.cpp create mode 100644 src/gps.cpp create mode 100644 src/init.cpp create mode 100644 src/main.cpp create mode 100644 src/nema.cpp create mode 100644 src/wifiBeacon.cpp create mode 100644 test/README diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..080e70d --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,10 @@ +{ + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "platformio.platformio-ide" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/include/astm.h b/include/astm.h new file mode 100644 index 0000000..94b56d2 --- /dev/null +++ b/include/astm.h @@ -0,0 +1,247 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +// Definitions and structures required by ASTM F3411-22a standard +#ifndef ASTM_F3411_22A_H +#define ASTM_F3411_22A_H + +// Enumeration for message types in ASTM F3411-22a standard +typedef enum Messages { + msBasicID = 0x0, // Basic ID message type (0x0) + msLocation = 0x1, // Location/Vector message type (0x1) + msAuthentication = 0x2, // Authentication message type (0x2) + msSelfID = 0x3, // Self ID message type (0x3) + msSystem = 0x4, // System message type (0x4) + msOperatorID = 0x5, // Operator ID message type (0x5) + msMessagePack = 0xF // Message Pack message type (0xF) +} msType; + +// Enumeration for different types of IDs in ASTM F3411-22a standard +typedef enum ID { + idNone = 0, // No ID type (0) + idSerial = 1, // Serial number ID type (1) + idCAA = 2, // CAA (Civil Aviation Authority) compliant ID type (2) + idUTM = 3, // UTM (Unmanned Traffic Management) compliant ID type (3) + idSession = 4 // Session ID type (4) +} idTypes; + +// Enumeration for different types of Unmanned Aircraft (UA) in ASTM F3411-22a standard +typedef enum UA { + uaUndefined = 0, // Undefined UA type (0) + uaAeroplane = 1, // Aeroplane UA type (1) + uaMultirotor = 2, // Multirotor UA type (2) + uaGyroplane = 3, // Gyroplane UA type (3) + uaFixedWing = 4, // Fixed-wing UA type (4) + uaOrnithopter = 5, // Ornithopter UA type (5) + uaGlider = 6, // Glider UA type (6) + uaKite = 7, // Kite UA type (7) + uaFreeBalloon = 8, // Free balloon UA type (8) + uaCaptiveBalloon = 9, // Captive balloon UA type (9) + uaAirship = 10, // Airship UA type (10) + uaFreeFall = 11, // Free-fall UA type (11) + uaRocket = 12, // Rocket UA type (12) + uaTethered = 13, // Tethered UA type (13) + uaGround = 14, // Ground UA type (14) + uaOther = 15 // Other UA type (15) +} uaTypes; + +// Enumeration for different types of Operational Status in ASTM F3411-22a standard +typedef enum OperationalStatus { + osUndeclared = 0, // Undeclared operational status (0) + osGround = 1, // Ground operational status (1) + osAirborne = 2, // Airborne operational status (2) + osEmergency = 3, // Emergency operational status (3) + osSystemFailure = 4 // System failure operational status (4) +} osTypes; + +// Enumeration for different types of Operator Location in ASTM F3411-22a standard +typedef enum OperatorLocation { + olTakeoff = 0, // Operator location at the takeoff point (0) + olDynamic = 1, // Dynamic operator location (moving) (1) + olFixed = 2 // Fixed operator location (stationary) (2) +} olTypes; + +// Enumeration for different classification types in ASTM F3411-22a standard +// Enumeration for different types of Classification in ASTM F3411-22a standard +typedef enum Classification { + cfUndeclared = 0, // Undeclared classification (0) + cfEuropeanUnion = 1, // European Union classification (1) + // 2 - 7 Reserved for future use +} cfTypes; + +// Enumeration for different types of Categories in ASTM F3411-22a standard +typedef enum Categories { + euUndefined = 0, // Undefined category (0) + euOpen = 1, // Open category (1) + euSpecific = 2, // Specific category (2) + euCertified = 3 // Certified category (3) + // 4 - 15 Reserved for future use +} euTypes; + +// Enumeration for different types of Classes in ASTM F3411-22a standard +typedef enum Classes { + clUndefined = 0, // Undefined class (0) + clClass0 = 1, // Class 0 (1) + clClass1 = 2, // Class 1 (2) + clClass2 = 3, // Class 2 (3) + clClass3 = 4, // Class 3 (4) + clClass4 = 5, // Class 4 (5) + clClass5 = 6 // Class 5 (6) + // 7 - 15 Reserved for future use +} clTypes; + + +// Structure for Message Header as defined in ASTM F3411-22a standard +typedef struct MessageHeader { + uint8_t messageType_protocolVersion; // Bits 7..4: Message Type, Bits 3..0: Protocol Version +} MessageHeader; + +// Structure for Basic ID Message as defined in ASTM F3411-22a standard +typedef struct BasicIDMessage { + MessageHeader header; // 1 byte + uint8_t idType_uaType; // ID Type (upper 4 bits) and UA Type (lower 4 bits) - 1 byte + uint8_t uasID[20]; // UAS ID (20 bytes) + uint8_t reserved[3]; // Reserved bytes (3 bytes) +} BasicIDMessage; + + +// Structure for Location/Vector Message as defined in ASTM F3411-22a standard +typedef struct LocationMessage { + MessageHeader header; // 1 byte + uint8_t status; // 1 byte + uint8_t trackDirection; // 1 byte + uint8_t speed; // 1 byte + int8_t verticalSpeed; // 1 byte + int32_t latitude; // 4 bytes + int32_t longitude; // 4 bytes + int16_t pressureAltitude; // 2 bytes + int16_t geodeticAltitude; // 2 bytes + int16_t height; // 2 bytes + uint8_t horVertAccuracy; // 1 byte (Combined Horizontal and Vertical Accuracy) + uint8_t baroSpeedAccuracy; // 1 byte (Combined Baro Altitude Accuracy and Speed Accuracy) + uint16_t timestamp; // 2 bytes + uint8_t reservedTimestampAccuracy; // 1 byte (Combined Reserved and Timestamp Accuracy) + uint8_t reserved; // 1 byte +} __attribute__((packed)) LocationMessage; + + +// Structure for Authentication Message as defined in ASTM F3411-22a standard +typedef struct AuthenticationMessage { + MessageHeader header; // Message header + uint8_t authType; // Authentication Type + uint8_t authData[23]; // Authentication Data +} AuthenticationMessage; + +// Structure for Self-ID Message as defined in ASTM F3411-22a standard +typedef struct SelfIDMessage { + MessageHeader header; // Message header + uint8_t descriptionType; // Description Type + char description[23]; // ASCII Text Description (padded with nulls) +} SelfIDMessage; + +// Structure for System Message as defined in ASTM F3411-22a standard +typedef struct SystemMessage { + MessageHeader header; // Message header + uint8_t flags; // Flags (1 byte) + int32_t operatorLatitude; // Latitude of Remote Pilot (4 bytes) + int32_t operatorLongitude; // Longitude of Remote Pilot (4 bytes) + uint16_t areaCount; // Number of aircraft in Area (2 bytes) + uint16_t areaRadius; // Radius of area (2 bytes) + int16_t areaCeiling; // Area ceiling (2 bytes) + int16_t areaFloor; // Area floor (2 bytes) + uint8_t classification; // Classification (1 byte) + int16_t operatorAltitude; // Altitude of Remote Pilot (2 bytes) + uint32_t timestamp; // Timestamp (4 bytes) + uint8_t reserved; // Reserved (1 byte) +} __attribute__((packed)) SystemMessage; + +// Structure for Operator-ID Message as defined in ASTM F3411-22a standard +typedef struct OperatorIDMessage { + MessageHeader header; // Message header + uint8_t operatorIDType; // Operator ID Type (0: Operator ID, 1-200: Reserved, 201-255: Available for private use) + char operatorID[20]; // ASCII Text for Operator ID (padded with nulls) + uint8_t reserved[3]; // Reserved bytes +} OperatorIDMessage; + +// Horizontal Accuracy Enumeration +typedef enum HorizontalAccuracy { + H_ACC_UNKNOWN = 0, + H_ACC_10_NM = 1, + H_ACC_4_NM = 2, + H_ACC_2_NM = 3, + H_ACC_1_NM = 4, + H_ACC_0_5_NM = 5, + H_ACC_0_3_NM = 6, + H_ACC_0_1_NM = 7, + H_ACC_0_05_NM = 8, + H_ACC_30_M = 9, + H_ACC_10_M = 10, + H_ACC_3_M = 11, + H_ACC_1_M = 12, + H_ACC_RESERVED = 13 +} HorizontalAccuracy; + +// Vertical Accuracy Enumeration +typedef enum VerticalAccuracy { + V_ACC_UNKNOWN = 0, + V_ACC_150_M = 1, + V_ACC_45_M = 2, + V_ACC_25_M = 3, + V_ACC_10_M = 4, + V_ACC_3_M = 5, + V_ACC_1_M = 6 +} VerticalAccuracy; + +// Speed Accuracy Enumeration +typedef enum SpeedAccuracy { + S_ACC_UNKNOWN = 0, + S_ACC_10_M_S = 1, + S_ACC_3_M_S = 2, + S_ACC_1_M_S = 3, + S_ACC_0_3_M_S = 4 +} SpeedAccuracy; + +// Define VendorSpecificTag structure +typedef struct VendorSpecificTag { + uint8_t length; + uint8_t oui[3]; + uint8_t vendType; + uint8_t msgCounter; + uint8_t advData[256]; // Example size, should be adjusted according to the actual requirement +} VendorSpecificTag; + +typedef struct MessagePack { + MessageHeader header; // Message header + uint8_t messageSize; // Message Size (25 Bytes) + uint8_t numMessages; // Number of messages in the pack (up to 9) + uint8_t messages[9][25]; // Array of messages, up to 9 messages, each 25 bytes long +} MessagePack; + +#endif // ASTM_F3411_22A_H \ No newline at end of file diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..2d1cb24 --- /dev/null +++ b/include/display.h @@ -0,0 +1,38 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#ifndef DISPLAY_H +#define DISPLAY_H +#include + +void displaySetup(const char* ssid); +void displayTask(void * parameters); + +#endif diff --git a/include/enc.h b/include/enc.h new file mode 100644 index 0000000..1b5dc65 --- /dev/null +++ b/include/enc.h @@ -0,0 +1,246 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#ifndef ENCODERS_H +#define ENCODERS_H + +#include + +// Function declarations for encoding messages +inline void encodeMessageHeader(MessageHeader *header, uint8_t messageType, uint8_t protocolVersion); +inline void encodeBasicIDMessage(BasicIDMessage *msg, idTypes idType, uaTypes uaType, const char *uasID); +inline void encodeLocationMessage(LocationMessage *msg, uint8_t status, uint8_t trackDirection, uint8_t speed, int8_t verticalSpeed, float latitude, float longitude, float pressureAltitude, float geodeticAltitude, int16_t height, uint32_t horAccuracy, uint32_t vertAccuracy, uint32_t speedAccuracy, uint16_t timestamp); +inline void encodeAuthenticationMessage(AuthenticationMessage *msg, uint8_t authType, const uint8_t *authData); +inline void encodeSelfIDMessage(SelfIDMessage *msg, uint8_t descriptionType, const char *description); +inline void encodeSystemMessage(SystemMessage *msg, uint8_t operatorLocationType, int32_t operatorLatitude, int32_t operatorLongitude, uint16_t areaCount, uint16_t areaRadius, int16_t areaCeiling, int16_t areaFloor, uint8_t classificationType, uint8_t uaCategory, uint8_t uaClass, int16_t operatorAltitude, uint32_t timestamp); +inline void encodeOperatorIDMessage(OperatorIDMessage *msg, uint8_t operatorIDType, const char *operatorID); +inline void encodeVendorSpecificTag(VendorSpecificTag *vendorTag, MessagePack *messagePack); +inline uint8_t encodeDirection(uint16_t direction, uint8_t *directionSegment); +inline uint8_t encodeSpeed(float speed, uint8_t *speedMultiplier); +inline int32_t convertLatLonFormat(float value); +inline int8_t encodeVerticalSpeed(float verticalSpeed); +inline uint16_t encodeAltitude(float altitude); +inline uint16_t encodeTimestamp(float timestamp); + +inline uint8_t convertHorizontalAccuracy(uint32_t accuracy) { + if (accuracy >= 18520000) return 0; + if (accuracy >= 7408000) return 1; + if (accuracy >= 3704000) return 2; + if (accuracy >= 1852000) return 3; + if (accuracy >= 926000) return 4; + if (accuracy >= 555600) return 5; + if (accuracy >= 185200) return 6; + if (accuracy >= 92600) return 7; + if (accuracy >= 30000) return 8; + if (accuracy >= 10000) return 9; + if (accuracy >= 3000) return 10; + if (accuracy >= 1000) return 11; + if (accuracy >= 100) return 12; + return 13; +} + +inline uint8_t convertVerticalAccuracy(uint32_t accuracy) { + if (accuracy >= 15000) return 0; + if (accuracy >= 4500) return 1; + if (accuracy >= 2500) return 2; + if (accuracy >= 1000) return 3; + if (accuracy >= 300) return 4; + if (accuracy >= 100) return 5; + return 6; +} + +inline uint8_t convertSpeedAccuracy(uint32_t accuracy) { + if (accuracy >= 10000) return 0; + if (accuracy >= 3000) return 1; + if (accuracy >= 1000) return 2; + if (accuracy >= 300) return 3; + return 4; +} + +inline uint8_t encodeTrackDirection(float course) { + if (course < 0 || course > 359) { + return 0; // Default value if the course is out of range + } + uint8_t direction = static_cast(course); + if (direction > 179) { + direction -= 180; + } + return direction; +} + + +// Function definitions +inline void encodeMessageHeader(MessageHeader *header, uint8_t messageType, uint8_t protocolVersion) { + header->messageType_protocolVersion = (messageType << 4) | (protocolVersion & 0x0F); +} + +inline void encodeBasicIDMessage(BasicIDMessage *msg, idTypes idType, uaTypes uaType, const char *uasID) { + encodeMessageHeader(&msg->header, msBasicID, 0x01); + msg->idType_uaType = (idType << 4) | (uaType & 0x0F); + memset(msg->uasID, 0, sizeof(msg->uasID)); + strncpy((char *)msg->uasID, uasID, sizeof(msg->uasID) - 1); + msg->uasID[sizeof(msg->uasID) - 1] = '\0'; + memset(msg->reserved, 0, sizeof(msg->reserved)); +} + +inline void encodeLocationMessage(LocationMessage *msg, uint8_t status, uint8_t trackDirection, uint8_t speed, int8_t verticalSpeed, float latitude, float longitude, float pressureAltitude, float geodeticAltitude, int16_t height, uint32_t horAccuracy, uint32_t vertAccuracy, uint32_t speedAccuracy, uint16_t timestamp) { + encodeMessageHeader(&msg->header, msLocation, 0x01); + msg->status = (status << 4); + msg->trackDirection = encodeTrackDirection(trackDirection); + msg->speed = speed; + msg->verticalSpeed = verticalSpeed; + msg->latitude = static_cast(latitude * 1e7); // Convert degrees to degrees * 10^7 + msg->longitude = static_cast(longitude * 1e7); // Convert degrees to degrees * 10^7 + msg->pressureAltitude = encodeAltitude(pressureAltitude); + msg->geodeticAltitude = encodeAltitude(geodeticAltitude); + msg->height = height; + msg->horVertAccuracy = (convertVerticalAccuracy(vertAccuracy) << 4) | (convertHorizontalAccuracy(horAccuracy) & 0x0F); + msg->baroSpeedAccuracy = (convertSpeedAccuracy(speedAccuracy) << 4) | (convertSpeedAccuracy(speedAccuracy) & 0x0F); + msg->timestamp = timestamp; + msg->reservedTimestampAccuracy = 0x00; + msg->reserved = 0x00; +} + +inline void encodeAuthenticationMessage(AuthenticationMessage *msg, uint8_t authType, const uint8_t *authData) { + encodeMessageHeader(&msg->header, msAuthentication, 0x1); + msg->authType = authType; + memcpy(msg->authData, authData, sizeof(msg->authData)); +} + +inline void encodeSelfIDMessage(SelfIDMessage *msg, uint8_t descriptionType, const char *description) { + encodeMessageHeader(&msg->header, msSelfID, 0x1); + msg->descriptionType = descriptionType; + strncpy(msg->description, description, sizeof(msg->description)); + if (strlen(description) < sizeof(msg->description)) { + memset(msg->description + strlen(description), 0, sizeof(msg->description) - strlen(description)); + } +} + +inline void encodeSystemMessage(SystemMessage *msg, uint8_t operatorLocationType, int32_t operatorLatitude, int32_t operatorLongitude, uint16_t areaCount, uint16_t areaRadius, int16_t areaCeiling, int16_t areaFloor, uint8_t classificationType, uint8_t uaCategory, uint8_t uaClass, int16_t operatorAltitude, uint32_t timestamp) { + encodeMessageHeader(&msg->header, msSystem, 0x1); + msg->flags = (classificationType << 4) | operatorLocationType; + msg->operatorLatitude = operatorLatitude; + msg->operatorLongitude = operatorLongitude; + msg->areaCount = areaCount; + msg->areaRadius = areaRadius; + msg->areaCeiling = areaCeiling; + msg->areaFloor = areaFloor; + msg->classification = (uaCategory << 4) | uaClass; + msg->operatorAltitude = operatorAltitude; + msg->timestamp = timestamp; + msg->reserved = 0; +} + +inline void encodeOperatorIDMessage(OperatorIDMessage *msg, uint8_t operatorIDType, const char *operatorID) { + encodeMessageHeader(&msg->header, msOperatorID, 0x1); + msg->operatorIDType = operatorIDType; + strncpy(msg->operatorID, operatorID, sizeof(msg->operatorID)); + if (strlen(operatorID) < sizeof(msg->operatorID)) { + memset(msg->operatorID + strlen(operatorID), 0, sizeof(msg->operatorID) - strlen(operatorID)); + } + memset(msg->reserved, 0, sizeof(msg->reserved)); +} + +inline void encodeVendorSpecificTag(VendorSpecificTag *vendorTag, MessagePack *messagePack) { + vendorTag->length = 3 + 1 + 1 + (messagePack->numMessages * messagePack->messageSize); + vendorTag->oui[0] = 0xFA; + vendorTag->oui[1] = 0x0B; + vendorTag->oui[2] = 0xBC; + vendorTag->vendType = 0x0D; + memcpy(vendorTag->advData, messagePack, sizeof(MessageHeader) + 1 + 1 + (messagePack->numMessages * messagePack->messageSize)); +} + +inline uint8_t encodeDirection(uint16_t direction, uint8_t *directionSegment) { + uint8_t encodedValue; + if (direction < 180) { + encodedValue = direction; + *directionSegment = 0; + } else { + encodedValue = direction - 180; + *directionSegment = 1; + } + return encodedValue; +} + +inline uint8_t encodeSpeed(float speed, uint8_t *speedMultiplier) { + uint8_t encodedValue; + if (speed <= 255 * 0.25) { + encodedValue = speed / 0.25; + *speedMultiplier = 0; + } else if (speed > 255 * 0.25 && speed < 254.25) { + encodedValue = (speed - (255 * 0.25)) / 0.75; + *speedMultiplier = 1; + } else { + encodedValue = 254; + *speedMultiplier = 1; + } + return encodedValue; +} + +inline int32_t convertLatLonFormat(float value) { + // Check if the value is already in the format of degrees * 10^7 + if (value > 1000000.0 || value < -1000000.0) { + return static_cast(value); + } else if (value > 360.0 || value < -360.0) { + // If value is in decimal format (e.g., 12345.6789), convert it to degrees first + int degrees = static_cast(value / 100); + float minutes = value - (degrees * 100); + float decimalDegrees = degrees + (minutes / 60.0); + return static_cast(decimalDegrees * 1e7); + } else { + // Convert degrees to degrees * 10^7 + return static_cast(value * 1e7); + } +} + + +inline int8_t encodeVerticalSpeed(float verticalSpeed) { + if (verticalSpeed > 62.0) { + verticalSpeed = 62.0; + } else if (verticalSpeed < -62.0) { + verticalSpeed = -62.0; + } + float dividedValue = verticalSpeed / 0.5; + int8_t encodedValue = static_cast(dividedValue); + return encodedValue; +} + +inline uint16_t encodeAltitude(float altitude) { + if (altitude == -1000) { + return 0; + } + return (altitude + 1000) / 0.5; +} + +inline uint16_t encodeTimestamp(float timestamp) { + return timestamp * 10; +} + +#endif // ENCODERS_H \ No newline at end of file diff --git a/include/gps.h b/include/gps.h new file mode 100644 index 0000000..003b9b9 --- /dev/null +++ b/include/gps.h @@ -0,0 +1,42 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#ifndef GPS_H +#define GPS_H +#include +#include "nema.h" // Include the header file where GGAData is defined + +// External declaration of ggaData +extern GGAData ggaData; + +extern HardwareSerial GPSSerial; +void gpsSetup(); +void gpsTask(void * parameters); +#endif diff --git a/include/init.h b/include/init.h new file mode 100644 index 0000000..5dbbe31 --- /dev/null +++ b/include/init.h @@ -0,0 +1,54 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#ifndef INIT_H +#define INIT_H + +#include +#include + +struct OperatorInfo { + String OperatorID; + int32_t OperatorLatitude; + int32_t OperatorLongitude; + String OperatorTimezone; +}; + +struct Config { + int enableBT; + int enableWiFi; + OperatorInfo operatorInfo; +}; + +extern Config config; + +void loadConfig(); + +#endif // INIT_H diff --git a/include/nema.h b/include/nema.h new file mode 100644 index 0000000..86f22ce --- /dev/null +++ b/include/nema.h @@ -0,0 +1,108 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#ifndef NEMA_H +#define NEMA_H + +#include + +struct GGAData { + String time; + double latitude; + char latDir; + double longitude; + char lonDir; + int fixQuality; + int numSatellites; + double hdop; + double altitude; + char altUnit; + double geoidHeight; + char geoUnit; + double dgpsAge; + int dgpsStationId; +}; + +struct RMCData { + String time; + char status; + double latitude; + char latDir; + double longitude; + char lonDir; + double speed; + double course; + String date; + double magVar; + char magVarDir; + char mode; +}; + +struct GSAData { + char mode1; + int mode2; + int sv[12]; + double pdop; + double hdop; + double vdop; +}; + +struct GSVData { + int numMessages; + int messageNum; + int numSVs; + struct { + int svID; + int elevation; + int azimuth; + int snr; + } svInfo[4]; +}; + +struct NAVACCData { + String time; + char status; + uint32_t pAcc; // Horizontal Accuracy + uint32_t vAcc; // Vertical Accuracy + uint32_t cAcc; // Speed Accuracy +}; + +void parseGGA(String nmea, GGAData &ggaData); +void parseRMC(String nmea, RMCData &rmcData); +void parseGSA(String nmea, GSAData &gsaData); +void parseGSV(String nmea, GSVData &gsvData); +void parseNAVACC(String nmea, NAVACCData &navaccData); +void parseNMEA(String nmea, GGAData &ggaData, RMCData &rmcData, GSAData &gsaData, GSVData &gsvData, NAVACCData &navaccData); + +double convertToDecimalDegrees(double coordinate, char direction); +double parseDouble(String value); +int parseInt(String value); + +#endif // NEMA_H diff --git a/include/wifiBeacon.h b/include/wifiBeacon.h new file mode 100644 index 0000000..fbd642f --- /dev/null +++ b/include/wifiBeacon.h @@ -0,0 +1,71 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#ifndef WIFI_H +#define WIFI_H + +#include +#include +#include + +// Define the WiFi channel and output power +extern const int wifiChannel; +extern const int wifiOutputPower; +extern const int ledPin; + +// Define UA Information +#define uasIDType 1 // Serial +#define uasType 2 // Multirotor + +// Define Location Information +// #define uasStatus 2 +// #define uasLatitude 336970783 // UAS Latitude +// #define uasLongitude -1171853531 // UAS Longitude +// #define uasAltitude 0 // UAS Altitude in meters +// #define uasHorizontalSpeed 60 // Horizontal Speed in cm/s +// #define uasVerticalSpeed 30 // Vertical Speed in cm/s +// #define uasTrack 180 // Track direction in degrees + +// Define Operator Information +// #define operatorID "FC:WorldDrknss" +// #define operatorLatitude 336970783 +// #define operatorLongitude -1171853531 + +// Define Self ID Information +#define selfIdType 0 +#define selfIdDescription "Recreational" + +void wifiBeaconSetup(const char* ssid); +void wifiBeaconTask(void* parameters); +void printRawBytes(uint8_t* data, size_t length); +void constructBeaconFrame(const char* ssid, int channel, int beaconInterval); +void encodeVendorSpecificElement(const char* ssid); + +#endif // WIFI_H diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..a10cade --- /dev/null +++ b/lib/README @@ -0,0 +1,46 @@ + +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 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 new file mode 100644 index 0000000..db619ae --- /dev/null +++ b/platformio.ini @@ -0,0 +1,18 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +[env:heltec_wireless_stick] +platform = espressif32 +board = heltec_wireless_stick +framework = arduino +lib_deps = + heltecautomation/Heltec ESP32 Dev-Boards@^2.0.2 + bblanchon/ArduinoJson@^7.1.0 + paulstoffregen/Time@^1.6.1 diff --git a/src/display.cpp b/src/display.cpp new file mode 100644 index 0000000..c28cced --- /dev/null +++ b/src/display.cpp @@ -0,0 +1,210 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +//display.cpp +#include "HT_st7735.h" +#include "init.h" +#include "gps.h" // Include the header file where ggaData is declared +#include // Include string.h for strlen and strcmp functions +#include + +extern TaskHandle_t wifiBeaconTaskHandle; // Declare the task handle as external + +HT_st7735 tft; +extern Config config; + +// Variables to store previous values +int prevNumSatellites = -1; +int prevNumSatellitesLength = strlen("PNDNG"); +double prevLatitude = -1; +int prevLatitudeLength = strlen("PNDNG"); +double prevLongitude = -1; +int prevLongitudeLength = strlen("PNDNG"); +char prevTime[20] = "PNDNG"; +int prevTimeLength = strlen(prevTime); + +// Variables to store previous WiFi status +char prevWifiStatus[10] = "PNDNG"; +int prevWifiStatusLength = strlen(prevWifiStatus); + +#define ST7735_PURPLE 0x780F // This is an example for purple + +int timezoneStringToOffset(const String& timezone) { + if (timezone == "PST") return -8; + if (timezone == "PDT") return -7; + if (timezone == "EST") return -5; + if (timezone == "EDT") return -4; + if (timezone == "CST") return -6; + if (timezone == "CDT") return -5; + // Add other timezones as needed + return 0; // Default to UTC if the timezone is not recognized +} + +void displaySetup(const char* ssid) { + int col1 = 0; + int col2 = 30; + + // Initialize Screen and Fill Black + tft.st7735_init(); + tft.st7735_fill_screen(ST7735_BLACK); + + // Beacon and FC Status + tft.st7735_write_str(col1, 0, "BTX:", Font_7x10, ST7735_PURPLE, ST7735_BLACK); + tft.st7735_write_str(col2, 0, "PNDNG", Font_7x10, ST7735_RED, ST7735_BLACK); + tft.st7735_write_str(85, 0, "FC:", Font_7x10, ST7735_PURPLE, ST7735_BLACK); + tft.st7735_write_str(110, 0, "PNDNG", Font_7x10, ST7735_RED, ST7735_BLACK); + + // Satellites + tft.st7735_write_str(col1, 13, "SAT:", Font_7x10, ST7735_PURPLE, ST7735_BLACK); + tft.st7735_write_str(col2, 13, "PNDNG", Font_7x10, ST7735_RED, ST7735_BLACK); + tft.st7735_write_str(85, 13, "TM:", Font_7x10, ST7735_PURPLE, ST7735_BLACK); + tft.st7735_write_str(110, 13, "PNDNG", Font_7x10, ST7735_RED, ST7735_BLACK); + + // Lat + tft.st7735_write_str(col1, 26, "LAT:", Font_7x10, ST7735_PURPLE, ST7735_BLACK); + tft.st7735_write_str(col2, 26, "PNDNG", Font_7x10, ST7735_RED, ST7735_BLACK); + + // Lon + tft.st7735_write_str(col1, 39, "LNG:", Font_7x10, ST7735_PURPLE, ST7735_BLACK); + tft.st7735_write_str(col2, 39, "PNDNG", Font_7x10, ST7735_RED, ST7735_BLACK); + + // Aircraft Serial + tft.st7735_write_str(col1, 52, "MFR:", Font_7x10, ST7735_PURPLE, ST7735_BLACK); + tft.st7735_write_str(col2, 52, "RRDI", Font_7x10, ST7735_RED, ST7735_BLACK); // Show RRDI + + tft.st7735_write_str(col1, 65, "S/N:", Font_7x10, ST7735_PURPLE, ST7735_BLACK); + tft.st7735_write_str(col2, 65, ssid + 4, Font_7x10, ST7735_RED, ST7735_BLACK); // Show the remaining of SSID +} + +String formatTime(int value) { + if (value < 10) return "0" + String(value); + return String(value); +} + +void displayTask(void* parameters) { + Serial.println("Display Task started"); + + for (;;) { + // Update the display with ggaData only if values have changed + char buffer[20]; + + // Update number of satellites if it has changed + if (ggaData.numSatellites > 0 && ggaData.numSatellites != prevNumSatellites) { + snprintf(buffer, sizeof(buffer), "%d", ggaData.numSatellites); + int newLength = strlen(buffer); + tft.st7735_write_str(30, 13, buffer, Font_7x10, ST7735_RED, ST7735_BLACK); + // Clear any extra characters + for (int i = newLength; i < prevNumSatellitesLength; i++) { + tft.st7735_write_str(30 + i * 7, 13, " ", Font_7x10, ST7735_BLACK, ST7735_BLACK); + } + prevNumSatellites = ggaData.numSatellites; + prevNumSatellitesLength = newLength; + } else if (ggaData.numSatellites == 0 && prevNumSatellites != 0) { + tft.st7735_write_str(30, 13, "PNDNG", Font_7x10, ST7735_RED, ST7735_BLACK); + prevNumSatellites = 0; + prevNumSatellitesLength = strlen("PNDNG"); + } + + // Update latitude if it has changed + if (ggaData.latitude != prevLatitude) { + snprintf(buffer, sizeof(buffer), "%.5f", ggaData.latitude); + int newLength = strlen(buffer); + tft.st7735_write_str(30, 26, buffer, Font_7x10, ST7735_RED, ST7735_BLACK); + // Clear any extra characters + for (int i = newLength; i < prevLatitudeLength; i++) { + tft.st7735_write_str(30 + i * 7, 26, " ", Font_7x10, ST7735_BLACK, ST7735_BLACK); + } + prevLatitude = ggaData.latitude; + prevLatitudeLength = newLength; + } else if (ggaData.latitude == 0.0 && prevLatitude != 0.0) { + tft.st7735_write_str(30, 26, "PNDNG", Font_7x10, ST7735_RED, ST7735_BLACK); + prevLatitude = 0.0; + prevLatitudeLength = strlen("PNDNG"); + } + + // Update longitude if it has changed + if (ggaData.longitude != prevLongitude) { + snprintf(buffer, sizeof(buffer), "%.5f", ggaData.longitude); + int newLength = strlen(buffer); + tft.st7735_write_str(30, 39, buffer, Font_7x10, ST7735_RED, ST7735_BLACK); + // Clear any extra characters + for (int i = newLength; i < prevLongitudeLength; i++) { + tft.st7735_write_str(30 + i * 7, 39, " ", Font_7x10, ST7735_BLACK, ST7735_BLACK); + } + prevLongitude = ggaData.longitude; + prevLongitudeLength = newLength; + } else if (ggaData.longitude == 0.0 && prevLongitude != 0.0) { + tft.st7735_write_str(30, 39, "PNDNG", Font_7x10, ST7735_RED, ST7735_BLACK); + prevLongitude = 0.0; + prevLongitudeLength = strlen("PNDNG"); + } + + // Get the timezone offset from config + int timezoneOffset = timezoneStringToOffset(config.operatorInfo.OperatorTimezone); + + // Convert GPS time to int + int hours = ggaData.time.substring(0, 2).toInt(); + int minutes = ggaData.time.substring(2, 4).toInt(); + + // Apply the timezone offset + hours = (hours + timezoneOffset + 24) % 24; // Ensure the hours wrap around correctly + + // Format the adjusted time back to a string + String formattedTime = formatTime(hours) + ":" + formatTime(minutes); + + // Update GPS time if it has changed + if (strcmp(formattedTime.c_str(), prevTime) != 0) { + snprintf(buffer, sizeof(buffer), "%s", formattedTime.c_str()); + int newLength = strlen(buffer); + tft.st7735_write_str(110, 13, buffer, Font_7x10, ST7735_RED, ST7735_BLACK); + // Clear any extra characters + for (int i = newLength; i < prevTimeLength; i++) { + tft.st7735_write_str(110 + i * 7, 13, " ", Font_7x10, ST7735_BLACK, ST7735_BLACK); + } + strcpy(prevTime, formattedTime.c_str()); + prevTimeLength = newLength; + } + + // Update WiFi Beacon Task status if it has changed + const char* wifiStatus = (eTaskGetState(wifiBeaconTaskHandle) != eSuspended) ? "TRSMTNG" : "PNDNG"; + if (strcmp(wifiStatus, prevWifiStatus) != 0) { + int newLength = strlen(wifiStatus); + tft.st7735_write_str(30, 0, wifiStatus, Font_7x10, ST7735_RED, ST7735_BLACK); + // Clear any extra characters + for (int i = newLength; i < prevWifiStatusLength; i++) { + tft.st7735_write_str(30 + i * 7, 0, " ", Font_7x10, ST7735_BLACK, ST7735_BLACK); + } + strcpy(prevWifiStatus, wifiStatus); + prevWifiStatusLength = newLength; + } + + vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay to yield control + } +} diff --git a/src/gps.cpp b/src/gps.cpp new file mode 100644 index 0000000..221ac29 --- /dev/null +++ b/src/gps.cpp @@ -0,0 +1,127 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#include "gps.h" +#include "nema.h" + +HardwareSerial GPSSerial(1); + +GGAData ggaData; +RMCData rmcData; +GSAData gsaData; +GSVData gsvData; +NAVACCData navaccData; + +#define VGNSS_CTRL 3 + +void gpsSetup() { + pinMode(VGNSS_CTRL, OUTPUT); + digitalWrite(VGNSS_CTRL, HIGH); + GPSSerial.begin(115200, SERIAL_8N1, 33, 34); // RX, TX + Serial.println("GPS setup complete"); + + // Enable NAVACC and VTG + GPSSerial.print("$CFGMSG,0,5,1\r\n"); // Enable VTG + delay(1000); // Wait for the GPS to respond + GPSSerial.print("$CFGMSG,1,3,1\r\n"); // Enable NAVACC + delay(1000); // Wait for the GPS to respond +} + +void gpsTask(void* parameters) { + Serial.println("GPS Task started"); + for (;;) { + while (GPSSerial.available()) { + String nmea = GPSSerial.readStringUntil('\n'); + parseNMEA(nmea, ggaData, rmcData, gsaData, gsvData, navaccData); + + // Print all NMEA messages for debugging + // Serial.println(nmea); + + if (nmea.startsWith("$GPGGA") || nmea.startsWith("$GBGGA") || nmea.startsWith("$GAGGA") || nmea.startsWith("$GLGGA") || nmea.startsWith("$GNGGA")) { + // Serial.print("Time: "); + // Serial.println(ggaData.time); + // Serial.print("GGA Latitude: "); + // Serial.println(ggaData.latitude); + // Serial.print("GGA Longitude: "); + // Serial.println(ggaData.longitude); + // Serial.println(ggaData.numSatellites); + } else if (nmea.startsWith("$GPRMC") || nmea.startsWith("$GBRMC") || nmea.startsWith("$GARMC") || nmea.startsWith("$GLRMC") || nmea.startsWith("$GNRMC")) { + // Serial.print("Time: "); + // Serial.println(rmcData.time); + // Serial.print("Status: "); + // Serial.println(rmcData.status); + // Serial.print("RMC Latitude: "); + // Serial.println(rmcData.latitude); + // Serial.print("RMC Longitude: "); + // Serial.println(rmcData.longitude); + } else if (nmea.startsWith("$GPGSA") || nmea.startsWith("$GBGSA") || nmea.startsWith("$GAGSA") || nmea.startsWith("$GLGSA") || nmea.startsWith("$GNGSA")) { + // Serial.print("Mode1: "); + // Serial.println(gsaData.mode1); + // Serial.print("Mode2: "); + // Serial.println(gsaData.mode2); + // Serial.print("PDOP: "); + // Serial.println(gsaData.pdop); + // Serial.print("HDOP: "); + // Serial.println(gsaData.hdop); + // Serial.print("VDOP: "); + // Serial.println(gsaData.vdop); + } else if (nmea.startsWith("$GPGSV") || nmea.startsWith("$GBGSV") || nmea.startsWith("$GAGSV") || nmea.startsWith("$GLGSV") || nmea.startsWith("$GNGSV")) { + // Serial.print("Number of Messages: "); + // Serial.println(gsvData.numMessages); + // Serial.print("Message Number: "); + // Serial.println(gsvData.messageNum); + // Serial.print("Number of SVs: "); + // Serial.println(gsvData.numSVs); + // for (int i = 0; i < 4; i++) { + // Serial.print("SV ID: "); + // Serial.println(gsvData.svInfo[i].svID); + // Serial.print("Elevation: "); + // Serial.println(gsvData.svInfo[i].elevation); + // Serial.print("Azimuth: "); + // Serial.println(gsvData.svInfo[i].azimuth); + // Serial.print("SNR: "); + // Serial.println(gsvData.svInfo[i].snr); + // } + } else if (nmea.startsWith("$NAVACC")) { + // Serial.print("NAVACC Time: "); + // Serial.println(navaccData.time); + // Serial.print("NAVACC Status: "); + // Serial.println(navaccData.status); + // Serial.print("Horizontal Accuracy: "); + // Serial.println(navaccData.pAcc); + // Serial.print("Vertical Accuracy: "); + // Serial.println(navaccData.vAcc); + // Serial.print("Ground Course Accuracy: "); + // Serial.println(navaccData.cAcc); + } + } + vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay to yield control + } +} diff --git a/src/init.cpp b/src/init.cpp new file mode 100644 index 0000000..24643a7 --- /dev/null +++ b/src/init.cpp @@ -0,0 +1,81 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#include +#include +#include +#include "init.h" + +Config config; + +void loadConfig() { + if (!SPIFFS.begin(true)) { + Serial.println("An error has occurred while mounting SPIFFS"); + return; + } + + File configFile = SPIFFS.open("/config.json", "r"); + if (!configFile) { + Serial.println("Failed to open config file"); + return; + } + + size_t size = configFile.size(); + if (size > 1024) { + Serial.println("Config file size is too large"); + return; + } + + std::unique_ptr buf(new char[size]); + configFile.readBytes(buf.get(), size); + + DynamicJsonDocument doc(1024); + auto error = deserializeJson(doc, buf.get()); + if (error) { + Serial.print(F("Failed to parse config file: ")); + Serial.println(error.c_str()); + return; + } + + config.enableBT = doc["config"]["enableBT"]; + config.enableWiFi = doc["config"]["enableWiFi"]; + config.operatorInfo.OperatorID = doc["operator"]["OperatorID"].as(); + config.operatorInfo.OperatorLatitude = doc["operator"]["OperatorLatitude"]; + config.operatorInfo.OperatorLongitude = doc["operator"]["OperatorLongitude"]; + config.operatorInfo.OperatorTimezone = doc["operator"]["OperatorTimezone"].as(); + + Serial.println("Config loaded successfully"); + Serial.println("enableBT: " + String(config.enableBT)); + Serial.println("enableWiFi: " + String(config.enableWiFi)); + Serial.println("OperatorID: " + config.operatorInfo.OperatorID); + Serial.println("OperatorLatitude: " + String(config.operatorInfo.OperatorLatitude)); + Serial.println("OperatorLongitude: " + String(config.operatorInfo.OperatorLongitude)); + Serial.println("OperatorTimezone: " + config.operatorInfo.OperatorTimezone); +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..199c9bc --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,131 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#include "Arduino.h" +#include "gps.h" +#include "display.h" +#include "wifiBeacon.h" +#include "init.h" +#include +#include + +#define VGNSS_CTRL 3 + +String ssidString; +const char* ssid; +TaskHandle_t wifiBeaconTaskHandle = NULL; // Declare the task handle globally + +void setup() { + ssidString = generateUASID(); + ssid = ssidString.c_str(); + + pinMode(VGNSS_CTRL, OUTPUT); + digitalWrite(VGNSS_CTRL, HIGH); + Serial.begin(115200); + + // Order of Operations + loadConfig(); + gpsSetup(); + displaySetup(ssid); + wifiBeaconSetup(ssid); + + + Serial.println("Creating GPS Task..."); + if (xTaskCreate( + gpsTask, // Function to be called + "GPS Task", // Name of the task + 8192, // Stack size (increased size) + NULL, // Task input parameter + 1, // Priority of the task + NULL // Task handle + ) + != pdPASS) { + Serial.println("Failed to create GPS Task"); + } else { + Serial.println("GPS Task created successfully"); + } + + Serial.println("Creating WiFi Beacon Task..."); + if (xTaskCreatePinnedToCore( + wifiBeaconTask, // Function to be called + "WiFi Beacon Task", // Name of the task + 8192, // Stack size (increased size) + (void*)ssid, // Task input parameter + 2, // Priority of the task + &wifiBeaconTaskHandle, // Task handle + 0 // Core number (0 or 1 for ESP32) + ) + != pdPASS) { + Serial.println("Failed to create WiFi Beacon Task"); + } else { + Serial.println("WiFi Beacon Task created successfully"); + vTaskSuspend(wifiBeaconTaskHandle); // Suspend the task immediately + } + + Serial.println("Creating Display Task..."); + if (xTaskCreate( + displayTask, // Function to be called + "Display Task", // Name of the task + 8192, // Stack size (increased size) + NULL, // Task input parameter + 3, // Priority of the task + NULL // Task handle + ) + != pdPASS) { + Serial.println("Failed to create Display Task"); + } else { + Serial.println("Display Task created successfully"); + } +} + +void loop() { + if (ggaData.numSatellites > 1) { + vTaskResume(wifiBeaconTaskHandle); // Resume the task if the condition is met + } else { + vTaskSuspend(wifiBeaconTaskHandle); // Suspend the task if the condition is not met + } + + vTaskDelay(1000 / portTICK_PERIOD_MS); // Delay to prevent rapid suspend/resume calls +} + +String generateUASID() { + String prefix = "RRDIF"; + uint64_t chipId = ESP.getEfuseMac(); + String serialNumber = String(chipId, HEX); + serialNumber.toUpperCase(); + while (serialNumber.length() < 7) { + serialNumber = "0" + serialNumber; + } + String uasID = prefix + serialNumber; + while (uasID.length() < 19) { + uasID += "0"; + } + return uasID; +} \ No newline at end of file diff --git a/src/nema.cpp b/src/nema.cpp new file mode 100644 index 0000000..20f29c6 --- /dev/null +++ b/src/nema.cpp @@ -0,0 +1,264 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#include "nema.h" + +double convertToDecimalDegrees(double coordinate, char direction) { + int degrees = int(coordinate / 100); + double minutes = coordinate - (degrees * 100); + double decimalDegrees = degrees + (minutes / 60.0); + + if (direction == 'S' || direction == 'W') { + decimalDegrees = -decimalDegrees; + } + + return decimalDegrees; +} + +double parseDouble(String value) { + return value.toFloat(); +} + +int parseInt(String value) { + return value.toInt(); +} + +void parseGGA(String gga, GGAData &ggaData) { + int idx = 0; + int fieldIndex = 0; + String field; + + while (idx < gga.length()) { + int endIdx = gga.indexOf(',', idx); + if (endIdx == -1) endIdx = gga.length(); + + field = gga.substring(idx, endIdx); + + switch (fieldIndex) { + case 1: // UTC Time + ggaData.time = field; + break; + case 2: // Latitude + ggaData.latitude = parseDouble(field); + break; + case 3: // Latitude Direction + ggaData.latDir = field.charAt(0); + break; + case 4: // Longitude + ggaData.longitude = parseDouble(field); + break; + case 5: // Longitude Direction + ggaData.lonDir = field.charAt(0); + break; + case 6: // Fix Quality + ggaData.fixQuality = parseInt(field); + break; + case 7: // Number of Satellites + ggaData.numSatellites = parseInt(field); + break; + case 8: // HDOP + ggaData.hdop = parseDouble(field); + break; + case 9: // Altitude + ggaData.altitude = parseDouble(field); + break; + case 10: // Altitude Unit + ggaData.altUnit = field.charAt(0); + break; + case 11: // Geoid Height + ggaData.geoidHeight = parseDouble(field); + break; + case 12: // Geoid Unit + ggaData.geoUnit = field.charAt(0); + break; + case 13: // DGPS Age + ggaData.dgpsAge = parseDouble(field); + break; + case 14: // DGPS Station ID + ggaData.dgpsStationId = parseInt(field); + break; + } + + idx = endIdx + 1; + fieldIndex++; + } + + // Convert latitude and longitude to proper format + ggaData.latitude = convertToDecimalDegrees(ggaData.latitude, ggaData.latDir); + ggaData.longitude = convertToDecimalDegrees(ggaData.longitude, ggaData.lonDir); +} + +void parseRMC(String rmc, RMCData &rmcData) { + int idx = 0; + int fieldIndex = 0; + String field; + + while (idx < rmc.length()) { + int endIdx = rmc.indexOf(',', idx); + if (endIdx == -1) endIdx = rmc.length(); + + field = rmc.substring(idx, endIdx); + + switch (fieldIndex) { + case 1: // UTC Time + rmcData.time = field; + break; + case 2: // Status + rmcData.status = field.charAt(0); + break; + case 3: // Latitude + rmcData.latitude = parseDouble(field); + break; + case 4: // Latitude Direction + rmcData.latDir = field.charAt(0); + break; + case 5: // Longitude + rmcData.longitude = parseDouble(field); + break; + case 6: // Longitude Direction + rmcData.lonDir = field.charAt(0); + break; + case 7: // Speed + rmcData.speed = parseDouble(field); + break; + case 8: // Course + rmcData.course = parseDouble(field); + break; + case 9: // Date + rmcData.date = field; + break; + case 10: // Magnetic Variation + rmcData.magVar = parseDouble(field); + break; + case 11: // Magnetic Variation Direction + rmcData.magVarDir = field.charAt(0); + break; + case 12: // Mode + rmcData.mode = field.charAt(0); + break; + } + + idx = endIdx + 1; + fieldIndex++; + } + + // Convert latitude and longitude to proper format + rmcData.latitude = convertToDecimalDegrees(rmcData.latitude, rmcData.latDir); + rmcData.longitude = convertToDecimalDegrees(rmcData.longitude, rmcData.lonDir); +} + + +void parseGSA(String gsa, GSAData &gsaData) { + int idx = 0; + int endIdx = gsa.indexOf(',', idx); + gsaData.mode1 = gsa.charAt(idx); + + idx = endIdx + 1; + endIdx = gsa.indexOf(',', idx); + gsaData.mode2 = parseInt(gsa.substring(idx, endIdx)); + + for (int i = 0; i < 12; i++) { + idx = endIdx + 1; + endIdx = gsa.indexOf(',', idx); + gsaData.sv[i] = parseInt(gsa.substring(idx, endIdx)); + } + + idx = endIdx + 1; + endIdx = gsa.indexOf(',', idx); + gsaData.pdop = parseDouble(gsa.substring(idx, endIdx)); + + idx = endIdx + 1; + endIdx = gsa.indexOf(',', idx); + gsaData.hdop = parseDouble(gsa.substring(idx, endIdx)); + + idx = endIdx + 1; + gsaData.vdop = parseDouble(gsa.substring(idx)); +} + +void parseGSV(String gsv, GSVData &gsvData) { + int idx = 0; + int endIdx = gsv.indexOf(',', idx); + gsvData.numMessages = parseInt(gsv.substring(idx, endIdx)); + + idx = endIdx + 1; + endIdx = gsv.indexOf(',', idx); + gsvData.messageNum = parseInt(gsv.substring(idx, endIdx)); + + idx = endIdx + 1; + endIdx = gsv.indexOf(',', idx); + gsvData.numSVs = parseInt(gsv.substring(idx, endIdx)); + + for (int i = 0; i < 4; i++) { + idx = endIdx + 1; + endIdx = gsv.indexOf(',', idx); + gsvData.svInfo[i].svID = parseInt(gsv.substring(idx, endIdx)); + + idx = endIdx + 1; + endIdx = gsv.indexOf(',', idx); + gsvData.svInfo[i].elevation = parseInt(gsv.substring(idx, endIdx)); + + idx = endIdx + 1; + endIdx = gsv.indexOf(',', idx); + gsvData.svInfo[i].azimuth = parseInt(gsv.substring(idx, endIdx)); + + idx = endIdx + 1; + endIdx = gsv.indexOf(',', idx); + gsvData.svInfo[i].snr = parseInt(gsv.substring(idx, endIdx)); + } +} + +void parseNAVACC(String nmea, NAVACCData &navaccData) { + int idx = nmea.indexOf(','); + int endIdx = nmea.indexOf(',', idx + 1); + navaccData.pAcc = nmea.substring(idx + 1, endIdx).toInt(); + + idx = endIdx; + endIdx = nmea.indexOf(',', idx + 1); + navaccData.vAcc = nmea.substring(idx + 1, endIdx).toInt(); + + idx = endIdx; + endIdx = nmea.indexOf(',', idx + 1); + navaccData.cAcc = nmea.substring(idx + 1, endIdx).toInt(); +} + + +void parseNMEA(String nmea, GGAData &ggaData, RMCData &rmcData, GSAData &gsaData, GSVData &gsvData, NAVACCData &navaccData) { + if (nmea.startsWith("$GPGGA") || nmea.startsWith("$GBGGA") || nmea.startsWith("$GAGGA") || nmea.startsWith("$GLGGA") || nmea.startsWith("$GNGGA")) { + parseGGA(nmea, ggaData); + } else if (nmea.startsWith("$GPRMC") || nmea.startsWith("$GBRMC") || nmea.startsWith("$GARMC") || nmea.startsWith("$GLRMC") || nmea.startsWith("$GNRMC")) { + parseRMC(nmea, rmcData); + } else if (nmea.startsWith("$GPGSA") || nmea.startsWith("$GBGSA") || nmea.startsWith("$GAGSA") || nmea.startsWith("$GLGSA") || nmea.startsWith("$GNGSA")) { + parseGSA(nmea, gsaData); + } else if (nmea.startsWith("$GPGSV") || nmea.startsWith("$GBGSV") || nmea.startsWith("$GAGSV") || nmea.startsWith("$GLGSV") || nmea.startsWith("$GNGSV")) { + parseGSV(nmea, gsvData); + } else if (nmea.startsWith("$NAVACC")) { + parseNAVACC(nmea, navaccData); + } +} diff --git a/src/wifiBeacon.cpp b/src/wifiBeacon.cpp new file mode 100644 index 0000000..82b83e4 --- /dev/null +++ b/src/wifiBeacon.cpp @@ -0,0 +1,203 @@ +/* + ___ _ ___ _ +| _ \ ( )_ | _ \ (_ ) +| (_) ) _ | _) _ _ __ | (_) ) __ _ _ | | ___ ___ +| / / _ \| | / _ \( __) | / / __ \/ _ )| |/ _ _ \ +| |\ \( (_) ) |_( (_) ) | | |\ \( ___/ (_| || || ( ) ( ) | +(_) (_)\___/ \__)\___/(_) (_) (_)\____)\__ _)___)_) (_) (_) + + * This file is part of Rotor Realm RemoteID + * + * Copyright (c) 2024 Rotor Realm + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. +*/ +#include "wifiBeacon.h" +#include "init.h" // Include init.h to access the config variable +#include "gps.h" // Include gps.h to access the GPS data +#include +#include + +// Define the WiFi channel and output power +const int wifiChannel = 6; // Set WiFi Channel +const int wifiOutputPower = 20; // dBm +// const int ledPin = LED_BUILTIN; + +// WiFi packet buffer +uint8_t beaconPacket[256]; +uint8_t packetIndex = 0; +static uint8_t beaconCounter = 0; // Static variable to keep track of how many times the beacon has been sent + +extern Config config; // Access the global config variable +extern GGAData ggaData; // Access the GPS GGA data +extern RMCData rmcData; +extern NAVACCData navaccData; // Access the GPS NAVACC data + +void wifiBeaconSetup(const char* ssid) { + pinMode(LED, OUTPUT); + Serial.begin(115200); + WiFi.mode(WIFI_MODE_STA); + esp_wifi_set_promiscuous(true); + esp_wifi_set_channel(wifiChannel, WIFI_SECOND_CHAN_NONE); + int channel = wifiChannel; + int beaconInterval = 100; + constructBeaconFrame(ssid, channel, beaconInterval); +} + +void wifiBeaconTask(void* parameters) { + for (;;) { + esp_wifi_80211_tx(WIFI_IF_STA, beaconPacket, packetIndex, false); + beaconCounter++; + // digitalWrite(LED, HIGH); + analogWrite(LED, 96); + constructBeaconFrame((const char*)parameters, wifiChannel, 100); + // digitalWrite(LED, LOW); + analogWrite(LED, 0); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } +} + +void constructBeaconFrame(const char* ssid, int channel, int beaconInterval) { + packetIndex = 0; + beaconPacket[packetIndex++] = 0x80; + beaconPacket[packetIndex++] = 0x00; + beaconPacket[packetIndex++] = 0x00; + beaconPacket[packetIndex++] = 0x00; + memset(&beaconPacket[packetIndex], 0xFF, 6); + packetIndex += 6; + uint8_t sourceAddress[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD }; + memcpy(&beaconPacket[packetIndex], sourceAddress, 6); + packetIndex += 6; + memcpy(&beaconPacket[packetIndex], sourceAddress, 6); + packetIndex += 6; + beaconPacket[packetIndex++] = 0x00; + beaconPacket[packetIndex++] = 0x00; + memset(&beaconPacket[packetIndex], 0x00, 8); + packetIndex += 8; + beaconPacket[packetIndex++] = beaconInterval & 0xFF; + beaconPacket[packetIndex++] = (beaconInterval >> 8) & 0xFF; + beaconPacket[packetIndex++] = 0x01; + beaconPacket[packetIndex++] = 0x00; + beaconPacket[packetIndex++] = 0x00; + uint8_t ssidLength = strlen(ssid); + beaconPacket[packetIndex++] = ssidLength; + memcpy(&beaconPacket[packetIndex], ssid, ssidLength); + packetIndex += ssidLength; + beaconPacket[packetIndex++] = 0x01; + beaconPacket[packetIndex++] = 0x08; + beaconPacket[packetIndex++] = 0x82; + beaconPacket[packetIndex++] = 0x84; + beaconPacket[packetIndex++] = 0x8B; + beaconPacket[packetIndex++] = 0x96; + beaconPacket[packetIndex++] = 0x0C; + beaconPacket[packetIndex++] = 0x12; + beaconPacket[packetIndex++] = 0x18; + beaconPacket[packetIndex++] = 0x24; + beaconPacket[packetIndex++] = 0x03; + beaconPacket[packetIndex++] = 0x01; + beaconPacket[packetIndex++] = channel; + encodeVendorSpecificElement(ssid); +} + +void encodeVendorSpecificElement(const char* ssid) { + VendorSpecificTag vendorTag; + MessagePack messagePack; + messagePack.messageSize = 25; + messagePack.numMessages = 5; + + // Basic ID Message + BasicIDMessage basicIDMessage; + encodeBasicIDMessage( + &basicIDMessage, // Basic ID message struct + idSerial, // UAS ID Type + uaMultirotor, // UAS Type + ssid // UAS ID (SSID) + ); + memcpy(messagePack.messages[0], &basicIDMessage, sizeof(BasicIDMessage)); + + // Location Message + LocationMessage locationMessage; + encodeLocationMessage(&locationMessage, + osAirborne, // Status + static_cast(rmcData.course), // Track Direction + static_cast(rmcData.speed), // Speed + 0, // Vertical Speed (can be replaced with actual value if available) + ggaData.latitude, // Latitude + ggaData.longitude, // Longitude + ggaData.altitude, // Pressure Altitude (float) + ggaData.geoidHeight, // Geodetic Altitude (float) + static_cast(ggaData.altitude), // Height + navaccData.pAcc, // Horizontal Accuracy + navaccData.vAcc, // Vertical Accuracy + navaccData.cAcc, // Speed/Baro Accuracy + ggaData.time.toInt() // Timestamp + ); // Timestamp + memcpy(messagePack.messages[1], &locationMessage, sizeof(LocationMessage)); + + // Self ID Message + SelfIDMessage selfIDMessage; + encodeSelfIDMessage( + &selfIDMessage, // Self ID message struct + selfIdType, // Self ID Type + selfIdDescription // Self ID Description + ); + memcpy(messagePack.messages[2], &selfIDMessage, sizeof(SelfIDMessage)); + + // System Message + SystemMessage systemMessage; + uint8_t uaClass = 0; + encodeSystemMessage( + &systemMessage, // System message struct + 0, // Operator Location Type + config.operatorInfo.OperatorLatitude, // Operator Latitude + config.operatorInfo.OperatorLongitude, // Operator Longitude + 1, // Area Count + 0, // Area Radius + 0, // Area Ceiling + 0, // Area Floor + cfUndeclared, // Classification Type + uaMultirotor, // UA Category + uaClass, // UA Class + 0, // Operator Altitude + 0 // Timestamp + ); + memcpy(messagePack.messages[3], &systemMessage, sizeof(SystemMessage)); + + // Operator ID Message + OperatorIDMessage operatorIDMessage; + encodeOperatorIDMessage( + &operatorIDMessage, // Operator ID message struct + 0, // Operator ID Type + config.operatorInfo.OperatorID.c_str() // Operator ID + ); + memcpy(messagePack.messages[4], &operatorIDMessage, sizeof(OperatorIDMessage)); + + encodeMessageHeader(&messagePack.header, msMessagePack, 0x01); + + encodeVendorSpecificTag(&vendorTag, &messagePack); + + beaconPacket[packetIndex++] = 0xDD; + beaconPacket[packetIndex++] = vendorTag.length; + memcpy(&beaconPacket[packetIndex], vendorTag.oui, 3); + packetIndex += 3; + beaconPacket[packetIndex++] = vendorTag.vendType; + beaconPacket[packetIndex++] = vendorTag.msgCounter; + memcpy(&beaconPacket[packetIndex], vendorTag.advData, vendorTag.length - 5); + packetIndex += vendorTag.length - 5; +} diff --git a/test/README b/test/README new file mode 100644 index 0000000..b0416ad --- /dev/null +++ b/test/README @@ -0,0 +1,11 @@ + +This directory is intended for PlatformIO Test Runner and project tests. + +Unit Testing is a software testing method by which individual units of +source code, sets of one or more MCU program modules together with associated +control data, usage procedures, and operating procedures, are tested to +determine whether they are fit for use. Unit testing finds problems early +in the development cycle. + +More information about PlatformIO Unit Testing: +- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html