123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706 |
- /*
- * Moistcontrol - Controller state machine
- *
- * Copyright (c) 2013 Michael Buesch <m@bues.ch>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
- #include "controller.h"
- #include "sensor.h"
- #include "util.h"
- #include "main.h"
- #include "rv3029.h"
- #include "ioext.h"
- #include "log.h"
- #include <string.h>
- #include <avr/eeprom.h>
- /* Controller interval time, in seconds.
- * This is the time the controller waits between measurements.
- */
- #define CTRL_INTERVAL_SEC 60
- /* First wait time, in seconds.
- * This is the time the controller waits before doing the first
- * measurement after a controller reset.
- */
- #define FIRST_CTRL_INTERVAL_SEC 10
- /* The time a valve is held "opened" when watering.
- * In milliseconds.
- */
- #define VALVE_OPEN_MS 3000
- /* The time a value is held "closed" when watering before doing
- * the next measurement. In milliseconds.
- */
- #define VALVE_CLOSE_MS 30000
- /* Flowerpot controller context data structure. */
- struct flowerpot {
- /* The ID-number of this pot. */
- uint8_t nr;
- /* The current state of the controller state machine
- * for this pot.
- */
- struct flowerpot_state state;
- /* Timestamp for the next measurement. */
- jiffies_t next_measurement;
- /* Timer variable for the VALVE_OPEN_MS and
- * VALVE_CLOSE_MS times.
- */
- jiffies_t valve_timer;
- /* Enable-state of manual-mode for this pot's valve.
- * Manual mode is enabled, if this bit is 1.
- */
- bool valve_manual_en;
- /* Manual-mode state for this pot's valve.
- * The valve is force opened, if this bit is 1
- * and manual mode is enabled.
- * The valve is force closed, if this bit is 0
- * and manual mode is enabled.
- */
- bool valve_manual_state;
- /* Automatic-mode state for this pot's valve.
- * The valve is opened, if this bit is 1
- * and manual mode is disabled.
- * The valve is closed, if this bit is 0
- * and manual mode is disabled.
- */
- bool valve_auto_state;
- };
- /* Controller context data structure. */
- struct controller {
- /* The active controller configuration.
- * This is a RAM-copy of the EEPROM contents.
- */
- struct controller_config config;
- /* The instances of the flowerpot contexts. */
- struct flowerpot pots[MAX_NR_FLOWERPOTS];
- /* Counter variable for the flowerpot cycle. */
- uint8_t current_pot;
- /* EEPROM-update flag.
- * If this bit is set, an EEPROM update is pending.
- * The EEPROM update will be performed as soon as
- * the timer has expired.
- */
- bool eeprom_update_required;
- /* The EEPROM-update timer. */
- jiffies_t eeprom_update_time;
- };
- /* Instance of the controller context. */
- static struct controller cont;
- /* Initialization helper macro for pot config. */
- #define POT_DEFAULT_CONFIG \
- { \
- .flags = 0, \
- .min_threshold = 85, \
- .max_threshold = 170, \
- .active_range = { \
- .from = 0, \
- .to = (time_of_day_t)(long)-1, \
- }, \
- .dow_on_mask = 0x7F, \
- }
- /* The EEPROM memory, for storage of configuration values. */
- static struct controller_config EEMEM eeprom_cont_config = {
- .pots[0] = POT_DEFAULT_CONFIG,
- .pots[1] = POT_DEFAULT_CONFIG,
- .pots[2] = POT_DEFAULT_CONFIG,
- .pots[3] = POT_DEFAULT_CONFIG,
- .pots[4] = POT_DEFAULT_CONFIG,
- .pots[5] = POT_DEFAULT_CONFIG,
- .global = {
- .flags = CONTR_FLG_ENABLE,
- .sensor_lowest_value = 0,
- .sensor_highest_value = SENSOR_MAX,
- },
- };
- /* Get a pointer to the configuration structure for a pot.
- * pot: A pointer to the flowerpot.
- */
- static inline struct flowerpot_config * pot_config(const struct flowerpot *pot)
- {
- return &cont.config.pots[pot->nr];
- }
- /* Emit a log message, if logging is enabled.
- * pot: A pointer to the flowerpot.
- * log_class: The logging class to use. May be LOG_INFO or LOG_ERROR.
- * log_code: The info or error code.
- * log_data: Additional user-data for this log message.
- */
- static void pot_info(struct flowerpot *pot,
- uint8_t log_class, uint8_t log_code, uint8_t log_data)
- {
- struct log_item log;
- if (!(pot_config(pot)->flags & POT_FLG_LOG)) {
- /* Logging is disabled. Do not emit the message. */
- return;
- }
- /* Construct the log data structure. */
- log_init(&log, log_class);
- log.code = log_code;
- log.data = log_data;
- /* Append the log message to the log queue. */
- log_append(&log);
- }
- /* Emit a log message, if verbose logging is enabled.
- * pot: A pointer to the flowerpot.
- * log_class: The logging class to use. May be LOG_INFO or LOG_ERROR.
- * log_code: The info or error code.
- * log_data: Additional user-data for this log message.
- */
- static void pot_info_verbose(struct flowerpot *pot,
- uint8_t log_class, uint8_t log_code, uint8_t log_data)
- {
- if (pot_config(pot)->flags & POT_FLG_LOGVERBOSE)
- pot_info(pot, log_class, log_code, log_data);
- }
- /* Switch the controller state machine into another state.
- * pot: A pointer to the flowerpot.
- * new_state: The new state to switch to.
- */
- static void pot_state_enter(struct flowerpot *pot,
- enum flowerpot_state_id new_state)
- {
- uint8_t data;
- /* Switch state, if new_state is different from the current state. */
- if (pot->state.state_id != new_state) {
- pot->state.state_id = new_state;
- /* Emit a verbose log message for this switch operation.
- * The log data holds the pot number in the lower 4 bits
- * and the new state number in the upper 4 bits.
- */
- data = ((uint8_t)new_state << 4) | (pot->nr & 0xF);
- pot_info_verbose(pot, LOG_INFO, LOG_INFO_CONTSTATCHG, data);
- }
- }
- /* Scale the raw sensor ADC value into the fixed 0-255 moisture range.
- * res: Pointer to the sensor result (ADC value).
- * Returns the 8-bit scaled value.
- */
- static uint8_t scale_sensor_val(const struct sensor_result *res)
- {
- uint16_t raw_value = res->value;
- uint16_t raw_range;
- uint16_t raw_lowest, raw_highest;
- uint8_t scaled_value;
- /* Clamp the raw sensor value between the lowest and highest
- * possible values. This aides minimal range overshoots.
- * Also subtract the lower boundary to adjust the range to zero.
- */
- raw_lowest = cont.config.global.sensor_lowest_value;
- raw_highest = cont.config.global.sensor_highest_value;
- raw_value = clamp(raw_value, raw_lowest, raw_highest);
- raw_value -= raw_lowest;
- raw_range = raw_highest - raw_lowest;
- /* Scale the value with the formula:
- * scaled_value = scale_max / raw_range * raw_value
- * where scale_max is UINT8_MAX (= 0xFF).
- * Honor division by zero and precision loss on division.
- * Round to the nearest integer.
- */
- if (raw_range) {
- scaled_value = div_round((uint32_t)UINT8_MAX * raw_value,
- (uint32_t)raw_range);
- } else
- scaled_value = 0;
- return scaled_value;
- }
- /* Get the output extender bit-number for a valve.
- * nr: The valve number to get the extender-bit-number for.
- * Returns the output extender bit value.
- */
- static uint8_t valvenr_to_bitnr(uint8_t nr)
- {
- switch (nr) {
- case 0:
- return EXTOUT_VALVE0;
- case 1:
- return EXTOUT_VALVE1;
- case 2:
- return EXTOUT_VALVE2;
- case 3:
- return EXTOUT_VALVE3;
- case 4:
- return EXTOUT_VALVE4;
- case 5:
- return EXTOUT_VALVE5;
- }
- return 0;
- }
- /* Write the current valve state out to the valve hardware.
- * pot: A pointer to the flowerpot.
- */
- static void valve_state_commit(struct flowerpot *pot)
- {
- uint8_t bitnr = valvenr_to_bitnr(pot->nr);
- bool state;
- if (pot->valve_manual_en)
- state = pot->valve_manual_state;
- else
- state = pot->valve_auto_state;
- /* Invert the state. The hardware will invert it again. */
- state = !state;
- /* And write it to the hardware. */
- ioext_write_bit(bitnr, state);
- ioext_commit();
- }
- /* Set the automatic-state of a valve to "closed"
- * and write the state to the hardware.
- * pot: A pointer to the flowerpot.
- */
- static void valve_close(struct flowerpot *pot)
- {
- pot->valve_auto_state = 0;
- valve_state_commit(pot);
- }
- /* Set the automatic-state of a valve to "opened"
- * and write the state to the hardware.
- * Also start the valve-open-timer and switch the state machine
- * into the "waiting-for-valve" state.
- * pot: A pointer to the flowerpot.
- */
- static void valve_open(struct flowerpot *pot)
- {
- pot->valve_auto_state = 1;
- valve_state_commit(pot);
- /* Set state machine to waiting-for-valve. */
- pot->valve_timer = jiffies_get() + msec_to_jiffies(VALVE_OPEN_MS);
- pot_state_enter(pot, POT_WAITING_FOR_VALVE);
- }
- /* Start a sensor measurement and switch the state machine
- * into the "measuring" state.
- * pot: A pointer to the flowerpot.
- */
- static void pot_start_measurement(struct flowerpot *pot)
- {
- sensor_start(pot->nr);
- pot_state_enter(pot, POT_MEASURING);
- }
- /* Switch the state machine into the "idle" state.
- * Also schedule the next measurement.
- * pot: A pointer to the flowerpot.
- */
- static void pot_go_idle(struct flowerpot *pot)
- {
- pot->next_measurement = jiffies_get() + sec_to_jiffies(CTRL_INTERVAL_SEC);
- pot_state_enter(pot, POT_IDLE);
- }
- /* Start watering.
- * This will set the "watering" state and open the valve.
- * pot: A pointer to the flowerpot.
- */
- static void pot_start_watering(struct flowerpot *pot)
- {
- /* Emit a "watering started" log message.
- * The lower 4 bits of the log data is the pot number
- * and the 7th bit is the "watering active" bit.
- */
- pot_info(pot, LOG_INFO, LOG_INFO_WATERINGCHG,
- (pot->nr & 0x0F) | 0x80);
- /* Go into watering state and open the valve. */
- pot->state.is_watering = 1;
- valve_open(pot);
- }
- /* Stop watering.
- * This will reset the "watering" state and close the valve.
- * Additionally it will reset the controller state machine to "idle".
- * pot: A pointer to the flowerpot.
- */
- static void pot_stop_watering(struct flowerpot *pot)
- {
- if (pot->state.is_watering) {
- /* Emit a "watering stopped" log message.
- * The lower 4 bits of the log data is the pot number
- * and the 7th bit is the "watering active" bit.
- */
- pot_info(pot, LOG_INFO, LOG_INFO_WATERINGCHG,
- pot->nr & 0x0F);
- /* Go out of watering state, close the valve and
- * set the state machine to "idle"
- */
- pot->state.is_watering = 0;
- }
- valve_close(pot);
- pot_go_idle(pot);
- }
- /* Reset the state machine on one pot.
- * pot: A pointer to the flowerpot.
- */
- static void pot_reset(struct flowerpot *pot)
- {
- if (pot->state.state_id == POT_MEASURING) {
- /* We are currently measuring on this
- * pot. Cancel the sensor measurement.
- */
- sensor_cancel();
- }
- /* Reset all state values. */
- pot->state.is_watering = 0;
- pot->next_measurement = jiffies_get() + sec_to_jiffies(FIRST_CTRL_INTERVAL_SEC);
- pot_state_enter(pot, POT_IDLE);
- pot->valve_manual_en = 0;
- pot->valve_manual_state = 0;
- /* Make sure the valve is closed. */
- valve_close(pot);
- }
- /* The pot controller state machine routine.
- * pot: A pointer to the flowerpot.
- */
- static void handle_pot(struct flowerpot *pot)
- {
- struct sensor_result result;
- uint8_t sensor_val;
- jiffies_t now;
- bool ok;
- struct rtc_time rtc;
- time_of_day_t tod;
- uint8_t dow_mask;
- const struct flowerpot_config *config = pot_config(pot);
- now = jiffies_get();
- switch (pot->state.state_id) {
- case POT_IDLE:
- /* Idle: We are not doing anything, yet. */
- if (!(config->flags & POT_FLG_ENABLED)) {
- /* This pot is disabled. Don't do anything. */
- break;
- }
- /* Get the current RTC time. */
- rv3029_get_time(&rtc);
- /* Create a day-of-week mask for today. */
- dow_mask = BITMASK8(rtc.day_of_week);
- if (!(config->dow_on_mask & dow_mask)) {
- /* This pot is disabled on today's weekday. */
- break;
- }
- /* Check if we are in the active-time-range. */
- tod = rtc_get_time_of_day(&rtc);
- if (time_of_day_before(tod, config->active_range.from) ||
- time_of_day_after(tod, config->active_range.to)) {
- /* Current time is not in the active range.
- * Don't run.
- */
- break;
- }
- /* Check, if the next measurement is pending. */
- if (time_before(now, pot->next_measurement)) {
- /* No. Don't run, yet. */
- break;
- }
- /* It's time to start a new measurement. */
- pot_state_enter(pot, POT_START_MEASUREMENT);
- break;
- case POT_START_MEASUREMENT:
- /* Start the measurement as soon as the sensors go idle. */
- if (!sensors_idle()) {
- /* Another sensor measurement is still running.
- * Cannot start this measurement, yet.
- */
- break;
- }
- /* Start a measurement on this pot, now. */
- pot_start_measurement(pot);
- break;
- case POT_MEASURING:
- /* Poll the sensor state. */
- ok = sensor_poll(&result);
- if (!ok) {
- /* The measurement did not finish, yet. */
- break;
- }
- /* Check if verbose logging is requested for this pot
- * and send the raw measurement result.
- */
- if (config->flags & POT_FLG_LOGVERBOSE) {
- struct log_item log;
- /* Get the current RTC time. */
- rv3029_get_time(&rtc);
- /* Fill out the log data structure
- * with the current timestamp and sensor values
- * and queue it in the log subsystem.
- */
- log_init(&log, LOG_SENSOR_DATA);
- log.sensor_data = LOG_SENSOR_DATA(result.nr,
- result.value);
- log_append(&log);
- }
- /* Scale the raw sensor value to the 8-bit
- * data type of the controller logics.
- */
- sensor_val = scale_sensor_val(&result);
- pot->state.last_measured_raw_value = result.value;
- pot->state.last_measured_value = sensor_val;
- /* Sensor value sanity check.
- * Too low and too big values are rejected. These values
- * indicate a short circuit or cable break.
- */
- if (result.value < 16 || result.value > (SENSOR_MAX - 16)) {
- pot_info(pot, LOG_ERROR, LOG_ERR_SENSOR, pot->nr);
- /* Force-stop watering and bail to idle state. */
- pot_stop_watering(pot);
- break;
- }
- if (pot->state.is_watering) {
- /* We are watering. Check if we reached the upper threshold.
- * If so, stop watering.
- * If not, go on watering.
- */
- if (sensor_val >= config->max_threshold)
- pot_stop_watering(pot);
- else
- valve_open(pot);
- } else {
- /* We are not watering, yet. Check if we dropped below
- * the lower threshold.
- * If so, start watering.
- * If not, don't do anything and go idle.
- */
- if (sensor_val < config->min_threshold)
- pot_start_watering(pot);
- else
- pot_go_idle(pot);
- }
- break;
- case POT_WAITING_FOR_VALVE:
- /* Wait for the valve open/close cycle to finish. */
- if (time_before(now, pot->valve_timer)) {
- /* Open or close time not expired, yet. */
- break;
- }
- if (pot->valve_auto_state) {
- /* Valve is currently opened.
- * Close it and start the close timer.
- */
- valve_close(pot);
- pot->valve_timer = now + msec_to_jiffies(VALVE_CLOSE_MS);
- } else {
- /* Close timer expired.
- * Perform a new measurement, now. */
- pot_state_enter(pot, POT_START_MEASUREMENT);
- }
- break;
- }
- }
- /* Completely reset all controllers.
- * This resets all state machines and the corresponding hardware.
- */
- static void controller_reset(void)
- {
- uint8_t i;
- for (i = 0; i < ARRAY_SIZE(cont.pots); i++)
- pot_reset(&cont.pots[i]);
- cont.current_pot = 0;
- }
- /* Get the controller configuration.
- * Copies the current config into "dest".
- * dest: Pointer to the destination buffer.
- */
- void controller_get_config(struct controller_config *dest)
- {
- *dest = cont.config;
- }
- /* Set a new controller configuration.
- * Copies the "new_config" into the current config
- * and schedules an EEPROM update.
- * The affected controllers are reset, if the configuration changed.
- * new_config: Pointer to the new configuration.
- */
- void controller_update_config(const struct controller_config *new_config)
- {
- struct controller_config *active;
- uint8_t i;
- /* Get pointer to the currently active configuration. */
- active = &cont.config;
- /* Check which part of the config changed, if any. */
- if (memcmp(&new_config->global, &active->global,
- sizeof(new_config->global)) == 0) {
- /* Global config did not change.
- * Check if a pot changed.
- */
- for (i = 0; i < MAX_NR_FLOWERPOTS; i++) {
- if (memcmp(&new_config->pots[i], &active->pots[i],
- sizeof(new_config->pots[i])) != 0) {
- /* This pot changed.
- * Reset the pot's state machine.
- */
- pot_reset(&cont.pots[i]);
- }
- }
- } else {
- /* Global config differs.
- * Reset the complete controller state machine (all pots).
- */
- controller_reset();
- }
- /* Copy the new configuration to the active config in RAM. */
- cont.config = *new_config;
- /* Schedule an EEPROM update. */
- cont.eeprom_update_time = jiffies_get() + msec_to_jiffies(3000);
- cont.eeprom_update_required = 1;
- }
- /* Get the state information for a given pot.
- * pot_number: The number of the pot to get the state for.
- * state: A pointer to the buffer the state will be copied into.
- */
- void controller_get_pot_state(uint8_t pot_number,
- struct flowerpot_state *state)
- {
- if (pot_number < ARRAY_SIZE(cont.pots))
- *state = cont.pots[pot_number].state;
- }
- /* Set the "manual mode" control bits.
- * force_stop_watering_mask: A bitmask of pots to force-stop watering on.
- * valve_manual_mask: A bitmask of pots to enable manual valve control on.
- * valve_manual_state: A bitmask of manual-mode valve states.
- */
- void controller_manual_mode(uint8_t force_stop_watering_mask,
- uint8_t valve_manual_mask,
- uint8_t valve_manual_state)
- {
- struct flowerpot *pot;
- uint8_t i, mask;
- for (i = 0, mask = 1; i < MAX_NR_FLOWERPOTS; i++, mask <<= 1) {
- pot = &cont.pots[i];
- if ((force_stop_watering_mask & mask) &&
- pot->state.is_watering)
- pot_stop_watering(pot);
- pot->valve_manual_en = !!(valve_manual_mask & mask);
- pot->valve_manual_state = !!(valve_manual_state & mask);
- valve_state_commit(pot);
- }
- }
- /* The main controller routine. */
- void controller_work(void)
- {
- if (cont.eeprom_update_required &&
- time_after(jiffies_get(), cont.eeprom_update_time)) {
- cont.eeprom_update_required = 0;
- /* An EEPROM write was scheduled.
- * Update the EEPROM contents.
- * This only updates the bytes that changed. (reduces wearout)
- */
- eeprom_update_block_wdtsafe(&cont.config, &eeprom_cont_config,
- sizeof(cont.config));
- }
- if (cont.config.global.flags & CONTR_FLG_ENABLE) {
- /* The controller is enabled globally.
- * Run the pot state machines. */
- handle_pot(&cont.pots[cont.current_pot]);
- cont.current_pot++;
- if (cont.current_pot >= ARRAY_SIZE(cont.pots))
- cont.current_pot = 0;
- }
- }
- /* Initialization of the controller data structures and hardware. */
- void controller_init(void)
- {
- struct flowerpot *pot;
- uint8_t i;
- /* Initialize the output extender hardware (shift register).
- * All valves are connected through this extender.
- */
- ioext_init(1);
- /* Read the configuration from EEPROM. */
- memset(&cont, 0, sizeof(cont));
- eeprom_read_block_wdtsafe(&cont.config, &eeprom_cont_config,
- sizeof(cont.config));
- /* Initialize and reset all pot states. */
- for (i = 0; i < ARRAY_SIZE(cont.pots); i++) {
- pot = &cont.pots[i];
- pot->nr = i;
- pot_reset(pot);
- }
- }
|