Przeglądaj źródła

persist state to SPIFFS

Chris Mullins 8 lat temu
rodzic
commit
167391ff4d

+ 3 - 2
lib/DataStructures/LinkedList.h

@@ -40,7 +40,7 @@ public:
 	/*
 		Returns current size of LinkedList
 	*/
-	virtual int size();
+	virtual int size() const;
 	/*
 		Adds a T object in the specified index;
 		Unlink and link the LinkedList correcly;
@@ -91,6 +91,7 @@ public:
 	ListNode<T>* getNode(int index);
   virtual void spliceToFront(ListNode<T>* node);
   ListNode<T>* getHead() { return root; }
+	T getLast() const { return last == NULL ? T() : last->data; }
 
 };
 
@@ -157,7 +158,7 @@ ListNode<T>* LinkedList<T>::getNode(int index){
 }
 
 template<typename T>
-int LinkedList<T>::size(){
+int LinkedList<T>::size() const{
 	return _size;
 }
 

+ 105 - 82
lib/MiLightState/GroupState.cpp

@@ -51,123 +51,146 @@ bool GroupId::operator==(const GroupId &other) {
 }
 
 GroupState::GroupState() {
-  _state                = 0;
-  _brightness           = 0;
-  _brightnessColor      = 0;
-  _brightnessMode       = 0;
-  _hue                  = 0;
-  _saturation           = 0;
-  _mode                 = 0;
-  _bulbMode             = 0;
-  _kelvin               = 0;
-  _isSetState           = 0;
-  _isSetHue             = 0;
-  _isSetBrightness      = 0;
-  _isSetBrightnessColor = 0;
-  _isSetBrightnessMode  = 0;
-  _isSetSaturation      = 0;
-  _isSetMode            = 0;
-  _isSetKelvin          = 0;
-  _isSetBulbMode        = 0;
-  _dirty                = 1;
-}
-
-bool GroupState::isSetState() const { return _isSetState; }
-MiLightStatus GroupState::getState() const { return _state ? ON : OFF; }
-void GroupState::setState(const MiLightStatus state) {
-  _isSetState = 1;
-  _state = state == ON ? 1 : 0;
+  state.fields._state                = 0;
+  state.fields._brightness           = 0;
+  state.fields._brightnessColor      = 0;
+  state.fields._brightnessMode       = 0;
+  state.fields._hue                  = 0;
+  state.fields._saturation           = 0;
+  state.fields._mode                 = 0;
+  state.fields._bulbMode             = 0;
+  state.fields._kelvin               = 0;
+  state.fields._isSetState           = 0;
+  state.fields._isSetHue             = 0;
+  state.fields._isSetBrightness      = 0;
+  state.fields._isSetBrightnessColor = 0;
+  state.fields._isSetBrightnessMode  = 0;
+  state.fields._isSetSaturation      = 0;
+  state.fields._isSetMode            = 0;
+  state.fields._isSetKelvin          = 0;
+  state.fields._isSetBulbMode        = 0;
+  state.fields._dirty                = 1;
+}
+
+bool GroupState::isSetState() const { return state.fields._isSetState; }
+MiLightStatus GroupState::getState() const { return state.fields._state ? ON : OFF; }
+void GroupState::setState(const MiLightStatus status) {
+  setDirty();
+  state.fields._isSetState = 1;
+  state.fields._state = status == ON ? 1 : 0;
 }
 
 bool GroupState::isSetBrightness() const {
-  if (! _isSetBulbMode) {
-    return _isSetBrightness;
+  if (! state.fields._isSetBulbMode) {
+    return state.fields._isSetBrightness;
   }
 
-  switch (_bulbMode) {
+  switch (state.fields._bulbMode) {
     case BULB_MODE_WHITE:
-      return _isSetBrightness;
+      return state.fields._isSetBrightness;
     case BULB_MODE_COLOR:
-      return _isSetBrightnessColor;
+      return state.fields._isSetBrightnessColor;
     case BULB_MODE_SCENE:
-      return _isSetBrightnessMode;
+      return state.fields._isSetBrightnessMode;
   }
 
   return false;
 }
 
 uint8_t GroupState::getBrightness() const {
-  switch (_bulbMode) {
+  switch (state.fields._bulbMode) {
     case BULB_MODE_WHITE:
-      return _brightness;
+      return state.fields._brightness;
     case BULB_MODE_COLOR:
-      return _brightnessColor;
+      return state.fields._brightnessColor;
     case BULB_MODE_SCENE:
-      return _brightnessMode;
+      return state.fields._brightnessMode;
   }
 
   return 0;
 }
 
 void GroupState::setBrightness(uint8_t brightness) {
-  uint8_t bulbMode = _bulbMode;
-  if (! _isSetBulbMode) {
+  setDirty();
+
+  uint8_t bulbMode = state.fields._bulbMode;
+  if (! state.fields._isSetBulbMode) {
     bulbMode = BULB_MODE_WHITE;
   }
 
   switch (bulbMode) {
     case BULB_MODE_WHITE:
-      _isSetBrightness = 1;
-      _brightness = brightness;
+      state.fields._isSetBrightness = 1;
+      state.fields._brightness = brightness;
       break;
     case BULB_MODE_COLOR:
-      _isSetBrightnessColor = 1;
-      _brightnessColor = brightness;
+      state.fields._isSetBrightnessColor = 1;
+      state.fields._brightnessColor = brightness;
       break;
     case BULB_MODE_SCENE:
-      _isSetBrightnessMode = 1;
-      _brightnessMode = brightness;
+      state.fields._isSetBrightnessMode = 1;
+      state.fields._brightnessMode = brightness;
   }
 }
 
-bool GroupState::isSetHue() const { return _isSetHue; }
-uint8_t GroupState::getHue() const { return _hue; }
+bool GroupState::isSetHue() const { return state.fields._isSetHue; }
+uint8_t GroupState::getHue() const { return state.fields._hue; }
 void GroupState::setHue(uint8_t hue) {
-  _isSetHue = 1;
-  _hue = hue;
+  setDirty();
+  state.fields._isSetHue = 1;
+  state.fields._hue = hue;
 }
 
-bool GroupState::isSetSaturation() const { return _isSetSaturation; }
-uint8_t GroupState::getSaturation() const { return _saturation; }
+bool GroupState::isSetSaturation() const { return state.fields._isSetSaturation; }
+uint8_t GroupState::getSaturation() const { return state.fields._saturation; }
 void GroupState::setSaturation(uint8_t saturation) {
-  _isSetSaturation = 1;
-  _saturation = saturation;
+  setDirty();
+  state.fields._isSetSaturation = 1;
+  state.fields._saturation = saturation;
 }
 
-bool GroupState::isSetMode() const { return _isSetMode; }
-uint8_t GroupState::getMode() const { return _mode; }
+bool GroupState::isSetMode() const { return state.fields._isSetMode; }
+uint8_t GroupState::getMode() const { return state.fields._mode; }
 void GroupState::setMode(uint8_t mode) {
-  _isSetMode = 1;
-  _mode = mode;
+  setDirty();
+  state.fields._isSetMode = 1;
+  state.fields._mode = mode;
 }
 
-bool GroupState::isSetKelvin() const { return _isSetKelvin; }
-uint8_t GroupState::getKelvin() const { return _kelvin; }
+bool GroupState::isSetKelvin() const { return state.fields._isSetKelvin; }
+uint8_t GroupState::getKelvin() const { return state.fields._kelvin; }
 void GroupState::setKelvin(uint8_t kelvin) {
-  _isSetKelvin = 1;
-  _kelvin = kelvin;
+  setDirty();
+  state.fields._isSetKelvin = 1;
+  state.fields._kelvin = kelvin;
 }
 
-bool GroupState::isSetBulbMode() const { return _isSetBulbMode; }
-BulbMode GroupState::getBulbMode() const { return static_cast<BulbMode>(_bulbMode); }
+bool GroupState::isSetBulbMode() const { return state.fields._isSetBulbMode; }
+BulbMode GroupState::getBulbMode() const { return static_cast<BulbMode>(state.fields._bulbMode); }
 void GroupState::setBulbMode(BulbMode bulbMode) {
-  _isSetBulbMode = 1;
-  _bulbMode = bulbMode;
+  setDirty();
+  state.fields._isSetBulbMode = 1;
+  state.fields._bulbMode = bulbMode;
 }
 
-bool GroupState::isDirty() const { return _dirty; }
-bool GroupState::setDirty() { _dirty = 1; }
-bool GroupState::clearDirty() { _dirty = 0; }
+bool GroupState::isDirty() const { return state.fields._dirty; }
+inline bool GroupState::setDirty() { state.fields._dirty = 1; }
+bool GroupState::clearDirty() { state.fields._dirty = 0; }
+
+void GroupState::load(Stream& stream) {
+  for (size_t i = 0; i < DATA_BYTES; i++) {
+    state.data[i] = stream.read();
+  }
+  clearDirty();
+}
+
+void GroupState::dump(Stream& stream) const {
+  for (size_t i = 0; i < DATA_BYTES; i++) {
+    uint32_t val = state.data[i];
+    uint8_t* bytePtr = reinterpret_cast<uint8_t*>(&val);
+    stream.write(bytePtr, 4);
+  }
+}
 
 void GroupState::patch(const JsonObject& state) {
   if (state.containsKey("state")) {
@@ -202,30 +225,30 @@ void GroupState::patch(const JsonObject& state) {
   }
 }
 
-void GroupState::applyState(JsonObject& state) {
-  if (_isSetState) {
-    state["state"] = getState() == ON ? "ON" : "OFF";
+void GroupState::applyState(JsonObject& partialState) {
+  if (state.fields._isSetState) {
+    partialState["state"] = getState() == ON ? "ON" : "OFF";
   }
-  if (_isSetBrightness) {
-    state["brightness"] = Units::rescale(getBrightness(), 255, 100);
+  if (state.fields._isSetBrightness) {
+    partialState["brightness"] = Units::rescale(getBrightness(), 255, 100);
   }
-  if (_isSetBulbMode) {
-    state["bulb_mode"] = BULB_MODE_NAMES[getBulbMode()];
+  if (state.fields._isSetBulbMode) {
+    partialState["bulb_mode"] = BULB_MODE_NAMES[getBulbMode()];
 
     if (getBulbMode() == BULB_MODE_COLOR) {
-      if (_isSetHue) {
-        state["hue"] = Units::rescale<uint8_t, uint16_t>(getHue(), 360, 255);
+      if (state.fields._isSetHue) {
+        partialState["hue"] = Units::rescale<uint8_t, uint16_t>(getHue(), 360, 255);
       }
-      if (_isSetSaturation) {
-        state["saturation"] = getSaturation();
+      if (state.fields._isSetSaturation) {
+        partialState["saturation"] = getSaturation();
       }
     } else if (getBulbMode() == BULB_MODE_SCENE) {
-      if (_isSetMode) {
-        state["mode"] = getMode();
+      if (state.fields._isSetMode) {
+        partialState["mode"] = getMode();
       }
     } else if (getBulbMode() == BULB_MODE_WHITE) {
-      if (_isSetKelvin) {
-        state["color_temp"] = Units::whiteValToMireds(getKelvin(), 100);
+      if (state.fields._isSetKelvin) {
+        partialState["color_temp"] = Units::whiteValToMireds(getKelvin(), 100);
       }
     }
   }

+ 33 - 23
lib/MiLightState/GroupState.h

@@ -77,32 +77,42 @@ public:
   void patch(const JsonObject& state);
   void applyState(JsonObject& state);
 
+  void load(Stream& stream);
+  void dump(Stream& stream) const;
+
   static const GroupState& defaultState(MiLightRemoteType remoteType);
 
 private:
-  uint32_t
-    _state      : 1,
-    _brightness : 7,
-    _hue        : 8,
-    _saturation : 7,
-    _mode       : 4,
-    _bulbMode   : 3,
-    _isSetState : 1,
-    _isSetHue   : 1;
-
-  uint32_t
-    _kelvin               : 7,
-    _isSetBrightness      : 1,
-    _isSetSaturation      : 1,
-    _isSetMode            : 1,
-    _isSetKelvin          : 1,
-    _isSetBulbMode        : 1,
-    _brightnessColor      : 7,
-    _brightnessMode       : 7,
-    _isSetBrightnessColor : 1,
-    _isSetBrightnessMode  : 1,
-    _dirty                : 1,
-                          : 5;
+  static const size_t DATA_BYTES = 2;
+  union Data {
+    uint32_t data[DATA_BYTES];
+    struct Fields {
+      uint32_t
+        _state                : 1,
+        _brightness           : 7,
+        _hue                  : 8,
+        _saturation           : 7,
+        _mode                 : 4,
+        _bulbMode             : 3,
+        _isSetState           : 1,
+        _isSetHue             : 1;
+      uint32_t
+        _kelvin               : 7,
+        _isSetBrightness      : 1,
+        _isSetSaturation      : 1,
+        _isSetMode            : 1,
+        _isSetKelvin          : 1,
+        _isSetBulbMode        : 1,
+        _brightnessColor      : 7,
+        _brightnessMode       : 7,
+        _isSetBrightnessColor : 1,
+        _isSetBrightnessMode  : 1,
+        _dirty                : 1,
+                              : 5;
+    } fields;
+  };
+
+  Data state;
 };
 
 struct GroupStateNode {

+ 14 - 8
lib/MiLightState/GroupStateCache.cpp

@@ -5,14 +5,7 @@ GroupStateCache::GroupStateCache(const size_t maxSize)
 { }
 
 GroupState* GroupStateCache::get(const GroupId& id) {
-  GroupState* state = getInternal(id);
-
-  if (state == NULL) {
-    state = set(id, GroupState::defaultState(id.deviceType));
-    return state;
-  } else {
-    return state;
-  }
+  return getInternal(id);
 }
 
 GroupState* GroupStateCache::set(const GroupId& id, const GroupState& state) {
@@ -41,6 +34,19 @@ GroupState* GroupStateCache::set(const GroupId& id, const GroupState& state) {
   return cachedState;
 }
 
+GroupId GroupStateCache::getLru() {
+  GroupCacheNode* node = cache.getLast();
+  return node->id;
+}
+
+bool GroupStateCache::isFull() const {
+  return cache.size() >= maxSize;
+}
+
+ListNode<GroupCacheNode*>* GroupStateCache::getHead() {
+  return cache.getHead();
+}
+
 GroupState* GroupStateCache::getInternal(const GroupId& id) {
   ListNode<GroupCacheNode*>* cur = cache.getHead();
 

+ 5 - 2
lib/MiLightState/GroupStateCache.h

@@ -1,5 +1,5 @@
+#include <GroupState.h>
 #include <LinkedList.h>
-#include <GroupStateStore.h>
 
 #ifndef _GROUP_STATE_CACHE_H
 #define _GROUP_STATE_CACHE_H
@@ -13,12 +13,15 @@ struct GroupCacheNode {
   GroupState state;
 };
 
-class GroupStateCache : public GroupStateStore {
+class GroupStateCache {
 public:
   GroupStateCache(const size_t maxSize);
 
   GroupState* get(const GroupId& id);
   GroupState* set(const GroupId& id, const GroupState& state);
+  GroupId getLru();
+  bool isFull() const;
+  ListNode<GroupCacheNode*>* getHead();
 
 private:
   LinkedList<GroupCacheNode*> cache;

+ 38 - 0
lib/MiLightState/GroupStatePersistence.cpp

@@ -0,0 +1,38 @@
+#include <GroupStatePersistence.h>
+#include <FS.h>
+
+static const char FILE_PREFIX[] = "group_states/";
+
+void GroupStatePersistence::get(const GroupId &id, GroupState& state) {
+  char path[30];
+  buildFilename(id, path);
+
+  if (SPIFFS.exists(path)) {
+    File f = SPIFFS.open(path, "r");
+    state.load(f);
+    f.close();
+  }
+}
+
+void GroupStatePersistence::set(const GroupId &id, const GroupState& state) {
+  char path[30];
+  buildFilename(id, path);
+
+  File f = SPIFFS.open(path, "w");
+  state.dump(f);
+  f.close();
+}
+
+void GroupStatePersistence::clear(const GroupId &id) {
+  char path[30];
+  buildFilename(id, path);
+
+  if (SPIFFS.exists(path)) {
+    SPIFFS.remove(path);
+  }
+}
+
+char* GroupStatePersistence::buildFilename(const GroupId &id, char *buffer) {
+  uint32_t compactId = (id.deviceId << 24) | (id.deviceType << 8) | id.groupId;
+  return buffer + sprintf(buffer, "%s%x", FILE_PREFIX, compactId);
+}

+ 24 - 0
lib/MiLightState/GroupStatePersistence.h

@@ -0,0 +1,24 @@
+#include <GroupState.h>
+
+#ifndef _GROUP_STATE_PERSISTENCE_H
+#define _GROUP_STATE_PERSISTENCE_H
+
+struct PersistedStateNode {
+  GroupState state;
+  GroupId next;
+  GroupId prev;
+};
+
+class GroupStatePersistence {
+public:
+  void get(const GroupId& id, GroupState& state);
+  void set(const GroupId& id, const GroupState& state);
+
+  void clear(const GroupId& id);
+
+private:
+
+  static char* buildFilename(const GroupId& id, char* buffer);
+};
+
+#endif

+ 40 - 0
lib/MiLightState/GroupStateStore.cpp

@@ -0,0 +1,40 @@
+#include <GroupStateStore.h>
+
+GroupStateStore::GroupStateStore(const size_t maxSize)
+  : cache(GroupStateCache(maxSize))
+{ }
+
+GroupState* GroupStateStore::get(const GroupId& id) {
+  GroupState* state = cache.get(id);
+
+  if (state == NULL) {
+    trackEviction();
+    state = cache.set(id, GroupState::defaultState(id.deviceType));
+  }
+
+  return state;
+}
+
+GroupState* GroupStateStore::set(const GroupId &id, const GroupState& state) {
+  *(get(id)) = state;
+}
+
+void GroupStateStore::trackEviction() {
+  if (cache.isFull()) {
+    evictedIds.add(cache.getLru());
+  }
+}
+
+void GroupStateStore::flush() {
+  ListNode<GroupCacheNode*>* curr = cache.getHead();
+
+  while (curr != NULL && curr->data->state.isDirty()) {
+    persistence.set(curr->data->id, curr->data->state);
+    curr->data->state.clearDirty();
+    curr = curr->next;
+  }
+
+  while (evictedIds.size() > 0) {
+    persistence.clear(evictedIds.shift());
+  }
+}

+ 15 - 2
lib/MiLightState/GroupStateStore.h

@@ -1,12 +1,25 @@
 #include <GroupState.h>
+#include <GroupStateCache.h>
+#include <GroupStatePersistence.h>
 
 #ifndef _GROUP_STATE_STORE_H
 #define _GROUP_STATE_STORE_H
 
 class GroupStateStore {
 public:
-  virtual GroupState* get(const GroupId& id) = 0;
-  virtual GroupState* set(const GroupId& id, const GroupState& state) = 0;
+  GroupStateStore(const size_t maxSize);
+
+  GroupState* get(const GroupId& id);
+  GroupState* set(const GroupId& id, const GroupState& state);
+
+  void flush();
+
+private:
+  GroupStateCache cache;
+  GroupStatePersistence persistence;
+  LinkedList<GroupId> evictedIds;
+
+  void trackEviction();
 };
 
 #endif

+ 5 - 4
src/main.cpp

@@ -16,7 +16,7 @@
 #include <RGBConverter.h>
 #include <MiLightDiscoveryServer.h>
 #include <MiLightClient.h>
-#include <GroupStateCache.h>
+#include <GroupStateStore.h>
 
 WiFiManager wifiManager;
 
@@ -28,7 +28,7 @@ MiLightHttpServer *httpServer = NULL;
 MqttClient* mqttClient = NULL;
 MiLightDiscoveryServer* discoveryServer = NULL;
 uint8_t currentRadioType = 0;
-GroupStateCache* stateCache = new GroupStateCache(100);
+GroupStateStore stateStore(100);
 
 int numUdpServers = 0;
 MiLightUdpServer** udpServers;
@@ -70,7 +70,7 @@ void initMilightUdpServers() {
 void onPacketSentHandler(uint8_t* packet, const MiLightRemoteConfig& config) {
   StaticJsonBuffer<200> buffer;
   JsonObject& result = buffer.createObject();
-  config.packetFormatter->parsePacket(packet, result, stateCache);
+  config.packetFormatter->parsePacket(packet, result, &stateStore);
 
   if (!result.containsKey("device_id")
     ||!result.containsKey("group_id")
@@ -84,7 +84,7 @@ void onPacketSentHandler(uint8_t* packet, const MiLightRemoteConfig& config) {
   const MiLightRemoteConfig* remoteConfig = MiLightRemoteConfig::fromType(result.get<String>("device_type"));
 
   GroupId bulbId(deviceId, groupId, remoteConfig->type);
-  GroupState* groupState = stateCache->get(bulbId);
+  GroupState* groupState = stateStore.get(bulbId);
   groupState->patch(result);
   groupState->applyState(result);
 
@@ -218,6 +218,7 @@ void loop() {
   }
 
   handleListen();
+  stateStore.flush();
 
   if (shouldRestart()) {
     Serial.println(F("Auto-restart triggered. Restarting..."));