// SPDX-License-Identifier: Apache-2.0 // Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors // // This file is part of WII5 Buoy firmware. // See LICENSE for full terms. /** * @file WII5BinData.cpp * @brief Binary message format for Iridium SBD: layout, packing, and split logic. */ /* WII5BinData - Simple utitlity class to create and manage binary data for Iridium How to manage the type requested. If it needs splitting, how to do, store etc. NOTE: LSB 0 */ #include void WII5BinData::begin() { } // getSize - Literally the size of the object including the headers uint16_t WII5BinData::getSize(uint32_t t) { return sizeof(WII5_BINDATA_HEADER) + getSizeObj(t); } uint16_t WII5BinData::getSizeOne(uint8_t b) { switch(b) { case 0: return sizeof(WII5_BINDATA_0); break; case 1: return sizeof(WII5_BINDATA_1); break; case 2: return sizeof(WII5_BINDATA_2); break; case 3: return sizeof(WII5_BINDATA_3); break; case 4: return sizeof(WII5_BINDATA_4); break; case 5: return sizeof(WII5_BINDATA_5); break; case 6: return sizeof(WII5_BINDATA_6); break; case 7: return sizeof(WII5_BINDATA_7); break; case 8: return sizeof(WII5_BINDATA_8); break; case 9: return sizeof(WII5_BINDATA_9); break; case 10: return sizeof(WII5_BINDATA_10); break; case 11: return sizeof(WII5_BINDATA_11); break; case 12: return sizeof(WII5_BINDATA_12); break; case 13: return sizeof(WII5_BINDATA_13); break; case 14: return sizeof(WII5_BINDATA_14); break; case 15: return sizeof(WII5_BINDATA_15); break; }; return 0; } /* Starting with a type of B01001011 uint8_t nextBit = 0; uint32_t outType = 0; uint32_t t = B01001011; uint8_t count = 0; while (getSplit(t, 300, &nextBit, %outType)) { console.printf(F("Splitting #=%d, original type=%lu, new type=%lu, size=%d, nextBit=%d\r\n") count, t, outType, getSize(outType), int(nextBit) ); count++; } There should be no overlapping bits at the end of this */ bool WII5BinData::getSplit(uint32_t in_t, uint16_t max_size, uint8_t* start_bit, uint32_t* out_t) { uint16_t ret_size = 0; uint16_t part_size = 0; ret_size = sizeof(WII5_BINDATA_HEADER); *out_t = 0; // TODO fix max bits for (uint8_t b = *start_bit; b < 16; b++) { if (BitVal(in_t, b)) { part_size = getSizeOne(b); if ((ret_size + part_size) > max_size) { // That is it, we can't do any more. *start_bit = b; // NOTE: This is an error condition if (*out_t == 0) { console.log(LOG_FATAL, F("BinData: Failed to split BinData to this size")); return false; } else return true; } else { SetBit(*out_t, b); ret_size += part_size; } } } *start_bit = 16; // Check if we have anything left if (*out_t == 0) return false; else return true; } uint16_t WII5BinData::setBit(uint32_t t, uint8_t b) { SetBit(t, b); return t; } // getSizeObj - The individual objects, added together but no header uint16_t WII5BinData::getSizeObj(uint32_t t) { uint16_t ret = 0; for (uint8_t b = 0; b < 16; b++) { if (BitVal(t, b)) ret += getSizeOne(b); } return ret; } // Create Data is called first - to initialize the Buffer bool WII5BinData::createData(uint32_t t, void* buf, uint16_t maxSize, uint32_t recordCount) { memset(buf, 0, maxSize); WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)buf; // Set the Header local->wii_binary_id = 60001; // Fixed ID for the old system - 60001 local->bindata_type = t; // Which packet type is this. local->bindata_packet = 0; // 0 means only? // NOTE: local->deviceId = wii5Config.getDeviceId(); // Custom recordCount (eg. we are sending something old, else current) if (recordCount > 0) local->recordCount = recordCount; else local->recordCount = wii5Config.getRecordCount(); /* console.log(LOG_DEBUG, F("*** (1) binary id = %d, binary type = %lu, packet = %d, recordCount = %lu, deviceId = %lu"), local->wii_binary_id, local->bindata_type, int(local->bindata_packet), local->recordCount, local->deviceId ); */ } // setData - literlaly set the data // - However, it pulls data live from devices, therefore you need next to get old data // - setBlockMetadata - Use a block to set the Metadata values // - setBlockResults - Set the block for the results values bool WII5BinData::setData(uint32_t t, void* buf, uint16_t maxSize) { char* ptr = (char*)buf; if (getSize(t) > maxSize) { console.log(LOG_FATAL, F("BinData: Standard Data too big for buger provided. Try splitting.")); return false; } { WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)ptr; if ( (local->wii_binary_id != 60001) || (local->bindata_type != t) ) { // TODO fix the %d here, should be unsigned. console.log(LOG_DEBUG, F("*** (2) binary id = %d, binary type = %lu, packet = %d, recordCount = %lu, deviceId = %lu"), local->wii_binary_id, local->bindata_type, int(local->bindata_packet), local->recordCount, local->deviceId ); console.log(LOG_FATAL, F("BinData: *** INTERNAL ERROR *** setData called without valid header first")); } } // Header ptr += sizeof(WII5_BINDATA_HEADER); if (BitVal(t, 0)) { WII5_BINDATA_0* local = (WII5_BINDATA_0*)ptr; if (wii5Weather_18B20.age < (WII5TIME_12HOUR * 1000)) local->temperature = wii5Weather_18B20.value; else local->temperature = 25500; // TODO Hard coded if (wii5Battery.age < (WII5TIME_12HOUR * 1000)) local->voltage = wii5Battery.value; else local->voltage = 25500; // TODO Hard coded local->last = now(); local->mode = wii5Controller.getMode(); // TODO Check valid ??? local->gps_lat = wii5Gps.gps->location.lat(); local->gps_lon = wii5Gps.gps->location.lng(); ptr += sizeof(WII5_BINDATA_0); } if (BitVal(t, 1)) { WII5_BINDATA_1* local = (WII5_BINDATA_1*)ptr; local->altitude = wii5Gps.gps->altitude.meters(); local->hdop = wii5Gps.gps->hdop.value(); local->satellites = wii5Gps.gps->satellites.value(); local->when = wii5Gps.when; local->lastRunTime = wii5Gps.lastRunTime; ptr += sizeof(WII5_BINDATA_1); } if (BitVal(t, 2)) { WII5_BINDATA_2* local = (WII5_BINDATA_2*)ptr; // TODO 2024 - signalQuality ? ptr += sizeof(WII5_BINDATA_2); } if (BitVal(t, 3)) { WII5_BINDATA_3* local = (WII5_BINDATA_3*)ptr; local->status = 0; // Special status bit fields to tell us what is going on local->command = wii5Commands.lastCommandCmd; // Commands as usual, top bit (32) is on for ACK // TODO local->id = wii5Commands.lastCommandId; // Resposne must contain request id in case we have many local->id = 0; local->response = wii5Commands.lastCommandResult; strncpy(local->message, wii5Commands.lastCommandMessage, sizeof(local->message)); ptr += sizeof(WII5_BINDATA_3); } if (BitVal(t, 4)) { WII5_BINDATA_4* local = (WII5_BINDATA_4*)ptr; strncpy(local->message, wii5Commands.lastCommandMessage, sizeof(local->message)); ptr += sizeof(WII5_BINDATA_4); } if (BitVal(t, 5)) { WII5_BINDATA_5* local = (WII5_BINDATA_5*)ptr; local->runCount = sh3dNodeConfig.getRunCount(); local->integerVersion = WII5_SOFTWARE_INTVER; ptr += sizeof(WII5_BINDATA_5); } if (BitVal(t, 6)) { ptr += sizeof(WII5_BINDATA_6); } if (BitVal(t, 7)) { ptr += sizeof(WII5_BINDATA_7); } if (BitVal(t, 8)) { ptr += sizeof(WII5_BINDATA_8); } if (BitVal(t, 9)) { ptr += sizeof(WII5_BINDATA_8); } if (BitVal(t, 10)) { ptr += sizeof(WII5_BINDATA_10); } if (BitVal(t, 11)) { ptr += sizeof(WII5_BINDATA_11); } if (BitVal(t, 12)) { ptr += sizeof(WII5_BINDATA_12); } if (BitVal(t, 13)) { ptr += sizeof(WII5_BINDATA_13); } if (BitVal(t, 14)) { ptr += sizeof(WII5_BINDATA_14); } if (BitVal(t, 15)) { ptr += sizeof(WII5_BINDATA_15); } return true; } bool WII5BinData::setBlockMetadata(uint32_t t, void* buf, uint16_t maxSize, WII5MetaDataObject* metadata) { if (getSize(t) > maxSize) { console.log(LOG_FATAL, F("BinData: Results Object too big for buger provided. Try splitting.")); return false; } char* ptr = (char*)buf; ptr += sizeof(WII5_BINDATA_HEADER); if (BitVal(t, 0)) { WII5_BINDATA_0* local = (WII5_BINDATA_0*)ptr; local->temperature = metadata->temperatureValue; local->voltage = metadata->batteryValue; local->mode = metadata->mode; local->gps_lat = metadata->gpsLat; local->gps_lon = metadata->gpsLon; ptr += sizeof(WII5_BINDATA_0); } if (BitVal(t, 1)) { WII5_BINDATA_1* local = (WII5_BINDATA_1*)ptr; /* local->altitude = wii5Gps.gps->altitude.meters(); local->hdop = wii5Gps.gps->hdop.value(); local->satellites = wii5Gps.gps->satellites.value(); local->when = wii5Gps.when; local->lastRunTime = wii5Gps.lastRunTime; */ ptr += sizeof(WII5_BINDATA_1); } if (BitVal(t, 2)) { ptr += sizeof(WII5_BINDATA_2); } if (BitVal(t, 3)) { WII5_BINDATA_3* local = (WII5_BINDATA_3*)ptr; ptr += sizeof(WII5_BINDATA_3); } if (BitVal(t, 4)) { ptr += sizeof(WII5_BINDATA_4); } if (BitVal(t, 5)) { ptr += sizeof(WII5_BINDATA_5); } if (BitVal(t, 6)) { ptr += sizeof(WII5_BINDATA_6); } if (BitVal(t, 7)) { ptr += sizeof(WII5_BINDATA_7); } if (BitVal(t, 8)) { ptr += sizeof(WII5_BINDATA_8); } if (BitVal(t, 9)) { ptr += sizeof(WII5_BINDATA_8); } if (BitVal(t, 10)) { ptr += sizeof(WII5_BINDATA_10); } if (BitVal(t, 11)) { ptr += sizeof(WII5_BINDATA_11); } if (BitVal(t, 12)) { ptr += sizeof(WII5_BINDATA_12); } if (BitVal(t, 13)) { ptr += sizeof(WII5_BINDATA_13); } if (BitVal(t, 14)) { ptr += sizeof(WII5_BINDATA_14); } if (BitVal(t, 15)) { ptr += sizeof(WII5_BINDATA_15); } } bool WII5BinData::setBlockResults(uint32_t t, void* buf, uint16_t maxSize, WII5Processed* processed) { char* ptr = (char*)buf; ptr += sizeof(WII5_BINDATA_HEADER); if (BitVal(t, 0)) { ptr += sizeof(WII5_BINDATA_0); } if (BitVal(t, 1)) { ptr += sizeof(WII5_BINDATA_1); } if (BitVal(t, 2)) { ptr += sizeof(WII5_BINDATA_2); } if (BitVal(t, 3)) { ptr += sizeof(WII5_BINDATA_3); } if (BitVal(t, 4)) { ptr += sizeof(WII5_BINDATA_4); } if (BitVal(t, 5)) { ptr += sizeof(WII5_BINDATA_5); } if (BitVal(t, 6)) { ptr += sizeof(WII5_BINDATA_6); } // processed1 if (BitVal(t, 7)) { WII5_BINDATA_7* local = (WII5_BINDATA_7*)ptr; /* local->tz_max[4]; local->htm_max[4]; local->hcm_max[4]; local->hz_max[4]; */ memcpy(local, &processed->processed1, sizeof(float) * 16); // 4 groups of floats ptr += sizeof(WII5_BINDATA_7); } // processed1 if (BitVal(t, 8)) { WII5_BINDATA_8* local = (WII5_BINDATA_8*)ptr; local->processed1 = processed->processed1; ptr += sizeof(WII5_BINDATA_8); } // processed2 if (BitVal(t, 9)) { WII5_BINDATA_9* local = (WII5_BINDATA_9*)ptr; local->processed2 = processed->processed2; ptr += sizeof(WII5_BINDATA_8); } if (BitVal(t, 11)) { WII5_BINDATA_11* local = (WII5_BINDATA_11*)ptr; memcpy(&local->direction, &processed->processed2.part2int[8], sizeof(int16_t) * 54); ptr += sizeof(WII5_BINDATA_11); } if (BitVal(t, 12)) { WII5_BINDATA_12* local = (WII5_BINDATA_12*)ptr; ptr += sizeof(WII5_BINDATA_12); } if (BitVal(t, 13)) { WII5_BINDATA_13* local = (WII5_BINDATA_13*)ptr; memcpy(&local->moments, &processed->processed2.part2float[0], sizeof(float) * 7); ptr += sizeof(WII5_BINDATA_13); } if (BitVal(t, 14)) { WII5_BINDATA_14* local = (WII5_BINDATA_14*)ptr; memcpy(&local->psd, &processed->processed1.part1float[18], sizeof(float) * 55); ptr += sizeof(WII5_BINDATA_14); } } WII5_BINDATA_3* WII5BinData::getCommand(void* buf, uint16_t maxSize) { uint32_t t = 0; SetBit(t, 3); char* ptr = (char*)buf; { WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)ptr; if (local->wii_binary_id != 60001) { console.log(LOG_WARN, F("BinData - not a valid header")); return NULL; } // Should upport multiple, but simplistic for now if (local->bindata_type != t) { console.log(LOG_WARN, F("BinData - could not find valid command")); return NULL; } ptr += sizeof(WII5_BINDATA_HEADER); } console.log(LOG_INFO, F("BinData - found valid command")); return (WII5_BINDATA_3*)ptr; } // dumpData - show the data to the console for debugging void WII5BinData::dumpData(void* buf, uint16_t maxSize, bool values) { uint32_t t = 0; char* ptr = (char*)buf; { WII5_BINDATA_HEADER* local = (WII5_BINDATA_HEADER*)ptr; // local->wii_binary_id = 60001; t = local->bindata_type; ptr += sizeof(WII5_BINDATA_HEADER); console.printf(F("# BinData Header found\r\n")); console.printf(F("# deviceId = %lu\r\n"), local->deviceId); console.printf(F("# recordCount = %lu\r\n"), local->recordCount); } if (BitVal(t, 0)) { WII5_BINDATA_0* local = (WII5_BINDATA_0*)ptr; console.printf(F("# BinData 0 - title=%s\r\n"), strTitle(0, false)); if (values) { console.printf(F("# temperature = %d\r\n"), int(local->temperature)); console.printf(F("# voltage = %d\r\n"), int(local->voltage)); console.printf(F("# last = %ld\r\n"), long(local->last)); console.printf(F("# mode = %d\r\n"), int(local->mode)); console.printf(F("# gps_lat = %ld\r\n"), long(local->gps_lat * GPS_POS_MULT)); console.printf(F("# gps_lon = %ld\r\n"), long(local->gps_lon * GPS_POS_MULT)); } ptr += sizeof(WII5_BINDATA_0); } if (BitVal(t, 1)) { WII5_BINDATA_1* local = (WII5_BINDATA_1*)ptr; console.printf(F("# BinData 1 - title=%s\r\n"), strTitle(1, false)); if (values) { console.printf(F("# gps_altitude = %ld\r\n"), long(local->altitude)); console.printf(F("# gps_hdop = %d\r\n"), int(local->hdop)); console.printf(F("# gps_satellites = %d\r\n"), int(local->satellites)); console.printf(F("# gps_when = %ld\r\n"), long(local->when)); console.printf(F("# gps_lastRunTime = %ld\r\n"), long(local->lastRunTime)); } ptr += sizeof(WII5_BINDATA_1); } if (BitVal(t, 2)) { WII5_BINDATA_2* local = (WII5_BINDATA_2*)ptr; console.printf(F("# BinData 2 - title=%s\r\n"), strTitle(2, false)); if (values) { console.printf(F("# lastRunTime = %d\r\n"), int(local->lastRunTime)); console.printf(F("# signalQuality = %d\r\n"), int(local->signalQuality)); } ptr += sizeof(WII5_BINDATA_2); } if (BitVal(t, 3)) { console.printf(F("# BinData 3 - title=%s\r\n"), strTitle(3, false)); ptr += sizeof(WII5_BINDATA_3); } if (BitVal(t, 4)) { console.printf(F("# BinData 4 - title=%s\r\n"), strTitle(4, false)); ptr += sizeof(WII5_BINDATA_4); } if (BitVal(t, 5)) { console.printf(F("# BinData 5 - title=%s\r\n"), strTitle(5, false)); ptr += sizeof(WII5_BINDATA_5); } if (BitVal(t, 6)) { console.printf(F("# BinData 6 - title=%s\r\n"), strTitle(6, false)); ptr += sizeof(WII5_BINDATA_6); } if (BitVal(t, 7)) { console.printf(F("# BinData 7 - title=%s\r\n"), strTitle(7, false)); ptr += sizeof(WII5_BINDATA_7); } if (BitVal(t, 8)) { console.printf(F("# BinData 8 - title=%s\r\n"), strTitle(8, false)); ptr += sizeof(WII5_BINDATA_8); } if (BitVal(t, 9)) { console.printf(F("# BinData 9 - title=%s\r\n"), strTitle(9, false)); ptr += sizeof(WII5_BINDATA_8); } if (BitVal(t, 10)) { console.printf(F("# BinData 10 - title=%s\r\n"), strTitle(10, false)); ptr += sizeof(WII5_BINDATA_10); } if (BitVal(t, 11)) { console.printf(F("# BinData 11 - title=%s\r\n"), strTitle(11, false)); ptr += sizeof(WII5_BINDATA_11); } if (BitVal(t, 12)) { console.printf(F("# BinData 12 - title=%s\r\n"), strTitle(12, false)); ptr += sizeof(WII5_BINDATA_12); } if (BitVal(t, 13)) { console.printf(F("# BinData 13 - title=%s\r\n"), strTitle(13, false)); ptr += sizeof(WII5_BINDATA_13); } if (BitVal(t, 14)) { console.printf(F("# BinData 14 - title=%s\r\n"), strTitle(14, false)); ptr += sizeof(WII5_BINDATA_14); } if (BitVal(t, 15)) { console.printf(F("# BinData 15 - title=%s\r\n"), strTitle(15, false)); ptr += sizeof(WII5_BINDATA_15); } } void WII5BinData::showBlocks(uint32_t t) { console.printf(F("BinData Type=%lu, Blocks="), t); for (uint8_t b = 0; b < 16; b++) { if (BitVal(t, b)) { console.printf(F("%d, "), int(b)); } } console.printf(F(" size=%d"), getSize(t)); } void WII5BinData::showSplit(uint32_t in_t, uint16_t max_size, char* bufout, uint16_t bufsize) { uint8_t nextBit = 0; uint32_t outType = 0; // TODO: also fill bufout/bufsize with the split summary so callers receive // structured data, not just console output. (void)bufout; (void)bufsize; console.printf(F("# Orig ")); showBlocks(in_t); console.printNewLine(); uint8_t count = 0; while (getSplit(in_t, max_size, &nextBit, &outType)) { console.printf(F("# New %d "), count); showBlocks(outType); console.printNewLine(); count++; if (count > 25) { console.log(LOG_FATAL, F("BinData: More than 25 splits, not supported")); return; } } } // showSizes - all sizes to console. void WII5BinData::showSizes(char* bufout, uint16_t bufsize) { console.printf(F("# BinData Size overhead=%d\r\n"), sizeof(WII5_BINDATA_HEADER)); // TODO: also fill bufout/bufsize with the size summary so callers receive // structured data, not just console output. (void)bufout; (void)bufsize; for (uint8_t x = 0; x < 16; x++) { console.printf(F("# BinData Size bit=%d, size=%d, title=%s\r\n"), int(x), getSizeOne(x), strTitle(x, false) ); if (getSizeOne(x) > 300) { console.printf(F("# *** WARNING *** Packet > 300 bytes ***\r\n")); } } } // strTitle - get the title for this object char* WII5BinData::strTitle(uint8_t bit, bool ext) { switch(bit) { case 0: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_0_TEXT)); break; case 1: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_1_TEXT)); break; case 2: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_2_TEXT)); break; case 3: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_3_TEXT)); break; case 4: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_4_TEXT)); break; case 5: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_5_TEXT)); break; case 6: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_6_TEXT)); break; case 7: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_7_TEXT)); break; case 8: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_8_TEXT)); break; case 9: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_9_TEXT)); break; case 10: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_10_TEXT)); break; case 11: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_11_TEXT)); break; case 12: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_12_TEXT)); break; case 13: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_13_TEXT)); break; case 14: strcpy_P(wii5BufferString ,(PGM_P) F(WII5_BINDATA_14_TEXT)); break; default: strcpy_P(wii5BufferString, (PGM_P) F("unknown")); break; }; return wii5BufferString; } bool WII5BinData::hasType(uint8_t bit, void* buf, uint16_t maxSize) { // TODO Check bit } void* WII5BinData::getData(uint8_t bit, void* buf, uint16_t maxSize) { // Find location in buffer } WII5BinData wii5BinData;