From 4a0ca11c9a6fe9f8929fe90995683fe67a337e1a Mon Sep 17 00:00:00 2001 From: bruno Date: Wed, 30 Apr 2025 15:48:50 +0200 Subject: [PATCH] Init --- .gitignore | 2 + .idea/.gitignore | 8 ++ .idea/candash.iml | 2 + .idea/misc.xml | 7 + .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + CMakeLists.txt | 10 ++ base.c | 88 +++++++++++++ base.h | 15 +++ main.c | 326 ++++++++++++++++++++++++++++++++++++++++++++++ packets.h | 133 +++++++++++++++++++ 11 files changed, 605 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/candash.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 CMakeLists.txt create mode 100644 base.c create mode 100644 base.h create mode 100644 main.c create mode 100644 packets.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43ed9b0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/cmake-build-debug/ +/cmake* diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/candash.iml b/.idea/candash.iml new file mode 100644 index 0000000..f08604b --- /dev/null +++ b/.idea/candash.iml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0b76fe5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..1377b45 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..359d332 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 4.0) +project(candash C) + +set(CMAKE_C_STANDARD 23) + +add_executable(candash main.c + base.c + base.h) + +target_link_libraries(candash m ncurses) \ No newline at end of file diff --git a/base.c b/base.c new file mode 100644 index 0000000..6edabad --- /dev/null +++ b/base.c @@ -0,0 +1,88 @@ +// +// Created by bruno on 4/29/25. +// + +#include +#include +#include +#include "base.h" + +// Base64 decoding table +const unsigned char dtable[256] = { + ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, + ['E'] = 4, ['F'] = 5, ['G'] = 6, ['H'] = 7, + ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11, + ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, + ['Q'] = 16, ['R'] = 17, ['S'] = 18, ['T'] = 19, + ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23, + ['Y'] = 24, ['Z'] = 25, + ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29, + ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, + ['i'] = 34, ['j'] = 35, ['k'] = 36, ['l'] = 37, + ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41, + ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, + ['u'] = 46, ['v'] = 47, ['w'] = 48, ['x'] = 49, + ['y'] = 50, ['z'] = 51, + ['0'] = 52, ['1'] = 53, ['2'] = 54, ['3'] = 55, + ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59, + ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63, +}; + + +int base64_decode(const char *in, unsigned char *out, size_t *out_len) { + size_t len = strlen(in); + size_t i, j; + unsigned char a, b, c, d; + + if (len % 4 != 0) return -1; + + for (i = 0, j = 0; i < len; i += 4) { + a = dtable[(unsigned char)in[i]]; + b = dtable[(unsigned char)in[i+1]]; + c = dtable[(unsigned char)in[i+2]]; + d = dtable[(unsigned char)in[i+3]]; + + out[j++] = (a << 2) | (b >> 4); + if (in[i+2] != '=') { + out[j++] = (b << 4) | (c >> 2); + if (in[i+3] != '=') + out[j++] = (c << 6) | d; + } + } + *out_len = j; + return 0; +} + +void hex_dump(const unsigned char *data, size_t len) { + for (size_t i = 0; i < len; i++) { + printf("%02X ", data[i]); + if ((i + 1) % 16 == 0) + printf("\n"); + } + printf("\n"); +} + +static uint32_t crc32_table[256]; + +// Call this once before computing CRCs +void init_crc32_table(void) { + uint32_t poly = 0xEDB88320; // reversed polynomial of 0x04C11DB7 + for (uint32_t i = 0; i < 256; i++) { + uint32_t crc = i; + for (uint8_t j = 0; j < 8; j++) { + if (crc & 1) + crc = (crc >> 1) ^ poly; + else + crc >>= 1; + } + crc32_table[i] = crc; + } +} + +uint32_t crc32_le(uint32_t crc, const uint8_t *buf, size_t len) { + crc = ~crc; + while (len--) { + crc = crc32_table[(crc ^ *buf++) & 0xFF] ^ (crc >> 8); + } + return ~crc; +} \ No newline at end of file diff --git a/base.h b/base.h new file mode 100644 index 0000000..f231f99 --- /dev/null +++ b/base.h @@ -0,0 +1,15 @@ +// +// Created by bruno on 4/29/25. +// + +#ifndef CANDASH_BASE_H +#define CANDASH_BASE_H + +#include + +void hex_dump(const unsigned char *data, size_t len); +int base64_decode(const char *in, unsigned char *out, size_t *out_len); +extern const unsigned char dtable[256]; +void init_crc32_table(void); +uint32_t crc32_le(uint32_t crc, const uint8_t *buf, size_t len); +#endif //CANDASH_BASE_H diff --git a/main.c b/main.c new file mode 100644 index 0000000..a04c26d --- /dev/null +++ b/main.c @@ -0,0 +1,326 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "packets.h" +#include "base.h" + +#define MAG_SCALE (4912.0f / 32760.0f) + +void printTelemetryPacketNcurses(TelemetryPacket *packet, DownBoundPacket *down) { + clear(); + int row = 0; + + + // Time + attroff(COLOR_PAIR(6)); + mvprintw(row++, 0, "Timestamp: %u", down->missionTimer); + + // MPU9250 + if (packet->presentDevices & MPU9250_PRESENT_BIT) { + attron(COLOR_PAIR(1)); + mvprintw(row++, 0, "-- MPU9250 --"); + } else { + attron(COLOR_PAIR(4)); + mvprintw(row++, 0, "-- MPU9250 (not present) --"); + } + float ax = packet->accelerationX / 16384.0f; + float ay = packet->accelerationY / 16384.0f; + float az = packet->accelerationZ / 16384.0f; + float gx = packet->gyroX / 131.0f; + float gy = packet->gyroY / 131.0f; + float gz = packet->gyroZ / 131.0f; + float mx = packet->magnetX * MAG_SCALE; + float my = packet->magnetY * MAG_SCALE; + float mz = packet->magnetZ * MAG_SCALE; + float temp = (packet->accelerometer_temperature / 333.87f) + 21.0f; + mvprintw(row++, 2, "Accel [g]: X=%.2f Y=%.2f Z=%.2f", ax, ay, az); + mvprintw(row++, 2, "Gyro [deg/s]: X=%.2f Y=%.2f Z=%.2f", gx, gy, gz); + mvprintw(row++, 2, "Magnet [uT]: X=%.2f Y=%.2f Z=%.2f", mx, my, mz); + mvprintw(row++, 2, "Temp [°C]: %.2f", temp); + attroff(COLOR_PAIR(1)); + attroff(COLOR_PAIR(4)); + + // CCS811 + if (packet->presentDevices & CCS811_PRESENT_BIT) { + attron(COLOR_PAIR(2)); + mvprintw(row++, 0, "-- CCS811 --"); + } else { + attron(COLOR_PAIR(4)); + mvprintw(row++, 0, "-- CCS811 (not present) --"); + } + mvprintw(row++, 2, "eCO2: %u ppm", packet->eCO2); + mvprintw(row++, 2, "TVOC: %u ppb", packet->tvoc); + mvprintw(row++, 2, "Current: %u", packet->currentCCS); + mvprintw(row++, 2, "Raw CCS: %u", packet->rawCCSData); + attroff(COLOR_PAIR(2)); + attroff(COLOR_PAIR(4)); + + // INA260 + if (packet->presentDevices & INA260_PRESENT_BIT) { + attron(COLOR_PAIR(3)); + mvprintw(row++, 0, "-- INA260 --"); + } else { + attron(COLOR_PAIR(4)); + mvprintw(row++, 0, "-- INA260 (not present) --"); + } + mvprintw(row++, 2, "Voltage [V]: %.4f", packet->volts * 0.00125); + mvprintw(row++, 2, "Current [A]: %.4f", packet->current * 0.00125); + mvprintw(row++, 2, "Power [W]: %.4f", packet->power * 0.01); + attroff(COLOR_PAIR(3)); + attroff(COLOR_PAIR(4)); + + // BME680 + if (packet->presentDevices & BME680_PRESENT_BIT) { + attron(COLOR_PAIR(5)); + mvprintw(row++, 0, "-- BME680 --"); + } else { + attron(COLOR_PAIR(4)); + mvprintw(row++, 0, "-- BME680 (not present) --"); + } + mvprintw(row++, 2, "Temp: %d - %.2f °C", packet->temperature, packet->air_temperature / 100.0f); + mvprintw(row++, 2, "Humidity: %d - %.2f %%", packet->humidity, packet->relative_humidity / 100.0f); + mvprintw(row++, 2, "Pressure: %d - %.2f hPa", packet->pressure, packet->barometric_pressure / 100.0f); + mvprintw(row++, 2, "Gas Resistance: %d - %.2f Ohm", packet->gas, packet->gas_resistance / 1000.0f); + mvprintw(row++, 2, "Gas Valid: %s", packet->gas_valid ? "Yes" : "No"); + mvprintw(row++, 2, "Heater Stable: %s", packet->heater_stable ? "Yes" : "No"); + + mvprintw(row++, 2, "IAQ: %u", packet->iaq_score); + mvprintw(row++, 2, "TempScore: %.2f", packet->temperature_score); + mvprintw(row++, 2, "HumidityScore: %.2f", packet->humidity_score); + mvprintw(row++, 2, "GasScore: %.2f", packet->gas_score); + attroff(COLOR_PAIR(5)); + attroff(COLOR_PAIR(4)); + + // GPS + attron(COLOR_PAIR(6)); + mvprintw(row++, 0, "-- GPS --"); + time_t rawtime = packet->time_seconds; + struct tm *ptm = gmtime(&rawtime); + + // Parse custom date format YYDDMM -> YYYY-MM-DD + int date = packet->date_yyddmm; + int yy = date / 10000; + int dd = (date / 100) % 100; + int mm = date % 100; + int year = 2000 + yy; + + mvprintw(row++, 2, "Time: %02d:%02d:%02d", ptm->tm_hour, ptm->tm_min, ptm->tm_sec); + mvprintw(row++, 2, "Date: %04d-%02d-%02d", year, mm, dd); + mvprintw(row++, 2, "Lat: %.4f°", packet->latitude_centi_degrees / 10000.0f); + mvprintw(row++, 2, "Long: %.4f°", packet->longitude_centi_degrees / 10000.0f); + mvprintw(row++, 2, "Alt: %.2f m", packet->altitude_centi_meters / 100.0f); + mvprintw(row++, 2, "Speed: %.2f knots", packet->speed_centi_knots / 100.0f); + mvprintw(row++, 2, "Satellites: %u", packet->num_satellites); + mvprintw(row++, 2, "Fix Quality: %u", packet->fix_quality); + + // GPS Prediction + mvprintw(row++, 2, "Predicted Lat: %.4f°", packet->predicted_latitude_centi_degrees / 10000.0f); + mvprintw(row++, 2, "Predicted Long: %.4f°", packet->predicted_longitude_centi_degrees / 10000.0f); + mvprintw(row++, 2, "Predicted Alt: %.2f m", packet->predicted_altitude_centi_meters / 100.0f); + + // MCP23018 ADC + if (packet->presentDevices & MCP23018_PRESENT_BIT) { + attron(COLOR_PAIR(3)); + mvprintw(row++, 0, "-- ADC sensors --"); + } else { + attron(COLOR_PAIR(4)); + mvprintw(row++, 0, "-- ADC sensors (not present) --"); + } + mvprintw(row++, 2, "NH3: %d", packet->NH3); + mvprintw(row++, 2, "CO: %d", packet->CO); + mvprintw(row++, 2, "NO2: %d", packet->NO2); + mvprintw(row++, 2, "UVC: %d", packet->UVC); + + // Servo + attron(COLOR_PAIR(5)); + mvprintw(row++, 0, "-- Servos --"); + mvprintw(row++, 2, "Servo A: Current=%d Target=%d", packet->currentServoA, packet->targetServoA); + mvprintw(row++, 2, "Servo B: Current=%d Target=%d", packet->currentServoB, packet->targetServoB); + + // Index + mvprintw(row++, 0, "Telemetry Index: %d", packet->telemetryIndex); + + + refresh(); +} + + +TelemetryPacket telemetryPacket; +DownBoundPacket down; + +void handle_downlink_packet(uint8_t *buf, uint8_t rxLen) { + if (rxLen < sizeof(DownBoundPacket)) { + printf("Received packet too small to be valid."); + return; + } + + memcpy(&down, buf, sizeof(DownBoundPacket)); + + printf("Downlink packet index: %u, type: %u", down.packetIndex, down.packetType); + + // Verify sync phrase + if (strncmp(down.syncPhrase, DownlinkSync, strlen(DownlinkSync)) != 0) { + printf("Invalid sync phrase, ignoring packet."); + return; + } + + uint8_t *payload = buf + sizeof(DownBoundPacket); + uint32_t payloadSize = rxLen - sizeof(DownBoundPacket); + + uint32_t crcCheck = crc32_le(0, payload, payloadSize); + + if (crcCheck != down.CRCCheck) { + printf("Received BAD CRC for packet %u, crc is %u, should be %u", down.packetIndex, crcCheck, down.CRCCheck); + return; + } + + switch (down.packetType) { + case DownlinkPacketType_Telemetry: + if (payloadSize >= sizeof(TelemetryPacket)) { + memcpy(&telemetryPacket, payload, sizeof(TelemetryPacket)); + printf("Telemetry packet received!"); + + printTelemetryPacketNcurses(&telemetryPacket, &down); + // Here you would actually do something with the telemetry, like save it or display it + } else { + printf("Telemetry packet too small (%u bytes)", payloadSize); + } + break; + case DownlinkPacketType_Ping: + printf("Ping packet received!"); + // Optionally respond with pong here + break; + case DownlinkPacketType_ACK: + printf("ACK packet received!"); + break; + default: + printf("Unknown packet type: %u", down.packetType); + break; + } +} + + +int open_serial(const char *device) { + int fd = open(device, O_RDWR | O_NOCTTY | O_SYNC); + if (fd < 0) { + perror("open"); + return -1; + } + + struct termios tty; + memset(&tty, 0, sizeof tty); + if (tcgetattr(fd, &tty) != 0) { + perror("tcgetattr"); + close(fd); + return -1; + } + + cfsetospeed(&tty, B115200); + cfsetispeed(&tty, B115200); + + tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars + tty.c_iflag &= ~IGNBRK; // disable break processing + tty.c_lflag = 0; // no signaling chars, no echo, no canonical processing + tty.c_oflag = 0; // no remapping, no delays + tty.c_cc[VMIN] = 1; // read doesn't block + tty.c_cc[VTIME] = 1; // 0.1 seconds read timeout + + tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl + + tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls, enable reading + tty.c_cflag &= ~(PARENB | PARODD); // shut off parity + tty.c_cflag &= ~CSTOPB; + tty.c_cflag &= ~CRTSCTS; + + if (tcsetattr(fd, TCSANOW, &tty) != 0) { + perror("tcsetattr"); + close(fd); + return -1; + } + + return fd; +} + +int subMain() { + + int fd = open_serial("/dev/ttyUSB0"); + if (fd < 0) return 1; + + char buf[4096]; + size_t buf_pos = 0; + + memset(&telemetryPacket, 0, sizeof(TelemetryPacket)); + memset(&down, 0, sizeof(DownBoundPacket)); + + unsigned char decoded[256]; + size_t decoded_len; +// if (base64_decode("UGxlY2hEb2xlADsAAAAADuo+Ai9is1eY/vw/dP4AAAAAAAAAAAAAAABgD8sBCAAB/wPGGEYARQCgHQgAoksEKgUAnwIBAQQAe5APQtL3pEGycXlEqDLaSDcAAAAAAJqZeUAAAFBCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/zw=", decoded, &decoded_len) == 0) { +// printf("Decoded data (%zu bytes):\n", decoded_len); +// handle_downlink_packet(decoded, decoded_len); +// } else { +// printf("Base64 decode failed\n"); +// } + + printTelemetryPacketNcurses(&telemetryPacket, &down); + while (1) { + char ch; + size_t n = read(fd, &ch, 1); + if (n > 0) { + if (ch == '\n') { + buf[buf_pos] = '\0'; + printf("%s\n", buf); + + if (strncmp(buf, "Got DownLink data", 17) == 0) { + char *colon = strchr(buf, ':'); + if (colon) { + colon++; + while (*colon == ' ') colon++; // skip spaces + printf("Detected base64 payload: %s\n", colon); + + if (base64_decode(colon, decoded, &decoded_len) == 0) { + printf("Decoded data (%zu bytes):\n", decoded_len); + handle_downlink_packet(decoded, decoded_len); + } else { + printf("Base64 decode failed\n"); + } + } + } + buf_pos = 0; + } else { + if (buf_pos < sizeof(buf) - 1) + buf[buf_pos++] = ch; + } + } else if (n < 0) { + perror("read"); + break; + } + } + + close(fd); + + return 0; +} + +int main(int argc, char *argv[]) { + + init_crc32_table(); + initscr(); // Start ncurses mode + start_color(); // Enable color + use_default_colors(); + init_pair(1, COLOR_CYAN, -1); + init_pair(2, COLOR_GREEN, -1); + init_pair(3, COLOR_YELLOW, -1); + init_pair(4, COLOR_RED, -1); + init_pair(5, COLOR_CYAN, -1); + init_pair(6, COLOR_MAGENTA, -1); + curs_set(0); // Hide cursor + int x = subMain(); + endwin(); // End ncurses mode + return x; +} diff --git a/packets.h b/packets.h new file mode 100644 index 0000000..1c9b551 --- /dev/null +++ b/packets.h @@ -0,0 +1,133 @@ +#ifndef PACKETS_STRUCTS +#define PACKETS_STRUCTS +#include "stdint.h" + +#define UplinkSync "PlechHore" +#define DownlinkSync "PlechDole" + +#define UplinkPacketType_SystemControl 0 +#define UplinkPacketType_Ping 1 +#define UplinkPacketType_ACK 255 + +#define DownlinkPacketType_Telemetry 0 +#define DownlinkPacketType_Ping 1 +#define DownlinkPacketType_ACK 255 + +#define BME680_PRESENT_BIT (1 << 0) +#define CCS811_PRESENT_BIT (1 << 1) +#define MPU9250_PRESENT_BIT (1 << 2) +#define INA260_PRESENT_BIT (1 << 3) +#define MCP23018_PRESENT_BIT (1 << 4) + +typedef struct __attribute__((packed)) +{ + char syncPhrase[10]; + uint32_t packetIndex; + uint8_t packetType; + uint32_t missionTimer; + uint32_t CRCCheck; +} DownBoundPacket; + +typedef struct __attribute__((packed)) +{ + // MPU data + int16_t accelerationX; + int16_t accelerationY; + int16_t accelerationZ; + int16_t gyroX; + int16_t gyroY; + int16_t gyroZ; + int16_t magnetX; + int16_t magnetY; + int16_t magnetZ; + int16_t accelerometer_temperature; + + // CCS data + uint16_t eCO2; + uint16_t tvoc; + uint8_t currentCCS; + uint16_t rawCCSData; + + // INA data + uint16_t volts; + uint16_t current; + uint16_t power; + + // BME DATA + uint32_t temperature; + uint16_t humidity; + uint32_t pressure; + uint16_t gas; + bool gas_valid; + bool heater_stable; + uint8_t gas_range; + uint8_t gas_index; + + float air_temperature; /*!< air temperature in degrees celsius */ + float relative_humidity; /*!< relative humidity in percent */ + float barometric_pressure; /*!< barometric pressure in hecto-pascal */ + float gas_resistance; /*!< gas resistance in ohms */ + uint16_t iaq_score; /*!< air quality index (0..500) */ + float temperature_score; + float humidity_score; + float gas_score; + + // GPS DATA + uint32_t time_seconds; // Seconds since start of day + int32_t latitude_centi_degrees; // Latitude * 10,000 + int32_t longitude_centi_degrees; // Longitude * 10,000 + int16_t altitude_centi_meters; // Altitude * 100 + uint8_t fix_quality; + uint8_t num_satellites; + uint16_t date_yyddmm; // YYDDMM (from GPRMC) + uint16_t speed_centi_knots; // Speed * 100 (from GPRMC) + + int32_t predicted_latitude_centi_degrees; // Latitude * 10,000 + int32_t predicted_longitude_centi_degrees; // Longitude * 10,000 + int16_t predicted_altitude_centi_meters; // Altitude * 100 + + // ADC DATA + int32_t NH3; + int32_t CO; + int32_t NO2; + int32_t UVC; + + int16_t currentServoA; + int16_t targetServoA; + int16_t currentServoB; + int16_t targetServoB; + + uint8_t presentDevices; + + uint8_t telemetryIndex; + +} TelemetryPacket; + +typedef struct __attribute__((packed)) +{ + char syncPhrase[10]; + uint32_t packetIndex; + uint8_t packetType; + uint32_t CRCCheck; +} UplinkPacket; + +typedef struct __attribute__((packed)) +{ + uint8_t powerMode; + uint8_t controlMode; + uint16_t servoA; + uint16_t servoB; +} SystemControlPacket; + +typedef struct __attribute__((packed)) +{ + uint8_t PingData[20]; +} PingPacket; + +typedef struct __attribute__((packed)) +{ + uint32_t packetIndex; + uint32_t crc32Checksum; +} ACKPacket; + +#endif \ No newline at end of file