Files
Dashboard/main.c

723 lines
28 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint-gcc.h>
#include <ncurses.h>
#include <time.h>
#include <assert.h>
#include <ctype.h>
#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<int> R<int> S<int>: <base64>
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;
}