Newer
Older
greenhouse / greenhouse.ino
#include <LowPower.h>
#include <SPI.h>
#include <LoRa.h>
#include <EEPROM.h>

int motor_in_1 = 5;
int motor_in_2 = 6;
int motor_sleep_pin = 4;
#define SAMPLES 5
int sample_idx = 0;
int voltage_reads[SAMPLES];
int moisture_reads[SAMPLES];
bool radio_initialized = false;

#define NODEBUG
 
#ifdef DEBUG
#define DPRINTLN(str)    Serial.println(str)
#define DPRINT(str)      Serial.print(str)
#define DPRINTHEX(b)     Serial.print(b, HEX)
#define DFLUSH           Serial.flush()
#else
#define DPRINTLN(str)
#define DPRINT(str)
#define DPRINTHEX(b)
#define DFLUSH
#endif

/*
 * voltage: 161 fully charged, 1.37V each cell
 * voltage: 162 fully charged + bit of sun.
 * voltage: 155 after running quite a bit
 * voltage: 130 minimum below which radio no longer works
 * moisture: 880 dry / 600 wet
 */

#define WAIT_FOR_DELAY_OR_DRY   0
#define WATER_TILL_WET_MAX_TIME 1
#define WAIT_FOR_MAX_TIME       2
#define DRY 690
#define WET 660
#define DELAY 8 // hours
#define MAX_TIME 20 // mins
#define PUMP_SPEED 128
#define STATION_ID 2
#define HOME_ID 1
#define INTERVAL 10 // seconds

struct Header {
  uint8_t to;
  uint8_t from;
  uint8_t seq;
  uint8_t flags;
};

struct State {
  unsigned int voltage, moisture, intervals;
  unsigned int dry, wet;
  byte state, delay_hours, max_time_mins, pumpSpeed;
  bool pumpRunning;
  byte errors;
};

struct Payload {
  Header header;
  State state;
};

State state;
Header my_header = {
  HOME_ID, STATION_ID, 0, 0
};

bool setupLoRa() {
  LoRa.setPins(10, 9, 2);
  LoRa.enableCrc();
  LoRa.onReceive(onReceive);
  return LoRa.begin(868E6);
}

void setup() {
#ifdef DEBUG
  Serial.begin(9600);
#endif
  state.state = WAIT_FOR_DELAY_OR_DRY;
  state.intervals = 0;
  state.pumpRunning = false;
  state.pumpSpeed = PUMP_SPEED;
  state.dry = DRY;
  state.wet = WET;
  state.delay_hours = DELAY;
  state.max_time_mins = MAX_TIME;
  state.errors = 0;
//  DPRINTLN("Setting one time state to EEPROM");
//  EEPROM.put(0, state);

  analogReference(DEFAULT);
  pumpOff();
  int voltage = 0;
  for (int i=0; i<SAMPLES; i++) {
    voltage_reads[i] = analogRead(A0);
    voltage = voltage + voltage_reads[i];
    moisture_reads[i] = analogRead(A1);
    delay(50);
  }
  voltage = voltage / SAMPLES;
  if (voltage > 130) {
    radio_initialized = setupLoRa();
    if (radio_initialized) {
      LoRa.receive();
    }
  }
}

void pumpOn(int speed) {
  pinMode(motor_in_1, OUTPUT);
  pinMode(motor_in_2, OUTPUT);
  pinMode(motor_sleep_pin, OUTPUT);
  digitalWrite(motor_sleep_pin, HIGH);
  digitalWrite(motor_in_1, LOW);
  analogWrite(motor_in_2, speed);
  state.pumpRunning = true;  
}

void pumpOff() {
  pinMode(motor_in_1, OUTPUT);
  pinMode(motor_in_2, OUTPUT);
  pinMode(motor_sleep_pin, OUTPUT);
  digitalWrite(motor_in_1, LOW);
  digitalWrite(motor_in_2, LOW);
  digitalWrite(motor_sleep_pin, LOW);
  state.pumpRunning = false;  
}

void loop() {
  voltage_reads[sample_idx] = analogRead(A0);
  moisture_reads[sample_idx] = analogRead(A1);
  sample_idx = (sample_idx + 1) % SAMPLES;
  
  int voltage = voltage_reads[0];
  int moisture = moisture_reads[0];
  for (int i = 1; i < SAMPLES; i++) {
    voltage = voltage + voltage_reads[i];
    moisture = moisture + moisture_reads[i];
  }
  state.voltage = voltage / SAMPLES;
  if (state.voltage <= 135) {
    LoRa.end();
    radio_initialized = false;
  }
  state.moisture = moisture / SAMPLES;
  DPRINT("voltage = ");
  DPRINTLN(state.voltage);
  DPRINT("moisture = ");
  DPRINTLN(state.moisture);
  DPRINT("state = ");
  DPRINTLN(state.state);
  DPRINT("intervals = ");
  DPRINTLN(state.intervals);
  DPRINT("radio = ");
  DPRINTLN(radio_initialized);
  DFLUSH;
  switch(state.state) {
    case WAIT_FOR_DELAY_OR_DRY:
      if ((state.moisture >= state.dry) || (state.intervals > (state.delay_hours * 60 * 60 / INTERVAL))) {
        state.state = WATER_TILL_WET_MAX_TIME;
        pumpOn(state.pumpSpeed);
        state.intervals = 0;
      }
      break;
    case WATER_TILL_WET_MAX_TIME:
      if (state.intervals > (state.max_time_mins * 60 / INTERVAL)) {
        // not yet wet enough, so leave for another MAX_TIME before watering
        state.state = WAIT_FOR_MAX_TIME;
        pumpOff();
        state.intervals = 0;
      } else if (state.moisture <= state.wet) {
        state.state = WAIT_FOR_DELAY_OR_DRY;
        pumpOff();
        state.intervals = 0;
      }
      break;
    case WAIT_FOR_MAX_TIME:
      if (state.intervals > (state.max_time_mins * 60 / INTERVAL)) {
        state.state = WATER_TILL_WET_MAX_TIME;
        pumpOn(PUMP_SPEED);
        state.intervals = 0;
      }
      break;
    default:
      state.state = WAIT_FOR_DELAY_OR_DRY;
      pumpOff();
      state.intervals = 0;
      state.errors++;
  }
  sendState();
  if (state.pumpRunning) {
    delay(INTERVAL * 1000);
//    LowPower.idle(SLEEP_8S, ADC_OFF, TIMER2_ON, TIMER1_ON, TIMER0_ON, 
//        SPI_OFF, USART0_OFF, TWI_OFF);
  } else {
//    LowPower.powerDown(SLEEP_2S, ADC_OFF, BOD_OFF);
    delay(2000);
    LoRa.sleep();
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
//    delay(8000);
  }
  state.intervals++;
}

void sendState() {
  if (radio_initialized) {
    LoRa.beginPacket();
    LoRa.write((byte *)&(my_header), sizeof(Header));
    LoRa.write((byte *)&(state), sizeof(State));
    LoRa.endPacket();
    LoRa.receive();
  } else {
    radio_initialized = setupLoRa();
  }
}

void onReceive(int packetSize) {
  DPRINT("Received packet, size=");
  DPRINTLN(packetSize);
  if (packetSize == (sizeof(Header) + sizeof(State))) {
    DPRINT("Reading: ");
    Header their_header;
    for (int i=0; i<sizeof(Header); i++) {
      byte b = LoRa.read();
      DPRINTHEX(b);
      ((byte *)&their_header)[i] = b;
    }
    bool for_me = ((their_header.to == STATION_ID) && (their_header.from == HOME_ID));
    DPRINTLN("");
    DPRINT("For me: ");
    DPRINTLN(for_me);
    for (int i=0; i<sizeof(State); i++) {
      byte b = LoRa.read();
      if (for_me) {
        ((byte *)&state)[i] = b;
      }
      DPRINTHEX(b);
    }
  }
  DPRINTLN("");
  DPRINTLN("Finished onReceive()");
  LoRa.sleep();
}