From 60c65d9ac75c989deb4b797622c689381691831d Mon Sep 17 00:00:00 2001 From: opekk Date: Sat, 6 Jun 2026 22:30:13 +0200 Subject: v1 --- src/main.cpp | 434 ++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 367 insertions(+), 67 deletions(-) (limited to 'src/main.cpp') diff --git a/src/main.cpp b/src/main.cpp index ef36f0b..caa2da7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,105 +1,405 @@ #include -#include "config.h" - #include -#include +#include +#include +#include +#include +#include #include -#include #include -#include -#include -/* todo - * pobierac temp z onewire (wybierz jakes piny) i aktualizowac w loopie co np 10 sekund. - * - * po wcisnieciu jakiegos guzika jest powinno być wybudzenie wyświetlacza. - * przyciski up/down zaznaczam gdy chce zmienić histereze, temp int lub temp ext. po wybraniu kliknąć - * confirm i wtedy up/down do zmiany temeratury, po zakończeniu exit. Do obsługi przycisków masz ezButton. - * - * zasada będzie taka, że jak jest mniej niż min temp internal to serwo zamyka zupełnie zawór. - * jeśli jest między min internal a max external to zawór otworzony w 70%. - * Jak więcej niż max external to zawór otworzony na 100%. - * - * serwo ma 270 stopni, ale uwzględniając przekładnie 3 stopnie na serwie to 1 stopień na zaworze. - * 0 stopni to zawór zamknięty (0%) - * 270 stopni to 90 stopni na zaworze, czyli jest otwarty w 100%. +#include "config.h" + +Adafruit_ST7735 tft(&SPI, TFT_CS, TFT_DC, TFT_RST); +Servo valveServo; +Preferences preferences; - */ +OneWire externalWire(EXTERNAL_BUS); +OneWire internalWire(INTERNAL_BUS); +DallasTemperature internalSensor(&internalWire); +DallasTemperature externalSensor(&externalWire); -Adafruit_ST7789 tft = Adafruit_ST7789(&SPI, TFT_CS, TFT_DC, TFT_RST); -Servo myServo; +ezButton buttonUp(BUTTON_UP); +ezButton buttonDown(BUTTON_DOWN); +ezButton buttonSelect(BUTTON_SELECT); +ezButton buttonExit(BUTTON_EXIT); -OneWire current_external_wire(EXTERNAL_BUS); -OneWire current_internal_wire(INTERNAL_BUS); +enum class MenuItem : uint8_t { + MinInternal, + MaxExternal, + Hysteresis, + Count +}; -DallasTemperature current_internal_sensor(¤t_internal_wire); -DallasTemperature current_external_sensor(¤t_external_wire); +struct Settings { + float minInternal = DEFAULT_MIN_INTERNAL_C; + float maxExternal = DEFAULT_MAX_EXTERNAL_C; + float hysteresis = DEFAULT_HYSTERESIS_C; +}; +Settings settings; +Settings settingsBeforeEdit; -float tempInternal; -float tempCurrent; +float tempInternal = DEVICE_DISCONNECTED_C; +float tempExternal = DEVICE_DISCONNECTED_C; +bool internalSensorOk = false; +bool externalSensorOk = false; +bool sensorConversionInProgress = false; +bool editing = false; +bool displayDirty = true; +bool settingsStorageOk = true; +MenuItem selectedItem = MenuItem::MinInternal; +uint8_t valvePercent = 0; -float temp_min_internal; -float temp_max_external; +unsigned long conversionStartedAt = 0; +unsigned long lastSensorRequestAt = 0; +uint16_t sensorConversionMs = 750; -float temp_cur_internal; -float temp_cur_external; +constexpr float MIN_SETTING_C = -40.0F; +constexpr float MAX_SETTING_C = 120.0F; +constexpr float MAX_HYSTERESIS_C = 20.0F; +constexpr float SETTING_STEP_C = 0.5F; +bool isValidTemperature(float temperature) { + return temperature != DEVICE_DISCONNECTED_C && isfinite(temperature) && + temperature >= -55.0F && temperature <= 125.0F; +} -void setupSensors(){ - internal_sensor.begin(); - external_sensor.begin(); +float sanitizeSetting(float value, float fallback, float minimum, + float maximum) { + if (!isfinite(value)) { + return fallback; + } + return constrain(value, minimum, maximum); +} - Serial.print("Searching for devices."); - - int internal_sensor_count = current_internal_sensor.getDeviceCount(); - int external_sensor_count = current_external_sensor.getDeviceCount(); +void loadSettings() { + // Tryb read-write tworzy przestrzen nazw przy pierwszym uruchomieniu. + if (!preferences.begin("thermostat", false)) { + settingsStorageOk = false; + return; + } - Serial.print("Internal sensors found: "); - Serial.println(internal_sensor_count); + settings.minInternal = + preferences.getFloat("minInternal", DEFAULT_MIN_INTERNAL_C); + settings.maxExternal = + preferences.getFloat("maxExternal", DEFAULT_MAX_EXTERNAL_C); + settings.hysteresis = + preferences.getFloat("hysteresis", DEFAULT_HYSTERESIS_C); + preferences.end(); - Serial.print("External sensors found: "); - Serial.println(external_sensor_count); + settings.minInternal = sanitizeSetting(settings.minInternal, + DEFAULT_MIN_INTERNAL_C, MIN_SETTING_C, + MAX_SETTING_C); + settings.maxExternal = sanitizeSetting(settings.maxExternal, + DEFAULT_MAX_EXTERNAL_C, MIN_SETTING_C, + MAX_SETTING_C); + settings.hysteresis = sanitizeSetting( + settings.hysteresis, DEFAULT_HYSTERESIS_C, 0.0F, MAX_HYSTERESIS_C); +} + +bool saveSettings() { + if (!preferences.begin("thermostat", false)) { + return false; + } + + bool saved = true; + saved &= preferences.putFloat("minInternal", settings.minInternal) == + sizeof(settings.minInternal); + saved &= preferences.putFloat("maxExternal", settings.maxExternal) == + sizeof(settings.maxExternal); + saved &= preferences.putFloat("hysteresis", settings.hysteresis) == + sizeof(settings.hysteresis); + preferences.end(); + return saved; +} + +void setValvePercent(uint8_t targetPercent) { + targetPercent = constrain(targetPercent, 0, 100); + if (targetPercent == valvePercent) { + return; + } + + valvePercent = targetPercent; + const int pulseWidth = + map(valvePercent, 0, 100, SERVO_MIN_US, SERVO_MAX_US); + valveServo.writeMicroseconds(pulseWidth); + displayDirty = true; + + Serial.print("Valve position: "); + Serial.print(valvePercent); + Serial.println("%"); +} + +void updateValveControl() { + if (!internalSensorOk || !externalSensorOk) { + setValvePercent(0); + return; + } + + if (tempInternal < settings.minInternal) { + setValvePercent(0); + return; + } + + // Po zamknieciu zawor otwiera sie dopiero po wyjsciu ponad histereze. + if (valvePercent == 0 && + tempInternal < settings.minInternal + settings.hysteresis) { + return; + } + + if (tempExternal > settings.maxExternal) { + setValvePercent(100); + return; + } + + // Z pozycji 100% wraca dopiero po spadku ponizej progu z histereza. + if (valvePercent == 100 && + tempExternal > settings.maxExternal - settings.hysteresis) { + return; + } + + setValvePercent(70); +} + +void requestTemperatures() { + internalSensor.requestTemperatures(); + externalSensor.requestTemperatures(); + conversionStartedAt = millis(); + lastSensorRequestAt = conversionStartedAt; + sensorConversionInProgress = true; +} + +void serviceSensors() { + const unsigned long now = millis(); + + if (sensorConversionInProgress && + now - conversionStartedAt >= sensorConversionMs) { + tempInternal = internalSensor.getTempCByIndex(0); + tempExternal = externalSensor.getTempCByIndex(0); + internalSensorOk = isValidTemperature(tempInternal); + externalSensorOk = isValidTemperature(tempExternal); + sensorConversionInProgress = false; + displayDirty = true; + + Serial.print("Internal: "); + internalSensorOk ? Serial.print(tempInternal, 1) : Serial.print("ERROR"); + Serial.print(" C, external: "); + externalSensorOk ? Serial.print(tempExternal, 1) : Serial.print("ERROR"); + Serial.println(" C"); + updateValveControl(); + } + + if (!sensorConversionInProgress && + now - lastSensorRequestAt >= SENSOR_INTERVAL_MS) { + requestTemperatures(); + } } +void setupSensors() { + internalSensor.begin(); + externalSensor.begin(); + internalSensor.setResolution(12); + externalSensor.setResolution(12); + internalSensor.setWaitForConversion(false); + externalSensor.setWaitForConversion(false); + sensorConversionMs = internalSensor.millisToWaitForConversion(); + const uint16_t externalConversionMs = + externalSensor.millisToWaitForConversion(); + if (externalConversionMs > sensorConversionMs) { + sensorConversionMs = externalConversionMs; + } + sensorConversionMs += 25; + + Serial.print("Internal sensors found: "); + Serial.println(internalSensor.getDeviceCount()); + Serial.print("External sensors found: "); + Serial.println(externalSensor.getDeviceCount()); -void setupDisplay(){ + requestTemperatures(); +} +void setupDisplay() { + pinMode(TFT_BL, OUTPUT); + digitalWrite(TFT_BL, TFT_BL_ON); SPI.begin(TFT_SCLK, -1, TFT_MOSI, TFT_CS); - tft.init(240, 240); - tft.fillScreen(ST77XX_BLACK); - - tft.setTextColor(ST77XX_CYAN); - tft.setTextSize(3); + tft.initR(INITR_BLACKTAB); + tft.setRotation(TFT_ROTATION); + tft.fillScreen(ST77XX_BLACK); + tft.setTextWrap(false); +} + +void setupButtons() { + buttonUp.setDebounceTime(BUTTON_DEBOUNCE_MS); + buttonDown.setDebounceTime(BUTTON_DEBOUNCE_MS); + buttonSelect.setDebounceTime(BUTTON_DEBOUNCE_MS); + buttonExit.setDebounceTime(BUTTON_DEBOUNCE_MS); +} + +void setupServo() { + valveServo.setPeriodHertz(50); + valveServo.attach(SERVO_PIN, SERVO_MIN_US, SERVO_MAX_US); + valveServo.writeMicroseconds(SERVO_MIN_US); + valvePercent = 0; +} + +void printTemperature(float temperature, bool valid) { + if (valid) { + tft.print(temperature, 1); + tft.print(" C"); + } else { + tft.print("---.- C"); + } +} + +void drawSettingRow(int16_t y, MenuItem item, const char* label, float value) { + const bool selected = selectedItem == item; + const uint16_t background = + selected ? (editing ? ST77XX_RED : ST77XX_BLUE) : ST77XX_BLACK; + const uint16_t foreground = selected ? ST77XX_WHITE : ST77XX_CYAN; + + tft.fillRect(0, y, tft.width(), 18, background); + tft.setTextColor(foreground, background); + tft.setCursor(2, y + 5); + tft.print(label); + tft.setCursor(72, y + 5); + tft.print(value, 1); + tft.print("C"); +} + +void drawDisplay() { + tft.fillScreen(ST77XX_BLACK); + tft.setTextSize(1); - tft.setCursor(0, 0); - tft.print("TEMP INTERNAL"); - tft.setCursor(0, 25); - tft.print("min: 50,5"); - tft.setCursor(0, 50); - tft.print("cur: 67,1"); + tft.setTextColor(ST77XX_WHITE, ST77XX_BLACK); + tft.setCursor(2, 3); + tft.print("WEW:"); + tft.setTextSize(2); + tft.setCursor(35, 1); + printTemperature(tempInternal, internalSensorOk); - tft.setCursor(0, 90); - tft.print("TEMP EXTERNAL"); - tft.setCursor(0, 115); - tft.print("max: 90,0"); - tft.setCursor(0, 140); - tft.print("cur: 80,5"); + tft.setTextSize(1); + tft.setCursor(2, 24); + tft.print("ZEW:"); + tft.setTextSize(2); + tft.setCursor(35, 22); + printTemperature(tempExternal, externalSensorOk); - tft.setCursor(0, 180); - tft.print("HYST: 5"); - tft.setCursor(0, 215); - tft.print("VALVE: 70%"); + tft.setTextSize(1); + drawSettingRow(47, MenuItem::MinInternal, "Min wew", settings.minInternal); + drawSettingRow(67, MenuItem::MaxExternal, "Max zew", settings.maxExternal); + drawSettingRow(87, MenuItem::Hysteresis, "Hister.", settings.hysteresis); + + tft.setTextColor(ST77XX_GREEN, ST77XX_BLACK); + tft.setTextSize(2); + tft.setCursor(2, 110); + tft.print("ZAWOR:"); + tft.setCursor(80, 110); + tft.print(valvePercent); + tft.print("%"); + + tft.setTextSize(1); + tft.setTextColor(settingsStorageOk ? ST77XX_WHITE : ST77XX_RED, + ST77XX_BLACK); + tft.setCursor(2, 137); + if (!settingsStorageOk) { + tft.print("BLAD ZAPISU"); + } else { + tft.print(editing ? "UP/DN zmien SEL zap" + : "UP/DN wybor SEL edit"); + } + tft.setCursor(2, 150); + tft.print(editing ? "EXIT anuluj" : "Blad czujnika = 0%"); + + displayDirty = false; +} + +float& selectedSetting() { + switch (selectedItem) { + case MenuItem::MinInternal: + return settings.minInternal; + case MenuItem::MaxExternal: + return settings.maxExternal; + case MenuItem::Hysteresis: + return settings.hysteresis; + default: + return settings.minInternal; + } +} + +void changeSelectedSetting(float delta) { + float& value = selectedSetting(); + const float maximum = + selectedItem == MenuItem::Hysteresis ? MAX_HYSTERESIS_C : MAX_SETTING_C; + const float minimum = + selectedItem == MenuItem::Hysteresis ? 0.0F : MIN_SETTING_C; + value = sanitizeSetting(value + delta, value, minimum, maximum); + displayDirty = true; +} + +void moveSelection(int8_t direction) { + int8_t selected = static_cast(selectedItem); + const int8_t count = static_cast(MenuItem::Count); + selected = (selected + direction + count) % count; + selectedItem = static_cast(selected); + displayDirty = true; } +void serviceButtons() { + buttonUp.loop(); + buttonDown.loop(); + buttonSelect.loop(); + buttonExit.loop(); + + if (buttonUp.isPressed()) { + editing ? changeSelectedSetting(SETTING_STEP_C) : moveSelection(-1); + } + + if (buttonDown.isPressed()) { + editing ? changeSelectedSetting(-SETTING_STEP_C) : moveSelection(1); + } + + if (buttonSelect.isPressed()) { + if (editing) { + settingsStorageOk = saveSettings(); + if (settingsStorageOk) { + editing = false; + updateValveControl(); + } + } else { + settingsBeforeEdit = settings; + editing = true; + } + displayDirty = true; + } + + if (buttonExit.isPressed() && editing) { + settings = settingsBeforeEdit; + editing = false; + displayDirty = true; + updateValveControl(); + } +} void setup() { Serial.begin(115200); - delay(100); + delay(100); + + loadSettings(); setupDisplay(); + setupButtons(); + setupServo(); + setupSensors(); + drawDisplay(); } void loop() { + serviceButtons(); + serviceSensors(); + + if (displayDirty) { + drawDisplay(); + } } -- cgit v1.2.3