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;
  byte pumpSpeed;
  bool pumpRunning;
  byte errors;
};

#define PROC_PUMP_OFF 0
#define PROC_PUMP_ON  1
#define PROC_SLEEP    2

struct Cmd {
  byte proc, arg;
};

Header my_header, their_header;

State state;
Cmd cmd;

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

void setup() {
#ifdef DEBUG
  Serial.begin(9600);
#endif
  my_header = { HOME_ID, STATION_ID, 0, 0 };
  their_header = { 0, 0, 0, 0 };
  state = { 0, 0, PUMP_SPEED, false, 0 };
  cmd = { 0, 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) {
  DPRINT("Switching pump on at ");
  DPRINTLN(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.pumpSpeed = speed;
  state.pumpRunning = true;  
}

void pumpOff() {
  DPRINTLN("Switching pump off");
  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;
  state.moisture = moisture / SAMPLES;
  if (state.voltage <= 135) {
    LoRa.sleep();
    LoRa.end();
    radio_initialized = false;
    pumpOff();
  } else {
    sendState();
    delay(2000);
    LoRa.sleep();
  }
  DPRINT("voltage = ");
  DPRINTLN(state.voltage);
  DPRINT("moisture = ");
  DPRINTLN(state.moisture);
  DPRINT("radio = ");
  DPRINTLN(radio_initialized);
  DFLUSH;
  if (state.pumpRunning) {
    delay(8000);
//    LowPower.idle(SLEEP_8S, ADC_OFF, TIMER2_ON, TIMER1_ON, TIMER0_ON, 
//        SPI_OFF, USART0_OFF, TWI_OFF);
  } else {
    LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
  }
}

void sendState() {
  if (radio_initialized) {
    my_header.seq++;
    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(Cmd))) {
    DPRINT("Reading: ");
    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(Cmd); i++) {
      byte b = LoRa.read();
      if (for_me) {
        ((byte *)&cmd)[i] = b;
      }
      DPRINTHEX(b);
    }
    if (for_me) {
      if (cmd.proc == PROC_PUMP_OFF) {
        pumpOff();
      } else if (cmd.proc == PROC_PUMP_ON) {
        pumpOn(cmd.arg);
      }
    }
  }
  DPRINTLN("");
  DPRINTLN("Finished onReceive()");
//  LoRa.sleep();
}