diff --git a/include/bluetooth.h b/include/bluetooth.h index 042f607..c74c0f0 100644 --- a/include/bluetooth.h +++ b/include/bluetooth.h @@ -4,9 +4,6 @@ void bluetoothConfigure(); -bool bluetoothIsConnected(); +void bluetoothStop(); -// In main.cpp -uint32_t secondsUntilNextTemperatureMeasurement(); - -uint8_t getWakeupCause(); \ No newline at end of file +bool bluetoothIsConnected(); \ No newline at end of file diff --git a/include/eepromInterface.h b/include/eepromInterface.h new file mode 100644 index 0000000..34378fa --- /dev/null +++ b/include/eepromInterface.h @@ -0,0 +1,90 @@ +#include +#include + +#pragma pack(push, 1) +struct DeviceInfo { + + // The index where the measurement data starts in the eeprom storage + uint16_t startIndex; + + // The time when the device was powered on (seconds since 1970) + uint32_t deviceStartTime; +}; + +struct Measurement { + + uint16_t measurementIntervalsSinceDeviceStart; + + uint8_t temperature0; + + uint8_t temperature1; +}; +#pragma pack(pop) + +constexpr size_t eepromTotalSize = 13350; +constexpr uint16_t maxiumDeviceInfoCount = 10; +constexpr uint16_t dataStorageStartIndex = sizeof(DeviceInfo) * maxiumDeviceInfoCount; +constexpr uint16_t sizeOfMeasurement = sizeof(Measurement); + +class EEPROMInterface { + +public: + + bool isInitialized; + + /** + * @brief Initialize the storage + * + */ + EEPROMInterface(); + + /** + * @brief + * + */ + uint8_t startNewDeviceSession(); + + /** + * @brief Remove all stored data + * + * @return true The data was deleted + * @return false There was an error deleting the data + */ + bool clearAllData(); + + bool writeData(uint8_t* data, uint16_t count); + + /** + * @brief Free space in the buffer + * + * @param buffer + * @param count + * @return uint16_t The number + */ + uint16_t readData(uint8_t* buffer, uint16_t count); + + uint16_t getStoredByteCount(); + +private: + + /** + * @brief The index into the buffer where the next byte should be written + * + */ + uint16_t writeIndex; + + /** + * @brief The index into the buffer where the first byte is stored + * + */ + uint16_t readIndex; + + /** + * @brief Find the write index, the first zero byte after a non-zero byte + * + * @return uint16_t The write index + */ + uint16_t findEndIndex(); + + uint16_t findStartIndex(); +}; \ No newline at end of file diff --git a/include/main.h b/include/main.h new file mode 100644 index 0000000..b688994 --- /dev/null +++ b/include/main.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +uint32_t secondsUntilNextTemperatureMeasurement(); +uint8_t getWakeupCause(); +uint32_t getUniqueID(); diff --git a/include/storage.h b/include/storage.h index a7f4c17..7d9b149 100644 --- a/include/storage.h +++ b/include/storage.h @@ -6,33 +6,17 @@ constexpr uint8_t temperatureSensorCount = 2; -constexpr size_t storageSize = 80000; -constexpr uint16_t storageIntervalInSeconds = 15; -constexpr size_t maximumStorageDurationInHours = (storageSize / TEMPERATURE_SENSOR_MAX_COUNT) * storageIntervalInSeconds / 3600; - // Max size: 7664 -constexpr size_t maxRtcStorageSize = 7664; -constexpr size_t rtcStorageSize = 7550; +constexpr size_t rtcStorageSize = 7580; constexpr size_t maxEepromSize = 13350; -constexpr size_t eepromSize = 13350; - -constexpr size_t totalStorageSize = rtcStorageSize + eepromSize; // The minimum temperature to store, in millidegrees celcius // True minimum will be higher by 1°, since two values are reserved constexpr long temperatureShiftForStorage = -40000; constexpr long maximumTemperature = temperatureShiftForStorage + 255 * 500; - constexpr uint8_t temperatureMaximumValue = 255; -enum class StorageFlags: uint8_t { - HasInitializedEEPROM = 1, - FailedToInitEEPROM = 2, - FailedToWriteEEPROM = 4, - RTCStorageOverflow = 5, -}; - void storageConfigure(bool isFirstRun); /** @@ -51,12 +35,14 @@ uint16_t getNumberOfMeasurements(); uint32_t getTotalNumberOfMeasurements(); +uint32_t getStartTimeOfCurrentRecording(); + uint8_t getLastTemperature(uint8_t sensorIndex); uint16_t getTimeSinceValidTemperature(uint8_t sensorIndex); uint16_t getRecordedBytesAtOffset(uint8_t* buffer, uint16_t offset, uint16_t count); -void discardAllRecordedBytes(); +uint16_t getDataChecksum(); -uint8_t getStorageErrorFlags(); \ No newline at end of file +void discardAllRecordedBytes(); diff --git a/src/bluetooth.cpp b/src/bluetooth.cpp index 848e88b..c805e6e 100644 --- a/src/bluetooth.cpp +++ b/src/bluetooth.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -10,6 +11,7 @@ #include "storage.h" #include "config.h" #include "temperature.h" +#include "main.h" constexpr size_t bluetoothMaxDataSize = 200; @@ -29,8 +31,6 @@ uint8_t* bluetoothDataBuffer; uint8_t* bluetoothResponse; size_t bluetoothDataCount = 0; -RTC_DATA_ATTR uint32_t deviceStartTime = 0; - void bluetoothStartAdvertising(); void bluetoothDidReceiveData(uint8_t* buffer, uint16_t count); @@ -87,7 +87,6 @@ void bluetoothConfigure() { BLEDevice::init(deviceName); server = BLEDevice::createServer(); server->setCallbacks(&bluetooth); - // Register message service that can receive messages and reply with a static message. BLEService *service = server->createService(serviceUUID); uint32_t properties = BLECharacteristic::PROPERTY_READ | BLECharacteristic::PROPERTY_NOTIFY | BLECharacteristic::PROPERTY_WRITE; @@ -114,6 +113,11 @@ void bluetoothConfigure() { bluetoothStartAdvertising(); } +void bluetoothStop() { + BLEDevice::deinit(); + esp_bt_controller_disable(); +} + void bluetoothStartAdvertising() { // Advertise services BLEAdvertising *advertisement = server->getAdvertising(); @@ -171,17 +175,6 @@ enum class BluetoothRequest: uint8_t { * This may happen when a new temperature recording is performed in between calls */ clearRecordingBuffer = 2, - - /** - * @brief Set the absolute start time of the device - * - * Request: - * - Bytes 1-4: Number of seconds since 1970 (uint32_t) - * - * Response: - * - BluetoothResponse::success - */ - setDeviceStartTime = 3, }; enum class BluetoothResponse: uint8_t { @@ -237,6 +230,11 @@ void fillInfo() { uint16_t value; uint32_t value32; + // The unique ID generated on power-on + value32 = getUniqueID(); + memcpy(bluetoothDataBuffer + bluetoothDataCount, &value32, sizeof(uint32_t)); + bluetoothDataCount += sizeof(uint32_t); + // the number of bytes as a uint16_t (2 bytes) value = getTotalNumberOfStoredBytes(); memcpy(bluetoothDataBuffer, &value, sizeof(uint16_t)); @@ -268,7 +266,7 @@ void fillInfo() { bluetoothDataCount += sizeof(uint16_t); // Total storage size of device - value = totalStorageSize; + value = rtcStorageSize; memcpy(bluetoothDataBuffer + bluetoothDataCount, &value, sizeof(uint16_t)); bluetoothDataCount += sizeof(uint16_t); @@ -277,10 +275,20 @@ void fillInfo() { memcpy(bluetoothDataBuffer + bluetoothDataCount, &value32, sizeof(uint32_t)); bluetoothDataCount += sizeof(uint32_t); - // Configured device start time - memcpy(bluetoothDataBuffer + bluetoothDataCount, &deviceStartTime, sizeof(uint32_t)); + // Start time of current recording + value32 = getStartTimeOfCurrentRecording(); + memcpy(bluetoothDataBuffer + bluetoothDataCount, &value32, sizeof(uint32_t)); bluetoothDataCount += sizeof(uint32_t); + // Checksum for all data + value = getDataChecksum(); + memcpy(bluetoothDataBuffer + bluetoothDataCount, &value, sizeof(uint16_t)); + bluetoothDataCount += sizeof(uint16_t); + + // The wakeup cause for the current run + bluetoothDataBuffer[bluetoothDataCount] = getWakeupCause(); + bluetoothDataCount += sizeof(uint8_t); + // Sensor 0 // Temperature sensor addresses @@ -309,9 +317,6 @@ void fillInfo() { memcpy(bluetoothDataBuffer + bluetoothDataCount, &value, sizeof(uint16_t)); bluetoothDataCount += sizeof(uint16_t); - bluetoothDataBuffer[bluetoothDataCount] = getWakeupCause(); - bluetoothDataCount += sizeof(uint8_t); - setResponse(BluetoothResponse::success); } @@ -350,17 +355,6 @@ void clearRecordingBuffer(uint8_t* buffer, uint16_t count) { setResponseWithoutData(BluetoothResponse::success); } -void setDeviceStartTime(uint8_t* buffer, uint16_t count) { - if (count != sizeof(uint32_t)) { - setResponseWithoutData(BluetoothResponse::invalidCommand); - return; - } - memcpy(&deviceStartTime, buffer, sizeof(uint32_t)); - Serial.print("Device start time: "); - Serial.println(deviceStartTime); - setResponseWithoutData(BluetoothResponse::success); -} - void bluetoothDidReceiveData(uint8_t* buffer, uint16_t count) { if (count < 1) { setResponseWithoutData(BluetoothResponse::invalidCommand); @@ -384,10 +378,6 @@ void bluetoothDidReceiveData(uint8_t* buffer, uint16_t count) { getRecordingData(payload, payloadSize); break; - case BluetoothRequest::setDeviceStartTime: - setDeviceStartTime(payload, payloadSize); - break; - default: setResponseWithoutData(BluetoothResponse::unknownCommand); break; diff --git a/src/eepromInterface.cpp b/src/eepromInterface.cpp new file mode 100644 index 0000000..911113c --- /dev/null +++ b/src/eepromInterface.cpp @@ -0,0 +1,6 @@ +#include "eepromInterface.h" +#include + +EEPROMInterface::EEPROMInterface() { + isInitialized = EEPROM.begin(eepromTotalSize); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 1f2af2a..7dbeba9 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include #include +#include "main.h" #include "config.h" #include "bluetooth.h" #include "storage.h" @@ -16,6 +17,9 @@ RTC_DATA_ATTR uint32_t nextTimeToMeasureTemperatureSeconds = 0; RTC_DATA_ATTR bool isFirstStart = true; +// The unique ID generated whenever the device is powered on +RTC_DATA_ATTR uint32_t uniqueID; + // Indicate the time until the device should stay awake // Updated when button is pressed uint32_t sleepStartAfterButtonPressOrConnection = 0; @@ -26,6 +30,10 @@ uint8_t getWakeupCause() { return wakeupCauseByte; } +uint32_t getUniqueID() { + return uniqueID; +} + /** * @brief Check if enough time has passed for the next temperature measurement * @@ -54,7 +62,8 @@ uint32_t secondsUntilNextTemperatureMeasurement() { if (currentTime >= nextTimeToMeasureTemperatureSeconds) { return 0; } - return nextTimeToMeasureTemperatureSeconds - currentTime; + uint32_t seconds = nextTimeToMeasureTemperatureSeconds - currentTime; + return min(seconds, temperatureMeasurementIntervalSeconds); } /** @@ -74,6 +83,7 @@ void deepSleepUntilNextTemperatureMeasurement() { Serial.print("Sleeping for "); Serial.print(seconds); Serial.println(" seconds"); + bluetoothStop(); esp_deep_sleep(seconds * 1000000); } @@ -125,7 +135,12 @@ void enableLED() { void setup() { // No need for serial output in production - Serial.begin(serialBaudRate); + Serial.begin(serialBaudRate); + + if (isFirstStart) { + // Generate unique ID on device start + uniqueID = random(); + } // LED useless inside case // enableLED(); diff --git a/src/storage.cpp b/src/storage.cpp index f4dae77..5a9a2c9 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -3,63 +3,21 @@ #include #include -constexpr uint8_t absoluteTemperatureIndicator = 0xFF; - -RTC_DATA_ATTR uint8_t storageFlags = 0; - // Storage for temperature measurements in RTC memory that survives deep sleep RTC_DATA_ATTR uint8_t data[rtcStorageSize]; -RTC_DATA_ATTR uint16_t dataIndex = 0; -RTC_DATA_ATTR uint16_t numberOfMeasurements = 0; -RTC_DATA_ATTR uint32_t numberOfDiscardedMeasurements = 0; +RTC_DATA_ATTR uint16_t dataIndex = 0; // Index of next byte / total byte count +RTC_DATA_ATTR uint16_t rtcDataSum = 0; // Checksum for the data storage +RTC_DATA_ATTR uint16_t numberOfMeasurements = 0; // Current number of measurements (dataIndex / 4) +RTC_DATA_ATTR uint32_t numberOfDiscardedMeasurements = 0; // Previous measurements already deleted -// The index into EEPROM storage where the next data should be written -RTC_DATA_ATTR uint32_t eepromIndex = 0; -RTC_DATA_ATTR bool isFirstRunAfterPowerOn = true; - -constexpr uint16_t eepromOffset = sizeof(uint16_t); -constexpr uint16_t eepromDataSize = eepromSize - eepromOffset; +// The time (in seconds since device start) when the current recording started +RTC_DATA_ATTR uint32_t startTimeOfCurrentRecording = 0; // Keeps the last valid temperatures for each sensor RTC_DATA_ATTR Temperature lastTemperatures[temperatureSensorCount]; RTC_DATA_ATTR uint32_t lastValidTemperatureTime[temperatureSensorCount]; RTC_DATA_ATTR uint8_t lastMeasurements[temperatureSensorCount] = {0, 0}; -void setStorageFlag(StorageFlags flag) { - storageFlags |= static_cast(flag); -} - -void clearStorageFlag(StorageFlags flag) { - storageFlags &= ~static_cast(flag); -} - -bool hasStorageFlag(StorageFlags flag) { - return storageFlags & static_cast(flag); -} - -bool hasInitializedEEPROM() { - return hasStorageFlag(StorageFlags::HasInitializedEEPROM); -} - -void didInitializeEEPROM() { - setStorageFlag(StorageFlags::HasInitializedEEPROM); -} - -void setupEEPROMIfNeeded() { - if (hasInitializedEEPROM()) { - return; - } - bool success = EEPROM.begin(eepromSize); - if (!success) { - Serial.println("Failed to set up EEPROM"); - setStorageFlag(StorageFlags::FailedToInitEEPROM); - } - Serial.print("EEPROM size: "); - Serial.println(eepromSize); - clearStorageFlag(StorageFlags::FailedToInitEEPROM); - setStorageFlag(StorageFlags::HasInitializedEEPROM); -} - void resetLastMeasurements() { for (uint8_t index = 0; index < temperatureSensorCount; index += 1) { lastTemperatures[index].status == TemperatureStatus::sensorNotFound; @@ -74,41 +32,6 @@ void storageConfigure(bool isFirstRun) { // Ensure that first values are stored resetLastMeasurements(); } - // Initialize EEPROM on every wake - // setupEEPROMIfNeeded(); - //setStorageFlag(StorageFlags::HasInitializedEEPROM); -} - -void moveDataToEEPROMIfNeeded() { - if (dataIndex < rtcStorageSize) { - return; - } - - setupEEPROMIfNeeded(); - if (!hasInitializedEEPROM()) { - return; - } - - // Write until EEPROM is full - uint16_t bytesRemaining = eepromDataSize - eepromIndex; - uint16_t bytesToWrite = min(dataIndex, bytesRemaining); - EEPROM.writeBytes(eepromIndex, data, bytesToWrite); // TODO: Check that result == bytesToWrite - EEPROM.commit(); - - // Move remaining data to front of array (if any) - uint16_t bytesToKeep = dataIndex - bytesToWrite; - memmove(data, data + bytesToWrite, bytesToKeep); - dataIndex -= bytesToWrite; -} - -void saveByteAtCurrentIndex(uint8_t byte) { - if (dataIndex >= rtcStorageSize) { - setStorageFlag(StorageFlags::RTCStorageOverflow); - return; - } - data[dataIndex] = byte; - dataIndex += 1; - //moveDataToEEPROMIfNeeded(); } uint8_t byteForAbsoluteTemperature(Temperature* temp) { @@ -139,79 +62,40 @@ uint8_t byteForAbsoluteTemperature(Temperature* temp) { return converted; } -bool needsAbsoluteTemperatureRecording(Temperature* temperatures) { - if (numberOfMeasurements == 0) { - return true; - } - for (uint8_t index = 0; index < temperatureSensorCount; index += 1) { - if (temperatures[index].status != TemperatureStatus::temperatureIsValid) { - // Error value can be encoded as differential (0x00) - continue; - } - // Note: If previously only errors, then last value is 0. This also work for differential temperatures - long diff = (lastTemperatures[index].value - temperatures[index].value) / 500 + 8; - if (diff < 1 || diff > 15) { - return true; - } - } - return false; -} - void saveTemperatures(Temperature* temperatures) { - if (needsAbsoluteTemperatureRecording(temperatures)) { - // Write absolute temperatures - saveByteAtCurrentIndex(absoluteTemperatureIndicator); - for (uint8_t sensorIndex = 0; sensorIndex < temperatureSensorCount; sensorIndex += 1) { - Temperature* temp = &temperatures[sensorIndex]; - uint8_t byte = byteForAbsoluteTemperature(temp); - saveByteAtCurrentIndex(byte); - lastMeasurements[sensorIndex] = byte; - if (temp->status == TemperatureStatus::temperatureIsValid) { - // Only update if temperature is valid - lastTemperatures[sensorIndex] = *temp; - lastValidTemperatureTime[sensorIndex] = time(NULL); - } - } - } else { - // Calculate temperature differences - uint8_t valueToStore = 0; - for (uint8_t index = 0; index < temperatureSensorCount; index += 1) { - Temperature* temp = &temperatures[index]; - uint8_t diff = 0; // Indicate sensor error - if (temp->status == TemperatureStatus::temperatureIsValid) { - diff = (lastTemperatures[index].value - temp->value) / 500 + 8; - } - if (index % 2) { - // Store second bits - valueToStore |= diff; // Valid range already ensured - saveByteAtCurrentIndex(valueToStore); - valueToStore = 0; - } else { - // Store in first four bits - valueToStore = (diff << 4); - } - lastMeasurements[index] = byteForAbsoluteTemperature(temp); + if (numberOfMeasurements == 0) { + // Adjust start time if new measurement is begun + startTimeOfCurrentRecording = time(NULL); + } + if (dataIndex + 4 >= rtcStorageSize) { + // Don't add any more measurements if storage is full + return; + } + // First: Write timestamp + // Timestamp is number of measurement intervals since start of recording + uint16_t timestamp = (time(NULL) - startTimeOfCurrentRecording) / 60; + memcpy(data + dataIndex, ×tamp, sizeof(uint16_t)); + rtcDataSum += data[dataIndex] + data[dataIndex+1]; // Update checksum + dataIndex += sizeof(uint16_t); - if (temp->status == TemperatureStatus::temperatureIsValid) { - // Only update if temperature is valid - lastTemperatures[index].status = TemperatureStatus::temperatureIsValid; - lastTemperatures[index].value = temp->value; - lastValidTemperatureTime[index] = time(NULL); - } else if (lastTemperatures[index].status != TemperatureStatus::temperatureIsValid) { - lastTemperatures[index].value = 0; - lastValidTemperatureTime[index] = 0; - } - } - // Ensure storage with uneven number of sensors - if (temperatureSensorCount % 2) { - saveByteAtCurrentIndex(valueToStore); + for (uint8_t sensorIndex = 0; sensorIndex < temperatureSensorCount; sensorIndex += 1) { + Temperature* temp = &temperatures[sensorIndex]; + uint8_t byte = byteForAbsoluteTemperature(temp); + data[dataIndex] = byte; + dataIndex += 1; + rtcDataSum += byte; // Update checksum + lastMeasurements[sensorIndex] = byte; + if (temp->status == TemperatureStatus::temperatureIsValid) { + // Only update if temperature is valid + lastTemperatures[sensorIndex] = *temp; + lastValidTemperatureTime[sensorIndex] = time(NULL); } } numberOfMeasurements += 1; } uint16_t getTotalNumberOfStoredBytes() { - return dataIndex; // + eepromIndex; + return dataIndex; } uint16_t getNumberOfMeasurements() { @@ -222,48 +106,35 @@ uint32_t getTotalNumberOfMeasurements() { return numberOfMeasurements + numberOfDiscardedMeasurements; } +uint32_t getStartTimeOfCurrentRecording() { + return startTimeOfCurrentRecording; +} + uint8_t getLastTemperature(uint8_t sensorIndex) { return lastMeasurements[sensorIndex]; } uint16_t getTimeSinceValidTemperature(uint8_t sensorIndex) { - return time(NULL) - lastValidTemperatureTime[sensorIndex]; + return min(time(NULL) - lastValidTemperatureTime[sensorIndex], 65535ul); } uint16_t getRecordedBytesAtOffset(uint8_t* buffer, uint16_t offset, uint16_t count) { - // Copy remaining bytes from RTC memory + if (offset >= dataIndex) { + return 0; + } uint16_t remainingBytes = dataIndex - offset; uint16_t bytesToCopy = min(count, remainingBytes); - memcpy(buffer, data + offset, count); + memcpy(buffer, data + offset, bytesToCopy); return bytesToCopy; - /* - // TODO: Check limits - uint16_t eepromByteCount = eepromIndex; - uint16_t endIndex = offset + count; - uint16_t eepromStart = min(offset, eepromByteCount); - uint16_t eepromEnd = min(endIndex, eepromByteCount); - uint16_t bytesToCopyFromEEPROM = eepromEnd - eepromStart; - if (bytesToCopyFromEEPROM > 0) { - EEPROM.readBytes(eepromStart, buffer, bytesToCopyFromEEPROM); // TODO: Check bytes read - // Reduce offset and count to point to RTC memory - offset -= bytesToCopyFromEEPROM; - count -= bytesToCopyFromEEPROM; - } - offset -= eepromByteCount; - // Copy remaining bytes from RTC memory - uint16_t bytesToCopyFromRTC = min(count, dataIndex); - memcpy(buffer + bytesToCopyFromEEPROM, data + offset, bytesToCopyFromRTC); - return bytesToCopyFromEEPROM + bytesToCopyFromRTC; - */ +} + +uint16_t getDataChecksum() { + return rtcDataSum; } void discardAllRecordedBytes() { - eepromIndex = 0; dataIndex = 0; + rtcDataSum = 0; numberOfDiscardedMeasurements += numberOfMeasurements; numberOfMeasurements = 0; -} - -uint8_t getStorageErrorFlags() { - return storageFlags; } \ No newline at end of file