Fermenter split into multiple files 2 + Fermenter Program
This commit is contained in:
parent
568880398f
commit
1e1a1c8a5b
8
data/Fermenter/config.json
Normal file
8
data/Fermenter/config.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"pid": {
|
||||||
|
"p": 5,
|
||||||
|
"i": 0,
|
||||||
|
"d": 0,
|
||||||
|
"target": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
24
data/Fermenter/program/demo.json
Normal file
24
data/Fermenter/program/demo.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "Demo",
|
||||||
|
"iterations": 1,
|
||||||
|
"points": [
|
||||||
|
{
|
||||||
|
"name": "Aufwärmen",
|
||||||
|
"start": 20,
|
||||||
|
"end": 28,
|
||||||
|
"seconds": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Halten",
|
||||||
|
"start": 28,
|
||||||
|
"end": 28,
|
||||||
|
"seconds": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Abkühlen",
|
||||||
|
"start": 28,
|
||||||
|
"end": 20,
|
||||||
|
"seconds": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
24
data/Fermenter/program/demo2.json
Normal file
24
data/Fermenter/program/demo2.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "Demo",
|
||||||
|
"iterations": 2,
|
||||||
|
"points": [
|
||||||
|
{
|
||||||
|
"name": "Aufwärmen",
|
||||||
|
"start": 20,
|
||||||
|
"end": 28,
|
||||||
|
"seconds": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Halten",
|
||||||
|
"start": 28,
|
||||||
|
"end": 28,
|
||||||
|
"seconds": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Abkühlen",
|
||||||
|
"start": 28,
|
||||||
|
"end": 20,
|
||||||
|
"seconds": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
24
data/Fermenter/program/endless.json
Normal file
24
data/Fermenter/program/endless.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "Demo",
|
||||||
|
"iterations": -1,
|
||||||
|
"points": [
|
||||||
|
{
|
||||||
|
"name": "Aufwärmen",
|
||||||
|
"start": 20,
|
||||||
|
"end": 28,
|
||||||
|
"seconds": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Halten",
|
||||||
|
"start": 28,
|
||||||
|
"end": 28,
|
||||||
|
"seconds": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Abkühlen",
|
||||||
|
"start": 28,
|
||||||
|
"end": 20,
|
||||||
|
"seconds": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -49,6 +49,6 @@ board_build.filesystem = ${common.board_build.filesystem}
|
|||||||
monitor_speed = ${common.monitor_speed}
|
monitor_speed = ${common.monitor_speed}
|
||||||
upload_flags = --auth=OtaAuthPatrixFermenter
|
upload_flags = --auth=OtaAuthPatrixFermenter
|
||||||
upload_protocol = ${common.upload_protocol}
|
upload_protocol = ${common.upload_protocol}
|
||||||
upload_port = 10.0.0.164
|
upload_port = 10.0.0.169
|
||||||
;upload_port = ${common.upload_port}
|
;upload_port = ${common.upload_port}
|
||||||
;upload_speed = ${common.upload_speed}
|
;upload_speed = ${common.upload_speed}
|
||||||
|
|||||||
@ -1,6 +1,13 @@
|
|||||||
#ifdef NODE_FERMENTER
|
#ifdef NODE_FERMENTER
|
||||||
|
|
||||||
#include "Fermenter.h"
|
#include <patrix/Patrix.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "display.h"
|
||||||
|
#include "http.h"
|
||||||
|
#include "pid.h"
|
||||||
|
#include "Program.h"
|
||||||
|
#include "rotary.h"
|
||||||
|
|
||||||
void patrixSetup() {
|
void patrixSetup() {
|
||||||
config.read();
|
config.read();
|
||||||
@ -18,9 +25,9 @@ void patrixLoop() {
|
|||||||
|
|
||||||
ds18b20.loop();
|
ds18b20.loop();
|
||||||
temperature.loop();
|
temperature.loop();
|
||||||
rotary.loop();
|
|
||||||
|
|
||||||
pid.loop();
|
pid.loop();
|
||||||
|
rotary.loop();
|
||||||
|
program.loop();
|
||||||
|
|
||||||
displayLoop();
|
displayLoop();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
#ifndef NODE_FERMENTER_H
|
|
||||||
#define NODE_FERMENTER_H
|
|
||||||
|
|
||||||
#define HEATER_POWER_W 30
|
|
||||||
|
|
||||||
/* rotary ----------------------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#include "patrix/Rotary.h"
|
|
||||||
|
|
||||||
extern Rotary rotary;
|
|
||||||
|
|
||||||
void rotaryCallback(int delta);
|
|
||||||
|
|
||||||
/* display ---------------------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#include <LedControl.h>
|
|
||||||
|
|
||||||
extern unsigned long displayModifyTarget;
|
|
||||||
|
|
||||||
extern LedControl display;
|
|
||||||
|
|
||||||
void displayPrintf(const char* format, ...);
|
|
||||||
|
|
||||||
void displayLoop();
|
|
||||||
|
|
||||||
/* config ----------------------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#include "patrix/Config.h"
|
|
||||||
|
|
||||||
extern Config config;
|
|
||||||
|
|
||||||
void configCollect(JsonDocument& json);
|
|
||||||
|
|
||||||
void configApply(JsonDocument& json);
|
|
||||||
|
|
||||||
/* pid -------------------------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#include "patrix/DS18B20Sensor.h"
|
|
||||||
#include "patrix/PIDController.h"
|
|
||||||
#include "patrix/PWMOutput.h"
|
|
||||||
|
|
||||||
extern DS18B20 ds18b20;
|
|
||||||
|
|
||||||
extern DS18B20Sensor temperature;
|
|
||||||
|
|
||||||
extern PWMOutput heater;
|
|
||||||
|
|
||||||
extern PIDController pid;
|
|
||||||
|
|
||||||
void addTarget(double delta);
|
|
||||||
|
|
||||||
/* http ------------------------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
#include "patrix/http.h"
|
|
||||||
|
|
||||||
void httpStatus(AsyncWebServerRequest* request);
|
|
||||||
|
|
||||||
void httpTargetAdd(AsyncWebServerRequest* request);
|
|
||||||
|
|
||||||
void httpSetup2();
|
|
||||||
|
|
||||||
/* patrix ----------------------------------------------------------------------------------------- */
|
|
||||||
|
|
||||||
void patrixSetup();
|
|
||||||
|
|
||||||
void patrixLoop();
|
|
||||||
|
|
||||||
#endif
|
|
||||||
7
src/node/Fermenter/Program.cpp
Normal file
7
src/node/Fermenter/Program.cpp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#ifdef NODE_FERMENTER
|
||||||
|
|
||||||
|
#include "Program.h"
|
||||||
|
|
||||||
|
Program program;
|
||||||
|
|
||||||
|
#endif
|
||||||
234
src/node/Fermenter/Program.h
Normal file
234
src/node/Fermenter/Program.h
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
#ifndef PROGRAM_H
|
||||||
|
#define PROGRAM_H
|
||||||
|
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
#include "pid.h"
|
||||||
|
#include "ProgramPoint.h"
|
||||||
|
|
||||||
|
class Program {
|
||||||
|
|
||||||
|
String name = "";
|
||||||
|
|
||||||
|
int iterations = 0;
|
||||||
|
|
||||||
|
int iteration = 0;
|
||||||
|
|
||||||
|
size_t pointCount = 0;
|
||||||
|
|
||||||
|
ProgramPoint* pointList = nullptr;
|
||||||
|
|
||||||
|
ProgramPoint* point = nullptr;
|
||||||
|
|
||||||
|
bool running = false;
|
||||||
|
|
||||||
|
bool paused = false;
|
||||||
|
|
||||||
|
unsigned long pauseAlreadyProgressedMillis = 0;
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
name = "";
|
||||||
|
iterations = 0;
|
||||||
|
iteration = 0;
|
||||||
|
pointCount = 0;
|
||||||
|
if (pointList != nullptr) {
|
||||||
|
free(pointList);
|
||||||
|
pointList = nullptr;
|
||||||
|
}
|
||||||
|
point = nullptr;
|
||||||
|
running = false;
|
||||||
|
paused = false;
|
||||||
|
pauseAlreadyProgressedMillis = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
String wantedName = "";
|
||||||
|
|
||||||
|
bool start() {
|
||||||
|
if (pointList == nullptr || point == nullptr) {
|
||||||
|
Log.warn("No program loaded.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (running) {
|
||||||
|
Log.warn("Program already running.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Log.info("Program started: \"%s\"", name.c_str());
|
||||||
|
running = true;
|
||||||
|
iteration = 1;
|
||||||
|
point = pointList;
|
||||||
|
point->start(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool stop() {
|
||||||
|
if (pointList == nullptr || point == nullptr) {
|
||||||
|
Log.warn("No program loaded.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!running) {
|
||||||
|
Log.warn("Program not running.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
running = false;
|
||||||
|
pid.setTarget(0);
|
||||||
|
Log.info("Program stopped.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pause() {
|
||||||
|
if (pointList == nullptr || point == nullptr) {
|
||||||
|
Log.warn("No program loaded.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!running) {
|
||||||
|
Log.warn("Program not running.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (paused) {
|
||||||
|
Log.warn("Program already paused.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
paused = true;
|
||||||
|
pauseAlreadyProgressedMillis = point->getProgressMillis();
|
||||||
|
Log.info("Program paused.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool resume() {
|
||||||
|
if (pointList == nullptr || point == nullptr) {
|
||||||
|
Log.warn("No program loaded.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!running) {
|
||||||
|
Log.warn("Program not running.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!paused) {
|
||||||
|
Log.warn("Program not paused.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
paused = false;
|
||||||
|
point->start(pauseAlreadyProgressedMillis);
|
||||||
|
Log.info("Program resumed.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
if (!wantedName.isEmpty()) {
|
||||||
|
load(wantedName);
|
||||||
|
wantedName = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!running || paused || pointList == nullptr || point == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point->isComplete()) {
|
||||||
|
point = (point - pointList + 1) % pointCount + pointList;
|
||||||
|
if (point == pointList) {
|
||||||
|
if (iterations < 0) {
|
||||||
|
Log.info("Program repeat (endless)");
|
||||||
|
} else if (iteration < iterations) {
|
||||||
|
iteration++;
|
||||||
|
Log.info("Program iteration %d/%d", iteration, iterations);
|
||||||
|
} else {
|
||||||
|
running = false;
|
||||||
|
applyTemperature(0, true);
|
||||||
|
Log.info("Program terminated");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
point->start(0);
|
||||||
|
}
|
||||||
|
applyTemperature(point->getTemperature(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
static void applyTemperature(const double temperature, bool forceLog) {
|
||||||
|
static auto last = temperature;
|
||||||
|
forceLog |= abs(temperature - last) >= 1;
|
||||||
|
if (forceLog) {
|
||||||
|
last = temperature;
|
||||||
|
}
|
||||||
|
pid.setTarget(temperature, forceLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load(const String& programName) {
|
||||||
|
reset();
|
||||||
|
|
||||||
|
const String path = String("/program/") + programName + ".json";
|
||||||
|
Log.info("Loading program: %s", programName.c_str());
|
||||||
|
|
||||||
|
File file = LittleFS.open(path, "r");
|
||||||
|
if (!file) {
|
||||||
|
Log.error(" Can't open file: %s", path.c_str());
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonDocument json;
|
||||||
|
deserializeJson(json, file);
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
if (!json.is<JsonObject>()) {
|
||||||
|
Log.error(" Not a JsonObject.");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json["name"].is<const char*>()) {
|
||||||
|
Log.info(" %-10s %s", "name", json["name"].as<const char*>());
|
||||||
|
name = json["name"].as<const char*>();
|
||||||
|
} else {
|
||||||
|
Log.error(" Missing attribute: %s", "name");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json["iterations"].is<int>()) {
|
||||||
|
Log.info(" %-10s %d", "iterations", json["iterations"].as<int>());
|
||||||
|
iterations = json["iterations"].as<int>();
|
||||||
|
} else {
|
||||||
|
Log.error(" Missing attribute: %s", "iterations");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonArray& jsonPoints = json["points"].as<JsonArray>();
|
||||||
|
pointCount = jsonPoints.size();
|
||||||
|
if (pointCount > 0) {
|
||||||
|
Log.info(" %-10s %d", "points", pointCount);
|
||||||
|
pointList = static_cast<ProgramPoint*>(malloc(sizeof(ProgramPoint) * pointCount));
|
||||||
|
if (pointList == nullptr) {
|
||||||
|
Log.error("Failed to allocate ProgramPoint memory");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
point = pointList;
|
||||||
|
} else {
|
||||||
|
Log.error(" Missing attribute: %s", "points");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
for (JsonVariant point : jsonPoints) {
|
||||||
|
if (!pointList[index].load(point, index)) {
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.info("Program loaded: %s", path.c_str());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Program program;
|
||||||
|
|
||||||
|
#endif
|
||||||
130
src/node/Fermenter/ProgramPoint.h
Normal file
130
src/node/Fermenter/ProgramPoint.h
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
#ifndef PROGRAM_POINT_H
|
||||||
|
#define PROGRAM_POINT_H
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <patrix/mqtt.h>
|
||||||
|
|
||||||
|
inline String durationString(const unsigned long millis) {
|
||||||
|
const unsigned long seconds = millis / 1000;
|
||||||
|
const unsigned long minutes = seconds / 60;
|
||||||
|
const unsigned long hours = minutes / 60;
|
||||||
|
const unsigned long days = hours / 24;
|
||||||
|
char buffer[15];
|
||||||
|
if (days > 0) {
|
||||||
|
snprintf(buffer, sizeof buffer, "%d. %2d:%02d:%02d", days, hours % 24, minutes % 60, seconds % 60);
|
||||||
|
} else {
|
||||||
|
snprintf(buffer, sizeof buffer, "%d:%02d:%02d", hours % 24, minutes % 60, seconds % 60);
|
||||||
|
}
|
||||||
|
return {buffer};
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProgramPoint {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
int index = -1;
|
||||||
|
|
||||||
|
String name = "";
|
||||||
|
|
||||||
|
double startTemperature = 0;
|
||||||
|
|
||||||
|
double endTemperature = 0;
|
||||||
|
|
||||||
|
unsigned long startMillis = 0;
|
||||||
|
|
||||||
|
unsigned long durationMillis = 0;
|
||||||
|
|
||||||
|
bool load(const JsonVariant& point, const int index) {
|
||||||
|
reset();
|
||||||
|
|
||||||
|
this->index = index;
|
||||||
|
Log.info(" #%d:", index);
|
||||||
|
if (!point.is<JsonObject>()) {
|
||||||
|
Log.error(" Not a JsonObject");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point["name"].is<const char*>()) {
|
||||||
|
Log.info(" %-10s %s", "name", point["name"].as<const char*>());
|
||||||
|
name = point["name"].as<const char*>();
|
||||||
|
} else {
|
||||||
|
Log.error(" Missing attribute: %s", "name");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point["start"].is<double>()) {
|
||||||
|
Log.info(" %-10s %.1f%cC", "start", point["start"].as<double>(), 176);
|
||||||
|
startTemperature = point["start"].as<double>();
|
||||||
|
} else {
|
||||||
|
Log.error(" Missing attribute: %s", "start");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point["end"].is<double>()) {
|
||||||
|
Log.info(" %-10s %.1f%cC", "end", point["end"].as<double>(), 176);
|
||||||
|
endTemperature = point["end"].as<double>();
|
||||||
|
} else {
|
||||||
|
Log.error(" Missing attribute: %s", "end");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (point["seconds"].is<unsigned long>()) {
|
||||||
|
Log.info(" %-10s %d", "seconds", point["seconds"].as<unsigned long>());
|
||||||
|
durationMillis = point["seconds"].as<unsigned long>() * 1000;
|
||||||
|
} else if (point["minutes"].is<unsigned long>()) {
|
||||||
|
Log.info(" %-10s %d", "minutes", point["minutes"].as<unsigned long>());
|
||||||
|
durationMillis = point["minutes"].as<unsigned long>() * 60 * 1000;
|
||||||
|
} else if (point["hours"].is<unsigned long>()) {
|
||||||
|
Log.info(" %-10s %d", "hours", point["hours"].as<unsigned long>());
|
||||||
|
durationMillis = point["hours"].as<unsigned long>() * 60 * 60 * 1000;
|
||||||
|
} else if (point["days"].is<unsigned long>()) {
|
||||||
|
Log.info(" %-10s %d", "days", point["days"].as<unsigned long>());
|
||||||
|
durationMillis = point["days"].as<unsigned long>() * 24 * 60 * 60 * 1000;
|
||||||
|
} else {
|
||||||
|
Log.error(" Missing attribute: %s", "seconds/minutes/hours/days");
|
||||||
|
reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
index = -1;
|
||||||
|
name = "";
|
||||||
|
startTemperature = 0;
|
||||||
|
endTemperature = 0;
|
||||||
|
startMillis = 0;
|
||||||
|
durationMillis = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void start(const unsigned long alreadyProgressedMillis) {
|
||||||
|
startMillis = max(1UL, millis() - alreadyProgressedMillis);
|
||||||
|
if (alreadyProgressedMillis == 0) {
|
||||||
|
Log.info("Starting ProgramPoint: #%d %s, %.1f%cC -> %.1f%cC: \"%s\"", index, durationString(durationMillis), startTemperature, 176, endTemperature, 176, name.c_str());
|
||||||
|
} else {
|
||||||
|
Log.info("Resuming ProgramPoint: #%d %s, %.1f%cC -> %.1f%cC: \"%s\", resumeAt=%s", index, durationString(durationMillis), startTemperature, 176, endTemperature, 176, name.c_str(), durationString(alreadyProgressedMillis));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool isComplete() const {
|
||||||
|
return millis() - startMillis >= durationMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] unsigned long getProgressMillis() const {
|
||||||
|
return max(0UL, min(durationMillis, millis() - startMillis));
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] double getTemperature() const {
|
||||||
|
const auto progressMillis = getProgressMillis();
|
||||||
|
const auto progressRatio = static_cast<double>(progressMillis) / static_cast<double>(durationMillis);
|
||||||
|
return startTemperature + (endTemperature - startTemperature) * progressRatio;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#ifdef NODE_FERMENTER
|
#ifdef NODE_FERMENTER
|
||||||
|
|
||||||
#include "Fermenter.h"
|
#include "config.h"
|
||||||
|
#include "pid.h"
|
||||||
|
|
||||||
Config config("/config.json", configCollect, configApply);
|
Config config("/config.json", configCollect, configApply);
|
||||||
|
|
||||||
|
|||||||
12
src/node/Fermenter/config.h
Normal file
12
src/node/Fermenter/config.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef CONFIG_H
|
||||||
|
#define CONFIG_H
|
||||||
|
|
||||||
|
#include "patrix/Config.h"
|
||||||
|
|
||||||
|
extern Config config;
|
||||||
|
|
||||||
|
void configCollect(JsonDocument& json);
|
||||||
|
|
||||||
|
void configApply(JsonDocument& json);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#ifdef NODE_FERMENTER
|
#ifdef NODE_FERMENTER
|
||||||
|
|
||||||
#include "Fermenter.h"
|
#include "display.h"
|
||||||
|
#include "pid.h"
|
||||||
|
|
||||||
LedControl display(D7, D5, D8, 1);
|
LedControl display(D7, D5, D8, 1);
|
||||||
|
|
||||||
@ -16,8 +17,7 @@ void displayPrintf(const char* format, ...) {
|
|||||||
auto thisChar = *b;
|
auto thisChar = *b;
|
||||||
if (thisChar == 'z' || thisChar == 'Z') {
|
if (thisChar == 'z' || thisChar == 'Z') {
|
||||||
thisChar = '2';
|
thisChar = '2';
|
||||||
}
|
} else if (thisChar == 'i' || thisChar == 'I') {
|
||||||
else if (thisChar == 'i' || thisChar == 'I') {
|
|
||||||
thisChar = '1';
|
thisChar = '1';
|
||||||
}
|
}
|
||||||
const auto nextIsDot = *(b + 1) == '.';
|
const auto nextIsDot = *(b + 1) == '.';
|
||||||
@ -47,8 +47,7 @@ void displayLoop() {
|
|||||||
|
|
||||||
if (displayModifyTarget != 0) {
|
if (displayModifyTarget != 0) {
|
||||||
displayPrintf("ZIEL %4.1f", pid.getTarget());
|
displayPrintf("ZIEL %4.1f", pid.getTarget());
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
displayPrintf("%4.1f %4.1f", temperature.getValue(), pid.getTarget());
|
displayPrintf("%4.1f %4.1f", temperature.getValue(), pid.getTarget());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
14
src/node/Fermenter/display.h
Normal file
14
src/node/Fermenter/display.h
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef DISPLAY_H
|
||||||
|
#define DISPLAY_H
|
||||||
|
|
||||||
|
#include <LedControl.h>
|
||||||
|
|
||||||
|
extern unsigned long displayModifyTarget;
|
||||||
|
|
||||||
|
extern LedControl display;
|
||||||
|
|
||||||
|
void displayPrintf(const char* format, ...);
|
||||||
|
|
||||||
|
void displayLoop();
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,6 +1,9 @@
|
|||||||
#ifdef NODE_FERMENTER
|
#ifdef NODE_FERMENTER
|
||||||
|
|
||||||
#include "Fermenter.h"
|
#include "http.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "pid.h"
|
||||||
|
#include "Program.h"
|
||||||
|
|
||||||
void httpStatus(AsyncWebServerRequest* request) {
|
void httpStatus(AsyncWebServerRequest* request) {
|
||||||
JsonDocument json;
|
JsonDocument json;
|
||||||
@ -89,6 +92,43 @@ void httpConfigSet(AsyncWebServerRequest* request) {
|
|||||||
httpStatus(request);
|
httpStatus(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void httpProgramLoad(AsyncWebServerRequest* request) {
|
||||||
|
const auto nameParam = request->getParam("name");
|
||||||
|
if (nameParam == nullptr) {
|
||||||
|
Log.error("Missing parameter: name (1)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto nameString = nameParam->value();
|
||||||
|
if (nameString == nullptr) {
|
||||||
|
Log.error("Missing parameter: name (2)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
program.wantedName = nameString;
|
||||||
|
request->send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpProgramStart(AsyncWebServerRequest* request) {
|
||||||
|
program.start();
|
||||||
|
request->send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpProgramStop(AsyncWebServerRequest* request) {
|
||||||
|
program.stop();
|
||||||
|
request->send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpProgramPause(AsyncWebServerRequest* request) {
|
||||||
|
program.pause();
|
||||||
|
request->send(200);
|
||||||
|
}
|
||||||
|
|
||||||
|
void httpProgramResume(AsyncWebServerRequest* request) {
|
||||||
|
program.resume();
|
||||||
|
request->send(200);
|
||||||
|
}
|
||||||
|
|
||||||
void httpSetup2() {
|
void httpSetup2() {
|
||||||
server.on("/status", httpStatus);
|
server.on("/status", httpStatus);
|
||||||
server.on("/status/", httpStatus);
|
server.on("/status/", httpStatus);
|
||||||
@ -96,6 +136,16 @@ void httpSetup2() {
|
|||||||
server.on("/target/add/", httpTargetAdd);
|
server.on("/target/add/", httpTargetAdd);
|
||||||
server.on("/config/set", httpConfigSet);
|
server.on("/config/set", httpConfigSet);
|
||||||
server.on("/config/set/", httpConfigSet);
|
server.on("/config/set/", httpConfigSet);
|
||||||
|
server.on("/program/load", httpProgramLoad);
|
||||||
|
server.on("/program/load/", httpProgramLoad);
|
||||||
|
server.on("/program/start", httpProgramStart);
|
||||||
|
server.on("/program/start/", httpProgramStart);
|
||||||
|
server.on("/program/stop", httpProgramStop);
|
||||||
|
server.on("/program/stop/", httpProgramStop);
|
||||||
|
server.on("/program/pause", httpProgramPause);
|
||||||
|
server.on("/program/pause/", httpProgramPause);
|
||||||
|
server.on("/program/resume", httpProgramResume);
|
||||||
|
server.on("/program/resume/", httpProgramResume);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
12
src/node/Fermenter/http.h
Normal file
12
src/node/Fermenter/http.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef HTTP_H
|
||||||
|
#define HTTP_H
|
||||||
|
|
||||||
|
#include "patrix/http.h"
|
||||||
|
|
||||||
|
void httpStatus(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
|
void httpTargetAdd(AsyncWebServerRequest* request);
|
||||||
|
|
||||||
|
void httpSetup2();
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#ifdef NODE_FERMENTER
|
#ifdef NODE_FERMENTER
|
||||||
|
|
||||||
#include "Fermenter.h"
|
#include "pid.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
DS18B20 ds18b20(
|
DS18B20 ds18b20(
|
||||||
"DS18B20",
|
"DS18B20",
|
||||||
|
|||||||
20
src/node/Fermenter/pid.h
Normal file
20
src/node/Fermenter/pid.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef PID_H
|
||||||
|
#define PID_H
|
||||||
|
|
||||||
|
#include "patrix/DS18B20Sensor.h"
|
||||||
|
#include "patrix/PIDController.h"
|
||||||
|
#include "patrix/PWMOutput.h"
|
||||||
|
|
||||||
|
#define HEATER_POWER_W 30
|
||||||
|
|
||||||
|
extern DS18B20 ds18b20;
|
||||||
|
|
||||||
|
extern DS18B20Sensor temperature;
|
||||||
|
|
||||||
|
extern PWMOutput heater;
|
||||||
|
|
||||||
|
extern PIDController pid;
|
||||||
|
|
||||||
|
void addTarget(double delta);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,12 +1,14 @@
|
|||||||
#ifdef NODE_FERMENTER
|
#ifdef NODE_FERMENTER
|
||||||
|
|
||||||
#include "Fermenter.h"
|
#include "rotary.h"
|
||||||
|
#include "display.h"
|
||||||
Rotary rotary(D1, D6, rotaryCallback);
|
#include "pid.h"
|
||||||
|
|
||||||
void rotaryCallback(const int delta) {
|
void rotaryCallback(const int delta) {
|
||||||
addTarget(delta);
|
pid.addTarget(delta);
|
||||||
displayModifyTarget = millis();
|
displayModifyTarget = millis();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rotary rotary(D1, D6, rotaryCallback);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
8
src/node/Fermenter/rotary.h
Normal file
8
src/node/Fermenter/rotary.h
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#ifndef ROTARY_H
|
||||||
|
#define ROTARY_H
|
||||||
|
|
||||||
|
#include "patrix/Rotary.h"
|
||||||
|
|
||||||
|
extern Rotary rotary;
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -1,8 +1,9 @@
|
|||||||
#ifndef CONFIG_H
|
#ifndef PATRIX_CONFIG_H
|
||||||
#define CONFIG_H
|
#define PATRIX_CONFIG_H
|
||||||
|
|
||||||
#include <ArduinoJson.h>
|
#include <ArduinoJson.h>
|
||||||
#include <LittleFS.h>
|
#include <LittleFS.h>
|
||||||
|
|
||||||
#include "mqtt.h"
|
#include "mqtt.h"
|
||||||
|
|
||||||
class Config {
|
class Config {
|
||||||
|
|||||||
@ -90,9 +90,15 @@ public:
|
|||||||
return targetValue;
|
return targetValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
double setTarget(double target) {
|
double setTarget(const double newTargetValue) {
|
||||||
targetValue = max(minValue, min(maxValue, target));
|
return setTarget(newTargetValue, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
double setTarget(const double newTargetValue, const bool doLog) {
|
||||||
|
targetValue = max(minValue, min(maxValue, newTargetValue));
|
||||||
|
if (doLog) {
|
||||||
Log.info("PID %s = %.1f", targetName.c_str(), targetValue);
|
Log.info("PID %s = %.1f", targetName.c_str(), targetValue);
|
||||||
|
}
|
||||||
return targetValue;
|
return targetValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@
|
|||||||
#define WIFI_SSID "HappyNet"
|
#define WIFI_SSID "HappyNet"
|
||||||
#define WIFI_PASSWORD "1Grausame!Sackratte7"
|
#define WIFI_PASSWORD "1Grausame!Sackratte7"
|
||||||
#define NTP_SERVER "107.189.12.98" /* pool.ntp.org */
|
#define NTP_SERVER "107.189.12.98" /* pool.ntp.org */
|
||||||
#define BOOT_DELAY_SEC 5
|
#define BOOT_DELAY_SEC 1
|
||||||
|
|
||||||
auto wifiConnected = false;
|
auto wifiConnected = false;
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user