#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(); }