diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..3ce1694 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,76 @@ +# Repository Guidelines + +## Project Structure & Module Organization + +This repository contains a small ESP8266/ESP32 Arduino project for monitoring water level with conductive electrodes. + +- `arduino/arduino.ino` contains the Arduino sketch and main application logic. +- `README.md` gives the high-level project purpose. +- `LICENSE` contains licensing information. +- `.agents/` is reserved for agent/tooling metadata and should not be treated as firmware source. + +Keep Arduino source files inside the `arduino/` sketch directory so Arduino IDE can open and build the project correctly. + +## Build, Test, and Development Commands + +Use Arduino IDE or `arduino-cli` with the ESP8266 or ESP32 board package installed. + +Examples: + +```sh +arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2 arduino +arduino-cli upload -p COM3 --fqbn esp8266:esp8266:nodemcuv2 arduino +arduino-cli monitor -p COM3 -c baudrate=115200 + +arduino-cli compile --fqbn esp32:esp32:esp32 arduino +arduino-cli upload -p COM3 --fqbn esp32:esp32:esp32 arduino +arduino-cli monitor -p COM3 -c baudrate=115200 +``` + +- `compile` verifies the sketch builds for the selected board. +- `upload` flashes the firmware to the connected board. +- `monitor` opens the serial console used by debug output. + +Adjust `--fqbn` and `COM3` for the actual board and port. + +## Coding Style & Naming Conventions + +Write C++ compatible with Arduino IDE and the ESP8266/ESP32 cores. Use two-space indentation, braces on the same line, and descriptive names. + +- Constants: uppercase or clearly prefixed names, for example `PIN_LEVEL1`, `SLEEP_TIME_SEC`. +- Structs and types: PascalCase, for example `WaterLevels`. +- Functions: lower camelCase, for example `readLevels()` and `connectWifi()`. + +Prefer small functions with one responsibility. Keep hardware pins, URLs, timings, and debug flags as constants near the top of the sketch. + +## Hardware Notes + +For ESP8266 conductive level sensing, use external pull-down resistors from each `PIN_LEVEL1..4` input to `GND`. The common electrode is driven to positive voltage only during measurement. + +Avoid ESP8266 boot/strap pins for level inputs when possible, especially `GPIO0`, `GPIO2`, and `GPIO15`. Also avoid `GPIO1`/`GPIO3` unless intentionally sharing the serial port. Prefer ordinary GPIO pins such as `GPIO5`, `GPIO4`, `GPIO14`, `GPIO12`, or `GPIO13`, depending on the board and wiring. + +## Testing Guidelines + +There is no automated test framework in this repository. Validate changes by compiling the sketch and testing on hardware. + +Before committing firmware changes: + +- Compile for the target ESP8266 or ESP32 board. +- Test `DEBUG == 1` for local level readings without Wi-Fi. +- Test `DEBUG == 2` for repeated Wi-Fi POST requests. +- Test `DEBUG == 0` for production sleep/wake behavior. + +## Commit & Pull Request Guidelines + +Current Git history is minimal and uses a simple message such as `Initial commit`. Keep future commits short, imperative, and specific, for example `Add WiFi timeout handling`. + +Pull requests should include: + +- A brief description of firmware behavior changed. +- Board model and Arduino ESP8266/ESP32 core version used for testing. +- Serial output or screenshots when debugging behavior changes. +- Any required configuration changes, especially Wi-Fi, server URL, or pin mapping. + +## Security & Configuration Tips + +Avoid committing real Wi-Fi passwords or private server endpoints when possible. For shared changes, replace secrets with placeholders and document required local values. diff --git a/arduino/arduino.ino b/arduino/arduino.ino new file mode 100644 index 0000000..ed40418 --- /dev/null +++ b/arduino/arduino.ino @@ -0,0 +1,245 @@ +#if defined(ESP8266) +#include +#include +#include +#elif defined(ESP32) +#include +#include +#include +#include +#else +#error "This sketch supports ESP8266 and ESP32 boards only." +#endif + +const int PIN_COMMON_ELECTRODE = 13; // GPIO13 / D7 +const int PIN_LEVEL1 = 5; // GPIO5 / D1 - najnizsia hladina +const int PIN_LEVEL2 = 4; // GPIO4 / D2 +const int PIN_LEVEL3 = 14; // GPIO14 / D5 +const int PIN_LEVEL4 = 12; // GPIO12 / D6 - najvyssia hladina + +const int DEBUG = 1; + +const char WIFI_SSID[] = "TP-Link_DD1E"; +const char WIFI_PASS[] = "33784120"; +const unsigned long SLEEP_TIME_SEC = 1800; +const char URL_LOG[] = "https://vihorlat.tpsoft.org/hladinac/server/log.php"; + +const unsigned long WIFI_TIMEOUT_MS = 15000; +const unsigned long MEASUREMENT_STABILIZE_MS = 50; + +struct WaterLevels { + int level1; + int level2; + int level3; + int level4; +}; + +void initPins(); +WaterLevels readLevels(); +void printLevels(const WaterLevels &levels); +String buildPostBody(const WaterLevels &levels); +bool connectWifi(); +bool sendLevelsToServer(const WaterLevels &levels); +void enterDeepSleep(); +void setupLevelPin(int pin); +void setCommonElectrodeOn(); +void setCommonElectrodeOff(); +int readLevelPin(int pin); + +void setup() { + Serial.begin(115200); + delay(200); + Serial.println("Hladinac v0.3 started."); + + initPins(); + + if (DEBUG == 0) { + WaterLevels levels = readLevels(); + printLevels(levels); + + if (connectWifi()) { + sendLevelsToServer(levels); + WiFi.disconnect(true); + WiFi.mode(WIFI_OFF); + } else { + Serial.println("WiFi connection failed, skipping upload."); + } + + enterDeepSleep(); + } else if (DEBUG == 2) { + connectWifi(); + } +} + +void loop() { + if (DEBUG == 1) { + WaterLevels levels = readLevels(); + printLevels(levels); + delay(1000); + } else if (DEBUG == 2) { + WaterLevels levels = readLevels(); + printLevels(levels); + + if (WiFi.status() != WL_CONNECTED) { + connectWifi(); + } + + sendLevelsToServer(levels); + delay(5000); + } +} + +void initPins() { + pinMode(PIN_COMMON_ELECTRODE, OUTPUT); + setCommonElectrodeOff(); + + setupLevelPin(PIN_LEVEL1); + setupLevelPin(PIN_LEVEL2); + setupLevelPin(PIN_LEVEL3); + setupLevelPin(PIN_LEVEL4); +} + +void setupLevelPin(int pin) { +#if defined(ESP32) + pinMode(pin, INPUT_PULLDOWN); +#else + // ESP8266 pouziva externe pull-down rezistory na GND. + pinMode(pin, INPUT); +#endif +} + +void setCommonElectrodeOn() { +#if defined(ESP8266) + digitalWrite(PIN_COMMON_ELECTRODE, HIGH); +#else + digitalWrite(PIN_COMMON_ELECTRODE, HIGH); +#endif +} + +void setCommonElectrodeOff() { +#if defined(ESP8266) + digitalWrite(PIN_COMMON_ELECTRODE, LOW); +#else + digitalWrite(PIN_COMMON_ELECTRODE, LOW); +#endif +} + +int readLevelPin(int pin) { +#if defined(ESP8266) + return digitalRead(pin) == HIGH ? 1 : 0; +#else + return digitalRead(pin) == HIGH ? 1 : 0; +#endif +} + +WaterLevels readLevels() { + WaterLevels levels; + + // Spolocna elektroda je zapnuta iba pocas merania, aby sa znizila korozia. + setCommonElectrodeOn(); + delay(MEASUREMENT_STABILIZE_MS); + + levels.level1 = readLevelPin(PIN_LEVEL1); + levels.level2 = readLevelPin(PIN_LEVEL2); + levels.level3 = readLevelPin(PIN_LEVEL3); + levels.level4 = readLevelPin(PIN_LEVEL4); + + setCommonElectrodeOff(); + + return levels; +} + +void printLevels(const WaterLevels &levels) { + Serial.print("level1="); + Serial.print(levels.level1); + Serial.print(" level2="); + Serial.print(levels.level2); + Serial.print(" level3="); + Serial.print(levels.level3); + Serial.print(" level4="); + Serial.println(levels.level4); +} + +String buildPostBody(const WaterLevels &levels) { + String body = "level1=" + String(levels.level1); + body += "&level2=" + String(levels.level2); + body += "&level3=" + String(levels.level3); + body += "&level4=" + String(levels.level4); + return body; +} + +bool connectWifi() { + if (WiFi.status() == WL_CONNECTED) { + return true; + } + + Serial.print("Connecting to WiFi"); + WiFi.mode(WIFI_STA); + WiFi.begin(WIFI_SSID, WIFI_PASS); + + unsigned long startedAt = millis(); + while (WiFi.status() != WL_CONNECTED && millis() - startedAt < WIFI_TIMEOUT_MS) { + delay(500); + Serial.print("."); + } + + if (WiFi.status() == WL_CONNECTED) { + Serial.println(); + Serial.print("WiFi connected, IP="); + Serial.println(WiFi.localIP()); + return true; + } + + Serial.println(); + Serial.println("WiFi connection timeout."); + return false; +} + +bool sendLevelsToServer(const WaterLevels &levels) { + if (WiFi.status() != WL_CONNECTED) { + Serial.println("Cannot send data, WiFi is not connected."); + return false; + } + +#if defined(ESP8266) + BearSSL::WiFiClientSecure client; +#else + WiFiClientSecure client; +#endif + client.setInsecure(); + + HTTPClient http; + if (!http.begin(client, URL_LOG)) { + Serial.println("HTTP begin failed."); + return false; + } + + String body = buildPostBody(levels); + http.addHeader("Content-Type", "application/x-www-form-urlencoded"); + + int httpCode = http.POST(body); + if (httpCode > 0) { + Serial.print("POST sent, HTTP code="); + Serial.println(httpCode); + } else { + Serial.print("POST failed: "); + Serial.println(http.errorToString(httpCode)); + } + + http.end(); + return httpCode > 0 && httpCode < 400; +} + +void enterDeepSleep() { + Serial.print("Entering deep sleep for "); + Serial.print(SLEEP_TIME_SEC); + Serial.println(" seconds."); + Serial.flush(); + +#if defined(ESP8266) + ESP.deepSleep((uint64_t)SLEEP_TIME_SEC * 1000000ULL); +#else + esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_TIME_SEC * 1000000ULL); + esp_deep_sleep_start(); +#endif +}