Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions lib/ConfigService/ConfigService.cpp
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
#include "ConfigService.hpp"
#include "FeedingService.hpp" // For FeedHistoryEntry definition

ConfigService::ConfigService() : portionUnitGrams(12) {}
ConfigService::ConfigService() : portionUnitGrams(12), manualPortionUnits(1) {}

bool ConfigService::begin() {
preferences.begin("feeder", false);

// Load portion unit grams
portionUnitGrams = preferences.getUChar("portionGrams", 12);
manualPortionUnits = preferences.getUChar("manualUnits", 1);

Serial.println("[CONFIG] ConfigService initialized");
Serial.printf("[CONFIG] Portion unit: %d grams\n", portionUnitGrams);
Serial.printf("[CONFIG] Manual feed amount: %d units\n", manualPortionUnits);

return true;
}
Expand Down Expand Up @@ -127,22 +129,37 @@ void ConfigService::setPortionUnitGrams(uint8_t grams) {
Serial.printf("[CONFIG] Portion unit updated to %d grams\n", grams);
}

bool ConfigService::saveFeedHistory(const FeedHistoryEntry* history, uint8_t count) {
uint8_t ConfigService::getManualPortionUnits() {
return manualPortionUnits;
}

void ConfigService::setManualPortionUnits(uint8_t units) {
manualPortionUnits = units;
preferences.putUChar("manualUnits", units);
Serial.printf("[CONFIG] Manual feed amount updated to %d units\n", units);
}

bool ConfigService::saveFeedHistory(const FeedHistoryEntry* history, uint8_t count, uint8_t writeIndex) {
if (count > MAX_FEED_HISTORY) {
count = MAX_FEED_HISTORY;
}
if (writeIndex >= MAX_FEED_HISTORY) {
writeIndex = count % MAX_FEED_HISTORY;
}

// Save history as binary blob for efficiency
size_t dataSize = count * sizeof(FeedHistoryEntry);
preferences.putBytes("feedHist", history, dataSize);
preferences.putUChar("feedHistCnt", count);
preferences.putUChar("feedHistIdx", writeIndex);

Serial.printf("[CONFIG] Saved %d feed history entries (%d bytes)\n", count, dataSize);
Serial.printf("[CONFIG] Saved %d feed history entries (%d bytes), write index %d\n", count, dataSize, writeIndex);
return true;
}

uint8_t ConfigService::loadFeedHistory(FeedHistoryEntry* history, uint8_t maxCount) {
uint8_t ConfigService::loadFeedHistory(FeedHistoryEntry* history, uint8_t maxCount, uint8_t &writeIndex) {
uint8_t count = preferences.getUChar("feedHistCnt", 0);
writeIndex = 0;

if (count == 0) {
Serial.println("[CONFIG] No feed history found");
Expand All @@ -161,13 +178,19 @@ uint8_t ConfigService::loadFeedHistory(FeedHistoryEntry* history, uint8_t maxCou
return 0;
}

Serial.printf("[CONFIG] Loaded %d feed history entries\n", count);
writeIndex = preferences.getUChar("feedHistIdx", count % MAX_FEED_HISTORY);
if (writeIndex >= MAX_FEED_HISTORY) {
writeIndex = count % MAX_FEED_HISTORY;
}

Serial.printf("[CONFIG] Loaded %d feed history entries, write index %d\n", count, writeIndex);
return count;
}

bool ConfigService::clearFeedHistory() {
preferences.remove("feedHist");
preferences.remove("feedHistCnt");
preferences.remove("feedHistIdx");
Serial.println("[CONFIG] Feed history cleared");
return true;
}
Expand All @@ -187,6 +210,7 @@ bool ConfigService::resetToDefaults() {

saveAllSchedules(defaults);
setPortionUnitGrams(12);
setManualPortionUnits(1);
clearFeedHistory();

Serial.println("[CONFIG] Reset complete");
Expand Down
9 changes: 6 additions & 3 deletions lib/ConfigService/ConfigService.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct Schedule {
bool enabled;
char time[6]; // "HH:MM"
uint8_t weekday_mask; // Bit 0=Sunday, 1=Monday, ..., 6=Saturday
uint8_t portion_units; // 1-5 units (12g each)
uint8_t portion_units; // 1-10 units
};

class ConfigService {
Expand All @@ -34,10 +34,12 @@ class ConfigService {
// Config metadata
uint8_t getPortionUnitGrams();
void setPortionUnitGrams(uint8_t grams);
uint8_t getManualPortionUnits();
void setManualPortionUnits(uint8_t units);

// Feed history management
bool saveFeedHistory(const FeedHistoryEntry* history, uint8_t count);
uint8_t loadFeedHistory(FeedHistoryEntry* history, uint8_t maxCount);
bool saveFeedHistory(const FeedHistoryEntry* history, uint8_t count, uint8_t writeIndex);
uint8_t loadFeedHistory(FeedHistoryEntry* history, uint8_t maxCount, uint8_t &writeIndex);
bool clearFeedHistory();

// Reset to defaults
Expand All @@ -46,6 +48,7 @@ class ConfigService {
private:
Preferences preferences;
uint8_t portionUnitGrams;
uint8_t manualPortionUnits;

void getScheduleKey(uint8_t index, char *key);
};
Expand Down
20 changes: 15 additions & 5 deletions lib/FeedingService/FeedingService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ void FeedingService::feed(uint8_t count) {

// Validate count
if (count < 1) count = 1;
if (count > 5) count = 5;
if (count > 10) count = 10;

feedCount = count;
feedsCompleted = 0;
Expand Down Expand Up @@ -256,7 +256,7 @@ void FeedingService::addFeedToHistory(uint32_t timestamp, uint8_t portionUnits)

// Immediately save to persistent storage
if (configService) {
configService->saveFeedHistory(feedHistory, feedHistoryCount);
configService->saveFeedHistory(feedHistory, feedHistoryCount, feedHistoryIndex);
Serial.println("[INFO] Feed history saved to NVS");
}
}
Expand All @@ -265,12 +265,13 @@ uint8_t FeedingService::getFeedHistoryCount() const {
return feedHistoryCount;
}

void FeedingService::loadFeedHistory(const FeedHistoryEntry* history, uint8_t count) {
void FeedingService::loadFeedHistory(const FeedHistoryEntry* history, uint8_t count, uint8_t writeIndex) {
if (count > MAX_FEED_HISTORY) {
count = MAX_FEED_HISTORY;
}

Serial.printf("[DEBUG] loadFeedHistory: loading %d entries\n", count);
lastFeedUnix = 0;

for (uint8_t i = 0; i < count; i++) {
feedHistory[i] = history[i];
Expand All @@ -279,9 +280,18 @@ void FeedingService::loadFeedHistory(const FeedHistoryEntry* history, uint8_t co
}

feedHistoryCount = count;
feedHistoryIndex = count % MAX_FEED_HISTORY;
feedHistoryIndex = (writeIndex < MAX_FEED_HISTORY) ? writeIndex : (count % MAX_FEED_HISTORY);

Serial.printf("[INFO] Loaded %d feed history entries, next index will be %d\n", count, feedHistoryIndex);
for (uint8_t i = 0; i < feedHistoryCount; i++) {
uint8_t ringIndex = (feedHistoryIndex + MAX_FEED_HISTORY - 1 - i) % MAX_FEED_HISTORY;
if (feedHistory[ringIndex].timestamp > 0) {
lastFeedUnix = feedHistory[ringIndex].timestamp;
break;
}
}

Serial.printf("[INFO] Loaded %d feed history entries, next index will be %d, last feed %lu\n",
count, feedHistoryIndex, lastFeedUnix);
}

void FeedingService::clearFeedHistory() {
Expand Down
5 changes: 3 additions & 2 deletions lib/FeedingService/FeedingService.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class FeedingService {
public:
FeedingService();
void setup();
void feed(uint8_t count = 1); // Feed with count portions (1-5)
void feed(uint8_t count = 1); // Feed with count portions (1-10)
void update(); // Must be called in loop()

uint8_t getPosition();
Expand All @@ -68,7 +68,8 @@ class FeedingService {
void addFeedToHistory(uint32_t timestamp, uint8_t portionUnits);
uint8_t getFeedHistoryCount() const;
const FeedHistoryEntry* getFeedHistory() const { return feedHistory; }
void loadFeedHistory(const FeedHistoryEntry* history, uint8_t count);
uint8_t getFeedHistoryWriteIndex() const { return feedHistoryIndex; }
void loadFeedHistory(const FeedHistoryEntry* history, uint8_t count, uint8_t writeIndex);
void clearFeedHistory();

private:
Expand Down
35 changes: 23 additions & 12 deletions lib/WebService/WebService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ void WebService::setupRoutes() {
});

// API endpoints
server.on("/api/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
server.on("/api/status/history", HTTP_GET, [this](AsyncWebServerRequest *request) {
updateClientActivity();
handleGetStatus(request);
handleGetFeedHistory(request);
});

server.on("/api/status/history", HTTP_GET, [this](AsyncWebServerRequest *request) {
server.on("/api/status", HTTP_GET, [this](AsyncWebServerRequest *request) {
updateClientActivity();
handleGetFeedHistory(request);
handleGetStatus(request);
});

server.on("/api/config", HTTP_GET, [this](AsyncWebServerRequest *request) {
Expand Down Expand Up @@ -309,12 +309,14 @@ void WebService::handleGetFeedHistory(AsyncWebServerRequest *request) {
}

// Add entries to JSON array (newest first)
// Ring buffer: entries are added at feedHistoryIndex, wrapping around
// We need to read them in reverse chronological order
// Ring buffer: feedHistoryIndex points to the next write position.
uint8_t historyCount = feedingService.getFeedHistoryCount();
uint8_t writeIndex = feedingService.getFeedHistoryWriteIndex();
for (uint8_t i = 0; i < count; i++) {
const FeedHistoryEntry& entry = history[i];
uint8_t ringIndex = (writeIndex + MAX_FEED_HISTORY - 1 - i) % MAX_FEED_HISTORY;
const FeedHistoryEntry& entry = history[ringIndex];

Serial.printf("[WEB] Entry %d: timestamp=%lu, portion_units=%d\n", i, entry.timestamp, entry.portion_units);
Serial.printf("[WEB] Entry %d (ring %d): timestamp=%lu, portion_units=%d\n", i, ringIndex, entry.timestamp, entry.portion_units);

if (entry.timestamp == 0) {
Serial.printf("[WEB] Skipping empty entry at index %d\n", i);
Expand Down Expand Up @@ -350,6 +352,7 @@ void WebService::handleGetConfig(AsyncWebServerRequest *request) {
JsonObject data = doc["data"].to<JsonObject>();
data["version"] = 1;
data["portion_unit_grams"] = configService.getPortionUnitGrams();
data["manual_portion_units"] = configService.getManualPortionUnits();

JsonArray schedules = data["schedules"].to<JsonArray>();

Expand Down Expand Up @@ -392,8 +395,8 @@ void WebService::handlePostConfig(AsyncWebServerRequest *request, uint8_t *data,
schedule.portion_units = s["portion_units"] | 1;

// Validate portion_units
if (schedule.portion_units < 1 || schedule.portion_units > 5) {
sendError(request, "Invalid portion size. Must be between 1-5 units (12-60g).", 400);
if (schedule.portion_units < 1 || schedule.portion_units > 10) {
sendError(request, "Invalid portion size. Must be between 1-10 units.", 400);
return;
}

Expand All @@ -407,6 +410,15 @@ void WebService::handlePostConfig(AsyncWebServerRequest *request, uint8_t *data,
configService.setPortionUnitGrams(grams);
}

if (!doc["manual_portion_units"].isNull()) {
uint8_t units = doc["manual_portion_units"];
if (units < 1 || units > 10) {
sendError(request, "Invalid manual feed amount. Must be between 1-10 units.", 400);
return;
}
configService.setManualPortionUnits(units);
}

JsonDocument response;
response["success"] = true;
response["message"] = "Configuration saved successfully";
Expand All @@ -423,8 +435,7 @@ void WebService::handlePostFeed(AsyncWebServerRequest *request) {
return;
}

// Trigger manual feed with 1 portion
feedingService.feed(1);
feedingService.feed(configService.getManualPortionUnits());

JsonDocument doc;
doc["success"] = true;
Expand Down
Loading