diff --git a/config.hpp b/config.hpp index e3e5c7e..6f2f950 100644 --- a/config.hpp +++ b/config.hpp @@ -1,30 +1,20 @@ #pragma once #include -struct Sensor { - /// Which analog pin this is connected to (A0, A1, ...) +typedef struct { uint8_t pin; /// The value sensors read when completely dry int calibration_dry; /// The value sensors read when completely wet int calibration_wet; - /// Pot group index this sensor belongs to - unsigned int pot_group; -}; +} Sensor; -struct Group { - /// Enable or disable the group - bool enabled; - /// Which pin the indicator light is connected to +typedef struct { + uint8_t valve_pin; uint8_t led_pin; -}; - -struct Valve { - /// The digital pin this valve is connected to - uint8_t pin; - /// Pot group index this valve belongs to - unsigned int pot_group; -}; + Sensor sensor; + // define additional per-pot config here +} PotConfig; constexpr unsigned int SECOND = 1000; constexpr unsigned int MINUTE = SECOND * 60; @@ -42,14 +32,14 @@ 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 szcttart/stop +/// the amount of time the pump needs to start/stop constexpr int PUMP_DELAY_MS = 1 * SECOND / 4; /// the amount of time to water the plot (valve open and pump running) -constexpr int WATER_TIME_MS = 1 * SECOND; +constexpr int WATER_TIME_MS = 1 * SECOND / 4; /// how long to wait between loops -constexpr int LOOP_DELAY_MS = 1 * SECOND; // TODO set higher once it is working +constexpr int LOOP_DELAY_MS = 1 * SECOND; /// minimum amount of time to wait between watering each pot, even when minimum wetness has been reached constexpr int MIN_WATERING_INTERVAL_MS = 10 * SECOND; // TODO set higher once it is working @@ -62,137 +52,153 @@ constexpr uint8_t PUMP_LED_PIN = 3; constexpr uint8_t OK_LED_PIN = 2; -constexpr struct Group GROUPS[] = { - { // 0 - .enabled = true, +/// Per-pot configuration +constexpr PotConfig POT_CONFIGS[] = { + { + .valve_pin = 23, .led_pin = 22, - }, { // 1 - .enabled = false, + .sensor = { + .pin = A0, + .calibration_dry = 540, + .calibration_wet = 250, + } + }, + { + .valve_pin = 25, .led_pin = 24, - }, { // 2 - .enabled = false, + .sensor = { + .pin = A1, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 27, .led_pin = 26, - }, { // 3 - .enabled = false, + .sensor = { + .pin = A2, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 29, .led_pin = 28, - }, { // 4 - .enabled = true, + .sensor = { + .pin = A3, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 31, .led_pin = 30, - }, { // 5 - .enabled = false, + .sensor = { + .pin = A4, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 33, .led_pin = 32, - }, { // 6 - .enabled = false, + .sensor = { + .pin = A5, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 35, .led_pin = 34, - }, { // 7 - .enabled = false, + .sensor = { + .pin = A6, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 37, .led_pin = 36, - }, { // 8 - .enabled = false, + .sensor = { + .pin = A7, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 39, .led_pin = 38, - }, { // 9 - .enabled = true, + .sensor = { + .pin = A8, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 41, .led_pin = 40, - }, { // 10 - .enabled = false, + .sensor = { + .pin = A9, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 43, .led_pin = 42, - }, { // 11 - .enabled = false, + .sensor = { + .pin = A10, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 45, .led_pin = 44, - }, { // 12 - .enabled = true, + .sensor = { + .pin = A11, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 47, .led_pin = 46, - }, { // 13 - .enabled = true, + .sensor = { + .pin = A12, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 49, .led_pin = 48, - }, { // 14 - .enabled = false, + .sensor = { + .pin = A13, + .calibration_dry = 520, + .calibration_wet = 100, + } + }, + { + .valve_pin = 51, .led_pin = 50, - }, { // 15 - .enabled = false, + .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 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, - }, +constexpr unsigned int POT_COUNT = sizeof(POT_CONFIGS) / sizeof(POT_CONFIGS[0]); - // 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 0a8d49b..27fa546 100644 --- a/drop.ino +++ b/drop.ino @@ -1,179 +1,43 @@ #include "config.hpp" -// 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; +/// a connected pot +typedef struct { + PotConfig *config; unsigned long last_watering; -}; + // anything changing per pot goes here +} Pot; /// all connected pots -GroupState group_states[GROUP_COUNT]; +Pot pots[POT_COUNT]; -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...")); +void setup() { + Serial.begin(9600); + Serial.println(""); + Serial.println("Initializing..."); pinMode(PUMP_LED_PIN, OUTPUT); pinMode(PUMP_PIN, OUTPUT); pinMode(LED_BUILTIN, OUTPUT); pinMode(OK_LED_PIN, OUTPUT); - 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); + set_pump(false); - Serial.println(F(" Done")); + // 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); + } } -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) { +void loop() { Serial.println(""); Serial.println("LOOP"); @@ -181,46 +45,66 @@ void loop(void) { 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; - 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); + for (unsigned int i = 0; i < POT_COUNT; i++) { + Serial.print("Pot "); + Serial.println(i); + per_pot(pots[i]); + Serial.println(""); } - + 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(const Sensor *&sensor) { +int get_humidity(Sensor &sensor) { // take average of multiple measurements int sensorVal {0}; for (unsigned int i = 0; i < MEASUREMENT_COUNT; i++) { - sensorVal += analogRead(sensor->pin); + sensorVal += analogRead(sensor.pin); + Serial.print(F("Measurement ")); + Serial.print(i); + Serial.print(F(" = ")); + Serial.println(sensorVal); delay(MEASUREMENT_DELAY); } @@ -231,15 +115,11 @@ int get_humidity(const 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/ - 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; + sensorVal = map(sensorVal, sensor.calibration_wet, sensor.calibration_dry, 100, 0); + Serial.print(F("Humidity: ")); + Serial.print(sensorVal); + Serial.println(F("%")); + return sensorVal; } void set_pump(bool on) { @@ -250,11 +130,11 @@ void set_pump(bool on) { delay(PUMP_DELAY_MS); } -void set_valve(const Valve *valve, bool open) { +void set_valve(uint8_t valve, bool open) { Serial.print("setting valve on pin "); - Serial.print(valve->pin); + Serial.print(valve); Serial.print(" to state "); Serial.println(open); - digitalWrite(valve->pin, open ? LOW : HIGH); + digitalWrite(valve, open ? LOW : HIGH); delay(VALVE_DELAY_MS); -} +} \ No newline at end of file