Skip to content

Commit

Permalink
Merge pull request #1 from reivaxy/bugfix
Browse files Browse the repository at this point in the history
Bugfix
  • Loading branch information
reivaxy authored May 19, 2022
2 parents 70f7c62 + 5e3173c commit bb1034d
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 35 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# iotFeeder

Fish feeder using an ESP8266, a stepper motor and an oled screen.
WiFi enabled fish feeder for aquarium, using an ESP8266, a stepper motor and an oled screen.

All settings are done using web page forms over Wifi. It records activiy in a Firebase DB, and you will receive notifications when it's empty or offline.

Expand All @@ -15,8 +15,7 @@ I can't be held responsible for any damage occurring while using a device you bu
If you do not agree with this disclaimer, do not build this device, or do not use it.
```


One is being used on my small reef aquarium for 3 weeks without any issue, and I'm building 5 more to equip bigger aquariums.
One is being used on my small reef aquarium for 3 weeks without any issue, I'm building 5 more to equip bigger aquariums and a friend is building one for his own aquarium.

It will eventually be a module of the Iotinator framework (https://github.com/reivaxy/iotinator), but it can already work autonomously (no master module is needed).

Expand Down
7 changes: 6 additions & 1 deletion include/FeederModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include "Messages.h"
#include "Stepper.h"

#define MAX_STATUS_LENGTH 150

class FeederModule:public XIOTModule {
public:
FeederModule(FeederConfigClass* config, int displayAddr, int displaySda, int displayScl, int forwardPin, int reversePin);
Expand All @@ -21,6 +23,7 @@ class FeederModule:public XIOTModule {
void feedOnce();
void logProgramedDispensing(uint16_t quantity);
void setCustomModuleRecordFields(JsonObject *jsonBufferRoot) override;
void dispensingFailed(boolean transientDisplay);

Stepper stepper;
FeederConfigClass* _config;
Expand All @@ -39,7 +42,9 @@ class FeederModule:public XIOTModule {
bool _oneTimeDispensing = false;

bool mustWarnNoFoodDetected = false;
uint16_t lastDispensedQuantity = 0;
int16_t lastDispensedQuantity = 0;

char lastStatus[MAX_STATUS_LENGTH + 1];


};
5 changes: 5 additions & 0 deletions include/Messages_en.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#define MSG_INIT_TEST_QUANTITY "One time dispensing"
#define MSG_INIT_ILLEGAL_VALUE "Illegal value"
#define MSG_INIT_DONE "Action done"
#define MSG_INIT_LAST_ACTION "Last status:"
#define MSG_INIT_CLOCK "Current time:"

#define MSG_SETINGS_WELCOME "Settings"
#define MSG_ALERT_DISPENSING_FAILURE "No food dispensed"
Expand All @@ -20,4 +22,7 @@
#define MSG_LOG_MANUAL_REVERSE "Manual reverse activation"
#define MSG_DISPLAY_AT "At"
#define MSG_DISPLAY_QTITY "Qtity"
#define MSG_DISPLAY_FAILED "FAILED"

#define MSG_LOG_SCHEDULE_UPDATED "Schedule updated"

5 changes: 5 additions & 0 deletions include/Messages_fr.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
#define MSG_INIT_TEST_QUANTITY "Distribution ponctuelle"
#define MSG_INIT_ILLEGAL_VALUE "Valeur non autorisée"
#define MSG_INIT_DONE "Action exécutée"
#define MSG_INIT_LAST_ACTION "Dernière opération:"
#define MSG_INIT_CLOCK "Date et heure:"

#define MSG_SETINGS_WELCOME "Réglages"
#define MSG_ALERT_DISPENSING_FAILURE "Nourriture non distribuée"
Expand All @@ -20,3 +22,6 @@
#define MSG_LOG_MANUAL_REVERSE "Activation manuelle marche arrière"
#define MSG_DISPLAY_AT "A"
#define MSG_DISPLAY_QTITY "Qtite"
#define MSG_DISPLAY_FAILED "ECHEC"

#define MSG_LOG_SCHEDULE_UPDATED "Programmation mise à jour"
6 changes: 4 additions & 2 deletions include/settingsPageHtml.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* iotinator XIOT module settings page
* iotFeeder module settings page
* Xavier Grosjean 2022
* Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Public License
*/
Expand All @@ -13,13 +13,15 @@ static const char settingsBeginingPage[] PROGMEM = "\
<meta charset='UTF-8'>\
<meta name='viewport' content='width=device-width, initial-scale=1'>\
<style>\
body{line-height:1.5em}\
body{line-height:1.5em;}\
input{left:100px;position:absolute}\
a{right:40px;position:absolute;text-decoration:none}\
</style>\
</head>\
<body>\
<h3>" MSG_SETINGS_WELCOME " %s <a href='/config'>⚙</a></h3>\
<h6>" MSG_INIT_CLOCK " %s</br>\
" MSG_INIT_LAST_ACTION " %s</h6><hr>\
<form action='/feederApi/saveSettings' method='post'>\
";

Expand Down
66 changes: 56 additions & 10 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,17 @@ board = esp12e
board_build.f_cpu = 160000000L
framework = arduino
monitor_speed = 115200
monitor_port = COM6
upload_port = COM6
monitor_port = COM4
upload_port = COM4
lib_deps =
bblanchon/[email protected]
gmag11/[email protected]
paulstoffregen/Time@^1.6.1
waspinator/AccelStepper@^1.61
https://github.com/reivaxy/XUtils/archive/refs/tags/v1.0.tar.gz
https://github.com/reivaxy/XUtils/archive/refs/tags/v1.1.tar.gz
https://github.com/reivaxy/XOLEDDisplay/archive/refs/tags/v1.0.tar.gz
https://github.com/reivaxy/XEEPROMConfig/archive/refs/tags/v0.1.tar.gz
https://github.com/reivaxy/XIOTModule/archive/refs/tags/v2.0.tar.gz
https://github.com/reivaxy/XIOTModule/archive/refs/tags/v2.1.tar.gz
https://github.com/reivaxy/XIOTConfig/archive/refs/tags/v2.0.tar.gz
https://github.com/reivaxy/XIOTDisplay/archive/refs/tags/v0.1.tar.gz
thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.3.0
Expand All @@ -47,10 +47,10 @@ lib_deps =
gmag11/[email protected]
paulstoffregen/Time@^1.6.1
waspinator/AccelStepper@^1.61
https://github.com/reivaxy/XUtils/archive/refs/tags/v1.0.tar.gz
https://github.com/reivaxy/XUtils/archive/refs/tags/v1.1.tar.gz
https://github.com/reivaxy/XOLEDDisplay/archive/refs/tags/v1.0.tar.gz
https://github.com/reivaxy/XEEPROMConfig/archive/refs/tags/v0.1.tar.gz
https://github.com/reivaxy/XIOTModule/archive/refs/tags/v2.0.tar.gz
https://github.com/reivaxy/XIOTModule/archive/refs/tags/v2.1.tar.gz
https://github.com/reivaxy/XIOTConfig/archive/refs/tags/v2.0.tar.gz
https://github.com/reivaxy/XIOTDisplay/archive/refs/tags/v0.1.tar.gz
thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.3.0
Expand All @@ -75,8 +75,8 @@ lib_deps =
https://github.com/reivaxy/XEEPROMConfig/archive/refs/tags/v0.1.tar.gz
thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.3.0

[env:esp12_local_espota_fr]
build_flags = !python gitVersion.py -DXIOT_LANGUAGE=fr -DDEBUG_XIOTMODULE=1 -DNO_IR=1
[env:esp12_local_espota_fr_debug]
build_flags = !python gitVersion.py -DXIOT_LANGUAGE=fr -DDEBUG_XIOTMODULE=1
platform = espressif8266@^2
board = esp12e
board_build.f_cpu = 160000000L
Expand All @@ -85,8 +85,54 @@ build_type = debug
monitor_filters = esp8266_exception_decoder
monitor_speed = 115200
monitor_port = COM4
upload_protocol = espota
upload_port = 192.168.0.26
upload_port = COM4
#upload_protocol = espota
#upload_port = 192.168.0.26
lib_extra_dirs = F:\DEV\arduino\libraries
lib_deps =
bblanchon/[email protected]
gmag11/[email protected]
paulstoffregen/Time@^1.6.1
waspinator/AccelStepper@^1.61
https://github.com/reivaxy/XEEPROMConfig/archive/refs/tags/v0.1.tar.gz
thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.3.0


[env:esp12_local_espota_fr_debug_SSL]
build_flags = !python gitVersion.py -DXIOT_LANGUAGE=fr -DDEBUG_ESP_SSL -DDEBUG_ESP_PORT=Serial
platform = espressif8266@^2
board = esp12e
board_build.f_cpu = 160000000L
framework = arduino
build_type = debug
monitor_filters = esp8266_exception_decoder
monitor_speed = 115200
monitor_port = COM4
upload_port = COM4
#upload_protocol = espota
#upload_port = 192.168.0.26
lib_extra_dirs = F:\DEV\arduino\libraries
lib_deps =
bblanchon/[email protected]
gmag11/[email protected]
paulstoffregen/Time@^1.6.1
waspinator/AccelStepper@^1.61
https://github.com/reivaxy/XEEPROMConfig/archive/refs/tags/v0.1.tar.gz
thingpulse/ESP8266 and ESP32 OLED driver for SSD1306 displays@^4.3.0

[env:esp12_local_espota_fr_debug_full]
build_flags = !python gitVersion.py -DXIOT_LANGUAGE=fr -DDEBUG_XIOTMODULE=1 -DDEBUG_XIOTMODULE_MEM=1
platform = espressif8266@^2
board = esp12e
board_build.f_cpu = 160000000L
framework = arduino
build_type = debug
monitor_filters = esp8266_exception_decoder
monitor_speed = 115200
monitor_port = COM4
upload_port = COM4
#upload_protocol = espota
#upload_port = 192.168.0.26
lib_extra_dirs = F:\DEV\arduino\libraries
lib_deps =
bblanchon/[email protected]
Expand Down
76 changes: 57 additions & 19 deletions src/FeederModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ FeederModule::FeederModule(FeederConfigClass* config, int displayAddr, int displ
int displayScl, int forwardPin, int reversePin):XIOTModule(config, displayAddr, displaySda, displayScl, false, config->getBrightness()) {
pinMode(forwardPin, INPUT);
pinMode(reversePin, INPUT);

lastStatus[0] = 0;
_forwardPin = forwardPin;
_reversePin = reversePin;
_oledDisplay->setLineAlignment(2, TEXT_ALIGN_CENTER);
Expand All @@ -30,7 +30,8 @@ FeederModule::FeederModule(FeederConfigClass* config, int displayAddr, int displ
initMsgSchedule();
_oledDisplay->setLineAlignment(1, TEXT_ALIGN_CENTER);
_oledDisplay->setLineAlignment(3, TEXT_ALIGN_CENTER);
_oledDisplay->setTransientDuration(2, 30000); // List to display step count at end of test session
_oledDisplay->setTransientDuration(2, 30000); // Line to display step count at end of test session
_oledDisplay->setTransientDuration(3, 30000); // Line to display possible dispensing failure (tank empty, clogged...)
_server->on("/feederApi/saveSettings", HTTP_POST, [&]() {
saveSettings();
});
Expand Down Expand Up @@ -73,13 +74,15 @@ void FeederModule::initMsgSchedule() {
}

void FeederModule::settingsPage() {
MemSize("before displaying settings page");
String pageTemplate(FPSTR(settingsBeginingPage));
String endingTemplate(FPSTR(settingsEndingPage));
String formTemplate(FPSTR(settingTemplate));
int maxSize = strlen(pageTemplate.c_str()) + strlen(endingTemplate.c_str()) + strlen(_config->getName()) + PROGRAM_COUNT * (strlen(formTemplate.c_str()) + 15) ;
int maxSize = strlen(pageTemplate.c_str()) + strlen(_config->getName()) + strlen(NTP.getTimeDateString().c_str()) + strlen(lastStatus)
+ strlen(endingTemplate.c_str())+ PROGRAM_COUNT * (strlen(formTemplate.c_str()) + 20) + 50 ;
Serial.printf("Size %d\n", maxSize);
char* result = (char*)malloc(maxSize);
sprintf(result, pageTemplate.c_str(), _config->getName());
sprintf(result, pageTemplate.c_str(), _config->getName(), NTP.getTimeDateString().c_str(), lastStatus);
char *resultPtr = result + strlen(result);
for (uint8_t p = 0; p < PROGRAM_COUNT; p ++) {
Program *prgm = _config->getProgram(p);
Expand All @@ -98,11 +101,11 @@ void FeederModule::settingsPage() {
// sprintf(message, "Message %d", i);
// firebase->differMessage(message);
// }
MemSize("after displaying settings page");
}

void FeederModule::saveSettings() {
uint32_t freeMem = system_get_free_heap_size();
Serial.printf("%s Heap before sorting programs: %d\n", NTP.getTimeDateString().c_str(), freeMem);
MemSize("before saving settings");
// sizing param names for PROGRAM_COUNT < 100
char hourParamName[7];
char quantityParamName[7];
Expand Down Expand Up @@ -138,14 +141,14 @@ void FeederModule::saveSettings() {
prgm->setActive(prgms[p]->isActive());
delete prgms[p];
}
_config->saveToEeprom();
freeMem = system_get_free_heap_size();
Serial.printf("%s Heap after sorting programs: %d\n", NTP.getTimeDateString().c_str(), freeMem);
_config->saveToEeprom();
MemSize("after saving settings");
// Trying to send a message while processing an incoming request crashes the module
// So we send it later
firebase->differMessage("Schedule updated");
firebase->differMessage(MSG_LOG_SCHEDULE_UPDATED);
sendHtml(MSG_INIT_DONE, 200);
initMsgSchedule();
MemSize("end of saving settings");
}

void FeederModule::setCustomModuleRecordFields(JsonObject *jsonBufferRoot) {
Expand All @@ -156,6 +159,16 @@ void FeederModule::setCustomModuleRecordFields(JsonObject *jsonBufferRoot) {
jsonBufferRoot->set("with_ir", with_ir);
}

void FeederModule::dispensingFailed(boolean transientDisplay) {
Serial.printf("%s WARNING NO FOOD DETECTED\n", NTP.getTimeDateString(now()).c_str());
_oledDisplay->setLine(3, MSG_DISPLAY_FAILED, transientDisplay, false, true);
strcat(lastStatus, ": ");
strcat(lastStatus, MSG_ALERT_DISPENSING_FAILURE);
firebase->differAlert(MSG_ALERT_DISPENSING_FAILURE);
// Sending notif Could be handled by a Firebase function but not sure it's best
sendPushNotif(_config->getName(), MSG_ALERT_DISPENSING_FAILURE);
}

void FeederModule::loop() {
XIOTModule::loop(); // takes care of server, display, ...

Expand All @@ -176,16 +189,18 @@ void FeederModule::loop() {
// If an active program is set for the h hour, it will return a non 0 quantity
quantity = _config->getProgram(p)->triggerQuantity(h);
if (quantity != 0) {
lastDispensedQuantity = quantity;
sprintf(lastStatus, "%s: %s %d ", NTP.getTimeDateString(now()).c_str(), MSG_LOG_AUTO_DISPENSING, lastDispensedQuantity);
Serial.printf("%s\n", NTP.getTimeDateString(now()).c_str());
char message[50];
sprintf(message, "%s %d:00, %s: %d\n", MSG_DISPLAY_AT, h, MSG_DISPLAY_QTITY, quantity);
sprintf(message, "%s %02d:00, %s: %d\n", MSG_DISPLAY_AT, h, MSG_DISPLAY_QTITY, lastDispensedQuantity);
Serial.printf(message);
_oledDisplay->setLine(2, message);
_oledDisplay->setLine(3, "");
lastTriggerTime = millis();
// Activate the stepper
// This also powers up the IR detector since it's plugged to the EN pin
stepper.start(quantity);
lastDispensedQuantity = quantity;
_automaticDispensing = true;
break;
}
Expand All @@ -209,6 +224,7 @@ void FeederModule::loop() {
mustWarnNoFoodDetected = true;
}
stepper.run();
#ifndef NO_IR
int level = analogRead(IR_IN)/10;
if (abs(_previousLevel - level) > 3) {
if (_previousLevel != -1 && isTimeInitialized()) {
Expand All @@ -217,15 +233,15 @@ void FeederModule::loop() {
}
}
_previousLevel = level;
#else
mustWarnNoFoodDetected = false;
#endif
} else {
// No step remainning
stepper.stop();
#ifndef NO_IR
if (mustWarnNoFoodDetected && !_manualReverse) {
Serial.printf("%s WARNING NO FOOD DETECTED\n", NTP.getTimeDateString(now()).c_str());
firebase->differAlert(MSG_ALERT_DISPENSING_FAILURE);
// Sending notif Could be handled by a Firebase function but not sure it's best
sendPushNotif(_config->getName(), MSG_ALERT_DISPENSING_FAILURE);
if (mustWarnNoFoodDetected && !_manualReverse && !_oneTimeDispensing) {
dispensingFailed(false);
}
#endif
if (_manualReverse) {
Expand All @@ -243,6 +259,20 @@ void FeederModule::loop() {

if (_oneTimeDispensing) {
_oneTimeDispensing = false;
// This features allows to go backward by providing a negative quantity:
// we don't want to display nor warn
if (lastDispensedQuantity > 0) {
sprintf(lastStatus, "%s: %s %d ", NTP.getTimeDateString(now()).c_str(), MSG_INIT_TEST_QUANTITY, lastDispensedQuantity);
char message[40];
sprintf(message, "%s %02d:%02d, %s: %d\n", MSG_DISPLAY_AT, hour(), minute(), MSG_DISPLAY_QTITY, lastDispensedQuantity);

_oledDisplay->setLine(2, message);
_oledDisplay->setLine(3, "");
if (mustWarnNoFoodDetected) {
mustWarnNoFoodDetected = false;
dispensingFailed(false);
}
}
DynamicJsonBuffer jsonBuffer(10);
JsonObject& jsonBufferRoot = jsonBuffer.createObject();
jsonBufferRoot["message"] = MSG_INIT_TEST_QUANTITY;
Expand All @@ -267,10 +297,18 @@ void FeederModule::loop() {
if (_manualForward) {
_manualForward = false;
long stepCount = MANUAL_STEP_COUNT - stepper.remaining();
sprintf(lastStatus, "%s: %s %ld ", NTP.getTimeDateString(now()).c_str(), MSG_LOG_MANUAL_DISPENSING, stepCount);
stepper.stop();
char message[40];
sprintf(message, "%s: %ld\n", MSG_DISPLAY_QTITY, stepCount);
_oledDisplay->setLine(2, message, true, false, true);
// sprintf(message, "%s: %ld\n", MSG_DISPLAY_QTITY, stepCount);
sprintf(message, "%s %02d:%02d, %s: %ld\n", MSG_DISPLAY_AT, hour(), minute(), MSG_DISPLAY_QTITY, stepCount);

_oledDisplay->setLine(2, message);
_oledDisplay->setLine(3, "");
if (mustWarnNoFoodDetected) {
mustWarnNoFoodDetected = false;
dispensingFailed(false);
}
DynamicJsonBuffer jsonBuffer(10);
JsonObject& jsonBufferRoot = jsonBuffer.createObject();
jsonBufferRoot["message"] = MSG_LOG_MANUAL_DISPENSING;
Expand Down
Loading

0 comments on commit bb1034d

Please sign in to comment.