From 7a25e4b46e331afde5bab75bbe3e211ca8aac3c3 Mon Sep 17 00:00:00 2001 From: Vinzenz Schroeter Date: Tue, 27 May 2025 14:25:20 +0200 Subject: [PATCH] restructure logic to work on groups of valves and sensors instead of being 1:1:1 --- config.hpp | 292 ++++++++++++++++++++++++++--------------------------- drop.ino | 288 +++++++++++++++++++++++++++++++++++++--------------- 2 files changed, 347 insertions(+), 233 deletions(-) diff --git a/config.hpp b/config.hpp index 5df2cc0..e3e5c7e 100644 --- a/config.hpp +++ b/config.hpp @@ -1,21 +1,30 @@ #pragma once #include -typedef struct { +struct Sensor { /// Which analog pin this is connected to (A0, A1, ...) uint8_t pin; /// The value sensors read when completely dry int calibration_dry; /// The value sensors read when completely wet int calibration_wet; -} Sensor; + /// Pot group index this sensor belongs to + unsigned int pot_group; +}; -typedef struct { - uint8_t valve_pin; +struct Group { + /// Enable or disable the group + bool enabled; + /// Which pin the indicator light is connected to uint8_t led_pin; - Sensor sensor; - // define additional per-pot config here -} PotConfig; +}; + +struct Valve { + /// The digital pin this valve is connected to + uint8_t pin; + /// Pot group index this valve belongs to + unsigned int pot_group; +}; constexpr unsigned int SECOND = 1000; constexpr unsigned int MINUTE = SECOND * 60; @@ -33,7 +42,7 @@ constexpr int MAX_HUMIDITY_PERCENT = 70; /// the amount of time a valve needs to open/close constexpr int VALVE_DELAY_MS = 1 * SECOND / 4; -/// the amount of time the pump needs to start/stop +/// the amount of time the pump needs to szcttart/stop constexpr int PUMP_DELAY_MS = 1 * SECOND / 4; /// the amount of time to water the plot (valve open and pump running) @@ -53,152 +62,137 @@ constexpr uint8_t PUMP_LED_PIN = 3; constexpr uint8_t OK_LED_PIN = 2; -/// Per-pot configuration -constexpr PotConfig POT_CONFIGS[] = { - { - .valve_pin = 23, +constexpr struct Group GROUPS[] = { + { // 0 + .enabled = true, .led_pin = 22, - .sensor = { - .pin = A0, - .calibration_dry = 540, - .calibration_wet = 250, - } - }, - //{ - // .valve_pin = 25, - // .led_pin = 24, - // .sensor = { - // .pin = A1, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - //{ - // .valve_pin = 27, - // .led_pin = 26, - // .sensor = { - // .pin = A2, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - //{ - // .valve_pin = 29, - // .led_pin = 28, - // .sensor = { - // .pin = A3, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - { - .valve_pin = 31, + }, { // 1 + .enabled = false, + .led_pin = 24, + }, { // 2 + .enabled = false, + .led_pin = 26, + }, { // 3 + .enabled = false, + .led_pin = 28, + }, { // 4 + .enabled = true, .led_pin = 30, - .sensor = { - .pin = A4, - .calibration_dry = 520, - .calibration_wet = 100, - } - }, - //{ - // .valve_pin = 33, - // .led_pin = 32, - // .sensor = { - // .pin = A5, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - //{ - // .valve_pin = 35, - // .led_pin = 34, - // .sensor = { - // .pin = A6, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - //{ - // .valve_pin = 37, - // .led_pin = 36, - // .sensor = { - // .pin = A7, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - //{ - // .valve_pin = 39, - // .led_pin = 38, - // .sensor = { - // .pin = A8, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - { - .valve_pin = 41, + }, { // 5 + .enabled = false, + .led_pin = 32, + }, { // 6 + .enabled = false, + .led_pin = 34, + }, { // 7 + .enabled = false, + .led_pin = 36, + }, { // 8 + .enabled = false, + .led_pin = 38, + }, { // 9 + .enabled = true, .led_pin = 40, - .sensor = { - .pin = A9, - .calibration_dry = 520, - .calibration_wet = 100, - } - }, - //{ - // .valve_pin = 43, - // .led_pin = 42, - // .sensor = { - // .pin = A10, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - //{ - // .valve_pin = 45, - // .led_pin = 44, - // .sensor = { - // .pin = A11, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - { - .valve_pin = 47, + }, { // 10 + .enabled = false, + .led_pin = 42, + }, { // 11 + .enabled = false, + .led_pin = 44, + }, { // 12 + .enabled = true, .led_pin = 46, - .sensor = { - .pin = A12, - .calibration_dry = 520, - .calibration_wet = 100, - } - }, - { - .valve_pin = 49, + }, { // 13 + .enabled = true, .led_pin = 48, - .sensor = { - .pin = A13, - .calibration_dry = 520, - .calibration_wet = 100, - } + }, { // 14 + .enabled = false, + .led_pin = 50, + }, { // 15 + .enabled = false, + .led_pin = 52, }, - //{ - // .valve_pin = 51, - // .led_pin = 50, - // .sensor = { - // .pin = A14, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, - //{ - // .valve_pin = 53, - // .led_pin = 52, - // .sensor = { - // .pin = A15, - // .calibration_dry = 520, - // .calibration_wet = 100, - // } - //}, }; -constexpr unsigned int POT_COUNT = sizeof(POT_CONFIGS) / sizeof(POT_CONFIGS[0]); +constexpr struct Sensor SENSORS[] = { + { + .pin = A0, + .calibration_dry = 540, + .calibration_wet = 250, + .pot_group = 0, + }, { + .pin = A4, + .calibration_dry = 520, + .calibration_wet = 100, + .pot_group = 4, + }, { + .pin = A9, + .calibration_dry = 520, + .calibration_wet = 100, + .pot_group = 9, + }, { + .pin = A12, + .calibration_dry = 520, + .calibration_wet = 100, + .pot_group = 12, + }, { + .pin = A13, + .calibration_dry = 520, + .calibration_wet = 100, + .pot_group = 13, + }, + + // the rest of A0-A15 are not used +}; + +constexpr struct Valve VALVES[] = { + { + .pin = 23, + .pot_group = 0, + }, { + .pin = 25, + .pot_group = 1, + }, { + .pin = 27, + .pot_group = 2, + }, { + .pin = 29, + .pot_group = 3, + }, { + .pin = 31, + .pot_group = 4, + }, { + .pin = 33, + .pot_group = 5, + }, { + .pin = 35, + .pot_group = 6, + }, { + .pin = 37, + .pot_group = 7, + }, { + .pin = 39, + .pot_group = 8, + }, { + .pin = 41, + .pot_group = 9, + }, { + .pin = 43, + .pot_group = 10, + }, { + .pin = 45, + .pot_group = 11, + }, { + .pin = 47, + .pot_group = 12, + }, { + .pin = 49, + .pot_group = 13, + }, { + .pin = 51, + .pot_group = 14, + }, { + .pin = 53, + .pot_group = 15, + } +}; diff --git a/drop.ino b/drop.ino index 27fa546..0a8d49b 100644 --- a/drop.ino +++ b/drop.ino @@ -1,43 +1,179 @@ #include "config.hpp" -/// a connected pot -typedef struct { - PotConfig *config; +// TODO: configuration validation subroutine +// - sensors belong to an existing group +// - valves belong to an existing group +// - all groups have a sensor + +constexpr unsigned int VALVE_COUNT = sizeof(VALVES) / sizeof(VALVES[0]); +constexpr unsigned int SENSOR_COUNT = sizeof(SENSORS) / sizeof(SENSORS[0]); +constexpr unsigned int GROUP_COUNT = sizeof(GROUPS) / sizeof(GROUPS[0]); + +struct GroupState { + const Group *group_config; + /// pointer to first element of sensors array + const Sensor **sensors; + /// element count of sensors array + unsigned int sensor_count; + /// pointer to first element of valves array + const Valve **valves; + /// element count of valves array + unsigned int valve_count; unsigned long last_watering; - // anything changing per pot goes here -} Pot; +}; /// all connected pots -Pot pots[POT_COUNT]; +GroupState group_states[GROUP_COUNT]; -void setup() { - Serial.begin(9600); - Serial.println(""); - Serial.println("Initializing..."); +void turn_everything_off() { + set_pump(false); + + for (int i = 0; i < VALVE_COUNT; i++) { + set_valve(&VALVES[i], false); + } +} + +/// (╯°□°)╯︵ ┻━┻ +/// +/// Will never return +void panic(const __FlashStringHelper *message) { + turn_everything_off(); + + // keep doing this until someone notices the blinking lights and looks at the serial output + while (true) { + Serial.print(F("PANIC: ")); + Serial.println(message); + + digitalWrite(PUMP_LED_PIN, HIGH); + digitalWrite(OK_LED_PIN, LOW); + delay(300); + digitalWrite(PUMP_LED_PIN, LOW); + digitalWrite(OK_LED_PIN, HIGH); + delay(300); + } +} + +void setup_pinmodes(void) { + Serial.print(F("Setting pin modes...")); pinMode(PUMP_LED_PIN, OUTPUT); pinMode(PUMP_PIN, OUTPUT); pinMode(LED_BUILTIN, OUTPUT); pinMode(OK_LED_PIN, OUTPUT); - set_pump(false); + for (unsigned int i = 0; i < VALVE_COUNT; i++) + pinMode(VALVES[i].pin, OUTPUT); + for (unsigned int i = 0; i < SENSOR_COUNT; i++) + pinMode(SENSORS[i].pin, INPUT); + for (unsigned int i = 0; i < GROUP_COUNT; i++) + pinMode(GROUPS[i].led_pin, OUTPUT); - // link pots to their config - for (unsigned int i = 0; i < POT_COUNT; i++) { - Pot *pot = &pots[i]; - - pot->config = &POT_CONFIGS[i]; - pot->last_watering = 0; - - pinMode(pot->config->valve_pin, OUTPUT); - pinMode(pot->config->led_pin, OUTPUT); - - set_valve(pot->config->valve_pin, false); - } + Serial.println(F(" Done")); } -void loop() { +void setup_config(void) { + Serial.print(F("Loading configuration...")); + + bool found_enabled = false; + + for (unsigned int group_index = 0; group_index < GROUP_COUNT; group_index++) { + struct GroupState *group = &group_states[group_index]; + *group = { + .group_config = &GROUPS[group_index], + .sensors = NULL, + .sensor_count = 0, + .valves = NULL, + .valve_count = 0, + .last_watering = 0, + }; + + digitalWrite(group->group_config->led_pin, HIGH); + + // link valves to group + for (unsigned int valve_index = 0; valve_index < VALVE_COUNT; valve_index++) { + if (VALVES[valve_index].pot_group != group_index) + continue; + + group->valve_count++; + group->valves = (const Valve **) realloc(group->valves, group->valve_count * sizeof(Valve *)); + if (group->valves == NULL) + panic(F("valve realloc failed")); + group->valves[group->valve_count - 1] = &VALVES[valve_index]; + } + + // link sensors to group + for (unsigned int sensor_index = 0; sensor_index < SENSOR_COUNT; sensor_index++) { + if (SENSORS[sensor_index].pot_group != group_index) + continue; + + group->sensor_count++; + group->sensors = (const Sensor **) realloc(group->sensors, group->sensor_count * sizeof(Sensor *)); + if (group->sensors == NULL) + panic(F("sensor realloc failed")); + group->sensors[group->sensor_count - 1] = &SENSORS[sensor_index]; + } + + // check that all enabled groups have valid configuration + if (group->group_config->enabled) { + found_enabled = true; + + if (group->valve_count == 0) + panic(F("invalid configuration - no valves in enabled group!")); + if (group->sensor_count == 0) + panic(F("invalid configuration - no sensors in enabled group!")); + } + + digitalWrite(group->group_config->led_pin, LOW); + } + + if (!found_enabled) + panic(F("invalid configuration - all groups are disabled")); + + for (unsigned int sensor_index = 0; sensor_index < SENSOR_COUNT; sensor_index++) { + if (SENSORS[sensor_index].pot_group >= GROUP_COUNT) + panic(F("sensor is mapped to group that does not exist")); + } + + for (unsigned int valve_index = 0; valve_index < VALVE_COUNT; valve_index++) { + if (VALVES[valve_index].pot_group >= GROUP_COUNT) + panic(F("valve is mapped to group that does not exist")); + } + + Serial.println(F(" Done")); +} + +void setup(void) { + Serial.begin(9600); + Serial.println(""); + Serial.println(F("Initializing...")); + setup_pinmodes(); + Serial.print(F("Turning everything off in case of power cycle...")); + turn_everything_off(); + setup_config(); + Serial.println(F("Initialization completed.")); +} + +void water_group(GroupState *state) { + digitalWrite(LED_BUILTIN, HIGH); + + for (unsigned int valve_index = 0; valve_index < VALVE_COUNT; valve_index++) { + const Valve *valve = state->valves[valve_index]; + + set_valve(valve, HIGH); + set_pump(HIGH); + + delay(WATER_TIME_MS); + state->last_watering = millis(); + + set_pump(LOW); + set_valve(valve, LOW); + } + + digitalWrite(LED_BUILTIN, LOW); +} + +void loop(void) { Serial.println(""); Serial.println("LOOP"); @@ -45,66 +181,46 @@ void loop() { delay(100); digitalWrite(OK_LED_PIN, LOW); + for (unsigned int g = 0; g < GROUP_COUNT; g++) { + GroupState *state = &group_states[g]; + if (!state->group_config->enabled) + continue; - for (unsigned int i = 0; i < POT_COUNT; i++) { - Serial.print("Pot "); - Serial.println(i); - per_pot(pots[i]); - Serial.println(""); + digitalWrite(state->group_config->led_pin, HIGH); + + Serial.print(F("Group ")); + Serial.print(g); + Serial.print(F(": ")); + + int humidity = get_group_humidity(state->sensors, state->sensor_count); + Serial.println(humidity); + + if (state->last_watering + MIN_WATERING_INTERVAL_MS > millis()) { + Serial.println(F("watered recently -> not watering")); + } else if (humidity > MAX_HUMIDITY_PERCENT) { + Serial.println(F("too wet -> not watering")); + } else if (humidity < MIN_HUMIDITY_PERCENT) { + Serial.println(F("too dry -> watering")); + water_group(state); + } else if (state->last_watering + MAX_WATERING_INTERVAL_MS < millis()) { + Serial.println(F("not been watered for a long time -> watering")); + water_group(state); + } else { + Serial.println(F("happy plant")); + } + + digitalWrite(state->group_config->led_pin, LOW); } - + delay(LOOP_DELAY_MS); } -void per_pot(Pot &pot) { - if (pot.last_watering + MIN_WATERING_INTERVAL_MS > millis()) { - Serial.println(F("watered recently -> not watering")); - return; - } - - int percentage = get_humidity(pot.config->sensor); - if (percentage > MAX_HUMIDITY_PERCENT) { - Serial.println(F("too wet -> not watering")); - } else if (percentage < MIN_HUMIDITY_PERCENT) { - Serial.println(F("too dry -> watering")); - water_pot(pot); - } else if (pot.last_watering + MAX_WATERING_INTERVAL_MS < millis()) { - Serial.println(F("not been watered for a long time -> watering")); - water_pot(pot); - } else { - Serial.println(F("happy plant")); - } -} - -void water_pot(Pot &pot) { - digitalWrite(LED_BUILTIN, HIGH); - - digitalWrite(pot.config->led_pin, HIGH); - - set_valve(pot.config->valve_pin, HIGH); - set_pump(HIGH); - - delay(WATER_TIME_MS); - pot.last_watering = millis(); - - set_pump(LOW); - set_valve(pot.config->valve_pin, LOW); - - digitalWrite(pot.config->led_pin, LOW); - - digitalWrite(LED_BUILTIN, LOW); -} - /// get humidity sensor value as a percentage -int get_humidity(Sensor &sensor) { +int get_humidity(const Sensor *&sensor) { // take average of multiple measurements int sensorVal {0}; for (unsigned int i = 0; i < MEASUREMENT_COUNT; i++) { - sensorVal += analogRead(sensor.pin); - Serial.print(F("Measurement ")); - Serial.print(i); - Serial.print(F(" = ")); - Serial.println(sensorVal); + sensorVal += analogRead(sensor->pin); delay(MEASUREMENT_DELAY); } @@ -115,11 +231,15 @@ int get_humidity(Sensor &sensor) { // Sensor has a range of e.g. 236 to 520 // We want to translate this to a scale or 0% to 100% // More info: https://www.arduino.cc/reference/en/language/functions/math/map/ - sensorVal = map(sensorVal, sensor.calibration_wet, sensor.calibration_dry, 100, 0); - Serial.print(F("Humidity: ")); - Serial.print(sensorVal); - Serial.println(F("%")); - return sensorVal; + return map(sensorVal, sensor->calibration_wet, sensor->calibration_dry, 100, 0); +} + +int get_group_humidity(const Sensor **sensors, unsigned int sensor_count) { + int sum = 0; + for (unsigned int s = 0; s < sensor_count; s++) { + sum += get_humidity(sensors[s]); + } + return sum / sensor_count; } void set_pump(bool on) { @@ -130,11 +250,11 @@ void set_pump(bool on) { delay(PUMP_DELAY_MS); } -void set_valve(uint8_t valve, bool open) { +void set_valve(const Valve *valve, bool open) { Serial.print("setting valve on pin "); - Serial.print(valve); + Serial.print(valve->pin); Serial.print(" to state "); Serial.println(open); - digitalWrite(valve, open ? LOW : HIGH); + digitalWrite(valve->pin, open ? LOW : HIGH); delay(VALVE_DELAY_MS); -} \ No newline at end of file +}