From 13881ae6c682e5b11d20398e60195f711966ed36 Mon Sep 17 00:00:00 2001 From: Scott Penrose Date: Sun, 7 Jun 2026 17:57:42 +1000 Subject: [PATCH] Attempt at building a platformio version before starting on changes to testing --- README.md | 32 +- boards/wii5_v2_2560.json | 19 + lib/AvgStd/AvgStd.cpp | 102 +++ lib/AvgStd/AvgStd.h | 50 ++ lib/MemoryFree/MemoryFree.cpp | 17 + lib/MemoryFree/MemoryFree.h | 17 + lib/PushButton/LICENSE | 21 + lib/PushButton/PushButton.cpp | 184 ++++ lib/PushButton/PushButton.h | 57 ++ lib/PushButton/library.properties | 10 + lib/SDBlock/SDBlock.cpp | 1296 +++++++++++++++++++++++++++++ lib/SDBlock/SDBlock.h | 259 ++++++ lib/SDBlock/SDBlockData.h | 214 +++++ pio_main.cpp | 23 + platformio.ini | 82 ++ 15 files changed, 2382 insertions(+), 1 deletion(-) create mode 100644 boards/wii5_v2_2560.json create mode 100644 lib/AvgStd/AvgStd.cpp create mode 100644 lib/AvgStd/AvgStd.h create mode 100644 lib/MemoryFree/MemoryFree.cpp create mode 100644 lib/MemoryFree/MemoryFree.h create mode 100644 lib/PushButton/LICENSE create mode 100644 lib/PushButton/PushButton.cpp create mode 100644 lib/PushButton/PushButton.h create mode 100644 lib/PushButton/library.properties create mode 100644 lib/SDBlock/SDBlock.cpp create mode 100644 lib/SDBlock/SDBlock.h create mode 100644 lib/SDBlock/SDBlockData.h create mode 100644 pio_main.cpp create mode 100644 platformio.ini diff --git a/README.md b/README.md index 2c0763c..1c7b2b4 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,33 @@ External vendor documentation is not redistributed in this repo; see ## Build & flash -This is an Arduino-IDE / `arduino-cli` project, not PlatformIO. +This builds with either **PlatformIO** or the original **Arduino-IDE / +`arduino-cli`** toolchain — the sources stay at the repo root and both flows +compile them in place. + +### PlatformIO (recommended) + +`platformio.ini` lives at the repo root and defines a single `wii5_buoy` +environment with a custom board (`boards/wii5_v2_2560.json`: ATmega2560 @ +11.0592 MHz, serial bootloader @ 230400). + +```sh +pio run # compile (env: wii5_buoy) +pio run -t upload # compile + flash (override port with --upload-port) +pio device monitor # serial console @ 230400 +``` + +Public library dependencies are declared in `lib_deps` and fetched +automatically. Scott's own libraries (`AvgStd`, `PushButton`, `MemoryFree`, +`SDBlock`) are vendored under `lib/`. The entry sketch +`app/wii5_buoy/wii5_buoy.ino` is pulled into the build by `pio_main.cpp` +(PlatformIO only auto-detects sketches at the source-dir root). + +> Note: PlatformIO's registry library versions differ from the historical +> arduino-cli set, so the flash footprint is larger than the figures quoted +> elsewhere in this repo. Pin specific `lib_deps` versions if you need parity. + +### Arduino IDE / arduino-cli ```sh # Verify the sketch compiles @@ -112,6 +138,10 @@ A running log of notable real-world deployments lives in `DEPLOYMENTS.md`. app/wii5_buoy/ Arduino sketch entry point WII5*.{h,cpp} Firmware source (board root, see Architecture) WII5_board_v2.h Pin/peripheral map for the WII5 v2 board +platformio.ini PlatformIO build configuration +boards/ PlatformIO custom board definition (wii5_v2_2560.json) +pio_main.cpp PlatformIO entry shim (#includes the .ino sketch) +lib/ Vendored libraries (AvgStd, PushButton, MemoryFree, SDBlock) doc/ Design notes, command reference, generated API docs test/ Smaller per-subsystem test sketches tools/ Build, flash, and size-profile helper scripts diff --git a/boards/wii5_v2_2560.json b/boards/wii5_v2_2560.json new file mode 100644 index 0000000..304f30a --- /dev/null +++ b/boards/wii5_v2_2560.json @@ -0,0 +1,19 @@ +{ + "build": { + "core": "arduino", + "extra_flags": "-DARDUINO_AVR_MEGA2560", + "f_cpu": "11059200L", + "mcu": "atmega2560", + "variant": "mega" + }, + "frameworks": ["arduino"], + "name": "WII5 v2 (ATmega2560 3V3 11.0592MHz)", + "url": "https://github.com/scottpenrose/WII5Firmware", + "upload": { + "maximum_ram_size": 8192, + "maximum_size": 258048, + "protocol": "arduino", + "speed": 230400 + }, + "vendor": "WII" +} diff --git a/lib/AvgStd/AvgStd.cpp b/lib/AvgStd/AvgStd.cpp new file mode 100644 index 0000000..9d0c5ef --- /dev/null +++ b/lib/AvgStd/AvgStd.cpp @@ -0,0 +1,102 @@ +/** + * Library to calculate the average and standard deviation of a + * reading iteratively: without storing the whole data set. + * + * Stores: + * double average + * double variance + * int numberOfReadings + * double min + * double max + **/ + + /* +Copyright (c) 2016 Edoardo Pasca + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "AvgStd.h" +#include + + +AvgStd::AvgStd(){ + AvgStd::reset(); +} + +void AvgStd::checkAndAddReading(float val){ + + if (N < 10) + AvgStd::addReading(val); + else { + if (r_sigma == -1) + AvgStd::addReading(val); + else { + if (abs(avg - val) <= (r_sigma * sqrt(var)) ) + AvgStd::addReading(val); + } + } + +} + +void AvgStd::addReading(float val){ + + if (N == 0) { + avg = val; + min = val; + max = val; + } else if (N == 1) { + //set min/max + max = val > max? val : max; + min = val < min? val : min; + + + float thisavg = (avg + val)/2; + // initial value is in avg + var = (avg - thisavg)*(avg-thisavg) + (val - thisavg) * (val-thisavg); + avg = thisavg; + } else { + // set min/max + max = val > max? val : max; + min = val < min? val : min; + + float M = (float)N; + + var = var * ((M-1)/M) + ((val - avg)*(val - avg)/(M+1)) ; + + avg = avg * (M/(M+1)) + val / (M+1); + } + N++; +} + + + +void AvgStd::reset(){ + N = 0; + avg = 0; + var = 0; + min = 0; + max = 0; + r_sigma = -1; +} + +float AvgStd::getMean(){return avg;} +float AvgStd::getStd() { + float ret = -1; + if (N>1) + ret = sqrt(var); + return ret; +} +float AvgStd::getVariance() {return var;} +unsigned int AvgStd::getN(){return N;} +float AvgStd::getMin() {return min;} +float AvgStd::getMax() {return max;} + +void AvgStd::setRejectionSigma(float sigmas){ + r_sigma = sigmas; +}; + diff --git a/lib/AvgStd/AvgStd.h b/lib/AvgStd/AvgStd.h new file mode 100644 index 0000000..23a61cc --- /dev/null +++ b/lib/AvgStd/AvgStd.h @@ -0,0 +1,50 @@ +/** + * Library to calculate the average and standard deviation of a + * reading iteratively: without storing the whole data set. + * + * Stores: + * double average + * double variance + * int numberOfReadings + * double min + * double max + **/ + + /* +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + + #ifndef AVGSTD_H + #define AVGSTD_H + + #include "Arduino.h" + +class AvgStd { + public: + float getMean(); + float getStd(); + float getMin(); + float getMax(); + float getVariance(); + unsigned int getN(); + void reset(); + void addReading(float); + void checkAndAddReading(float); + void setRejectionSigma(float); + int getTrend(); + void setTrendInterval(int); + void setSamplingInterval(int); + AvgStd(); + private: + float min, max, var, avg, r_sigma; + unsigned int N; + + }; + + #endif diff --git a/lib/MemoryFree/MemoryFree.cpp b/lib/MemoryFree/MemoryFree.cpp new file mode 100644 index 0000000..9c93e87 --- /dev/null +++ b/lib/MemoryFree/MemoryFree.cpp @@ -0,0 +1,17 @@ +extern unsigned int __bss_end; +extern unsigned int __heap_start; +extern void *__brkval; + +#include "MemoryFree.h" + +int freeMemory() { + int free_memory; + + if((int)__brkval == 0) + free_memory = ((int)&free_memory) - ((int)&__bss_end); + else + free_memory = ((int)&free_memory) - ((int)__brkval); + + return free_memory; +} + diff --git a/lib/MemoryFree/MemoryFree.h b/lib/MemoryFree/MemoryFree.h new file mode 100644 index 0000000..5c2b261 --- /dev/null +++ b/lib/MemoryFree/MemoryFree.h @@ -0,0 +1,17 @@ +// memoryFree header + +#ifndef MEMORY_FREE_H +#define MEMORY_FREE_H + +#ifdef __cplusplus +extern "C" { +#endif + +int freeMemory(); + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/lib/PushButton/LICENSE b/lib/PushButton/LICENSE new file mode 100644 index 0000000..a910dc9 --- /dev/null +++ b/lib/PushButton/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Kristian Klein Jacobsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/lib/PushButton/PushButton.cpp b/lib/PushButton/PushButton.cpp new file mode 100644 index 0000000..11abf8d --- /dev/null +++ b/lib/PushButton/PushButton.cpp @@ -0,0 +1,184 @@ +/* +MIT License + +Copyright (c) 2018 Kristian Klein Jacobsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "PushButton.h" +#include + +#define IDLE 0 +#define ACTIVE 1 +#define CLICKED 2 +#define DOUBLE_CLICKED 3 +#define RELEASED 4 +#define FIRST_HELD 5 +#define HELD 6 + +PushButton::PushButton(int pin) +{ + pin_ = pin; + state_ = IDLE; + debounced_ = true; + doubleClickable_ = true; + activeLogic_ = true; + debounceTime_ = 10; + holdTime_ = 2000; + doubleClickTime_ = 300; + lastClickTime_ = 0; + lastReleaseTime_ = 0; +} + +void PushButton::update() +{ + switch (state_) + { + case IDLE: + if (debounced_) + { + if (isActive()) // Button clicked + { + if (doubleClickable_ && millis() - lastClickTime_ < doubleClickTime_) + { + state_ = DOUBLE_CLICKED; + } + else + { + state_ = CLICKED; + } + + debounced_ = false; + lastClickTime_ = millis(); + } + } + else if (millis() - lastReleaseTime_ > debounceTime_) + { + debounced_ = true; + } + break; + + case ACTIVE: + if (debounced_) // Only check button again when it has been debounced + { + if (!isActive()) // Button not active, hence released + { + state_ = RELEASED; + } + else if (millis() - lastClickTime_ > holdTime_) // Button held + { + state_ = FIRST_HELD; + } + } + else if (millis() - lastClickTime_ > debounceTime_) // Check if debounce time has passed + { + debounced_ = true; + } + break; + + case CLICKED: + case DOUBLE_CLICKED: + state_ = ACTIVE; + break; + + case RELEASED: + state_ = IDLE; + debounced_ = false; + lastReleaseTime_ = millis(); + break; + + case FIRST_HELD: + state_ = HELD; + break; + + case HELD: + if (!isActive()) + { + state_ = RELEASED; + } + break; + + default: // This should never execute + state_ = IDLE; + break; + } +} + +bool PushButton::isActive() +{ + return (digitalRead(pin_) == activeLogic_); +} + +bool PushButton::isClicked() +{ + return (state_ == CLICKED); +} + +bool PushButton::isDoubleClicked() +{ + if (doubleClickable_) + { + return (state_ == DOUBLE_CLICKED); + } + else + { + return false; + } +} + +bool PushButton::isHeld() +{ + return (state_ == FIRST_HELD); +} + +bool PushButton::isReleased() +{ + return (state_ == RELEASED); +} + +void PushButton::disableDoubleClick() +{ + doubleClickable_ = false; +} + +void PushButton::enableDoubleClick() +{ + doubleClickable_ = true; +} + +void PushButton::setDebounceTime(int ms) +{ + debounceTime_ = ms; +} + +void PushButton::setHoldTime(int ms) +{ + holdTime_ = ms; +} + +void PushButton::setDoubleClickTime(int ms) +{ + doubleClickTime_ = ms; +} + +void PushButton::setActiveLogic(int high_or_low) +{ + activeLogic_ = high_or_low; +} diff --git a/lib/PushButton/PushButton.h b/lib/PushButton/PushButton.h new file mode 100644 index 0000000..c9b11c8 --- /dev/null +++ b/lib/PushButton/PushButton.h @@ -0,0 +1,57 @@ +/* +MIT License + +Copyright (c) 2018 Kristian Klein Jacobsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#ifndef PUSHBUTTON_H +#define PUSHBUTTON_H + +class PushButton +{ + public: + PushButton(int pin); + void update(); + bool isActive(); + bool isClicked(); + bool isDoubleClicked(); + bool isHeld(); + bool isReleased(); + void disableDoubleClick(); + void enableDoubleClick(); + void setDebounceTime(int ms); + void setHoldTime(int ms); + void setDoubleClickTime(int ms); + void setActiveLogic(int high_or_low); + private: + int state_; + long lastClickTime_; + long lastReleaseTime_; + int pin_; + bool doubleClickable_; + bool activeLogic_; + bool debounced_; + int debounceTime_; + int holdTime_; + int doubleClickTime_; +}; + +#endif \ No newline at end of file diff --git a/lib/PushButton/library.properties b/lib/PushButton/library.properties new file mode 100644 index 0000000..11e982d --- /dev/null +++ b/lib/PushButton/library.properties @@ -0,0 +1,10 @@ +name=PushButton +version=1.0.0 +author=Kristian Klein Jacobsen +maintainer=Kristian Klein Jacobsen +sentence=Makes it much easier for you to use push buttons in your application. +paragraph=Easily check for events such as click, double-click or hold on a push button, without worrying about debouncing or blocking the rest of your application with a delay. +category=Signal Input/Output +url=http://github.com/kristianklein/PushButton +architectures=* +includes=PushButton.h \ No newline at end of file diff --git a/lib/SDBlock/SDBlock.cpp b/lib/SDBlock/SDBlock.cpp new file mode 100644 index 0000000..532d1f8 --- /dev/null +++ b/lib/SDBlock/SDBlock.cpp @@ -0,0 +1,1296 @@ +#include "SDBlock.h" + +void SDBlock::begin(uint32_t dId) { + deviceId = dId; + + // XXX Change to checking each is SECTOR_SIZE or report ! + if (stream && debug) { + stream->println(F("# SDBlock sizes: ")); + stream->print(F("# buffer: ")); + stream->println(sizeof(buffer)); + stream->print(F("# allocation: ")); + stream->println(sizeof(SDBlock_Allocation)); + stream->print(F("# metadata: ")); + stream->println(sizeof(SDBlock_Metadata)); + stream->print(F("# loglines: ")); + stream->println(sizeof(SDBlock_LogLines)); + stream->print(F("# queue: ")); + stream->println(sizeof(SDBlock_Queue)); + stream->print(F("# block: ")); + stream->println(sizeof(SDBlock_Block)); + + stream->print(F("# SDBlock Address Defaults: ")); + stream->print(F(" allocation: ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_ALLOCATION); + stream->print(F(" - ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_ALLOCATION + SDBLOCK_ALLOCATION_MAX); + stream->println(); + + stream->print(F("# metadata: ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_METADATA); + stream->print(F(" - ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_METADATA + SDBLOCK_METADATA_MAX); + stream->println(); + + stream->print(F("# single: ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_SINGLE); + stream->print(F(" - ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_SINGLE + SDBLOCK_SINGLE_MAX); + stream->println(); + + stream->print(F("# loglines: ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_LOGLINES); + stream->print(F(" - ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_LOGLINES + SDBLOCK_LOGLINES_MAX); + stream->println(); + + stream->print(F("# queue: ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_QUEUE); + stream->print(F(" - ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_QUEUE + SDBLOCK_QUEUE_MAX); + stream->println(); + + stream->print(F("# data: ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_DATA); + stream->print(F(" - ")); + stream->print((uint32_t)SDBLOCK_ACTUAL_DATA + SDBLOCK_DATA_MAX); + stream->println(); + + stream->print(F("# max: ")); + stream->print((uint32_t)SDBLOCK_MAX); + stream->println(); + + // Coming up as 3% but shoudl be 72% + // stream->print(F(" percent: ")); + // stream->print((uint32_t)(((SDBLOCK_ACTUAL_DATA + SDBLOCK_DATA_MAX) * (uint32_t)100) / (uint32_t)SDBLOCK_MAX)); + // stream->println("%"); + } + + autoFormat = false; +} +void SDBlock::end() { + // SPI.end() /? +} + +void SDBlock::setAutoFormat(bool in) { + autoFormat = in; +} +bool SDBlock::getAutoFormat() { + return autoFormat; +} + +void SDBlock::setDeviceId(uint32_t dId) { + deviceId = dId; +} + +void SDBlock::setRecordId(uint32_t rId) { + recordId = rId; +} + +bool SDBlock::cardIsOpen() { + return valid; +} + +// Close off card, ignoreing any data, ie, it will all be lose +void SDBlock::cardForceClose() { + valid = false; + dataBlockCurrent = 0; + dataBlockStart = 0; + metadata_dataBlockStart = 0; + metadata_dataBlocks = 0; + metadata_resultsBlockStart = 0; + metadata_resultsBlocks = 0; +} + +bool SDBlock::cardErase() { +#ifdef USE_SDFS + if (!_sdinit()) { + if (stream) + stream->println(F("# SDBlock - Init card failed")); + return false; + } + + if (stream) + stream->println(F("# SDBlock: erase started: ")); + + uint32_t firstBlock = 0; + uint32_t lastBlock; + uint16_t n = 0; + uint32_t cardSectorCount = sd.card()->sectorCount(); + + do { + lastBlock = firstBlock + ERASE_SIZE - 1; + if (lastBlock >= cardSectorCount) { + lastBlock = cardSectorCount - 1; + } + if (!sd.card()->erase(firstBlock, lastBlock)) { + if (stream) + stream->println(F("# SDBlock: ERROR - erase failed")); + return false; + } + if (stream) + stream->print('.'); + if ((n++)%64 == 63) { + if (stream) + stream->println(); + } + firstBlock += ERASE_SIZE; + } while (firstBlock < cardSectorCount); + stream->println(); + + if (!read(0)) { + if (stream) + stream->println(F("# SDBlock: ERROR - read block failed")); + return false; + } + if (stream) + stream->println(F("# SDBlock: erase done")); + return true; +#else + if (stream) + stream->println(F("# SDBlock: driver does not support erase")); + return false; +#endif +} + +bool SDBlock::_sdinit() { + // Initialize SD Card + #ifdef USE_SDCORE + if (!SDCore::begin()) { + if (!SDCore::begin()) { + if (stream) { + stream->println(F("# SDBlock Initialization: FAILED - Make sure than you have your wiring correct and a compatible SD card inserted.")); + } + return false; + } + } + #endif + + #ifdef USE_SD2CARD + if (!sd.init()) { + if (!sd.init()) { + if (stream) + stream->println(F("# SDBlock Initialization: FAILED - Make sure than you have your wiring correct and a compatible SD card inserted.")); + return false; + } + } + // TODO Keep + size = sd.cardSize(); + if (stream) { + stream->print(F("# SDBlock CardSize (blocks): ")); + stream->print((uint32_t)size); + stream->println(); + } + #endif + + #ifdef USE_SDFS + SdCardFactory cardFactory; + m_card = cardFactory.newCard(SD_CONFIG); + + /* + if (!sd.cardBegin(SD_CONFIG)) { + if (!sd.cardBegin(SD_CONFIG)) { + if (stream) + stream->println(F("# SDBlock Initialization: FAILED - Make sure than you have your wiring correct and a compatible SD card inserted.")); + return false; + } + } + */ + #endif + + return true; +} + +bool SDBlock::cardOpen(uint32_t cId, bool format) { + checkInternals(); + + valid = false; + dataBlockCurrent = 0; + dataBlockStart = 0; + + dataBlockNext = 0; + metadataBlockNext = 0; + singleBlockNext = 0; + loglinesBlockNext = 0; + queueBlockNext = 0; + metadata_dataBlockStart = 0; + metadata_dataBlocks = 0; + metadata_resultsBlockStart = 0; + metadata_resultsBlocks = 0; + + // Setup pointers (can be done just once) + allocation = (SDBlock_Allocation*)&buffer; + metadata = (SDBlock_Metadata*)&buffer; + loglines = (SDBlock_LogLines*)&buffer; + block = (SDBlock_Block*)&buffer; + + if (!_sdinit()) { + if (stream) + stream->println(F("# SDBlock - Init card failed")); + return false; + } + + cardId = cId; + + if (format) { + if (stream) + stream->println(F("# SDBlock - Format Requested")); + allocationDefault(); + } + + if (!allocationRead()) { + if (stream) { + stream->println(F("# SDBlock Initialization: Allocation Read Failed")); + } + return false; + } + + dataBlockNext = allocation->dataBlockNext; // Next, becasue first record is 0, so initilaly we write there + metadataBlockNext = allocation->metadataBlockNext; // And of course where is the Metadata + singleBlockNext = allocation->singleBlockNext; // And of course where is the Metadata + loglinesBlockNext = allocation->loglinesBlockNext; // And of course where is the Metadata + queueBlockNext = allocation->queueBlockNext; // And of course where is the Metadata + + if (stream && debug) { + stream->print(F("# SDBlock next: ")); + stream->print(F("; cardId: expected=")); + stream->print(cardId); + stream->print(F(" allocated=")); + stream->println(allocation->cardId); + stream->print(F("# metadata: next=")); + stream->print(metadataBlockNext); + stream->print(F(" records=")); + stream->print(metadataBlockNext - SDBLOCK_ACTUAL_METADATA); + + stream->print(F("; data: next=")); + stream->print(dataBlockNext); + stream->print(F(" records=")); + stream->println(dataBlockNext - SDBLOCK_ACTUAL_DATA); + + stream->print(F("# single: next=")); + stream->print(singleBlockNext); + stream->print(F(" records=")); + stream->print(singleBlockNext - SDBLOCK_ACTUAL_SINGLE); + + stream->print(F("; loglines: next=")); + stream->print(loglinesBlockNext); + stream->print(F(" records=")); + stream->print(loglinesBlockNext - SDBLOCK_ACTUAL_LOGLINES); + + stream->print(F("; queue: next=")); + stream->print(queueBlockNext); + stream->print(F(" records=")); + stream->print(queueBlockNext - SDBLOCK_ACTUAL_QUEUE); + stream->println(); + + } + + valid = true; + return true; +} + +void SDBlock::checkInternals() { + if (stream) { + if (sizeof(SDBlock_Allocation) != SDBLOCK_SECTOR_SIZE) { + stream->print(F("\n\n\n*********\nAllocation Wrong size: ")); + stream->println(sizeof(SDBlock_Allocation)); + } + if (sizeof(SDBlock_Metadata) != SDBLOCK_SECTOR_SIZE) { + stream->print(F("\n\n\n*********\nMetadata Wrong size: ")); + stream->println(sizeof(SDBlock_Metadata)); + } + if (sizeof(SDBlock_LogLines) != SDBLOCK_SECTOR_SIZE) { + stream->print(F("\n\n\n*********\nLogLines Wrong size: ")); + stream->println(sizeof(SDBlock_LogLines)); + } + if (sizeof(SDBlock_Queue) != SDBLOCK_SECTOR_SIZE) { + stream->print(F("\n\n\n*********\nQueue Wrong size: ")); + stream->println(sizeof(SDBlock_Queue)); + } + if (sizeof(SDBlock_Block) != SDBLOCK_SECTOR_SIZE) { + stream->print(F("\n\n\n*********\nBlock Wrong size: ")); + stream->println(sizeof(SDBlock_Block)); + } + } +} + +bool SDBlock::cardClose() { + allocationUpdate(); + allocationRead(); + // XXX Write allocation changes + #ifdef USE_SDCORE + SDCore::end(); + #endif + + // SPI.end(); + valid = false; + dataBlockCurrent = 0; + dataBlockStart = 0; + return true; +} + +bool SDBlock::read(unsigned long address_in) { + #ifdef USE_SDCORE + if (!SDCore::read(address_in, buffer)) { + if (!SDCore::read(address_in, buffer)) { + if (stream) { + stream->print(F("# SDBlock: Failed to read at block: ")); + stream->println(address_in); + } + return false; + } + } + #endif + + #ifdef USE_SD2CARD + if (!sd.readBlock(address_in, (byte*)buffer)) { + if (!sd.readBlock(address_in, (byte*)buffer)) { + if (stream) { + stream->print(F("# SDBlock: Failed to read at block: ")); + stream->println(address_in); + } + return false; + } + } + #endif + + #ifdef USE_SDFS + if (!sd.card()->readSingle(address_in, (uint8_t*)&buffer)) { + if (!sd.card()->readSingle(address_in, (uint8_t*)&buffer)) { + if (stream) { + stream->print(F("# SDBlock: Failed to read at block: ")); + stream->println(address_in); + } + return false; + } + } + #endif + + return true; +} + +bool SDBlock::write(unsigned long address_in) { + // XXX Check address_in in range !!! + #ifdef USE_SDCORE + if (!SDCore::write(address_in, buffer)) { + if (!SDCore::write(address_in, buffer)) { + if (stream) { + stream->print(F("# SDBlock: Failed to write at block: ")); + stream->println(address_in); + } + return false; + } + } + #endif + #ifdef USE_SD2CARD + if (!sd.writeBlock(address_in, (byte*)buffer)) { + if (!sd.writeBlock(address_in, (byte*)buffer)) { + if (stream) { + stream->print(F("# SDBlock: Failed to write at block: ")); + stream->println(address_in); + } + return false; + } + } + #endif + #ifdef USE_SDFS + if (!sd.card()->writeSingle(address_in, (uint8_t*)&buffer)) { + if (!sd.card()->writeSingle(address_in, (uint8_t*)&buffer)) { + if (stream) { + stream->print(F("# SDBlock: Failed to read at block: ")); + stream->println(address_in); + } + return false; + } + } + #endif + + return true; +} + +// Create a BRAND NEW DEVICE +bool SDBlock::allocationDefault() { + memset(buffer, 0, sizeof(buffer)); + metadata->blockType = SDBLOCK_TYPE_ALLOCATION_FULL; + allocation->deviceId = deviceId; + allocation->recordId = recordId; + allocation->currentTime = now(); + + // First 2 blocks Allocaiton table + // Next = Metadata + allocation->metadataBlockNext = SDBLOCK_ACTUAL_METADATA; + // Next = Single + allocation->singleBlockNext = SDBLOCK_ACTUAL_SINGLE; + // Next = Log Lines + allocation->loglinesBlockNext = SDBLOCK_ACTUAL_LOGLINES; + // Next = Data + allocation->dataBlockNext = SDBLOCK_ACTUAL_DATA; + // Queue + allocation->queueBlockNext = SDBLOCK_ACTUAL_QUEUE; + + + // ESSENTAIL for alloction bloc, or this block will be rejected. + // I only keep one other copy + crcSet(); + + return allocationWrite(); +} + +// Special update in place +bool SDBlock::allocationUpdate() { + if (!allocationRead()) { + if (stream) + stream->println(F("# SDBlock: Failed to update allocation table")); + return false; + } + + if (stream) { + stream->println(F("# SDBlock: Allocation changes: ")); + + stream->print(F("# metadata: ")); + stream->print(allocation->metadataBlockNext); + stream->print(F(" -> ")); + stream->println(metadataBlockNext); + + stream->print(F("# single: ")); + stream->print(allocation->singleBlockNext); + stream->print(F(" -> ")); + stream->println(singleBlockNext); + + stream->print(F("# loglines: ")); + stream->print(allocation->loglinesBlockNext); + stream->print(F(" -> ")); + stream->println(loglinesBlockNext); + + stream->print(F("# queue: ")); + stream->print(allocation->queueBlockNext); + stream->print(F(" -> ")); + stream->println(queueBlockNext); + + stream->print(F("# data: ")); + stream->print(allocation->dataBlockNext); + stream->print(F(" -> ")); + stream->println(dataBlockNext); + } + + allocation->dataBlockNext = dataBlockNext; + allocation->metadataBlockNext = metadataBlockNext; + allocation->singleBlockNext = singleBlockNext; + allocation->loglinesBlockNext = loglinesBlockNext; + allocation->queueBlockNext = queueBlockNext; + allocation->currentTime = now(); + allocation->deviceId = deviceId; + allocation->recordId = recordId; + + allocation->cardId = cardId; + + // Update and set the CRC + crcSet(); + + return allocationWrite(); +} + +bool SDBlock::allocationWrite() { + response = write(SDBLOCK_OFFSET); + if (!response) { + response = write(SDBLOCK_OFFSET); + if (!response) { + if (stream) { + stream->println("# SDBlock: DISASTER: Failed to write allocation block 1"); + } + } + } + + response = write(SDBLOCK_OFFSET + 1); + if (!response) { + response = write(SDBLOCK_OFFSET + 1); + if (!response) { + if (stream) { + stream->println("# SDBlock: DISASTER: Failed to write allocation block 2"); + } + } + } + + return true; +} + +/* +bool SDBlock::metadataStatusBLK(uint32_t block, uint32_t status) { + response = read(block); + if (!response) { + response = read(block); + if (!response) { + stream->println(F("# SDBlock - metadataStatusBLK - Catastrophic Failure")); + return false; + } + } + metadata->status = status; + crcSet(); + response = write(block); + if (!response) { + response = write(block); + if (!response) { + if (stream) + stream->println(F("# SDBlock: DISASTER: Can't write update BLK")); + return false; + } + } + return true; +} + +bool SDBlock::dataStatusBLK(uint32_t block, uint32_t status) { + response = read(block); + if (!response) { + response = read(block); + if (!response) { + stream->println(F("# SDBlock - dataStatusBLK - Catastrophic Failure")); + return false; + } + } + data->status = status; + response = write(block); + if (!response) { + response = write(block); + if (!response) { + if (stream) + stream->println(F("# SDBlock: DISASTER: Can't write update BLK")); + return false; + } + } + return true; +} +*/ + +uint32_t SDBlock::getDataNext() { + return dataBlockNext; +} + +uint32_t SDBlock::getMetadataNext() { + return metadataBlockNext; +} +uint32_t SDBlock::getMetadataFirst() { + return SDBLOCK_ACTUAL_METADATA; +} +uint32_t SDBlock::getDataFirst() { + return SDBLOCK_ACTUAL_DATA; +} +uint8_t SDBlock::getCardId() { + return cardId; +} +bool SDBlock::validRange(char *str, uint32_t test, uint32_t start, uint32_t max) { + if ( + (test < start) + || ((test - start) > max) + ) { + if (str && stream) { + stream->print(F("# SDBlock: ERROR ")); + stream->print(str); + stream->print(F(" out of range: current=")); + stream->print(test); + stream->print(F(", start=")); + stream->print(start); + stream->print(F(", max=")); + stream->print(max); + stream->println(); + } + return false; + } + return true; +} + +// XXX Generalisation of Valid - e.g. CRC and Block type +bool SDBlock::allocationValid() { + if (!crcCheck()) { + if (stream) + stream->println(F("# SDBlock: Allocation: CRC Failed")); + return false; + } + + if (allocation->blockType != SDBLOCK_TYPE_ALLOCATION_FULL) { + if (stream && debug) { + stream->print(F("# SDBlock: BlockType: ")); + stream->println(allocation->blockType); + stream->print(F("Expected: ")); + stream->println(SDBLOCK_TYPE_ALLOCATION_FULL); + } + return false; + } + + // Example Special case - we can write some areas update ourselives + if (!validRange(NULL, allocation->queueBlockNext, SDBLOCK_ACTUAL_QUEUE, SDBLOCK_QUEUE_MAX)) { + if (stream) + stream->println("# SDBlock: NOTE: queueBlock automatically updated"); + allocation->queueBlockNext = SDBLOCK_ACTUAL_QUEUE; + } + + // XXX These could be improved ! Fully check range etc + if ( + !validRange("metadataBlock", allocation->metadataBlockNext, SDBLOCK_ACTUAL_METADATA, SDBLOCK_METADATA_MAX) + || !validRange("singleBlock", allocation->singleBlockNext, SDBLOCK_ACTUAL_SINGLE, SDBLOCK_SINGLE_MAX) + || !validRange("loglinesBlock", allocation->loglinesBlockNext, SDBLOCK_ACTUAL_LOGLINES, SDBLOCK_LOGLINES_MAX) + || !validRange("queueBlock", allocation->queueBlockNext, SDBLOCK_ACTUAL_QUEUE, SDBLOCK_QUEUE_MAX) + || !validRange("dataBlock", allocation->dataBlockNext, SDBLOCK_ACTUAL_DATA, SDBLOCK_DATA_MAX) + ) { + if (stream) + stream->println(F("# SDBlock: Failed one or more dataBlock tests")); + return false; + } + + return true; +} + +bool SDBlock::allocationRead() { + // Read first record + response = read(SDBLOCK_OFFSET); + if (!response) + response = read(SDBLOCK_OFFSET); + + // Read second record + if (!response || !crcCheck()) { + if (stream) { + stream->println(F("# SDBlock: Failed to read allocation block 1, trying block 2")); + } + response = read(SDBLOCK_OFFSET + 1); + if (!response) + response = read(SDBLOCK_OFFSET + 1); + } + + // Check valid Allocation table, and CRC Valid. + if (!allocationValid()) { + // Time to assume this is a new disk (VERY DANGEROUS) + + if (autoFormat) { + if (!allocationDefault()) { + // console.log + if (stream) + stream->println(F("# SDBlock: Failed... like real bad - unable to make new allocation table!")); + return false; + } + return true; + } + else { + if (stream) + stream->println(F("# SDBlock: Failed... like real bad - no allocation valid, and no autoFormat")); + return false; + } + } + + return true; +} + +void SDBlock::crcUpdate() { + // 512 byte buffer - whole thing, except the CRC (last 4 bytes); + crc = CRC32::calculate(buffer, 508); +} + +bool SDBlock::crcCheck() { + // Update the CRC from Buffer + crcUpdate(); + // Compare to what is stored in buffer + return (crc == block->crc); +} + +void SDBlock::crcSet() { + crcUpdate(); + block->crc = crc; +} + +bool SDBlock::getMBR() { + // NOTE: Assumes openCard() + + // XXX Keep the esentils. + + // Read MBR + response = read(0); + if (!response) { + // console.log(LOG_INFO, F("MBR Reading: FAILED")); + stream->println(F("# SDBlock: Failed read MBR")); + return false; + } + + // Check if MBR is valid (MBR Signature: 0x55AA at end of the sector) + if (buffer[0x1FE] != 0x55 || buffer[0x1FF] != 0xAA) { + stream->println(F("# SDBlock: This SD Card doesnt use MBR")); + return false; + } + + // Show all information from MBR + // Reference for MBR Sector: https://en.wikipedia.org/wiki/Master_boot_record + stream->println(F("# SD Card Information:")); + + // Original Physical Drive + stream->print(F("# Physical Drive: ")); + stream->println(buffer[0x0DC], HEX); + + // Timestamp + stream->print(F("# Timestamp: ")); + stream->print(buffer[0x0DD]); + stream->print(F(":")); + stream->print(buffer[0x0DE]); + stream->print(F(":")); + stream->println(buffer[0x0DF]); + + // Disk Signature + stream->print(F("# Disk Signature: ")); + stream->print(buffer[0x1B8], HEX); + stream->print(buffer[0x1B9], HEX); + stream->print(buffer[0x1BA], HEX); + stream->print(buffer[0x1BB], HEX); + stream->print(buffer[0x1BC], HEX); + stream->println(buffer[0x1BD], HEX); + + // Partition Entries + for (byte i = 0; i < 4; i++) { + + // Calculate blocks count + unsigned long blocks = (unsigned long)buffer[0x1CA + (i * 16)] + | (unsigned long)buffer[0x1CB + (i * 16)] << 8 + | (unsigned long)buffer[0x1CC + (i * 16)] << 16 + | (unsigned long)buffer[0x1CD + (i * 16)] << 24; + + // If its a partition with 0 blocks, its not a partition ;) + if (blocks == 0) { + continue; + } + + // XXX Urgent - convert to console ! + + // Partition Number + stream->print(F("# Partition ")); + stream->println(i + 1); + + // Is bootable + stream->print(F("#\tBootable: ")); + if (buffer[0x1BE + (i * 16)]) { + stream->println(F("YES")); + } else { + stream->println(F("NO")); + } + + // Partition Type + stream->print(F("#\tPartition Type: ")); + stream->println(buffer[0x1C2], HEX); + + // LBA + stream->print(F("#\tLBA: ")); + stream->print(buffer[0x1C9 + (i * 16)], HEX); + stream->print(buffer[0x1C8 + (i * 16)], HEX); + stream->print(buffer[0x1C7 + (i * 16)], HEX); + stream->println(buffer[0x1C6 + (i * 16)], HEX); + + // Number of blocks + stream->print(F("#\tBlocks: ")); + stream->println(blocks); + + // Size + stream->print(F("#\tSize: ")); + stream->print(blocks >> 11); + stream->println(F(" MB")); + } +} + +bool SDBlock::dataIsOpen() { + return (valid && (dataBlockStart || dataBlockCurrent)); +} + +bool SDBlock::dataWrite(void* in, uint16_t size) { + if (!dataIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was never open. Nothing to do")); + return false; + } + + // Helpful all in one write + if (size > 0) { + if (!dataPrepare(in, size)) { + if (stream) + stream->println(F("# SDBlock: Failed inline prepare")); + return false; + } + } + + // Leave off for data blocs - just too slow (About 80% of the time in SDBlock) + // crcSet(); + response = write(dataBlockCurrent); + if (!response) { + response = write(dataBlockCurrent); + if (!response) { + if (stream) + stream->println(F("# SDBlock: DISASTER: Can't write")); + return false; + } + } + + dataLast = dataBlockNext; + dataBlockCurrent++; + return true; +} + +bool SDBlock::dataOpen(uint32_t recId) { + // Woops.... already started... Record to serial port then disreguard + if (dataIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was already started, that run will be lost (TODO record numbers)")); + } + + dataBlockStart = dataBlockNext; + dataBlockCurrent = dataBlockNext; + dataBlockRecordId = recId; + recordId = recId; // XXX Both? + + if (stream) { + stream->print(F("# SDBlock: dataOpen at ")); + stream->println(dataBlockNext); + } + return true; +} + +// dataClose - assumed to be input, but could also be output, only decide on close +bool SDBlock::dataClose(bool output) { + if (!dataIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was never open. Nothing to do")); + return false; + } + + // Update our allocation table ready to save + dataBlockNext = dataBlockCurrent; + + if (output) { + metadata_resultsBlockStart = dataBlockStart; + metadata_resultsBlocks = dataBlockCurrent - dataBlockStart; + if (stream) { + stream->print(F("# SDBlock: dataClose added ")); + stream->println(metadata_resultsBlocks); + } + } + else { + metadata_dataBlockStart = dataBlockStart; + metadata_dataBlocks = dataBlockCurrent - dataBlockStart; + if (stream) { + stream->print(F("# SDBlock: dataClose added ")); + stream->println(metadata_dataBlocks); + } + } + + // Clear data + dataBlockStart = 0; + dataBlockCurrent = 0; + + return true; +} + +bool SDBlock::dataPrepare(void* in, uint16_t size) { + memset(buffer, 0, sizeof(buffer)); + block->blockType = SDBLOCK_TYPE_BLOCK_FULL; + block->recordId = dataBlockRecordId; + + if (size > 0) { + if (size > sizeof(block->data)) { + if (stream) + stream->println(F("# SDBlock: ERROR: Trying to set data beyond size")); + return false; + } + memcpy(block->data, in, size); + } + return true; +} + +//bool SDBlock::dataRead(uint32_t fromEnd) { +//} +/* +void SDBlock::dataList(unsigned long address, Print* altstream = NULL, CallbackFunction metadata_cb = NULL, CallbackFunction data_cb = NULL) { +} +*/ + +bool SDBlock::metadataPrepare(void* in, uint16_t size) { + memset(buffer, 0, sizeof(buffer)); + metadata->blockType = SDBLOCK_TYPE_METADATA_FULL; + metadata->status = 0; + metadata->deviceId = deviceId; + metadata->recordId = recordId; + metadata->dataBlockStart = metadata_dataBlockStart; + metadata->dataBlocks = metadata_dataBlocks; + metadata->resultsBlockStart = metadata_resultsBlockStart; + metadata->resultsBlocks = metadata_resultsBlocks; + + // Clear the data blocks + metadata_dataBlockStart = 0; + metadata_dataBlocks = 0; + metadata_resultsBlockStart = 0; + metadata_resultsBlocks = 0; + + if (size > 0) { + if (size > sizeof(metadata->data)) { + if (stream) + stream->println(F("# SDBlock: ERROR: Trying to set data beyond size")); + return false; + } + memcpy(metadata->data, in, size); + } + return true; +} + +bool SDBlock::metadataWrite(void* in, uint16_t size) { + if (!cardIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was never open. Nothing to do")); + return false; + } + // Helpful all in one write + if (size > 0) { + if (!metadataPrepare(in, size)) { + if (stream) + stream->println(F("# SDBlock: Failed inline prepare")); + return false; + } + } + crcSet(); + response = write(metadataBlockNext); + if (!response) { + response = write(metadataBlockNext); + if (!response) { + if (stream) + stream->println(F("# SDBlock: DISASTER: Can't write metadata")); + return false; + } + } + + metadataLast = metadataBlockNext; + metadataBlockNext++; + return true; +} + +uint32_t SDBlock::metadataCount() { + return metadataBlockNext - SDBLOCK_ACTUAL_METADATA; +} + +// fromEnd is first, then num back from there +void SDBlock::metadataList(uint32_t fromEnd, uint32_t num, Print *altstream) { + if (!altstream) + altstream = stream; + + if (debug) { + altstream->print(F("# SDBlock: metadataList - fromEnd/num= metadtaaBlockNext=")); + altstream->print(fromEnd); + altstream->print("/"); + altstream->print(num); + altstream->print(" "); + altstream->print(metadataBlockNext); + altstream->println(); + } + + altstream->println(F("# SDBlock: Start of metadata requested")); + for (count = 0; count < num; count++) { + if (metadataRead(fromEnd + count)) { + altstream->print(F("@SDBlock,metadata")); + altstream->print(F(",count=")); + altstream->print(count); + altstream->print(F(",add=")); + altstream->print(metadataBlockNext - (fromEnd + count) - 1); // NOTE: -1 because Next is next to use not this + altstream->print(F(",dev=")); + altstream->print(metadata->deviceId); + altstream->print(F(",rec=")); + altstream->print(metadata->recordId); + altstream->print(F(",bSt=")); + altstream->print(metadata->dataBlockStart); + altstream->print(F(",bs=")); + altstream->print(metadata->dataBlocks); + altstream->print(F(",rSt=")); + altstream->print(metadata->resultsBlockStart); + altstream->print(F(",rs=")); + altstream->print(metadata->resultsBlocks); + altstream->print(F(",status=")); + altstream->print(metadata->status); + altstream->println(); + } + else { + altstream->println(F("# SDBlock: End of metadata reached (before number requested)")); + break; + } + } + altstream->println(F("# SDBlock: End of metadata requested")); +} + +bool SDBlock::metadataUpdateStatus(uint32_t address_in, uint32_t record_in, uint32_t status_in) { + if (!metadataReadBLK(address_in)) { + if (stream) + stream->println(F("# SDBlock: Failed to update metadata status")); + return false; + } + + // Check record is valid + if (metadata->recordId != record_in) { + if (stream) { + stream->print(F("# SDBlock: Failed to match record Id: Request=")); + stream->print(record_in); + stream->print(F(", rec=")); + stream->println(metadata->recordId); + } + return false; + } + + if (metadata->status == status_in) { + if (stream && debug) + stream->println(F("SDBlock: status not changed, not writing")); + return true; + } + metadata->status = status_in; + crcSet(); + response = write(address_in); + if (!response) { + response = write(address_in); + if (!response) { + if (stream) + stream->println(F("# SDBlock: DISASTER: Can't write metadata")); + return false; + } + } + return true; +} + +bool SDBlock::metadataRead(uint32_t fromEnd) { + if (stream && debug) { + stream->print(F("# SDBlock: metadataRead - fromEnd=")); + stream->print(fromEnd); + stream->println(); + stream->print(F("# SDBlock: metadataRead - metadataBlockNext=")); + stream->print(metadataBlockNext); + stream->println(); + stream->print(F("# SDBlock: metadataBlockNext - fromEnd - 1=")); + stream->print(metadataBlockNext - fromEnd - 1); + stream->println(); + } + return metadataReadBLK(metadataBlockNext - fromEnd - 1); +} + +bool SDBlock::metadataReadBLK(uint32_t address_in) { + if (stream && debug) { + stream->print(F("# SDBlock: metadataReadBLK - block/start ")); + stream->print(address_in); + stream->print("/"); + stream->print(SDBLOCK_ACTUAL_METADATA); + stream->println(); + } + if (!cardIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was never open. Nothing to do")); + return false; + } + + if (address_in >= SDBLOCK_ACTUAL_METADATA) { + return read(address_in); + // XXX crc check, record type check + } + return false; +} + +bool SDBlock::resultsRead(uint32_t fromEnd) { + return resultsReadBLK(dataBlockNext - fromEnd - 1); +} + +bool SDBlock::resultsReadBLK(uint32_t address_in) { + if (!cardIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was never open. Nothing to do")); + return false; + } + + if (address_in >= SDBLOCK_ACTUAL_DATA) { + return read(address_in); + // XXX crc check, record type check + } + return false; +} + +/* + +bool SDBlock::metadataUpdate(uint32_t fromEnd) { + if (!metadataRead(fromEnd)) { + if (stream) + stream->println(F("# SDBlock: Failed to read metadata block during update")); + return false; + } + + // Allocation blocsk + + // Update and set the CRC + crcSet(); + + response = write(metadataBlockNext); + if (!response) { + response = write(metadataBlockNext); + if (!response) { + if (stream) + stream->println(F("# SDBlock: DISASTER: Can't write metadata")); + return false; + } + } + +*/ + + +bool SDBlock::singlePrepare(void* in, uint16_t size) { + memset(buffer, 0, sizeof(buffer)); + block->blockType = SDBLOCK_TYPE_SINGLE_FULL; + block->recordId = recordId; + if (size > 0) { + if (size > sizeof(block->data)) { + if (stream) + stream->println(F("# SDBlock: ERROR: Trying to set data beyond size")); + return false; + } + memcpy(block->data, in, size); + } + return true; +} + +uint32_t SDBlock::singleCount() { + return singleBlockNext - SDBLOCK_ACTUAL_SINGLE; +} + +bool SDBlock::singleRead(uint32_t fromEnd) { + if (!cardIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was never open. Nothing to do")); + return false; + } + + address_local = singleBlockNext - fromEnd - 1; + if (address_local >= SDBLOCK_ACTUAL_SINGLE) { + if (stream) { + stream->print("# SingleRead Address="); + stream->println(address_local); + } + return read(address_local); + // optional crc check, record type check + } + return false; +} + +bool SDBlock::singleWrite(void* in, uint16_t size) { + if (!cardIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was never open. Nothing to do")); + return false; + } + + if (size > 0) { + if (!singlePrepare(in, size)) { + if (stream) + stream->println(F("# SDBlock: Failed inline prepare")); + return false; + } + } + + // crcSet(); + response = write(singleBlockNext); + if (!response) { + response = write(singleBlockNext); + if (!response) { + if (stream) + stream->println(F("# SDBlock: DISASTER: Can't write single")); + return false; + } + } + + singleLast = singleBlockNext; + singleBlockNext++; + if (stream && debug) { + stream->print(F("# SDBlock: Single Write complete, nextblock=")); + stream->println(singleBlockNext); + } + return true; +} + +bool SDBlock::queuePrepare(uint32_t status, void* in, uint16_t in_size, void* out, uint16_t out_size) { + memset(buffer, 0, sizeof(buffer)); + block->blockType = SDBLOCK_TYPE_QUEUE_FULL; + block->recordId = recordId; + if (in_size > 0) { + if (in_size > sizeof(queue->dataIn)) { + if (stream) + stream->println(F("# SDBlock: ERROR: Trying to set data beyond size")); + } + else { + memcpy(queue->dataIn, in, in_size); + } + } + if (out_size > 0) { + if (out_size > sizeof(queue->dataOut)) { + if (stream) + stream->println(F("# SDBlock: ERROR: Trying to set data beyond size")); + } + else { + memcpy(queue->dataOut, out, out_size); + } + } + return true; +} + +uint32_t SDBlock::queueCount() { + return queueBlockNext - SDBLOCK_ACTUAL_SINGLE; +} + + bool queueUpdate(uint32_t fromEnd, uint32_t status, void* out = NULL, uint16_t out_size = 0); + bool queueRead(uint32_t fromEnd); + uint32_t queueCount(); + +bool SDBlock::queueRead(uint32_t fromEnd) { + if (!cardIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was never open. Nothing to do")); + return false; + } + + address_local = queueBlockNext - fromEnd - 1; + if (address_local >= SDBLOCK_ACTUAL_SINGLE) { + if (stream) { + stream->print("SingleRead Address="); + stream->println(address_local); + } + return read(address_local); + // optional crc check, record type check + } + return false; +} + +bool SDBlock::queueWrite(uint32_t status, void* in, uint16_t in_size, void* out, uint16_t out_size) { + if (!cardIsOpen()) { + if (stream) + stream->println(F("# SDBlock: Data was never open. Nothing to do")); + return false; + } + + if (size > 0) { + if (!queuePrepare(in, size)) { + if (stream) + stream->println(F("# SDBlock: Failed inline prepare")); + return false; + } + } + + if (status > 0) + queue->status = status; + + // (slow) crcSet(); + response = write(queueBlockNext); + if (!response) { + response = write(queueBlockNext); + if (!response) { + if (stream) + stream->println(F("# SDBlock: DISASTER: Can't write queue")); + return false; + } + } + + queueLast = queueBlockNext; + queueBlockNext++; + if (stream && debug) { + stream->print(F("# SDBlock: Single Write complete, nextblock=")); + stream->println(queueBlockNext); + } + return true; +} + +SDBlock sdBlock; diff --git a/lib/SDBlock/SDBlock.h b/lib/SDBlock/SDBlock.h new file mode 100644 index 0000000..07fecf5 --- /dev/null +++ b/lib/SDBlock/SDBlock.h @@ -0,0 +1,259 @@ +#ifndef SDBlock_h +#define SDBlock_h + +// #define USE_SDCORE +#define USE_SD2CARD +// #define USE_SDFS + +#ifdef USE_SDCORE +#include +#endif + +#ifdef USE_SD2CARD +#include +#include +#endif + +#ifdef USE_SDFS +#include +// if (!sd.card()->readSector(0, (uint8_t*)&mbr)) { +#define SD_CS_PIN SS +#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI) +#endif + +#include +#include +#include "SDBlockData.h" + +// Define Callback with buffers +// typedef void (*CallbackFunction) (uint32_t recId, uint32_t blockId, uint32_t blockCount, void *buf, uint16_t size); + +#define ERASE_SIZE 262144L + +class SDBlock { + public: + void begin(uint32_t dId); + void end(); + + bool debug; + Stream *stream; + + #ifdef USE_SD2CARD + Sd2Card sd; + #endif + + #ifdef USE_SDFS + // SdFat sd; + // SdExFat sd; + SdCardFactory cardFactory; + SdFs sd; + #endif + + uint32_t size; + + // Erase card etc + SDBlock_Allocation* allocation; + SDBlock_Metadata* metadata; + SDBlock_LogLines* loglines; + SDBlock_Queue* queue; + SDBlock_Block* block; + + // Thew actual Buffer + byte buffer[SDBLOCK_SECTOR_SIZE] = {0x00}; + + bool getMBR(); + + bool _sdinit(); + bool cardIsOpen(); + bool cardErase(); + bool cardOpen(uint32_t cId, bool format = false); + bool cardClose(); + void cardForceClose(); + void checkInternals(); + + bool read(unsigned long address_in); + bool write(unsigned long address_in); + + bool allocationDefault(); + bool allocationUpdate(); + bool allocationWrite(); + bool allocationValid(); + bool allocationRead(); + + void crcUpdate(); + bool crcCheck(); + void crcSet(); + + bool validRange(char *str, uint32_t test, uint32_t start, uint32_t max); + + bool dataIsOpen(); + bool dataOpen(uint32_t recId); + bool dataClose(bool output); + bool dataPrepare(void* in = NULL, uint16_t size = 0); + bool dataWrite(void* in = NULL, uint16_t size = 0); + + bool metadataPrepare(void* in = NULL, uint16_t size = 0); + bool metadataWrite(void* in = NULL, uint16_t size = 0); + bool metadataRead(uint32_t fromEnd); + bool metadataReadBLK(uint32_t address_in); + bool metadataUpdateStatus(uint32_t address_in, uint32_t record_in, uint32_t status_in); + void metadataList(uint32_t fromEnd, uint32_t num, Print* altstream = NULL); //, CallbackFunction cb = NULL); + + bool resultsRead(uint32_t adddress); + bool resultsReadBLK(uint32_t adddress); + + // Status Updats, note: BLK - means actual block (where ever it is) + /* + bool metadataStatusBLK(uint32_t block, uint32_t status); + bool dataStatusBLK(uint32_t block, uint32_t status); + */ + + uint32_t metadataCount(); + + // void dataList(unsigned long address, Print* altstream = NULL, CallbackFunction metadata_cb = NULL, CallbackFunction data_cb = NULL); + + bool singlePrepare(void* in = NULL, uint16_t size = 0); + bool singleWrite(void* in = NULL, uint16_t size = 0); + bool singleRead(uint32_t fromEnd); + uint32_t singleCount(); + + void setDeviceId(uint32_t dId); + void setRecordId(uint32_t rId); + + void setAutoFormat(bool in); + bool getAutoFormat(); + + // Very basic queue + bool queuePrepare(uint32_t status, void* in = NULL, uint16_t in_size = 0, void* out = NULL, uint16_t out_size = 0); + bool queueWrite(uint32_t status = 0, void* in = NULL, uint16_t in_size = 0, void* out = NULL, uint16_t out_size = 0); + + // Update queue - fromEnd - like Single - include status cahnge, and optional data out + bool queueUpdate(uint32_t fromEnd, uint32_t status, void* out = NULL, uint16_t out_size = 0); + bool queueRead(uint32_t fromEnd); + uint32_t queueCount(); + + uint32_t getDataNext(); // Get the next block for manual reading + uint32_t getMetadataNext(); // Get the next block for manual reading + uint8_t getCardId(); // Get the next block for manual reading + + uint32_t getMetadataFirst(); // Get the next block for manual reading + uint32_t getDataFirst(); // Get the next block for manual reading + /* + + Logging: + + Logging: + - Can be implemented completely outside of here. + - And just use single or even a pecial set of singles. + + Basics: + - Balance memory with Blocks - a full 512 byte block in + memory is probably too much for logging. + - logAdd(level, string); + . Return something.... to say buffer full, need to writeBlock + + - logAuto(); // Automatiaclly prepare, write, etc. + // Maybe pass param true, says write even if + // only one log in the queue + + - logPrepare(); + - logWrite(); + + */ + + // Queue. - standard queue commands, work like Single + /* + + More advanced Queue Reader + + - Next Low|High, Type + e.g. High Iridium Send + then low Iridium Send + Iridium receive + Slow to find? + Status can be used for all the lookups + - 32 bit, + 8 bit type + 8 bit status + 16 bit info (e.g. why it failed, or number of blocsk) + + Size vs Data + + WII4 Data + * 2 blocks (two transmits) + * 299 bytes - 6 blocks + * 336 bytes - 7 blocsk + * AKA - two blocks on the SD Card + So we do have to use a whole SD Card block + + Early optomisation is death + * Use CaptureMode code or Iridium code to manage. + + * Simplified walking + queueRead(rec); + + if (status - in range - ie. not sent, right type) + sendIridium + recordResults back in records + queueUpdate(rec, X,Y,Z) + + rec++; + + if (rec > 10000) { + // That will do. exit + } + + queueNextHigh(X) + + queuePrepare + queueWrite + queueRead - like single, from end + queueReadHigh - Read most resent high + queueReadLow - Read most resent low + queueUpdate(id, stuff - eg status) + */ + + + uint32_t dataLast; + uint32_t metadataLast; + uint32_t queueLast; + uint32_t singleLast; + + + protected: + byte response; // Resuable for all read/write + uint32_t crc; + uint32_t address_local; // Temporary location of calculated addresses + uint32_t count; // Reusable counter (for loops) + + bool autoFormat; // Automatically format on failure + + // Allocation tabler + uint32_t dataBlockNext; // Next, becasue first record is 0, so initilaly we write there + uint32_t metadataBlockNext; // And of course where is the Metadata + uint32_t singleBlockNext; // And of course where is the Metadata + uint32_t loglinesBlockNext; // And of course where is the Metadata + uint32_t queueBlockNext; // And of course where is the Metadata + + // Card read, open and valid + bool valid; + + // Current DATA + uint32_t dataBlockStart; + uint32_t dataBlockCurrent; + uint32_t dataBlockRecordId; + + uint32_t metadata_dataBlockStart; + uint32_t metadata_dataBlocks; + + uint32_t metadata_resultsBlockStart; + uint32_t metadata_resultsBlocks; + + uint32_t cardId; + uint32_t deviceId; + uint32_t recordId; +}; + +extern SDBlock sdBlock; + +#endif diff --git a/lib/SDBlock/SDBlockData.h b/lib/SDBlock/SDBlockData.h new file mode 100644 index 0000000..c5a1d60 --- /dev/null +++ b/lib/SDBlock/SDBlockData.h @@ -0,0 +1,214 @@ +#ifndef SDBlockData_h +#define SDBlockData_h + +/* + +Misc notes: + + * Structure of all blocks: + * Block type + * Free form set of fields + * Free block to be used + * Status field - re-writable + * CRC32 - TODO, consider leaving status out of the CRC + (although only for time, as the block has to be in memory to change the status) + +Sizes: + * 32 Bit blocks allows for up to 2000 GB of Storage + * Where as 32 Bit bytes (when moving an address) allows only 4GB + +TODO: + + * Status based searcher - like that being considered for Queues + * Find me next with Status X or bitmap or < or > + * But.... keep where we were up to to, so we don't have to serarh/load all them blocks again + * Consider this only for memory for now. + +Notes on WII5 + + + One Run = 40960 records. 15 Per Sector + 2730 data packets + + Total packets = 62500000 + / runs = 22893 + + 4 per hour, 24 hours per day + 238 days + + 2 Cards - actually = 476 + + So make it 150 days + 14400 records + + Data Size = 14400 * 2730 = 39312000 = 62% + + Therefore Metadata minimum 14400, up to 20000 + + Queue - could have up to 8 messages per metadata, so 20000*8 = 160000 + + Number Start End Size Type File system Flags + 1 2048s 1251327s 1249280s primary fat32 lba + + + sudo dd if=/dev/mmcblk2 of=block_fat1.img bs=512 count=1251328 + + +*/ + +#define SDBLOCK_SECTOR_SIZE 512 + +// XXX Add super block information +// Bytes: +// 1 = Fixed = SDBlock, always 10101111 +// 2 = Version of SDBlock, 00000001 +// 3 = Block type & Bersion 1, 2, 3,4 + 1 +#define SDBLOCK_TYPE_SDBLOCK 101 +#define SDBLOCK_TYPE_SDBLOCK_VERSION 1 +#define SDBLOCK_TYPE_ALLOCATION 1 +#define SDBLOCK_TYPE_ALLOCATION_VERSION 1 +#define SDBLOCK_TYPE_ALLOCATION_FULL ((SDBLOCK_TYPE_SDBLOCK << 24) | (SDBLOCK_TYPE_SDBLOCK_VERSION << 16) | (SDBLOCK_TYPE_ALLOCATION << 8) | SDBLOCK_TYPE_ALLOCATION_VERSION) +#define SDBLOCK_TYPE_METADATA 2 +#define SDBLOCK_TYPE_METADATA_VERSION 1 +#define SDBLOCK_TYPE_METADATA_FULL ((SDBLOCK_TYPE_SDBLOCK << 24) | (SDBLOCK_TYPE_SDBLOCK_VERSION << 16) | (SDBLOCK_TYPE_METADATA << 8) | SDBLOCK_TYPE_METADATA_VERSION) +#define SDBLOCK_TYPE_LOGLINES 3 +#define SDBLOCK_TYPE_LOGLINES_VERSION 1 +#define SDBLOCK_TYPE_LOGLINES_FULL ((SDBLOCK_TYPE_SDBLOCK << 24) | (SDBLOCK_TYPE_SDBLOCK_VERSION << 16) | (SDBLOCK_TYPE_LOGLINES << 8) | SDBLOCK_TYPE_LOGLINES_VERSION) +#define SDBLOCK_TYPE_SINGLE 4 +#define SDBLOCK_TYPE_SINGLE_VERSION 1 +#define SDBLOCK_TYPE_SINGLE_FULL ((SDBLOCK_TYPE_SDBLOCK << 24) | (SDBLOCK_TYPE_SDBLOCK_VERSION << 16) | (SDBLOCK_TYPE_SINGLE << 8) | SDBLOCK_TYPE_SINGLE_VERSION) +#define SDBLOCK_TYPE_BLOCK 5 +#define SDBLOCK_TYPE_BLOCK_VERSION 1 +#define SDBLOCK_TYPE_BLOCK_FULL ((SDBLOCK_TYPE_SDBLOCK << 24) | (SDBLOCK_TYPE_SDBLOCK_VERSION << 16) | (SDBLOCK_TYPE_BLOCK << 8) | SDBLOCK_TYPE_BLOCK_VERSION) +#define SDBLOCK_TYPE_QUEUE 5 +#define SDBLOCK_TYPE_QUEUE_VERSION 1 +#define SDBLOCK_TYPE_QUEUE_FULL ((SDBLOCK_TYPE_SDBLOCK << 24) | (SDBLOCK_TYPE_SDBLOCK_VERSION << 16) | (SDBLOCK_TYPE_QUEUE << 8) | SDBLOCK_TYPE_QUEUE_VERSION) + + +// Special - Block Offset ! Where does this start +#define SDBLOCK_OFFSET (uint32_t)1300000 // Absolute position start of allocation table +#define SDBLOCK_MAX (uint32_t)62500000 +#define SDBLOCK_ALLOCATION_MAX (uint32_t)2 // How many we can have +#define SDBLOCK_METADATA_MAX (uint32_t)20000 // How many we can have +#define SDBLOCK_SINGLE_MAX (uint32_t)20000 // How many we can have +#define SDBLOCK_LOGLINES_MAX (uint32_t)10000 // How many we can have +#define SDBLOCK_QUEUE_MAX (uint32_t)300000 // How many we can have +#define SDBLOCK_DATA_MAX (uint32_t)45000000 // How many we can have + +#define SDBLOCK_ACTUAL_ALLOCATION (uint32_t)SDBLOCK_OFFSET +#define SDBLOCK_ACTUAL_METADATA (uint32_t)(SDBLOCK_OFFSET + SDBLOCK_ALLOCATION_MAX) +#define SDBLOCK_ACTUAL_SINGLE (uint32_t)(SDBLOCK_OFFSET + SDBLOCK_ALLOCATION_MAX + SDBLOCK_METADATA_MAX) +#define SDBLOCK_ACTUAL_LOGLINES (uint32_t)(SDBLOCK_OFFSET + SDBLOCK_ALLOCATION_MAX + SDBLOCK_METADATA_MAX + SDBLOCK_SINGLE_MAX) +#define SDBLOCK_ACTUAL_QUEUE (uint32_t)(SDBLOCK_OFFSET + SDBLOCK_ALLOCATION_MAX + SDBLOCK_METADATA_MAX + SDBLOCK_SINGLE_MAX + SDBLOCK_LOGLINES_MAX) +#define SDBLOCK_ACTUAL_DATA (uint32_t)(SDBLOCK_OFFSET + SDBLOCK_ALLOCATION_MAX + SDBLOCK_METADATA_MAX + SDBLOCK_SINGLE_MAX + SDBLOCK_LOGLINES_MAX + SDBLOCK_QUEUE_MAX) + +// ALLOCATION Block +#define SDBLOCK_ALLOCATION_DATA_SIZE 460 +typedef struct { + // 12 + uint32_t blockType; + uint32_t deviceId; // Device and Record + uint32_t recordId; // In this case, it will be the last actually used one + uint32_t cardId; // In this case, it will be the last actually used one + + // 16 + time_t currentTime; // Last updated? + + // 16 + uint32_t metadataBlockNext; // And of course where is the Metadata + uint32_t singleBlockNext; // And of course where is the Metadata + uint32_t loglinesBlockNext; // And of course where is the Metadata + uint32_t dataBlockNext; // Next, becasue first record is 0, so initilaly we write there + + uint32_t queueBlockNext; // Next, becasue first record is 0, so initilaly we write there + uint32_t queueHighDone; // What queue high have we done, read from here + uint32_t queueLowDone; // What queue low have we done, read from here + + uint8_t data[SDBLOCK_ALLOCATION_DATA_SIZE]; // Calcu;ate + + uint32_t crc; +} SDBlock_Allocation; + +// METADATA Block +#define SDBLOCK_METADATA_DATA_SIZE 476 +typedef struct { + // 12 + uint32_t blockType; + uint32_t deviceId; // Device and Record + uint32_t recordId; // OR recordCount + + // 8 + uint32_t dataBlockStart; // DATA: Where does it start + uint32_t dataBlocks; // DATA: Number of blocks, 0 being none at all + + // Keep these variables outside of this structure - see wii5Metadata + char data[SDBLOCK_METADATA_DATA_SIZE]; + + // 8 + uint32_t resultsBlockStart; // RESULTS: Where does it start + uint32_t resultsBlocks; // RESULTS: Number of blocks, 0 being none at all + + // 8 + uint32_t status; // WII5 example - 0=NoResults, 1=Results, 4=Results+Sent, 5= + /* + + uint8_t + . ? + . ? + . > 0 = Number of attempts sending - 1111 = Success + . > 0 = Data saved + */ + + uint32_t crc; +} SDBlock_Metadata; + +#define SDBLOCK_LOG_DATA_SIZE 42 +#define SDBLOCK_LOG_DATA_LINES 10 + +// LogLine +typedef struct { + time_t logTime; + // NOTE: Most of these integers, normally 1 byte, are set to 4 bytes for packing. + uint32_t logLevel; + char data[SDBLOCK_LOG_DATA_SIZE]; +} SDBlock_LogLine; + +// LOG Block +typedef struct { + uint32_t blockType; + uint32_t recordId; // OR recordCount + + SDBlock_LogLine lines[SDBLOCK_LOG_DATA_LINES]; + + uint32_t crc; +} SDBlock_LogLines; + +#define SDBLOCK_QUEUE_DATAIN_SIZE 400 +#define SDBLOCK_QUEUE_DATAOUT_SIZE 92 +// Queue +typedef struct { + uint32_t blockType; // 4 + // deviceId, recordId ? + + time_t currentTime; + uint32_t priority; + char dataIn[SDBLOCK_QUEUE_DATAIN_SIZE]; + char dataOut[SDBLOCK_QUEUE_DATAOUT_SIZE]; + uint32_t status; + uint32_t crc; +} SDBlock_Queue; + +// BLOCK - Let us make them all the same ! +#define SDBLOCK_BLOCK_DATA_SIZE 496 +typedef struct { + uint32_t blockType; // 4 + // Note: deviceId etc can all be taken from Metadata. + uint32_t recordId; // Important to group these together. + + uint8_t data[SDBLOCK_BLOCK_DATA_SIZE]; // All the data in the middle + + uint32_t status; + uint32_t crc; +} SDBlock_Block; + +#endif diff --git a/pio_main.cpp b/pio_main.cpp new file mode 100644 index 0000000..6560def --- /dev/null +++ b/pio_main.cpp @@ -0,0 +1,23 @@ +// 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 pio_main.cpp + * @brief PlatformIO entry shim. + * + * PlatformIO only auto-detects a sketch (`*.ino`) at the top level of its + * source directory, but this project's sketch lives at + * `app/wii5_buoy/wii5_buoy.ino`. This shim pulls that sketch into the + * PlatformIO build (where `setup()`/`loop()` are defined). + * + * It is gated on the `PLATFORMIO` macro (defined only by PlatformIO) so that + * the arduino-cli build — which compiles the repo root as a flat library and + * the .ino as the sketch — never sees a second definition of setup()/loop(). + */ + +#if defined(PLATFORMIO) +#include "app/wii5_buoy/wii5_buoy.ino" +#endif diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 0000000..e2b39a8 --- /dev/null +++ b/platformio.ini @@ -0,0 +1,82 @@ +; SPDX-License-Identifier: Apache-2.0 +; Copyright (c) 2012-2024 Scott Penrose and WII5 Buoy contributors +; +; PlatformIO build configuration for the WII5 Buoy firmware. +; This sits alongside the existing arduino-cli flow (tools/build_local.sh); +; both can build the same sources, which stay in place at the repo root. +; +; Usage: +; pio run -e wii5_buoy # compile +; pio run -e wii5_buoy -t upload # compile + flash +; pio device monitor -e wii5_buoy # serial console @ 230400 +; +; See platformio.org for the full reference. + +[platformio] +; Sources live at the repo root, not in a src/ dir. Compile them in place. +src_dir = . + +[env:wii5_buoy] +platform = atmelavr +framework = arduino + +; Custom board: ATmega2560, 3V3, 11.0592 MHz crystal, serial bootloader @ 230400. +; Definition lives in boards/wii5_v2_2560.json (auto-discovered). +board = wii5_v2_2560 + +; Upload over the Arduino serial bootloader (avrdude -c arduino). +upload_protocol = arduino +upload_speed = 230400 +; upload_port = /dev/ttyUSB0 ; uncomment / override, or rely on auto-detect +monitor_speed = 230400 + +; Compile the firmware sources at the repo root. The "*.cpp" glob is +; non-recursive, so it matches only the root-level WII5*.cpp plus pio_main.cpp +; (the entry shim) and naturally excludes app/, test/, tools/ and doc/. +; The entry sketch (app/wii5_buoy/wii5_buoy.ino) is pulled in by pio_main.cpp. +build_src_filter = + +<*.cpp> + +; The project's own headers are included with angle brackets (, ...), +; so the repo root must be on the include path. Software version/commit are +; normally injected by tools/build_local.sh; default them here. +build_flags = + -I. + ; Board-variant macros that the arduino-cli custom board (WII:avr) injected: + -D__WII5_V02__ ; selects WII5_board_v2.h in WII5_board.h + -DF_CPU_11 ; 11.0592 MHz timing -> 230400 console / 115200 IMU baud + ; Older Arduino AVR cores exposed NUM_PINS; current cores use NUM_DIGITAL_PINS. + -DNUM_PINS=NUM_DIGITAL_PINS + -DWII5_SOFTWARE_VERSION=\"WII5Buoy_dev\" + -DWII5_SOFTWARE_COMMIT=\"dev\" + +; Public libraries pulled from the PlatformIO registry. Scott's own libraries +; (AvgStd, PushButton, MemoryFree, SDBlock) are vendored under lib/ instead. +lib_deps = + paulstoffregen/Time + mikalhart/TinyGPSPlus + milesburton/DallasTemperature + paulstoffregen/OneWire + pfeerick/elapsedMillis + rocketscream/Low-Power + sparkfun/IridiumSBDi2c + thijse/EEPROMEx + bakercp/CRC32 + arduino-libraries/SD + adafruit/RTClib + +; LoRa-enabled variant (experimental). This path additionally depends on the +; Sh3dNode networking stack (sh3dNodeNetwork / SH3D_NODE_Packet_* / RH_RF95), +; which is NOT bundled in this repository, so the env does not build as-is. +; RadioHead is also GPLv2 (dual-licensed); binaries built from it inherit GPLv2 +; terms. Enable once the Sh3dNode library is available, e.g.: +; +; [env:wii5_buoy_lora] +; extends = env:wii5_buoy +; build_flags = +; ${env:wii5_buoy.build_flags} +; -DWII5_RADIO_LORA +; lib_deps = +; ${env:wii5_buoy.lib_deps} +; mikem/RadioHead +;