Compare commits

..

No commits in common. "387aecc02a78f8e9aa7ad4a5c34db1f4391ac505" and "674fafc318c78728fdb42de499772568257eea6f" have entirely different histories.

4 changed files with 38 additions and 210 deletions

View File

@ -3,18 +3,12 @@
#include <ESPAsyncWebServer.h> #include <ESPAsyncWebServer.h>
#include <LedControl.h> #include <LedControl.h>
#include <LittleFS.h> #include <LittleFS.h>
#include <ArduinoOTA.h>
#include "patrix/DS18B20Sensor.h" #include "patrix/DS18B20Sensor.h"
#include "patrix/PIDController.h" #include "patrix/PIDController.h"
#include "patrix/PWMOutput.h" #include "patrix/PWMOutput.h"
#include "patrix/Rotary.h"
#define HEATER_POWER_W 30 #define HEATER_POWER_W 30
#define TARGET_STORE_DELAY_MS 10000
void rotaryCallback(int delta);
AsyncWebServer server(80); AsyncWebServer server(80);
@ -22,151 +16,63 @@ DS18B20 ds18b20("DS18B20", D4);
DS18B20Sensor input(ds18b20, 0, ""); DS18B20Sensor input(ds18b20, 0, "");
PWMOutput heater(D2, "", 100); PWMOutput heater(D2, "");
PIDController pid("fermenter", input, heater, UNIT_TEMPERATURE_C, 0, 40, 500, 0.00000002, 0); PIDController pid("fermenter", input, heater, UNIT_TEMPERATURE_C, 500, 0.00000002, 0);
Rotary rotary(D1, D6, rotaryCallback); auto display = LedControl(13, 14, 15, 1);
LedControl display(D7, D5, D8, 1); void displayDecimal(int *digit, const double value) {
const auto integer = static_cast<int>(value);
auto displayModifyTarget = 0UL; const auto decimal = static_cast<int>((value - integer) * 10) % 10;
display.setDigit(0, (*digit)++, decimal, false);
double targetStored = NAN; display.setDigit(0, (*digit)++, integer % 10, true);
display.setDigit(0, (*digit)++, integer / 10 % 10, false);
auto targetMillis = 0UL;
void addTarget(double delta) {
pid.addTarget(delta);
if (targetStored != pid.getTarget()) {
targetMillis = millis();
} else {
targetMillis = 0;
}
}
void targetFileSetup() {
File file = LittleFS.open("target", "r");
if (!file) {
Log.error("Failed to load target");
return;
}
const String& string = file.readString();
file.close();
if (string == nullptr) {
Log.error("Target file empty");
return;
}
const auto value = string.toDouble();
if (isnan(value)) {
Log.error("Target file does not contain a double");
return;
}
pid.setTarget(value);
targetStored = value;
targetMillis = 0;
Log.info("Target loaded.");
}
void targetFileLoop() {
if (targetStored != pid.getTarget() && targetMillis != 0 && millis() - targetMillis >= TARGET_STORE_DELAY_MS) {
File file = LittleFS.open("target", "w");
if (!file) {
Log.error("Failed to store target");
return;
}
file.write(String(pid.getTarget()).c_str());
file.close();
targetStored = pid.getTarget();
targetMillis = 0;
Log.info("Target stored.");
}
}
void rotaryCallback(int delta) {
addTarget(delta);
displayModifyTarget = millis();
}
void displayPrintf(const char *format, ...) {
va_list args;
va_start(args, format);
char buffer[17];
vsnprintf(buffer, sizeof buffer, format, args);
int position = 0;
for (char *b = buffer; *b != 0 && b < buffer + sizeof buffer; b++) {
char thisChar = *b;
if (thisChar == 'z' || thisChar == 'Z') {
thisChar = '2';
} else if (thisChar == 'i' || thisChar == 'I') {
thisChar = '1';
}
const auto nextIsDot = *(b + 1) == '.';
display.setChar(0, 7 - position++, thisChar, nextIsDot);
if (nextIsDot) {
b++;
}
}
va_end(args);
} }
void displayLoop() { void displayLoop() {
const auto now = millis(); static unsigned long lastDisplayInit = 0;
if (lastDisplayInit == 0 || millis() - lastDisplayInit > 60 * 60 * 1000) {
static unsigned long lastInit = 0; lastDisplayInit = millis();
if (lastInit == 0 || now - lastInit >= 60 * 60 * 1000) {
lastInit = now;
display.shutdown(0, true); display.shutdown(0, true);
display.shutdown(0, false); display.shutdown(0, false);
display.setIntensity(0, 2); display.setIntensity(0, 4);
display.clearDisplay(0); display.clearDisplay(0);
} }
auto digit = 0;
if (displayModifyTarget != 0 && now - displayModifyTarget >= 2000) { displayDecimal(&digit, input.getValue());
displayModifyTarget = 0; digit++;
} digit++;
displayDecimal(&digit, pid.targetValue);
if (displayModifyTarget != 0) {
displayPrintf("ZIEL %4.1f", pid.getTarget());
} else {
displayPrintf("%4.1f %4.1f", input.getValue(), pid.getTarget());
}
} }
void httpStatus(AsyncWebServerRequest *request) { void httpStatus(AsyncWebServerRequest *request) {
char buffer[256]; char buffer[256];
snprintf(buffer, sizeof buffer, R"({"target": %f, "input": %f, "outputPercent": %f, "outputPowerW": %f})", pid.getTarget(), input.getValue(), heater.getPercent(), heater.getPercent() / 100.0 * HEATER_POWER_W); snprintf(buffer, sizeof buffer, R"({"target": %f, "input": %f, "outputPercent": %f, "outputPowerW": %f})", pid.targetValue, input.getValue(), heater.getPercent(), heater.getPercent() / 100.0 * HEATER_POWER_W);
request->send(200, "application/json", buffer); request->send(200, "application/json", buffer);
} }
void httpTargetAdd(AsyncWebServerRequest *request) { void httpTargetAdd(AsyncWebServerRequest *request) {
const auto param = request->getParam("delta"); const auto delta = request->getParam("delta");
if (param == nullptr) { if (delta == nullptr) {
Log.error("Missing parameter: delta (1)"); Log.error("Missing parameter: delta (1)");
return; return;
} }
const auto string = param->value(); const auto string = delta->value();
if (string == nullptr) { if (string == nullptr) {
Log.error("Missing parameter: delta (2)"); Log.error("Missing parameter: delta (2)");
return; return;
} }
const auto delta = string.toDouble(); const auto value = string.toDouble();
if (isnan(delta)) { if (isnan(value)) {
Log.error("Missing parameter: delta (3)"); Log.error("Missing parameter: delta (3)");
return; return;
} }
addTarget(delta); pid.targetValue = max(0.0, min(40.0, pid.targetValue + value));
Log.info("Set targetValue = %.1f%cC", pid.targetValue, 176);
httpStatus(request); httpStatus(request);
} }
@ -180,19 +86,15 @@ void httpNotFound(AsyncWebServerRequest *request) {
} }
void patrixSetup() { void patrixSetup() {
ds18b20.setup();
heater.setup();
pid.setup();
if (LittleFS.begin()) { if (LittleFS.begin()) {
Log.info("Filesystem mounted."); Log.info("Filesystem mounted.");
} else { } else {
Log.error("Failed to mount filesystem!"); Log.error("Failed to mount filesystem!");
} }
ds18b20.setup();
heater.setup();
rotary.setup();
targetFileSetup();
pid.setup();
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Origin", "*");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Methods", "GET, POST, PUT");
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type"); DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type");
@ -208,12 +110,7 @@ void patrixSetup() {
void patrixLoop() { void patrixLoop() {
ds18b20.loop(); ds18b20.loop();
input.loop(); input.loop();
rotary.loop();
targetFileLoop();
pid.loop(); pid.loop();
displayLoop();
} }
#endif #endif

View File

@ -20,10 +20,6 @@ class PIDController {
ArduPID controller; ArduPID controller;
double minValue;
double maxValue;
double p = 0; double p = 0;
double i = 0; double i = 0;
@ -36,12 +32,12 @@ class PIDController {
unsigned long lastSent = 0UL; unsigned long lastSent = 0UL;
double targetValue = 0;
public: public:
PIDController(String name, const IValueSensor& sensor, PWMOutput& pwmOutput, const char *unit, const double minValue, const double maxValue, const double p, const double i, const double d) double targetValue = 28;
: name(std::move(name)), input(sensor), output(pwmOutput), unit(unit), controller(), minValue(minValue), maxValue(maxValue), p(p), i(i), d(d) {
PIDController(String name, const IValueSensor& sensor, PWMOutput& pwmOutput, const char *unit, const double p, const double i, const double d)
: name(std::move(name)), input(sensor), output(pwmOutput), unit(unit), controller(), p(p), i(i), d(d) {
// //
} }
@ -69,20 +65,6 @@ public:
output.setPercent(outputPercent); output.setPercent(outputPercent);
} }
double addTarget(const double delta) {
return setTarget(targetValue + delta);
}
[[nodiscard]] double getTarget() const {
return targetValue;
}
double setTarget(double target) {
targetValue = max(minValue, min(maxValue, target));
Log.info("PID \"%s\" target = %.1f", name.c_str(), targetValue);
return targetValue;
}
}; };
#endif #endif

View File

@ -10,21 +10,18 @@ class PWMOutput {
String name; String name;
uint32_t frequency = 0;
int value = 0; int value = 0;
double percent = 0; double percent = 0;
public: public:
explicit PWMOutput(const uint8_t gpio, String name, uint32_t frequency) : gpio(gpio), name(std::move(name)), frequency(frequency) { explicit PWMOutput(const uint8_t gpio, String name): gpio(gpio), name(std::move(name)) {
// //
} }
void setup() { void setup() {
analogWriteResolution(CONTROL_PWM_BITS); analogWriteResolution(CONTROL_PWM_BITS);
analogWriteFreq(frequency);
setValue(0); setValue(0);
} }
@ -34,8 +31,8 @@ public:
analogWrite(gpio, value); analogWrite(gpio, value);
} }
void setPercent(const double newPercent) { void setPercent(const double percent) {
setValue(static_cast<int>(newPercent / 100.0 * CONTROL_PWM_MAX)); setValue(static_cast<int>(percent / 100.0 * CONTROL_PWM_MAX));
} }
[[nodiscard]] double getPercent() const { [[nodiscard]] double getPercent() const {

View File

@ -1,48 +0,0 @@
#ifndef HELLIGKEIT_ROTARY_H
#define HELLIGKEIT_ROTARY_H
#include <Arduino.h>
class Rotary {
public:
typedef void (*callback_t)(int delta);
private:
const uint8_t pinCLK;
const uint8_t pinDT;
bool lastCLK = false;
callback_t callback;
public:
Rotary(const uint8_t pinCLK, const uint8_t pinDT, callback_t callback) : pinCLK(pinCLK), pinDT(pinDT), callback(callback) {
//
}
void setup() {
pinMode(pinCLK, INPUT_PULLUP);
pinMode(pinDT, INPUT_PULLUP);
lastCLK = digitalRead(pinCLK) == HIGH;
}
void loop() {
const bool currentCLK = digitalRead(pinCLK) == HIGH;
if (currentCLK != lastCLK && currentCLK) {
if ((digitalRead(pinDT) == HIGH) != currentCLK) {
callback(+1);
} else {
callback(-1);
}
}
lastCLK = currentCLK;
}
};
#endif