#include <IRremote.h> #include <IRremoteInt.h> #include <Wire.h> #include <OneWire.h> #include <TimeLord.h> #include <Time.h> #include <TimeAlarms.h> #include <DS1307RTC.h> /** * Copyright 2012, Roger Reed * * 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. */ // Whether to print debug messages to Serial const boolean SERIAL_DEBUG = false; // Pin definitions const byte LIGHT_TOGGLE_BUTTON_PIN = 2, WATER_BUTTON_PIN = 3, DOOR_RELAY_CLOSE_PIN = 4, DOOR_RELAY_OPEN_PIN = 5, AIR_TEMP_SENSOR_PIN = 6, IR_RECV_PIN = 7, LIGHT_RELAY_PIN = 8, FEEDER_RELAY_PIN = 9, FAN_RELAY_PIN = 10, WATER_SOLENOID_RELAY_PIN = 11, DOOR_OPEN_SWITCH_PIN = A0, DOOR_CLOSE_SWITCH_PIN = A1, FEED_BUTTON_PIN = A2, WATER_FLOAT_SWITCH_PIN = A3, DOOR_CLOSE_STOP_PIN = A6, DOOR_OPEN_STOP_PIN = A7; // Infrared constants const int SONY_IR_0 = 0x910, SONY_IR_1 = 0x10, SONY_IR_2 = 0x810, SONY_IR_3 = 0x410, SONY_IR_4 = 0xC10, SONY_IR_5 = 0x210, SONY_IR_6 = 0xA10, SONY_IR_7 = 0x610, SONY_IR_8 = 0xE10, SONY_IR_9 = 0x110; // Delay constants const int POST_IR_HANDLE_DELAY_MS = 1250, POST_BUTTON_HANDLE_DELAY_MS = 750; // Duration constants const int FEED_DURATION_SEC = 20, WATER_DURATION_SEC = 20; // Time constants const int DOOR_SHUT_AFTER_SUNSET_MIN = 30, LIGHT_PER_DAY_HRS = 16, WATER_AFTER_SUNRISE_MIN = 15, FEED_MORNING_AFTER_SUNRISE_MIN = 30, FEED_AFTERNOON_BEFORE_SUNSET_HR = 4, FEED_EVENING_BEFORE_SUNSET_HR = 1, FEED_MID_DAY_HR = 12, FEED_MID_DAY_MIN = 0, LIGHT_ON_BEFORE_SUNSET_HRS = 2; // Temperature constants const float MIN_REASONABLE_AIR_TEMP_F = 0.0f, // low air temp we should never see; if we do most likely a temp probe issue MAX_REASONABLE_AIR_TEMP_F = 150.0f, // high air temp we should never see INIT_TEMP = -1000.0f, FAN_ON_TEMP_F = 77.0f, // temp fan is turned on FAN_OFF_TEMP_F = 70.0f; // temp fan is turned off; // Location constants const float LATITUDE = 33.0369867, LONGITUDE = -117.2919818; const int TIMEZONE = -8; // Infrared variables IRrecv irRecv(IR_RECV_PIN); decode_results irResults; // Temperature variables OneWire airTempOneWire(AIR_TEMP_SENSOR_PIN); boolean airTempError = false; float airTemp = INIT_TEMP; // Time variables TimeLord coopTimeLord; long feedOnTimeSec = 0, waterOnTimeSec = 0, lastLoopSec = now(); byte openDoorHr, openDoorMin, closeDoorHr, closeDoorMin, lightOnHr, lightOnMin, lightOffHr, lightOffMin, waterHr, waterMin, feedMorningHr, feedMorningMin, feedAfternoonHr, feedAfternoonMin, feedEveningHr, feedEveningMin; // Control variables boolean manualWater = false; void setup() { if(SERIAL_DEBUG){ Serial.begin(57600); // initialize hardware serial port } Wire.begin(); irRecv.enableIRIn(); setSyncProvider(RTC.get); coopTimeLord.TimeZone(TIMEZONE * 60); coopTimeLord.Position(LATITUDE, LONGITUDE); setupOutputPins(); initState(); scheduleDailyAlarms(); scheduleTodayAlarms(); } void loop(){ if (SERIAL_DEBUG) { serialDebugState(); } handleDoor(); updateAirTemp(); handleFan(); handleDurations(); handleControls(); handleFloat(); handleInfrared(); Alarm.delay(0); lastLoopSec = now(); } /** * Calculates alarm times used for scheduling and init state. */ void calculateAlarmTimes(){ int nowHour = hour(), nowMinute = minute(), nowDay = day(), nowMonth = month(), nowYear = year(); Serial.print("nowHour: "); Serial.println(nowHour); Serial.print("nowMinute: "); Serial.println(nowMinute); Serial.print("nowDay: "); Serial.println(nowDay); Serial.print("nowMonth: "); Serial.println(nowMonth); Serial.print("nowYear: "); Serial.println(nowYear); byte timeLordSunRise[] = {0, 0, 0, nowDay, nowMonth, nowYear}; byte timeLordSunSet[] = {0, 0, 0, nowDay, nowMonth, nowYear}; coopTimeLord.SunRise(timeLordSunRise); coopTimeLord.SunSet(timeLordSunSet); if(SERIAL_DEBUG){ Serial.print("sunrise: "); Serial.print(timeLordSunRise[2]); Serial.print(":"); Serial.println(timeLordSunRise[1]); Serial.print("sunset: "); Serial.print(timeLordSunSet[2]); Serial.print(":"); Serial.println(timeLordSunSet[1]); } openDoorHr = timeLordSunRise[2]; openDoorMin = timeLordSunRise[1]; closeDoorHr = timeLordSunSet[2]; closeDoorMin = timeLordSunSet[1]; if(closeDoorMin + DOOR_SHUT_AFTER_SUNSET_MIN >= 60){ closeDoorHr += (closeDoorMin + DOOR_SHUT_AFTER_SUNSET_MIN) / 60; closeDoorMin = (closeDoorMin + DOOR_SHUT_AFTER_SUNSET_MIN) % 60; }else{ closeDoorMin += DOOR_SHUT_AFTER_SUNSET_MIN; } if(SERIAL_DEBUG){ Serial.print("open door: "); Serial.print(openDoorHr); Serial.print(":"); Serial.println(openDoorMin); Serial.print("close door: "); Serial.print(closeDoorHr); Serial.print(":"); Serial.println(closeDoorMin); } float naturalDaylightHr; naturalDaylightHr = 12.0f-(timeLordSunRise[2] + timeLordSunRise[1]/60.0f); naturalDaylightHr += (timeLordSunSet[2] + timeLordSunSet[1]/60.0f)-12.0f; if(SERIAL_DEBUG){ Serial.print("natural daylight (hrs): "); Serial.println(naturalDaylightHr); } lightOnHr = timeLordSunSet[2]; lightOnMin = timeLordSunSet[1]; lightOffHr = timeLordSunSet[2]; lightOffMin = timeLordSunSet[1]; float lightNeededHrs = LIGHT_PER_DAY_HRS - naturalDaylightHr; int lightNeededMins = lightNeededHrs * 60; Serial.print("light needed (mins): "); Serial.println(lightNeededMins); lightOnHr -= LIGHT_ON_BEFORE_SUNSET_HRS; if(lightOffMin + lightNeededMins >= 60){ lightOffHr += (lightOffMin + lightNeededMins) / 60; lightOffMin = (lightOffMin + lightNeededMins) % 60; }else{ lightOffMin += lightNeededMins; } if(SERIAL_DEBUG){ Serial.print("light on: "); Serial.print(lightOnHr); Serial.print(":"); Serial.println(lightOnMin); Serial.print("light off: "); Serial.print(lightOffHr); Serial.print(":"); Serial.println(lightOffMin); } waterHr = timeLordSunRise[2]; waterMin = timeLordSunRise[1]; feedMorningHr = timeLordSunRise[2]; feedMorningMin = timeLordSunRise[1]; feedAfternoonHr = timeLordSunSet[2]; feedAfternoonMin = timeLordSunSet[1]; feedEveningHr = timeLordSunSet[2]; feedEveningMin = timeLordSunSet[1]; if(waterMin + WATER_AFTER_SUNRISE_MIN >= 60){ waterHr += (waterMin + WATER_AFTER_SUNRISE_MIN) / 60; waterMin = (waterMin + WATER_AFTER_SUNRISE_MIN) % 60; }else{ waterMin += WATER_AFTER_SUNRISE_MIN; } if(feedMorningMin + FEED_MORNING_AFTER_SUNRISE_MIN >= 60){ feedMorningHr += (feedMorningMin + FEED_MORNING_AFTER_SUNRISE_MIN) / 60; feedMorningMin = (feedMorningMin + FEED_MORNING_AFTER_SUNRISE_MIN) % 60; }else{ feedMorningMin += FEED_MORNING_AFTER_SUNRISE_MIN; } feedAfternoonHr -= FEED_AFTERNOON_BEFORE_SUNSET_HR; feedEveningHr -= FEED_EVENING_BEFORE_SUNSET_HR; if(SERIAL_DEBUG){ Serial.print("water: "); Serial.print(waterHr); Serial.print(":"); Serial.println(waterMin); Serial.print("feed (morning): "); Serial.print(feedMorningHr); Serial.print(":"); Serial.println(feedMorningMin); Serial.print("feed (afternoon): "); Serial.print(feedAfternoonHr); Serial.print(":"); Serial.println(feedAfternoonMin); Serial.print("feed (evening): "); Serial.print(feedEveningHr); Serial.print(":"); Serial.println(feedEveningMin); } } /** * Schedule daily alarms. */ void scheduleDailyAlarms(){ Alarm.alarmRepeat(1, 0, 0, scheduleTodayAlarms); Alarm.alarmRepeat(FEED_MID_DAY_HR, FEED_MID_DAY_MIN, 0, feed); } /** * Schedule today alarms. */ void scheduleTodayAlarms(){ calculateAlarmTimes(); int nowHr = hourMinuteToHour(hour(), minute()); if(nowHr < hourMinuteToHour(openDoorHr, openDoorMin)){ Alarm.alarmOnce(openDoorHr, openDoorMin, 0, enableDoorOpening); } if(nowHr < hourMinuteToHour(closeDoorHr, closeDoorMin)){ Alarm.alarmOnce(closeDoorHr, closeDoorMin, 0, enableDoorClosing); } if(nowHr < hourMinuteToHour(lightOnHr, lightOnMin)){ Alarm.alarmOnce(lightOnHr, lightOnMin, 0, lightOn); } if(nowHr < hourMinuteToHour(lightOffHr, lightOffMin)){ Alarm.alarmOnce(lightOffHr, lightOffMin, 0, lightOff); } if(nowHr < hourMinuteToHour(waterHr, waterMin)){ Alarm.alarmOnce(waterHr, waterMin, 0, water); } if(nowHr < hourMinuteToHour(feedMorningHr, feedMorningMin)){ Alarm.alarmOnce(feedMorningHr, feedMorningMin, 0, feed); } // mid day feed scheduled as daily at noon in scheduleDailyAlarms method if(nowHr < hourMinuteToHour(feedAfternoonHr, feedAfternoonMin)){ Alarm.alarmOnce(feedAfternoonHr, feedAfternoonMin, 0, feed); } if(nowHr < hourMinuteToHour(feedEveningHr, feedEveningMin)){ Alarm.alarmOnce(feedEveningHr, feedEveningMin, 0, feed); } } /** * Initialize state based on current time */ void initState(){ fanOff(); feedOff(); waterOff(); calculateAlarmTimes(); int nowHr = hourMinuteToHour(hour(), minute()); if(nowHr > hourMinuteToHour(lightOnHr, lightOnMin) && nowHr < hourMinuteToHour(lightOffHr, lightOffMin)){ Serial.println("light init on"); lightOn(); }else{ Serial.println("light init off"); lightOff(); } if(nowHr > hourMinuteToHour(openDoorHr, openDoorMin) && nowHr < hourMinuteToHour(closeDoorHr, closeDoorMin)){ Serial.println("door init open"); enableDoorOpening(); }else{ Serial.println("door init close"); enableDoorClosing(); } } /** * Set pin modes */ void setupOutputPins(){ // relays pinMode(DOOR_RELAY_OPEN_PIN, OUTPUT); pinMode(DOOR_RELAY_CLOSE_PIN, OUTPUT); pinMode(FEEDER_RELAY_PIN, OUTPUT); pinMode(LIGHT_RELAY_PIN, OUTPUT); pinMode(FAN_RELAY_PIN, OUTPUT); pinMode(WATER_SOLENOID_RELAY_PIN, OUTPUT); } void serialDebugState(){ // Serial.print("LIGHT_TOGGLE_BUTTON_PIN ("); // Serial.print(LIGHT_TOGGLE_BUTTON_PIN, DEC); // Serial.print("): "); // Serial.print(digitalRead(LIGHT_TOGGLE_BUTTON_PIN)); // Serial.print(", WATER_BUTTON_PIN ("); // Serial.print(WATER_BUTTON_PIN, DEC); // Serial.print("): "); // Serial.print(digitalRead(WATER_BUTTON_PIN)); // Serial.print(", DOOR_OPEN_SWITCH_PIN ("); // Serial.print(DOOR_OPEN_SWITCH_PIN, DEC); // Serial.print("): "); // Serial.print(digitalRead(DOOR_OPEN_SWITCH_PIN)); // Serial.print(", DOOR_CLOSE_SWITCH_PIN ("); // Serial.print(DOOR_CLOSE_SWITCH_PIN, DEC); // Serial.print("): "); // Serial.print(digitalRead(DOOR_CLOSE_SWITCH_PIN)); // Serial.print(", WATER_FLOAT_SWITCH_PIN ("); // Serial.print(WATER_FLOAT_SWITCH_PIN, DEC); // Serial.print("): "); // Serial.print(digitalRead(WATER_FLOAT_SWITCH_PIN)); // Serial.print(", FEED_BUTTON_PIN ("); // Serial.print(FEED_BUTTON_PIN, DEC); // Serial.print("): "); // Serial.print(digitalRead(FEED_BUTTON_PIN)); // Serial.print(", DOOR_CLOSE_STOP_PIN ("); // Serial.print(DOOR_CLOSE_STOP_PIN, DEC); // Serial.print("): "); // Serial.print(analogRead(DOOR_CLOSE_STOP_PIN) > 500); // Serial.print(", DOOR_OPEN_STOP_PIN ("); // Serial.print(DOOR_OPEN_STOP_PIN, DEC); // Serial.print("): "); // Serial.print(analogRead(DOOR_OPEN_STOP_PIN) > 500); // Serial.println(); Serial.print("airTemp: "); if(!airTempError){ Serial.print(airTemp); Serial.println(); }else{ Serial.println("ERROR"); } Serial.print("time: "); Serial.print(year(), DEC); Serial.print('/'); Serial.print(month(), DEC); Serial.print('/'); Serial.print(day(), DEC); Serial.print(' '); Serial.print(hour(), DEC); Serial.print(':'); Serial.print(minute(), DEC); Serial.print(':'); Serial.print(second(), DEC); Serial.println(); } /** * Handles any action that needs to be taken onfloat switch. */ void handleFloat(){ if(digitalRead(WATER_FLOAT_SWITCH_PIN)){ waterOn(); }else if(!manualWater){ waterOff(); } } /** * Handles any action that needs to be taken on control buttons or switches. */ void handleControls(){ if(isLightToggleButton()){ Serial.println("toggle light button"); toggleLight(); delay(POST_BUTTON_HANDLE_DELAY_MS); } if(isFeedButton()){ Serial.println("feed button"); feed(); delay(POST_BUTTON_HANDLE_DELAY_MS); } if(isWaterButton()){ Serial.println("water button"); manualWater = true; water(); delay(POST_BUTTON_HANDLE_DELAY_MS); } if(isDoorOpenSwitch()){ Serial.println("door open switch"); enableDoorOpening(); } if(isDoorCloseSwitch()){ Serial.println("door close switch"); enableDoorClosing(); } } /** * Handles any action that needs to be taken on the fan. */ void handleFan() { if(!airTempError){ if(airTemp >= FAN_ON_TEMP_F){ fanOn(); } if(airTemp <= FAN_OFF_TEMP_F){ fanOff(); } } } /** * Updates air temp and checks for probe error. */ void updateAirTemp() { airTempError = !updateTemp(&airTempOneWire, &airTemp, airTempError); if(airTempError || airTemp <= MIN_REASONABLE_AIR_TEMP_F || airTemp >= MAX_REASONABLE_AIR_TEMP_F){ airTempError = true; } } /** * Handles any action that needs to be taken on the door motor. */ void handleDoor() { if(isDoorClosing() && isDoorCloseStop()){ disableDoor(); } if(isDoorOpening() && isDoorOpenStop()){ disableDoor(); } } /** * Handles any durations (e.g. feed, water) that need to be monitored and turned off after set time. */ void handleDurations(){ if(isFeedOn()){ feedOnTimeSec += now() - lastLoopSec; if(SERIAL_DEBUG){ Serial.print("feeding ("); Serial.print(feedOnTimeSec); Serial.println(" sec)"); } if(feedOnTimeSec >= FEED_DURATION_SEC){ feedOff(); } } if(isWaterOn()){ waterOnTimeSec += now() - lastLoopSec; if(SERIAL_DEBUG){ Serial.print("watering ("); Serial.print(waterOnTimeSec); Serial.println(" sec)"); } if(waterOnTimeSec >= WATER_DURATION_SEC){ waterOff(); manualWater = false; } } } /** * Handles any action that need to be taken from Infrared */ void handleInfrared() { if (!irRecv.decode(&irResults)) { irRecv.resume(); return; } if(SERIAL_DEBUG){ Serial.print("irResults.value: "); Serial.println(irResults.value, HEX); } switch(irResults.value){ case SONY_IR_4: // water Serial.println("water remote"); manualWater = true; water(); delay(POST_IR_HANDLE_DELAY_MS); break; case SONY_IR_5: // toggle light Serial.println("toggle light remote"); toggleLight(); delay(POST_IR_HANDLE_DELAY_MS); break; case SONY_IR_6: // feed Serial.println("feed remote"); feed(); delay(POST_IR_HANDLE_DELAY_MS); break; case SONY_IR_7: // open door Serial.println("door open remote"); enableDoorOpening(); delay(POST_IR_HANDLE_DELAY_MS); break; case SONY_IR_8: // close door Serial.println("door close remote"); enableDoorClosing(); delay(POST_IR_HANDLE_DELAY_MS); break; case SONY_IR_9: // toggle fan Serial.println("toggle fan remote"); toggleFan(); delay(POST_IR_HANDLE_DELAY_MS); break; } irRecv.resume(); } // feed methods void feed(){ feedOnTimeSec = 0; feedOn(); } void feedOn(){ digitalWrite(FEEDER_RELAY_PIN, LOW); } void feedOff(){ digitalWrite(FEEDER_RELAY_PIN, HIGH); } boolean isFeedOn(){ return digitalRead(FEEDER_RELAY_PIN) == LOW; } boolean isFeedButton(){ return digitalRead(FEED_BUTTON_PIN) == HIGH; } // end feed methods // water methods void water(){ waterOnTimeSec = 0; waterOn(); } void waterOn(){ digitalWrite(WATER_SOLENOID_RELAY_PIN, HIGH); } void waterOff(){ digitalWrite(WATER_SOLENOID_RELAY_PIN, LOW); } boolean isWaterOn(){ return digitalRead(WATER_SOLENOID_RELAY_PIN) == HIGH; } boolean isWaterButton(){ return digitalRead(WATER_BUTTON_PIN) == HIGH; } // end feed methods // light methods void toggleLight(){ if(isLightOn()){ lightOff(); }else{ lightOn(); } } boolean isLightOn(){ return digitalRead(LIGHT_RELAY_PIN) == LOW; } void lightOn(){ digitalWrite(LIGHT_RELAY_PIN, LOW); } void lightOff(){ digitalWrite(LIGHT_RELAY_PIN, HIGH); } boolean isLightToggleButton(){ return digitalRead(LIGHT_TOGGLE_BUTTON_PIN) == HIGH; } // end light methods // fan methods void toggleFan(){ if(isFanOn()){ fanOff(); }else{ fanOn(); } } boolean isFanOn(){ return digitalRead(FAN_RELAY_PIN) == LOW; } void fanOn(){ digitalWrite(FAN_RELAY_PIN, LOW); } void fanOff(){ digitalWrite(FAN_RELAY_PIN, HIGH); } // end fan methods // door methods boolean isDoorOpenSwitch(){ return digitalRead(DOOR_OPEN_SWITCH_PIN); } boolean isDoorCloseSwitch(){ return digitalRead(DOOR_CLOSE_SWITCH_PIN); } boolean isDoorClosing(){ return digitalRead(DOOR_RELAY_CLOSE_PIN) == HIGH; } boolean isDoorOpening(){ return digitalRead(DOOR_RELAY_OPEN_PIN) == HIGH; } void enableDoorClosing(){ if(!isDoorCloseStop()){ digitalWrite(DOOR_RELAY_OPEN_PIN, LOW); digitalWrite(DOOR_RELAY_CLOSE_PIN, HIGH); } } void enableDoorOpening(){ if(!isDoorOpenStop()){ digitalWrite(DOOR_RELAY_CLOSE_PIN, LOW); digitalWrite(DOOR_RELAY_OPEN_PIN, HIGH); } } boolean isDoorInMiddle(){ return !isDoorOpenStop() && !isDoorCloseStop(); } boolean isDoorCloseStop(){ return analogRead(DOOR_CLOSE_STOP_PIN) > 500; } boolean isDoorOpenStop(){ return analogRead(DOOR_OPEN_STOP_PIN) > 500; } void disableDoor(){ digitalWrite(DOOR_RELAY_OPEN_PIN, LOW); digitalWrite(DOOR_RELAY_CLOSE_PIN, LOW); } // end door methods /** * Updates a single temp */ boolean updateTemp(OneWire * oneWireTherm, float * fltTemp, boolean previousError){ byte thermAddr[8], data[12]; oneWireTherm->reset_search(); oneWireTherm->search(thermAddr); if(OneWire::crc8(thermAddr,7)!=thermAddr[7]) { // checksum invalid return false; } if(!oneWireTherm->reset()){ return false; } oneWireTherm->select(thermAddr); oneWireTherm->write(0x44, 1); // start conversation w/ parasite power if(previousError){ delay(1000); } if(!oneWireTherm->reset()){ return false; } oneWireTherm->select(thermAddr); oneWireTherm->write(0xBE); // read scratchpad for(int i=0;i<9;i++) { // need 9 bytes data[i] = oneWireTherm->read(); } *fltTemp = getTemperature(data[0], data[1]); return true; } /** * Converts celsius to fahrenheit */ float convertCeliusToFahrenheit(float c) { return((c*1.8)+32); } /** * Converts fahrenheit to celsius */ float convertFahrenheitToCelius(float f) { return((f-32)*0.555555556); } float hourMinuteToHour(int hour, int minute){ return hour + minute/60.0f; } /** * Gets temp from bytes for OneWire */ float getTemperature(int lowByte, int highByte) { int intPostDecimal, boolSign, intHexTempReading, intTempReadingBeforeSplit, preDecimal, i; float fltPostDecimal, fltTemp; intHexTempReading = (highByte << 8) + lowByte; boolSign = intHexTempReading & 0x8000; if(boolSign) { intHexTempReading = (intHexTempReading ^ 0xffff) + 1; } intTempReadingBeforeSplit = 6.25 * intHexTempReading; // multiply by (100 * precision) = (100 * 0.0625) = 6.25 = 12-bit precision preDecimal = intTempReadingBeforeSplit / 100; intPostDecimal = intTempReadingBeforeSplit % 100; fltPostDecimal = intPostDecimal; if(intPostDecimal<10) { fltTemp = preDecimal+(fltPostDecimal/1000); } else { fltTemp = preDecimal+(fltPostDecimal/100); } if(boolSign) { fltTemp = -fltTemp; } return convertCeliusToFahrenheit(fltTemp); }