#include #include #include #include #include #include #include #include #include #include #include #include "packets.h" #include "base.h" FILE *csvFile; int rssi24 = 0; void write_grayscale_bmp(FILE *f, const uint8_t *pixels, int width, int height) { if (!f || !pixels || width <= 0 || height <= 0) return; int rowSize = (width + 3) & ~3; // Align to 4 bytes int pixelDataSize = rowSize * height; int paletteSize = 256 * 4; // 256 entries, 4 bytes each (BGRA) int offset = 14 + 40 + paletteSize; // BMP header + DIB header + palette int fileSize = offset + pixelDataSize; // --- BMP Header (14 bytes) --- uint8_t bmpHeader[14] = { 'B', 'M', fileSize & 0xFF, (fileSize >> 8) & 0xFF, (fileSize >> 16) & 0xFF, (fileSize >> 24) & 0xFF, 0, 0, 0, 0, offset & 0xFF, (offset >> 8) & 0xFF, (offset >> 16) & 0xFF, (offset >> 24) & 0xFF }; fwrite(bmpHeader, 1, 14, f); // --- DIB Header (40 bytes - BITMAPINFOHEADER) --- uint8_t dibHeader[40] = { 40, 0, 0, 0, // Header size width & 0xFF, (width >> 8) & 0xFF, (width >> 16) & 0xFF, (width >> 24) & 0xFF, height & 0xFF, (height >> 8) & 0xFF, (height >> 16) & 0xFF, (height >> 24) & 0xFF, 1, 0, // Planes 8, 0, // Bits per pixel (8-bit indexed) 0, 0, 0, 0, // Compression (0 = BI_RGB) pixelDataSize & 0xFF, (pixelDataSize >> 8) & 0xFF, (pixelDataSize >> 16) & 0xFF, (pixelDataSize >> 24) & 0xFF, 0x13, 0x0B, 0, 0, // X pixels per meter (2835 = 72 DPI) 0x13, 0x0B, 0, 0, // Y pixels per meter 256, 0, 0, 0, // Number of colors in palette 0, 0, 0, 0 // Important colors }; fwrite(dibHeader, 1, 40, f); // --- Grayscale Palette (BGRA format) --- for (int i = 0; i < 256; i++) { uint8_t entry[4] = {i, i, i, 0}; // Blue, Green, Red, Reserved fwrite(entry, 1, 4, f); } // --- Pixel Data (bottom-up, padded rows) --- uint8_t padding[3] = {0}; // Max padding = 3 bytes for (int y = height - 1; y >= 0; y--) { const uint8_t *row = &pixels[y * width]; fwrite(row, 1, width, f); fwrite(padding, 1, rowSize - width, f); } } #define MAG_SCALE (4912.0f / 32760.0f) void printTelemetryPacketNcurses(TelemetryPacket *packet, DownBoundPacket *down, int rssi868, int snr) { clear(); int row = 0; // Time attroff(COLOR_PAIR(6)); mvprintw(row++, 0, "Timestamp: %lu, 868RSSI: %d, 868SNR: %d, 24RSSI: %d", down->missionTimer, rssi868, snr, rssi24); // 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(); } uint16_t x = 0; TelemetryPacket telemetryPacket; DownBoundPacket down; typedef enum { PIXFORMAT_RGB565, // 2BPP/RGB565 PIXFORMAT_YUV422, // 2BPP/YUV422 PIXFORMAT_YUV420, // 1.5BPP/YUV420 PIXFORMAT_GRAYSCALE, // 1BPP/GRAYSCALE PIXFORMAT_JPEG, // JPEG/COMPRESSED PIXFORMAT_RGB888, // 3BPP/RGB888 PIXFORMAT_RAW, // RAW PIXFORMAT_RGB444, // 3BP2P/RGB444 PIXFORMAT_RGB555, // 3BP2P/RGB555 } pixformat_t; typedef struct { bool isVisible; uint32_t len; uint16_t width; uint16_t height; pixformat_t format; int64_t timeStamp; } PreData; PreData preData; uint16_t imageIndex = 0; FILE *imageFile = NULL; FILE *imageFileMetadata = NULL; uint8_t *fileBuffer = NULL; void handle_frame(uint8_t *buf, size_t rxLen) { char fname[40]; snprintf(fname, sizeof(fname), "rawdatn0%ldpckt%d.bin", time(NULL), x++); FILE *file = fopen(fname, "wb"); if (file) { fwrite(buf + 71, rxLen - 73, 1, file); fclose(file); file = NULL; } else { fprintf(stderr, "Failed to open file %s\n", fname); return; } size_t currentByteIndex = 72; if (rxLen < currentByteIndex + 1 + 2 + 2 + 6) { fprintf(stderr, "Frame too short (header)\n"); return; } uint8_t type = buf[currentByteIndex++]; uint16_t seq = (buf[currentByteIndex++] << 8) | buf[currentByteIndex++]; uint16_t total_chunks = (buf[currentByteIndex++] << 8) | buf[currentByteIndex++]; if (!(buf[currentByteIndex++] == 'P' && buf[currentByteIndex++] == 'l' && buf[currentByteIndex++] == 'e' && buf[currentByteIndex++] == 'c' && buf[currentByteIndex++] == 'y' && buf[currentByteIndex++] == 'C')) { fprintf(stderr, "Magic string 'PlecyC' not found\n"); return; } if (seq == 0) { preData.isVisible = buf[currentByteIndex++]; preData.len = buf[currentByteIndex++] << 24; preData.len |= buf[currentByteIndex++] << 16; preData.len |= buf[currentByteIndex++] << 8; preData.len |= buf[currentByteIndex++]; preData.width = buf[currentByteIndex++] << 8; preData.width |= buf[currentByteIndex++]; preData.height = buf[currentByteIndex++] << 8; preData.height |= buf[currentByteIndex++]; preData.format = buf[currentByteIndex++]; preData.timeStamp = ((int64_t) buf[currentByteIndex++]) << 56; preData.timeStamp |= ((int64_t) buf[currentByteIndex++]) << 48; preData.timeStamp |= ((int64_t) buf[currentByteIndex++]) << 40; preData.timeStamp |= ((int64_t) buf[currentByteIndex++]) << 32; preData.timeStamp |= buf[currentByteIndex++] << 24; preData.timeStamp |= buf[currentByteIndex++] << 16; preData.timeStamp |= buf[currentByteIndex++] << 8; preData.timeStamp |= buf[currentByteIndex++]; if (imageFileMetadata != NULL) { fclose(imageFileMetadata); imageFileMetadata = NULL; } if (imageFile != NULL && fileBuffer != NULL) { if (preData.isVisible) { fwrite(fileBuffer, 1, preData.len, imageFile); } else { write_grayscale_bmp(imageFile, fileBuffer, preData.width, preData.height); } fclose(imageFile); imageFile = NULL; } if (fileBuffer != NULL) { free(fileBuffer); fileBuffer = NULL; } fileBuffer = malloc(preData.len); char fnameImg[40]; char fnameMeta[40]; snprintf(fnameMeta, sizeof(fnameMeta), "imgMeta%sn%ldp%d.txt", preData.isVisible ? "OV" : "Lep", time(NULL), x++); snprintf(fnameImg, sizeof(fnameImg), "img%sn%ldp%d.%s", preData.isVisible ? "OV" : "Lep", time(NULL), x++, preData.isVisible ? "jpg" : "bmp"); imageFile = fopen(fnameImg, "wb"); imageFileMetadata = fopen(fnameMeta, "wb"); if (rxLen < currentByteIndex) { fprintf(stderr, "Frame too short\n"); return; } fprintf(imageFileMetadata, " Camera: %s\n", preData.isVisible ? "VIS" : "INVIS"); fprintf(imageFileMetadata, " width: %hu\n", preData.width); fprintf(imageFileMetadata, " height: %hu\n", preData.height); fprintf(imageFileMetadata, " len: %u\n", preData.len); fprintf(imageFileMetadata, " format: %u\n", preData.format); } if (fileBuffer && imageFileMetadata && imageFile) { fprintf(imageFileMetadata, "Type: %u\n", type); fprintf(imageFileMetadata, "Sequence number: %u\n", seq); fprintf(imageFileMetadata, "Total chunks: %u\n", total_chunks); fprintf(imageFileMetadata, "PreData:\n"); //fseek(imageFile, (seq * 1459) - 18, SEEK_SET); size_t offset = (seq * 1458) - 18; if (seq == 0) { offset = 0; } // Debug: print key values //printf("[DEBUG] offset: %zu, currentByteIndex: %d, rxLen: %d, seq: %d, total_chunks: %d\n", //offset, currentByteIndex, rxLen, seq, total_chunks); // Calculate length safely size_t length = rxLen - currentByteIndex - ((seq == total_chunks - 1) ? 1 : 2); // Sanity checks if (offset + length > preData.len) { length = preData.len - offset; } fprintf(imageFileMetadata, "RxLen: %zu, length: %zu\n", rxLen, length); assert(fileBuffer != NULL); assert(buf != NULL); assert(length >= 0); assert(offset + length <= preData.len); // assuming fileBufferSize is known assert(currentByteIndex >= 0 && currentByteIndex < rxLen); assert(seq >= 0 && seq < total_chunks); // Do the copy memcpy(fileBuffer + offset, buf + currentByteIndex, length); buf[rxLen - 2] = buf[rxLen - 1]; //fwrite(buf + currentByteIndex, 1, rxLen - currentByteIndex - (seq == total_chunks - 1 ? 1 : 2), imageFile); } if (seq == total_chunks - 1) { if (imageFileMetadata != NULL) { fclose(imageFileMetadata); imageFileMetadata = NULL; } if (imageFile != NULL && fileBuffer != NULL) { if (preData.isVisible) { fwrite(fileBuffer, 1, preData.len, imageFile); } else { write_grayscale_bmp(imageFile, fileBuffer, preData.width, preData.height); } fclose(imageFile); imageFile = NULL; } } } void hexdump(const void *data, size_t size) { const unsigned char *byte = (const unsigned char *)data; for (size_t i = 0; i < size; i += 16) { //printf("%08zx ", i); // print offset // print hex bytes for (size_t j = 0; j < 16; j++) { if (i + j < size) { //printf("%02x ", byte[i + j]); } else { //printf(" "); } } //printf(" "); // print ASCII representation for (size_t j = 0; j < 16 && i + j < size; j++) { unsigned char c = byte[i + j]; //printf("%c", isprint(c) ? c : '.'); } //printf("\n"); } } void handle_downlink_packet(uint8_t *buf, uint8_t rxLen, int rssi868, int snr) { //hexdump(buf, 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\n", 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!"); float ax = telemetryPacket.accelerationX / 16384.0f; float ay = telemetryPacket.accelerationY / 16384.0f; float az = telemetryPacket.accelerationZ / 16384.0f; float gx = telemetryPacket.gyroX / 131.0f; float gy = telemetryPacket.gyroY / 131.0f; float gz = telemetryPacket.gyroZ / 131.0f; float mx = telemetryPacket.magnetX * MAG_SCALE; float my = telemetryPacket.magnetY * MAG_SCALE; float mz = telemetryPacket.magnetZ * MAG_SCALE; float temp = (telemetryPacket.accelerometer_temperature / 333.87f) + 21.0f; printTelemetryPacketNcurses(&telemetryPacket, &down, rssi868, snr); fprintf(csvFile, "MissionTimer,PacketIndex,TelemetryIndex,PacketType,RSSI868,SNR868,RSSI24," "AccelerationRawX,AccelerationRawY,AccelerationRawZ," "GyroscopeRawX,GyroscopeRawY,GyroscopeRawZ," "MagnetometerRawX,MagnetometerRawY,MagnetometerRawZ," "MPURawTemp," "AccelerationX,AccelerationY,AccelerationZ," "GyroscopeX,GyroscopeY,GyroscopeZ," "MagnetometerX,MagnetometerY,MagnetometerZ," "MPUTemp," "eCO2,tVOC,currentCCS,rawCCS," "volts,current,power,rawtemperature,rawhumidity,rawpressure,rawgas," "gasValid,heaterStable,gasRange,gasIndex," "airTemp,relHumidity,baroPressure,gasResistance," "iaqScore,tempScore,humidityScore,gasScore," "gpsTime,latitude,longitude,altitude,fixQuality,numSatellites,date,speed," "predLatitude,predLongitude,predAltitude," "NH3,CO,NO2,UVC," "currentServoA,targetServoA,currentServoB,targetServoB," "presentDevices\n"); fprintf(csvFile, "%u,%u,%u,%u,%d,%d,%d," // missionTimer, packetIndex, telemetryIndex, packetType "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d," // MPU "%f,%f,%f,%f,%f,%f,%f,%f,%f,%f," // MPU "%u,%u,%d,%u," // CCS "%u,%u,%u,%u,%u,%u,%u," // INA, BME basic "%s,%s,%u,%u," // bools and gas index/range "%.2f,%.2f,%.2f,%.2f," // Bosch env data "%u,%.2f,%.2f,%.2f," // IAQ + scores "%u,%d,%d,%d,%u,%u,%u,%u," // GPS data "%d,%d,%d," // Predicted GPS "%d,%d,%d,%d," // ADC "%d,%d,%d,%d," // Servo "%u\n", // presentDevices down.missionTimer, down.packetIndex, telemetryPacket.telemetryIndex, down.packetType, rssi868, snr, rssi24, telemetryPacket.accelerationX, telemetryPacket.accelerationY, telemetryPacket.accelerationZ, telemetryPacket.gyroX, telemetryPacket.gyroY, telemetryPacket.gyroZ, telemetryPacket.magnetX, telemetryPacket.magnetY, telemetryPacket.magnetZ, telemetryPacket.accelerometer_temperature, ax, ay, az, gx, gy, gz, mx, my, mz, temp, telemetryPacket.eCO2, telemetryPacket.tvoc, telemetryPacket.currentCCS, telemetryPacket.rawCCSData, telemetryPacket.volts, telemetryPacket.current, telemetryPacket.power, telemetryPacket.temperature, telemetryPacket.humidity, telemetryPacket.pressure, telemetryPacket.gas, telemetryPacket.gas_valid ? "Yes" : "No", telemetryPacket.heater_stable ? "Yes" : "No", telemetryPacket.gas_range, telemetryPacket.gas_index, telemetryPacket.air_temperature, telemetryPacket.relative_humidity, telemetryPacket.barometric_pressure, telemetryPacket.gas_resistance, telemetryPacket.iaq_score, telemetryPacket.temperature_score, telemetryPacket.humidity_score, telemetryPacket.gas_score, telemetryPacket.time_seconds, telemetryPacket.latitude_centi_degrees, telemetryPacket.longitude_centi_degrees, telemetryPacket.altitude_centi_meters, telemetryPacket.fix_quality, telemetryPacket.num_satellites, telemetryPacket.date_yyddmm, telemetryPacket.speed_centi_knots, telemetryPacket.predicted_latitude_centi_degrees, telemetryPacket.predicted_longitude_centi_degrees, telemetryPacket.predicted_altitude_centi_meters, telemetryPacket.NH3, telemetryPacket.CO, telemetryPacket.NO2, telemetryPacket.UVC, telemetryPacket.currentServoA, telemetryPacket.targetServoA, telemetryPacket.currentServoB, telemetryPacket.targetServoB, telemetryPacket.presentDevices); fflush(csvFile); fsync(fileno(csvFile)); // Critical: this ensures actual write to disk } 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, B921600); cfsetispeed(&tty, B921600); 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() { char *csvName[40]; snprintf((char *) csvName, sizeof(csvName), "sens%ld", time(NULL)); csvFile = fopen((const char *) csvName, "w"); int fd = open_serial("/dev/ttyUSB0"); if (fd < 0) return 1; char buf[8192]; size_t buf_pos = 0; memset(&telemetryPacket, 0, sizeof(TelemetryPacket)); memset(&down, 0, sizeof(DownBoundPacket)); unsigned char *decoded; size_t decoded_len; printTelemetryPacketNcurses(&telemetryPacket, &down, 0, 0);; while (1) { char ch; size_t n = read(fd, &ch, 1); if (n > 0) { ////printf("%c", ch); if (ch == '\n') { buf[buf_pos] = '\0'; if (strncmp(buf, "Got DownLink data", 17) == 0) { int length = 0, rssi = 0, snr = 0; char *base64 = NULL; // Match the format: L R S: if (sscanf(buf, "Got DownLink data L%d R%d S%d", &length, &rssi, &snr) == 3) { char *colon = strchr(buf, ':'); if (colon) { colon+= 2; base64 = malloc(strlen(colon) + 1); memcpy(base64, colon, strlen(colon)); //printf("Detected base64 payload: %s\n", base64); //printf("Length: %d, RSSI: %d dBm, SNR: %d\n", length, rssi, snr); decoded = base64_decode(base64, strlen(base64) - 1, &decoded_len); if (decoded != NULL) { //printf("Decoded data (%zu bytes):\n", decoded_len); handle_downlink_packet(decoded, decoded_len, rssi, snr); free(decoded); } else { //printf("Base64 decode failed for\nPAY:%s\nBUF: %s\n", base64, buf); } free(base64); } } else { //printf("Failed to parse DownLink line\n"); } } else if (strncmp(buf, "Got 2400mhZ frame on", 20) == 0) { // Parse RSSI and base64 string char *payload = NULL; if (sscanf(buf, "Got 2400mhZ frame on %d", &rssi24) == 1) { //printf("RSSI: %d dBm\n", rssi24); //printf("Payload (base64): %s\n", payload); char *colon = strchr(buf, ':'); if (colon) { colon+= 2; char *base64 = malloc(strlen(colon) + 1); memcpy(base64, colon, strlen(colon) + 1); if (base64_decode(payload, strlen(payload), &decoded_len) == NULL) { handle_frame(decoded, decoded_len); } else { fprintf(stderr, "Base64 decode failed\n"); } free(payload); // %ms allocates memory, free it free(base64); } } else { fprintf(stderr, "Failed to parse RSSI and payload\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; }