summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpoek <maciek09874@gmail.com>2026-06-08 00:34:27 +0200
committerGitHub <noreply@github.com>2026-06-08 00:34:27 +0200
commit0f42e7895afd0f1029488dd0b0613fe89194b8cb (patch)
tree9403e109e12962bd6ad5dbf15f316d411d44aedc
parent70dd357e2841bed77d37316833881bfb72cf60f0 (diff)
parent60c65d9ac75c989deb4b797622c689381691831d (diff)
Merge pull request #1 from opekk/feat/temp-sensors
-rw-r--r--include/config.h37
-rw-r--r--platformio.ini5
-rw-r--r--src/main.cpp427
3 files changed, 406 insertions, 63 deletions
diff --git a/include/config.h b/include/config.h
index 3cd6bca..b9e5d0f 100644
--- a/include/config.h
+++ b/include/config.h
@@ -1,20 +1,37 @@
#ifndef CONFIG_H
#define CONFIG_H
-#define TFT_CS 37
-#define TFT_DC 36
-#define TFT_RST 35
-#define TFT_MOSI 34
-#define TFT_SCLK 33
+// ST7735S 128x160
+#define TFT_CS 37
+#define TFT_DC 36
+#define TFT_RST 35
+#define TFT_MOSI 34
+#define TFT_SCLK 33
+#define TFT_BL 38
+#define TFT_BL_ON HIGH
+#define TFT_ROTATION 0
+
+// Przyciski zwieraja wejscie do GND (uzywany jest INPUT_PULLUP).
+#define BUTTON_UP 3
+#define BUTTON_DOWN 5
+#define BUTTON_SELECT 7
+#define BUTTON_EXIT 9
+#define BUTTON_CONFIRM BUTTON_SELECT
#define SERVO_PIN 13
#define INTERNAL_BUS 17
-#define CURRENT_BUS 16
+#define EXTERNAL_BUS 16
+
+// Kalibracja DS3240 270 stopni.
+#define SERVO_MIN_US 500
+#define SERVO_MAX_US 2500
+
+#define SENSOR_INTERVAL_MS 5000UL
+#define BUTTON_DEBOUNCE_MS 35UL
-#define BUTTON_UP 3
-#define BUTTON_DOWN 5
-#define BUTTON_CONFIRM 7
-#define BUTTON_EXIT 9
+#define DEFAULT_MIN_INTERNAL_C 50.5F
+#define DEFAULT_MAX_EXTERNAL_C 90.0F
+#define DEFAULT_HYSTERESIS_C 5.0F
#endif
diff --git a/platformio.ini b/platformio.ini
index 8c7f43a..31416c6 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -14,14 +14,11 @@ board = lolin_s2_mini
framework = arduino
lib_deps =
adafruit/Adafruit GFX Library@^1.12.0
- adafruit/Adafruit SSD1306@^2.5.13
- mbed-aluqard/arduino@0.0.0+sha.3b83fc30bbdf
- mbed-cognoscan/Adafruit_ST7735@0.0.0+sha.20134e1718b2
adafruit/Adafruit ST7735 and ST7789 Library@^1.11.0
madhephaestus/ESP32Servo @ ^3.0.5
arduinogetstarted/ezButton@^1.0.6
paulStoffregen/OneWire@^2.3.0
- milesburton/DallasTemperature@^4.0.1
+ milesburton/DallasTemperature@^4.0.1
build_flags =
-Iinclude
-D ARDUINO_USB_CDC_ON_BOOT=1
diff --git a/src/main.cpp b/src/main.cpp
index ef8a702..caa2da7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,76 +1,405 @@
#include <Arduino.h>
-#include "config.h"
-
#include <Adafruit_GFX.h>
-#include <Adafruit_ST7789.h>
+#include <Adafruit_ST7735.h>
+#include <DallasTemperature.h>
+#include <ESP32Servo.h>
+#include <OneWire.h>
+#include <Preferences.h>
#include <SPI.h>
-#include <ESP32Servo.h>
#include <ezButton.h>
-
-/* 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);
+
+ezButton buttonUp(BUTTON_UP);
+ezButton buttonDown(BUTTON_DOWN);
+ezButton buttonSelect(BUTTON_SELECT);
+ezButton buttonExit(BUTTON_EXIT);
+
+enum class MenuItem : uint8_t {
+ MinInternal,
+ MaxExternal,
+ Hysteresis,
+ Count
+};
+
+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 = 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;
+
+unsigned long conversionStartedAt = 0;
+unsigned long lastSensorRequestAt = 0;
+uint16_t sensorConversionMs = 750;
+
+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;
+}
+
+float sanitizeSetting(float value, float fallback, float minimum,
+ float maximum) {
+ if (!isfinite(value)) {
+ return fallback;
+ }
+ return constrain(value, minimum, maximum);
+}
+
+void loadSettings() {
+ // Tryb read-write tworzy przestrzen nazw przy pierwszym uruchomieniu.
+ if (!preferences.begin("thermostat", false)) {
+ settingsStorageOk = false;
+ return;
+ }
- */
+ 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();
-Adafruit_ST7789 tft = Adafruit_ST7789(&SPI, TFT_CS, TFT_DC, TFT_RST);
-Servo myServo;
+ 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;
+}
-float temp_min_internal;
-float temp_max_external;
+void serviceSensors() {
+ const unsigned long now = millis();
-float temp_cur_internal;
-float temp_cur_external;
+ 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");
-void setupDisplay(){
+ 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());
+
+ 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);
+}
- 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");
+void setupButtons() {
+ buttonUp.setDebounceTime(BUTTON_DEBOUNCE_MS);
+ buttonDown.setDebounceTime(BUTTON_DEBOUNCE_MS);
+ buttonSelect.setDebounceTime(BUTTON_DEBOUNCE_MS);
+ buttonExit.setDebounceTime(BUTTON_DEBOUNCE_MS);
+}
- 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");
+void setupServo() {
+ valveServo.setPeriodHertz(50);
+ valveServo.attach(SERVO_PIN, SERVO_MIN_US, SERVO_MAX_US);
+ valveServo.writeMicroseconds(SERVO_MIN_US);
+ valvePercent = 0;
+}
- tft.setCursor(0, 180);
- tft.print("HYST: 5");
- tft.setCursor(0, 215);
- tft.print("VALVE: 70%");
+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.setTextColor(ST77XX_WHITE, ST77XX_BLACK);
+ tft.setCursor(2, 3);
+ tft.print("WEW:");
+ tft.setTextSize(2);
+ tft.setCursor(35, 1);
+ printTemperature(tempInternal, internalSensorOk);
+
+ tft.setTextSize(1);
+ tft.setCursor(2, 24);
+ tft.print("ZEW:");
+ tft.setTextSize(2);
+ tft.setCursor(35, 22);
+ printTemperature(tempExternal, externalSensorOk);
+
+ 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<int8_t>(selectedItem);
+ const int8_t count = static_cast<int8_t>(MenuItem::Count);
+ selected = (selected + direction + count) % count;
+ selectedItem = static_cast<MenuItem>(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();
+ }
}