To get this project running quickly, I will be using an Arduino for a lot of the control. As time goes by I plan on replacing the Arduino funcions with code on the EZR32WG, but this is the only way I can see to complet the project by the deadline. The Arduino will be directly interfacing with the sensors and relays, and the EZR32WG will be sending commands to it through UART. These commands will all be in the form c, ##, val; where c is the command type, ## is the ID, and val is a value. There are 3 types of commands the Arduino can receive:
- Input commands.
When c='i', the Arduino will read the input that corresponds to the ID # and report that command back over the serial line. The val field is irrelevant. Here is a table of the inputs:
Input InputID Pin Type
Light1 1 A5 Bool <<evaluates to a bool
Light2 2 A4 Bool <<evaluates to a bool
Light3 3 A3 Bool <<evaluates to a bool
Current 4 A2 Float <<Instaneous current
TempGerm 5 2 Float <<One Wire bus
Temp2 6 2 Float <<One Wire bus
Temp1 7 2 Float <<One Wire bus
TempRes 8 2 Float <<One Wire bus
WaterLvl 9 3 Float <<Water level/volume after processing of Echo value from HC-SR04
2. Output commands.
When c='o', the Arduino will set the output that corresponds to the ID # to the value in the value field. For this application, this value will always be 1 or 0. This will turn the corresponding output to on or off, and report the change over the serial line. Here is a table of the outputs:
Output OutputID Pin
Light3 1 5 << Top level light turned on and off on a schedule
Light2 2 7 << Middle level light turned on and off on a schedule
Light1 3 8 << Bottom level light turned on and off on a schedule
GermHeat 4 6 << Heat pad for germination zone. Controlled by evaluation of TempGerm and GermTempGoal
MainHeat 5 9 << Main Space Heater. Controlled by evaluation of Temp1, Temp2, and Temp1Goal
ResHeat 6 10 << Reservoir Heater. Controlled by evaluation of TempRes, and ResTemp1Goal
Pump1 7 11 << Pump for bottom level. Controlled by timer set by Pump1OnTime and Pump1OffTime
Pump2 8 12 << Pump for middle level. Controlled by timer set by Pump2OnTime and Pump2OffTime
WaterTrigger 9 4 << Trigger for water level sensor. Contolled by sensor reading function.
3. Variable changes.
When c='v', the Arduino will change the value of one of the control varibles and echo that change over the serial line. These variables include current time, on/off times, alarm settings, etc. Here is a table of all of the variables.
Variable VarID Default Val
GermTempGoal 1 75.0 << Temperature goal for germinating seedlings
Temp1Goal 2 65.0 << Goal for level one and two
Temp2Goal 3 65.0 << Currently unused
ResTempGoal 4 70.0 << Goal for reservoir temperature
Pump1OnTime 5 20000 << Pump 1 is on for 20 seconds and off an hour by default
Pump1OffTime 6 3600000
Pump2OnTime 7 20000 << Pump 2 is on for 20 seconds and off an hour by default
Pump2OffTime 8 3600000
WaterLowLevel 9 20 << By default an alarm will be sent if the water level drops below 20L
Time 10 unset <<Time is a special value in Time.h that has time_t format
Light1OnHour 11 8 <<hour value for turing light1 on
Light1OffHour 12 24 <<hour value for turing light1 off
Light2OnHour 13 8 <<hour value for turing light2 on
Light2OffHour 14 24 <<hour value for turing light2 off
Light3OnHour 15 6 <<hour value for turing light3 on
Light3OffHour 16 24 <<hour value for turing light3 off
LastScanPower 17 0 <<amount of power used since the last system scan
TotalPower 18 0 <<total amount of power used since startup.
The Arduino will monitor the serial line for an input from the EZR32WG. If it doesn't receive an input, it will scan the I/O and make changes based on the current time and the state of the inputs and outputs compared with control variables. If sensor input doesn't match the output state, for example if the photocell detects a light is off when it should be on, the Arduino will send the alarm to the host. These alarms will trigger emails from the BBB to the user. If a scan of the variables indicates a needed variable is not set, the Arduino will request it from the host.
Here is the code. I have also attached it. It has not been tested yet, and it is really pretty sloppy. However, like everything else with this project, I am just trying to get something finished in the limited amount of time left and with my hectic schedule. As long as it works I am ok with the sloppiness. Code can always be tidied up later. (although it usually isn't if it works). Next task is to wire this into the control system and make sure it works. Then I will get the communications working between the EZR32WG and the Arduino. The end is in sight!
// Sketch for HydroNode1 // Reads sensors, activates outputs, and reports information to the controller on demand // // Code by James O'Donnell - #include <OneWire.h> #include <DallasTemperature.h> #include <Time.h> /* Device and variable list. All inputs have float or boolean values Input InputID Pin Type Light1 1 A5 Bool <<evaluates to a bool Light2 2 A4 Bool <<evaluates to a bool Light3 3 A3 Bool <<evaluates to a bool Current 4 A2 Float <<Instaneous current TempGerm 5 2 Float <<One Wire bus Temp2 6 2 Float <<One Wire bus Temp1 7 2 Float <<One Wire bus TempRes 8 2 Float <<One Wire bus WaterLvl 9 3 Float <<Water level/volume after processing of Echo value from HC-SR04 All Outputs also have an associated boolean value stored in the Outputs array Output OutputID Pin Light3 1 5 << Top level light turned on and off on a schedule Light2 2 7 << Middle level light turned on and off on a schedule Light1 3 8 << Bottom level light turned on and off on a schedule GermHeat 4 6 << Heat pad for germination zone. Controlled by evaluation of TempGerm and GermTempGoal MainHeat 5 9 << Main Space Heater. Controlled by evaluation of Temp1, Temp2, and Temp1Goal ResHeat 6 10 << Reservoir Heater. Controlled by evaluation of TempRes, and ResTemp1Goal Pump1 7 11 << Pump for bottom level. Controlled by timer set by Pump1OnTime and Pump1OffTime Pump2 8 12 << Pump for middle level. Controlled by timer set by Pump2OnTime and Pump2OffTime WaterTrigger 9 4 << Trigger for water level sensor. Contolled by sensor reading function. All Variables are floats or ints (except for Time). Variable VarID Default Val GermTempGoal 1 75.0 << Temperature goal for germinating seedlings Temp1Goal 2 65.0 << Goal for level one and two Temp2Goal 3 65.0 << Currently unused ResTempGoal 4 70.0 << Goal for reservoir temperature Pump1OnTime 5 20000 << Pump 1 is on for 20 seconds and off an hour by default Pump1OffTime 6 3600000 Pump2OnTime 7 20000 << Pump 2 is on for 20 seconds and off an hour by default Pump2OffTime 8 3600000 WaterLowLevel 9 20 << By default an alarm will be sent if the water level drops below 20L Time 10 unset <<Time is a special value in Time.h that has year, month, day, hour, minute, second format Light1OnHour 11 unset <<hour value for turing light1 on Light1OffHour 12 unset <<hour value for turing light1 off Light2OnHour 13 unset <<hour value for turing light2 on Light2OffHour 14 unset <<hour value for turing light2 off Light3OnHour 15 unset <<hour value for turing light3 on Light3OffHour 16 unset <<hour value for turing light3 off LastScanPower 17 0 <<amount of power used since the last system scan TotalPower 18 0 <<total amount of power used since startup. */ #define SERIAL_BAUD 115200 //default for communications over UART with EZR32WG #define MAX_INPUT_SIZE 20 //ten characters max per command /************ Define One Wire sensors ************************************/ // Setup a oneWire instance to communicate with any OneWire devices OneWire oneWire(2); // Pass our oneWire reference to Dallas Temperature. DallasTemperature sensors(&oneWire); DeviceAddress TempGerm_add = { 0x28, 0x3A, 0xDF, 0x8A, 0x06, 0x00, 0x00, 0x1E }; DeviceAddress Temp2_add = { 0x28, 0xC5, 0xF5, 0x89, 0x06, 0x00, 0x00, 0x85 }; DeviceAddress Temp1_add = { 0x28, 0xBD, 0x80, 0x89, 0x06, 0x00, 0x00, 0x03 }; DeviceAddress TempRes_add = { 0x28, 0x0D, 0xBD, 0x8A, 0x06, 0x00, 0x00, 0xE4 }; /*************************************************************************/ /**************************** Define Ultrasonic Sensor *******************/ #define TRIGGER_PIN 4 // Arduino pin tied to trigger pin on the ultrasonic sensor. #define ECHO_PIN 3 // Arduino pin tied to echo pin on the ultrasonic sensor. /*************************************************************************/ /***************************Define Analog sensors ************************/ #define Light1_Apin A5 #define Light2_Apin A4 #define Light3_Apin A3 #define Current_Apin A2 #define LightOnThresh 55 //If An in is over this value light is on /*************************************************************************/ /************************* Define Outputs ********************************/ #define Light3_pin 5 //1 #define Light2_pin 7 //2 #define Light1_pin 8 //3 #define GermHeat_pin 6 //4 #define MainHeat_pin 9 //5 #define ResHeat_pin 10 //6 #define Pump1_pin 11 //7 #define Pump2_pin 12 //8 int OutputStates[8] = {0, 0, 0, 0, 0, 0, 0, 0}; //here 0 = off. /*************************************************************************/ /************************* Define Inputs ********************************/ float TempGermVal, Temp1Val, Temp2Val, TempResVal, ResVolume; bool Light1, Light2, Light3; /*************************************************************************/ /************************* Define/Initialize Global Variables **************************/ float GermTempGoal = 75.0, Temp1Goal = 65.0, Temp2Goal = 65.0, TempResGoal = 70; //Temperature goals in deg F int Pump1OnTime = 20000, Pump1OffTime = 600000, Pump2OnTime = 20000, Pump2OffTime = 200000; //Pump Times in milliseconds float WaterLowLevel = 20, TotalPower = 0; // Level to give alarm for low water level. unsigned long ScanPeriod = 1000, LastScanTime = 0, CurrentTime = 0, LastGermHeatChangeTime = 0, LastMainHeatChangeTime = 0, LastResHeatChangeTime = 0, LastPump1ChangeTime = 0, LastPump2ChangeTime = 0, TempChangeMin = 30000, TransmitAllPeriod = 60000; //scan i/o to every second. Temp can only change every 30 seconds. time_t currentTime; int Light1OnHour, Light1OffHour, Light2OnHour, Light2OffHour, Light3OnHour, Light3OffHour; /*************************************************************************/ /************************* Functions *************************************/ //Reads WaterLevel and returns volume. float WaterLevel(){ long pulsewidth, distance; float volume; float distToVolumeMult = 1; digitalWrite(TRIGGER_PIN, LOW); delayMicroseconds(10); digitalWrite(TRIGGER_PIN, HIGH); delayMicroseconds(10); digitalWrite(TRIGGER_PIN, LOW); pulsewidth = pulseIn(ECHO_PIN, HIGH); distance = (((float)pulsewidth)/58.2); if (distance >= 200 || distance <= 0){ Serial.println("Out of range"); } else { distance * distToVolumeMult; } delay(10); return volume; } //Compares WaterLevel to alarm level and sends an alarm to the host if it is too low. int EvalH2OLevel(){ float volume = WaterLevel(); if (volume < WaterLowLevel){ Serial.println("v,9,low"); return 1; } return 0; } //Changes the light state based on the time set. int EvalLightState(int LightNum){ time_t currentHour = hour(); switch(LightNum){ case 1: if(currentHour == Light1OnHour){ OutputStates[LightNum - 1] = 1; digitalWrite(Light1_pin, HIGH); }else if (currentHour == Light1OffHour){ OutputStates[LightNum - 1] = 0; digitalWrite(Light1_pin, LOW); } case 2: if(currentHour == Light2OnHour){ OutputStates[LightNum - 1] = 1; digitalWrite(Light2_pin, HIGH); }else if (currentHour == Light2OffHour){ OutputStates[LightNum - 1] = 0; digitalWrite(Light2_pin, LOW); } case 3: if(currentHour == Light3OnHour){ OutputStates[LightNum - 1] = 1; digitalWrite(Light3_pin, HIGH); }else if (currentHour == Light3OffHour){ OutputStates[LightNum - 1] = 0; digitalWrite(Light3_pin, LOW); } } } //Reads lights and compare to threshhold value to see if light is on or off. Returns 1 or 0 int ReadLight(int LightNum){ int analogVal = 0, state = 0; switch(LightNum){ case 1: analogVal = analogRead(Light1_Apin); break; case 2: analogVal = analogRead(Light2_Apin); break; case 3: analogVal = analogRead(Light3_Apin); break; } if (analogVal > LightOnThresh){ state = 1; } return state; } //Compares light input with the output to make sure light is on. Returns 0 if there is an error, and 1 if not. int EvalLight(int LightNum){ int outState = OutputStates[LightNum - 1]; int inState = ReadLight(LightNum); if (inState != outState){ //SendAlarm(LightNum, outState, inState); return 0; } else{ return 1; } } //Reads Current float ReadCurrent(){ float current; current = (float) analogRead(Current_Apin); return (current * 4.9)/100; } //Calculates power float CalculatePower(){ float instCurrent, power; instCurrent = ReadCurrent(); power = instCurrent * 115 * ((CurrentTime - LastScanTime) / 1000); //current * volts * time since last scan. **approximation only Serial.print("v,17,"); Serial.print(power); Serial.println(";"); return power; } //Gets Temperature from Dallas One Wire sensors float GetTemperature(DeviceAddress deviceAddress) { float tempC = sensors.getTempC(deviceAddress); float tempF = DallasTemperature::toFahrenheit(tempC); if (tempC == -127.00) { Serial.println("problem"); //SendAlarm(); //send an alarm that the sensor is not working. } return tempF; } //Evaluates whether or not to turn on/off heater based on current temperature and last time the heat was switched. Returns a 1 if turned on, a 0 if turned off or a 2 if nothing. int EvalTemp(int heater){ float temp; int state, newState; switch (heater){ case 4: state = OutputStates[3]; temp = GetTemperature(TempGerm_add); //Germination heater if (state == 1 && (temp > GermTempGoal + 5) && ((CurrentTime - LastGermHeatChangeTime) > TempChangeMin)){ digitalWrite(GermHeat_pin, HIGH); LastGermHeatChangeTime = CurrentTime; newState = 0; }else if(state == 0 && (temp < GermTempGoal + 5) && ((CurrentTime - LastGermHeatChangeTime) > TempChangeMin)){ digitalWrite(GermHeat_pin, LOW); LastGermHeatChangeTime = CurrentTime; newState = 1; } break; case 5: state = OutputStates[4]; temp = (GetTemperature(Temp2_add) + GetTemperature(Temp1_add))/2; //Average of the level one and two temperatures if (state == 1 && (temp > Temp1Goal + 5) && ((CurrentTime - LastMainHeatChangeTime) > TempChangeMin)){ digitalWrite(MainHeat_pin, HIGH); LastMainHeatChangeTime = CurrentTime; newState = 0; }else if(state == 0 && (temp < Temp1Goal + 5) && ((CurrentTime - LastMainHeatChangeTime) > TempChangeMin)){ digitalWrite(MainHeat_pin, LOW); LastMainHeatChangeTime = CurrentTime; newState = 1; } break; case 6: state = OutputStates[4]; temp = GetTemperature(TempRes_add); //Reservoir Temp if (state == 1 && (temp > TempResGoal + 5) && ((CurrentTime - LastResHeatChangeTime) > TempChangeMin)){ digitalWrite(ResHeat_pin, HIGH); LastResHeatChangeTime = CurrentTime; newState = 0; }else if(state == 0 && (temp < TempResGoal + 5) && ((CurrentTime - LastResHeatChangeTime) > TempChangeMin)){ digitalWrite(ResHeat_pin, LOW); LastResHeatChangeTime = CurrentTime; newState = 1; } break; } if (newState != state ){ Serial.print('o'); Serial.print(","); Serial.print(heater); Serial.print(","); Serial.print(newState); Serial.println(";"); OutputStates[heater - 1] = newState; return newState; } return 2; } //Checks if pump shoud turn on or off. Returns a 1 if turned on, a 0 if turned off or a 2 if nothing. int EvalPump(int pump){ int state, newState; state = OutputStates[pump-1]; newState = state; // Serial.print(state); Serial.print(","); Serial.print(pump); Serial.print(","); Serial.print(newState); Serial.println(";"); switch(pump){ case 7: if ((state == 1) && ((CurrentTime - LastPump1ChangeTime) > Pump1OnTime)){ digitalWrite(Pump1_pin, HIGH); LastPump1ChangeTime = CurrentTime; newState = 0; }else if ((state == 0) && ((CurrentTime - LastPump1ChangeTime) > Pump1OffTime)){ digitalWrite(Pump1_pin, LOW); LastPump1ChangeTime = CurrentTime; newState = 1; } break; case 8: if ((state == 1) && ((CurrentTime - LastPump2ChangeTime) > Pump2OnTime)){ digitalWrite(Pump2_pin, HIGH); LastPump2ChangeTime = CurrentTime; newState = 0; }else if ((state == 0) && ((CurrentTime - LastPump2ChangeTime) > Pump2OffTime)){ digitalWrite(Pump2_pin, LOW); LastPump2ChangeTime = CurrentTime; newState = 1; } break; } if (newState != state ){ Serial.print('o'); Serial.print(","); Serial.print(pump); Serial.print(","); Serial.print(newState); Serial.println(";"); OutputStates[pump - 1] = newState; return newState; } } //Transfers all output states, variable values, and input data to host void SendAll(){ } void setup() { // setup pinmodes pinMode(Light3_pin, OUTPUT); // Light3_pin pinMode(GermHeat_pin, OUTPUT); // GermHeat_pin pinMode(Light2_pin, OUTPUT); // Light2_pin pinMode(Light1_pin, OUTPUT); // Light1_pin pinMode(MainHeat_pin, OUTPUT); // MainHeat_pin pinMode(ResHeat_pin, OUTPUT); // ResHeat_pin pinMode(Pump1_pin, OUTPUT); // Pump1_pin pinMode(Pump2_pin, OUTPUT); // Pump2_pin pinMode(Light1_Apin, INPUT); // Light1 Analog Input pinMode(Light2_Apin, INPUT); // Light2 Analog Input pinMode(Light3_Apin, INPUT); // Light3 Analog Input pinMode(Current_Apin, INPUT); // Current Analog Input pinMode(ECHO_PIN, INPUT); // ultrasonic echo pin pinMode(TRIGGER_PIN, OUTPUT); // ultrasonic trigger pin //intialize outputs to off. Outputs will sink current to turn on Relays digitalWrite(Light3_pin, HIGH); digitalWrite(GermHeat_pin, HIGH); digitalWrite(Light2_pin, HIGH); digitalWrite(Light1_pin, HIGH); digitalWrite(MainHeat_pin, HIGH); digitalWrite(ResHeat_pin, HIGH); digitalWrite(Pump1_pin, HIGH); digitalWrite(Pump2_pin, HIGH); digitalWrite(Pump2_pin, HIGH); digitalWrite(TRIGGER_PIN, LOW); // Start Dallas Temp library and set the resolution to 10 bit sensors.begin(); sensors.setResolution(TempGerm_add, 10); sensors.setResolution(Temp2_add, 10); sensors.setResolution(Temp1_add, 10); sensors.setResolution(TempRes_add, 10); //Begin Serial Comms Serial.begin(SERIAL_BAUD); } /******************************************************************************************************************************************* * Main Loop will check for and process serial data, scan inputs, and adjust outputs based on input values. * */ void loop() { //get time since program started CurrentTime = millis(); // read and parse serial input // Protocol will be: x,xx,xxx = i/o/v,##,val // All input will be in the form: input/output/variable, i/o/v number, value, except for time: v,10, year, month, day, hour, minute, second;.... // Inputs will read the input number, outputs will turn on or off based on 1 or 0 variable value, variables will be modified by a numerical value or read by an 'r' value if (Serial.available() > 0) { char input[MAX_INPUT_SIZE+1]; byte size = Serial.readBytes(input, MAX_INPUT_SIZE); input[size] = 0; char type; int id, y, m, d, h, mi, s; float value; int count = 0; char* inPart = strtok(input, ",;"); type = inPart[0]; count++; while (inPart != 0) { inPart = strtok(0, ",;"); if (count == 1) { id = atoi(inPart); count++; }else if (count == 2) { value = atof(inPart); count++; }else if (count == 3) { y = (int) value; m = atoi(inPart); count++; }else if (count == 4) { d = atoi(inPart); count++; }else if (count == 5) { h = atoi(inPart); count++; }else if (count == 6) { mi = atoi(inPart); count++; }else if (count == 7) { s = atoi(inPart); count++; } } //Serial.print("Received: "); Serial.print(type); Serial.print(","); Serial.print(id); Serial.print(","); Serial.print(value); Serial.println(";"); if (type == 'i') //handle input request { switch (id) { case 1: value = (float) ReadLight(1); break; //create read light function to return 1 if light is sensed and 0 if not. case 2: value = (float) ReadLight(2); break; case 3: value = (float) ReadLight(3); break; case 4: value = ReadCurrent(); break; //create ReadCurrent function to return two digit precision float case 5: value = GetTemperature(TempGerm_add); break; case 6: value = GetTemperature(Temp2_add); break; case 7: value = GetTemperature(Temp1_add); break; case 8: value = GetTemperature(TempRes_add); break; case 9: value = WaterLevel(); break; default: value = 99.99; break; } Serial.print(type); Serial.print(","); Serial.print(id); Serial.print(","); Serial.print(value); Serial.println(";"); }else if(type == 'o'){ //handle output change (Low turns relays on) bool out = LOW; if (value == 0){ out = HIGH; }else{ out = LOW; } switch (id) { case 1: digitalWrite(Light3_pin, out); OutputStates[id - 1] = (int)value; break; case 2: digitalWrite(Light2_pin, out); OutputStates[id - 1] = (int)value; break; case 3: digitalWrite(Light1_pin, out); OutputStates[id - 1] = (int)value; break; case 4: digitalWrite(GermHeat_pin, out); OutputStates[id - 1] = (int)value; break; case 5: digitalWrite(MainHeat_pin, out); OutputStates[id - 1] = (int)value; break; case 6: digitalWrite(ResHeat_pin, out); OutputStates[id - 1] = (int)value; break; case 7: digitalWrite(Pump1_pin, out); OutputStates[id - 1] = (int)value; break; case 8: digitalWrite(Pump2_pin, out); OutputStates[id - 1] = (int)value; break; default: value = 99.99; break; } Serial.print(type); Serial.print(","); Serial.print(id); Serial.print(","); Serial.print(out); Serial.println(";"); }else if(type == 'v'){ //handle change of control variables. switch (id) { case 1: GermTempGoal = value; break; case 2: Temp1Goal = value; break; case 3: Temp2Goal = value; break; case 4: TempResGoal = value; break; case 5: Pump1OnTime = (int) value; break; case 6: Pump1OffTime = (int) value; break; case 7: Pump2OnTime = (int) value; break; case 8: Pump2OffTime = (int) value; break; case 9: WaterLowLevel = value; break; case 10: setTime(h, m, s, d, mi, y); break; case 11: Light1OnHour = (int) value; break; case 12: Light1OffHour = (int) value; break; case 13: Light2OnHour = (int) value; break; case 14: Light2OffHour = (int) value; break; case 15: Light3OnHour = (int) value; break; case 16: Light3OffHour = (int) value; break; default: value = 99.99; break; } Serial.print(type); Serial.print(","); Serial.print(id); Serial.print(","); Serial.print(value); Serial.println(";"); } } if(CurrentTime - LastScanTime > ScanPeriod) { //Serial.println("Scanning"); if (timeStatus() == timeNotSet){ //Check if the time is set before looking to set the time Serial.println("v,10,error"); //If the time is not set, ask the host to set the time. }else{ EvalLightState(1); EvalLightState(2); EvalLightState(3); } EvalLight(1); //check if the lights are in proper state if not send error. EvalLight(2); EvalLight(3); EvalTemp(4); //Evaluates temperature and heater state based on output number. EvalTemp(5); EvalTemp(6); EvalPump(7); //Check if pumps should turn on or off. EvalPump(8); EvalH2OLevel(); //check level of h20 and send alarm if it is too low. TotalPower += CalculatePower(); //Calculate power used last second and send to host. LastScanTime = CurrentTime; } }