Simplified data storage without eeprom, include timestamps

This commit is contained in:
Christoph Hagen 2023-07-02 17:28:39 +02:00
parent 6bcc22c045
commit 7c2162a5bf
8 changed files with 198 additions and 236 deletions

View File

@ -4,9 +4,6 @@
void bluetoothConfigure();
bool bluetoothIsConnected();
void bluetoothStop();
// In main.cpp
uint32_t secondsUntilNextTemperatureMeasurement();
uint8_t getWakeupCause();
bool bluetoothIsConnected();

90
include/eepromInterface.h Normal file
View File

@ -0,0 +1,90 @@
#include <stdint.h>
#include <stddef.h>
#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();
};

7
include/main.h Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include <stdint.h>
uint32_t secondsUntilNextTemperatureMeasurement();
uint8_t getWakeupCause();
uint32_t getUniqueID();

View File

@ -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();
void discardAllRecordedBytes();

View File

@ -1,5 +1,6 @@
#include <string.h>
#include <Esp.h>
#include <stdint.h>
#include <BLEDevice.h>
#include <BLEUtils.h>
@ -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;

6
src/eepromInterface.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "eepromInterface.h"
#include <EEPROM.h>
EEPROMInterface::EEPROMInterface() {
isInitialized = EEPROM.begin(eepromTotalSize);
}

View File

@ -2,6 +2,7 @@
#include <esp_wifi.h>
#include <driver/rtc_io.h>
#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();

View File

@ -3,63 +3,21 @@
#include <EEPROM.h>
#include <esp_attr.h>
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<uint8_t>(flag);
}
void clearStorageFlag(StorageFlags flag) {
storageFlags &= ~static_cast<uint8_t>(flag);
}
bool hasStorageFlag(StorageFlags flag) {
return storageFlags & static_cast<uint8_t>(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, &timestamp, 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;
}