654 lines
24 KiB
C
654 lines
24 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 "packets.h"
|
|
#include "base.h"
|
|
|
|
FILE *csvFile;
|
|
|
|
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) {
|
|
clear();
|
|
int row = 0;
|
|
|
|
|
|
// Time
|
|
attroff(COLOR_PAIR(6));
|
|
mvprintw(row++, 0, "Timestamp: %lu", 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();
|
|
}
|
|
|
|
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 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);
|
|
fprintf(csvFile,
|
|
"%lu,%u,%u,%u," // missionTimer, packetIndex, telemetryIndex, packetType
|
|
"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d," // 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,
|
|
|
|
telemetryPacket.accelerationX, telemetryPacket.accelerationY, telemetryPacket.accelerationZ,
|
|
telemetryPacket.gyroX, telemetryPacket.gyroY, telemetryPacket.gyroZ,
|
|
telemetryPacket.magnetX, telemetryPacket.magnetY, telemetryPacket.magnetZ,
|
|
telemetryPacket.accelerometer_temperature,
|
|
|
|
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/ttyUSB2");
|
|
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);
|
|
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) {
|
|
char *colon = strchr(buf, ':');
|
|
if (colon) {
|
|
colon++;
|
|
while (*colon == ' ') colon++; // skip spaces
|
|
printf("Detected base64 payload: %s\n", colon);
|
|
|
|
decoded = base64_decode(colon, strlen(colon) - 1, &decoded_len);
|
|
if (decoded != NULL) {
|
|
printf("Decoded data (%zu bytes):\n", decoded_len);
|
|
handle_downlink_packet(decoded, decoded_len);
|
|
} else {
|
|
printf("Base64 decode failed\n");
|
|
}
|
|
if (decoded != NULL) {
|
|
free(decoded);
|
|
}
|
|
}
|
|
} else if (strncmp(buf, "Got 2400mhZ frame", 17) == 0) {
|
|
char *colon = strchr(buf, ':');
|
|
if (colon) {
|
|
colon++;
|
|
while (*colon == ' ') colon++; // skip spaces
|
|
printf("Detected base64 payload: %s\n", colon);
|
|
|
|
if (colon[strlen(colon) - 1] == '\r') {
|
|
colon[strlen(colon) - 1] = 0;
|
|
}
|
|
|
|
decoded = base64_decode(colon, strlen(colon), &decoded_len);
|
|
if (decoded != NULL) {
|
|
printf("Decoded data (%zu bytes):\n", decoded_len);
|
|
handle_frame(decoded, decoded_len);
|
|
} else {
|
|
printf("Base64 decode failed\n");
|
|
}
|
|
if (decoded != NULL) {
|
|
free(decoded);
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|