Compare commits
No commits in common. "387aecc02a78f8e9aa7ad4a5c34db1f4391ac505" and "674fafc318c78728fdb42de499772568257eea6f" have entirely different histories.
387aecc02a
...
674fafc318
@ -3,170 +3,76 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <LedControl.h>
|
||||
#include <LittleFS.h>
|
||||
#include <ArduinoOTA.h>
|
||||
|
||||
#include "patrix/DS18B20Sensor.h"
|
||||
#include "patrix/PIDController.h"
|
||||
#include "patrix/PWMOutput.h"
|
||||
#include "patrix/Rotary.h"
|
||||
|
||||
#define HEATER_POWER_W 30
|
||||
|
||||
#define TARGET_STORE_DELAY_MS 10000
|
||||
|
||||
void rotaryCallback(int delta);
|
||||
|
||||
AsyncWebServer server(80);
|
||||
|
||||
DS18B20 ds18b20("DS18B20", D4);
|
||||
|
||||
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);
|
||||
|
||||
auto displayModifyTarget = 0UL;
|
||||
|
||||
double targetStored = NAN;
|
||||
|
||||
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 displayDecimal(int *digit, const double value) {
|
||||
const auto integer = static_cast<int>(value);
|
||||
const auto decimal = static_cast<int>((value - integer) * 10) % 10;
|
||||
display.setDigit(0, (*digit)++, decimal, false);
|
||||
display.setDigit(0, (*digit)++, integer % 10, true);
|
||||
display.setDigit(0, (*digit)++, integer / 10 % 10, false);
|
||||
}
|
||||
|
||||
void displayLoop() {
|
||||
const auto now = millis();
|
||||
|
||||
static unsigned long lastInit = 0;
|
||||
if (lastInit == 0 || now - lastInit >= 60 * 60 * 1000) {
|
||||
lastInit = now;
|
||||
static unsigned long lastDisplayInit = 0;
|
||||
if (lastDisplayInit == 0 || millis() - lastDisplayInit > 60 * 60 * 1000) {
|
||||
lastDisplayInit = millis();
|
||||
display.shutdown(0, true);
|
||||
display.shutdown(0, false);
|
||||
display.setIntensity(0, 2);
|
||||
display.setIntensity(0, 4);
|
||||
display.clearDisplay(0);
|
||||
}
|
||||
|
||||
if (displayModifyTarget != 0 && now - displayModifyTarget >= 2000) {
|
||||
displayModifyTarget = 0;
|
||||
}
|
||||
|
||||
if (displayModifyTarget != 0) {
|
||||
displayPrintf("ZIEL %4.1f", pid.getTarget());
|
||||
} else {
|
||||
displayPrintf("%4.1f %4.1f", input.getValue(), pid.getTarget());
|
||||
}
|
||||
auto digit = 0;
|
||||
displayDecimal(&digit, input.getValue());
|
||||
digit++;
|
||||
digit++;
|
||||
displayDecimal(&digit, pid.targetValue);
|
||||
}
|
||||
|
||||
void httpStatus(AsyncWebServerRequest *request) {
|
||||
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);
|
||||
}
|
||||
|
||||
void httpTargetAdd(AsyncWebServerRequest *request) {
|
||||
const auto param = request->getParam("delta");
|
||||
if (param == nullptr) {
|
||||
const auto delta = request->getParam("delta");
|
||||
if (delta == nullptr) {
|
||||
Log.error("Missing parameter: delta (1)");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto string = param->value();
|
||||
const auto string = delta->value();
|
||||
if (string == nullptr) {
|
||||
Log.error("Missing parameter: delta (2)");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto delta = string.toDouble();
|
||||
if (isnan(delta)) {
|
||||
const auto value = string.toDouble();
|
||||
if (isnan(value)) {
|
||||
Log.error("Missing parameter: delta (3)");
|
||||
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);
|
||||
}
|
||||
@ -180,19 +86,15 @@ void httpNotFound(AsyncWebServerRequest *request) {
|
||||
}
|
||||
|
||||
void patrixSetup() {
|
||||
ds18b20.setup();
|
||||
heater.setup();
|
||||
pid.setup();
|
||||
|
||||
if (LittleFS.begin()) {
|
||||
Log.info("Filesystem mounted.");
|
||||
} else {
|
||||
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-Methods", "GET, POST, PUT");
|
||||
DefaultHeaders::Instance().addHeader("Access-Control-Allow-Headers", "Content-Type");
|
||||
@ -208,12 +110,7 @@ void patrixSetup() {
|
||||
void patrixLoop() {
|
||||
ds18b20.loop();
|
||||
input.loop();
|
||||
rotary.loop();
|
||||
|
||||
targetFileLoop();
|
||||
pid.loop();
|
||||
|
||||
displayLoop();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@ -20,10 +20,6 @@ class PIDController {
|
||||
|
||||
ArduPID controller;
|
||||
|
||||
double minValue;
|
||||
|
||||
double maxValue;
|
||||
|
||||
double p = 0;
|
||||
|
||||
double i = 0;
|
||||
@ -36,12 +32,12 @@ class PIDController {
|
||||
|
||||
unsigned long lastSent = 0UL;
|
||||
|
||||
double targetValue = 0;
|
||||
|
||||
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)
|
||||
: name(std::move(name)), input(sensor), output(pwmOutput), unit(unit), controller(), minValue(minValue), maxValue(maxValue), p(p), i(i), d(d) {
|
||||
double targetValue = 28;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -10,21 +10,18 @@ class PWMOutput {
|
||||
|
||||
String name;
|
||||
|
||||
uint32_t frequency = 0;
|
||||
|
||||
int value = 0;
|
||||
|
||||
double percent = 0;
|
||||
|
||||
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() {
|
||||
analogWriteResolution(CONTROL_PWM_BITS);
|
||||
analogWriteFreq(frequency);
|
||||
setValue(0);
|
||||
}
|
||||
|
||||
@ -34,8 +31,8 @@ public:
|
||||
analogWrite(gpio, value);
|
||||
}
|
||||
|
||||
void setPercent(const double newPercent) {
|
||||
setValue(static_cast<int>(newPercent / 100.0 * CONTROL_PWM_MAX));
|
||||
void setPercent(const double percent) {
|
||||
setValue(static_cast<int>(percent / 100.0 * CONTROL_PWM_MAX));
|
||||
}
|
||||
|
||||
[[nodiscard]] double getPercent() const {
|
||||
|
||||
@ -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
|
||||
Loading…
Reference in New Issue
Block a user