Things used in this project

Hardware components:
Photon new
Particle Photon
×1
Mfr 25frf52 10k sml
Resistor 10k ohm
×1
Mfr 25fbf52 221r sml
Resistor 221 ohm
×1
10988 01
Temperature Sensor
×1
Mfr 25fbf52 2k21 sml
Resistor 2.21k ohm
×1
Panasonic eca2am101
Capacitor 100 µF
×1
Fairchild semiconductor pn2222abu. image
General Purpose Transistor NPN
×2
Fairchild semiconductor 1n4004. image
1N4007 – High Voltage, High Current Rated Diode
×2
Mfr 25fbf52 4k75 sml
Resistor 4.75k ohm
×1
Relay (generic)
×1
12002 04
Breadboard (generic)
×1
11026 02
Jumper wires (generic)
×1
Software apps and online services:
Q8wtlimqnp04fzhtr9v5
IFTTT Maker service

Schematics

Breadboard View of Circuit
K4q3inpqo08cwzujwigo
Schematic
B2qodyjj5lr7uyb40vfa

Code

GATEKEEPER.INOC/C++
This code includes the conversation outline necessary to decide with thermostat is allowed to run. This code runs on both Photons involved.
// This #include statement was automatically added by the Particle IDE.
#include "OneWire/OneWire.h"

OneWire ds = OneWire(D4);  // 1-wire signal on pin D4

String localDataString; // strings that will be populated for personal datalogging
String foreignDataString;
String prevLocalData;
String prevForeignData;

int setPoint;
int error = 0;

int myTemp; // This is the local temperature
int myDev; // Calculated deviation from setpoint
bool myCall = false; // Whether or not the thermostat is trying to turn on
bool myRun = false; // whether the ac is allowed to run
int myPriority; // status of priority
int myReq; // used to request priority from foreign member
int myAns; // used to answer requests from foreign memeber

bool prevRun; // used to decide when to activate timer
int prevTemp; // used for error-checking

// variables from foreign member that mirror local data
int forTemp;
int forDev;
bool forCall;
bool forRun;
bool forPriority;
int forReq;
int forAns;
int forTimer;

Timer oneMin(30000, everyMinute);
Timer thirtyMinutes(1800000, thirtyMinutesPassed, true);

SYSTEM_MODE(AUTOMATIC);

void setup() {
    
    Serial.begin(9600);
    
    setPoint = 75;
    
    oneMin.start(); // start the timer that calls the function for posting logged data
    
    pinMode(D7, OUTPUT); // Relay (LOW = block)
    pinMode(D0, INPUT_PULLUP); // Thermostat call (LOW = no call)
    pinMode(D2, OUTPUT);
    
    digitalWrite(D2, HIGH);
  
    // Each unit will send publish different types of data, but they will be in a single subscribed event
    // Since these events are public, a random 8-digit number is assigned to each member in the system for uniqueness for event name
    // letter at the end of even name discerns the two photons
    Particle.subscribe("32420324_A", foreignDataHandler);
    
    // Monitor variables for debugging
    Particle.variable("myRun", myRun);
    Particle.variable("Temperature", myTemp);
}

void loop() {
    
    //int callState = digitalRead(D0);
    if (digitalRead(D0) == LOW) {
        myCall = false;
    }
    else if (digitalRead(D0) == HIGH) {
        myCall = true;
    }
    
    if (myPriority == 0) {
        digitalWrite(D7, LOW);
        myRun = 0;
        delay(50);
    }
    
    if (myRun == true) {
        digitalWrite(D0, HIGH); // activate relay to AC signal
    }
    else digitalWrite(D0, LOW); // de-activate relay to AC signal
    
    // When something in the local or foreign data string changes, it is published
    if (strcmp(prevLocalData,localDataString) != 0) {
        Particle.publish("local_GateKeeper", localDataString, PRIVATE);
        delay(500);
    }
    
    if (strcmp(prevForeignData,foreignDataString) != 0) {
        Particle.publish("Foreign_GateKeeper", foreignDataString, PRIVATE);
        delay(500);
    }
    
    // comparison values for next loop
    prevLocalData = localDataString;
    prevForeignData = foreignDataString;
}

void everyMinute() {
    
    // Get current temperature and then println
    // getCurrentTemp returns nothing, but sets varaible temperature in the function
    getCurrentTemp();
    delay(50);
    
    while (error == 1) {
        getCurrentTemp();
        delay(50);
    }
    
    // Calculates and prints the current setpoint deviation
    // Will be used for priority assignment
    myDev = abs(myTemp - setPoint);
    
    /**************************************************Requests******************************************/
    // local thermostat calls to run, but isnt running
    if (myCall == true && myRun == false) {
        if (myPriority == false) {
            if (forAns == 0 || forAns == -1) {
                myReq = 1; //make request - ask if it's okay to turn on
            }
            //foreign member approves local priority
            else if (forAns == 1) { 
                myPriority = true; 
                myReq = 0; // answer received, no more asking
            }
        }
        else {
            myRun = true; // if local already has priority
            myReq = 0; // if local has priority, no need for requests
        }
    }
    
    // if local thermostat is idle
    if (myCall == false) {
        myReq = 0;
        myRun = false;
    }
    
    // Check for okay to start running the AC
    if (myRun == true && myPriority == true) {
        myReq = 0;
        if (prevRun == false && myRun == true) {
            thirtyMinutes.start(); //thiry minute timer starts
        }
    }
    
    // Put together the data to be published
    // Tilde acts as a delimiter so all data can be published in a single string and broken apart later
    localDataString = String(myTemp) + "~" + String(myDev) + "~" + String(myCall) + "~" + String(myRun) + "~" + String(myPriority) + "~" + String(myReq) + "~" + String(myAns) + "~" + String(thirtyMinutes.isActive());
    
    Particle.publish("32420324_E", localDataString);
    delay(500);
    
    
    prevRun = myRun;
}
  
// Code copied from ONEWIRE library to read temperature from single-wire temperature probe
// Much of the troubleshooting println commands have been commented out
void getCurrentTemp() { 
      
  byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;
  float lastTemp;

  if ( !ds.search(addr)) {
    //Serial.println("No more addresses.");
    //Serial.println();
    ds.reset_search();
    delay(250);
    return;
  }

  // The order is changed a bit in this example
  // first the returned address is printed

  //Serial.print("ROM =");
  for( i = 0; i < 8; i++) {
    //Serial.write(' ');
    //Serial.print(addr[i], HEX);
  }

  // second the CRC is checked, on fail,
  // print error and just return to try again

  if (OneWire::crc8(addr, 7) != addr[7]) {
      Serial.println("CRC is not valid!");
      return;
  }
  Serial.println();

  // we have a good address at this point
  // what kind of chip do we have?
  // we will set a type_s value for known types or just return

  // the first ROM byte indicates which chip
  switch (addr[0]) {
    case 0x10:
      //Serial.println("  Chip = DS1820/DS18S20");
      type_s = 1;
      break;
    case 0x28:
     // Serial.println("  Chip = DS18B20");
      type_s = 0;
      break;
    case 0x22:
      //Serial.println("  Chip = DS1822");
      type_s = 0;
      break;
    case 0x26:
      //Serial.println("  Chip = DS2438");
      type_s = 2;
      break;
    default:
      //Serial.println("Unknown device type.");
      return;
  }

  // this device has temp so let's read it

  ds.reset();               // first clear the 1-wire bus
  ds.select(addr);          // now select the device we just found
  // ds.write(0x44, 1);     // tell it to start a conversion, with parasite power on at the end
  ds.write(0x44, 0);        // or start conversion in powered mode (bus finishes low)

  // just wait a second while the conversion takes place
  // different chips have different conversion times, check the specs, 1 sec is worse case + 250ms
  // you could also communicate with other devices if you like but you would need
  // to already know their address to select them.

  delay(1000);     // maybe 750ms is enough, maybe not, wait 1 sec for conversion

  // we might do a ds.depower() (parasite) here, but the reset will take care of it.

  // first make sure current values are in the scratch pad

  present = ds.reset();
  ds.select(addr);
  ds.write(0xB8,0);         // Recall Memory 0
  ds.write(0x00,0);         // Recall Memory 0

  // now read the scratch pad

  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE,0);         // Read Scratchpad
  if (type_s == 2) {
    ds.write(0x00,0);       // The DS2438 needs a page# to read
  }

  // transfer and print the values

  //Serial.print("  Data = ");
  //Serial.print(present, HEX);
  //Serial.print(" ");
  for ( i = 0; i < 9; i++) {           // we need 9 bytes
    data[i] = ds.read();
    //Serial.print(data[i], HEX);
    //Serial.print(" ");
  }
  //Serial.print(" CRC=");
 // Serial.print(OneWire::crc8(data, 8), HEX);
  //Serial.println();

  // Convert the data to actual 
  // because the result is a 16 bit signed integer, it should
  // be stored to an "int16_t" type, which is always 16 bits
  // even when compiled on a 32 bit processor.
  int16_t raw = (data[1] << 8) | data[0];
  if (type_s == 2) raw = (data[2] << 8) | data[1];
  byte cfg = (data[4] & 0x60);

  switch (type_s) {
    case 1:
      raw = raw << 3; // 9 bit resolution default
      if (data[7] == 0x10) {
        // "count remain" gives full 12 bit resolution
        raw = (raw & 0xFFF0) + 12 - data[6];
      }
      celsius = (float)raw * 0.0625;
      break;
    case 0:
      // at lower res, the low bits are undefined, so let's zero them
      if (cfg == 0x00) raw = raw & ~7;  // 9 bit resolution, 93.75 ms
      if (cfg == 0x20) raw = raw & ~3; // 10 bit res, 187.5 ms
      if (cfg == 0x40) raw = raw & ~1; // 11 bit res, 375 ms
      // default is 12 bit resolution, 750 ms conversion time
      celsius = (float)raw * 0.0625;
      break;

    case 2:
      data[1] = (data[1] >> 3) & 0x1f;
      if (data[2] > 127) {
        celsius = (float)data[2] - ((float)data[1] * .03125);
      }else{
        celsius = (float)data[2] + ((float)data[1] * .03125);
      }
  }

  // remove random errors
  if((((celsius <= 0 && celsius > -1) && lastTemp > 5)) || celsius > 125) {
      celsius = lastTemp;
  }

  fahrenheit = celsius * 1.8 + 32.0;
  lastTemp = celsius;
  
  /*
  Serial.print("  Temperature = ");
  Serial.print(celsius);
  Serial.print(" Celsius, ");
  Serial.print(fahrenheit);
  Serial.println(" Fahrenheit");
  */
    
  // now that we have the readings, we can publish them to the cloud
  String temp = String(fahrenheit); // store temp in "temperature" string
  
  myTemp = temp.toInt();
  delay(100);
  
  
  if (myTemp == 0) {
      error = 1;
  }
  else error = 0;
  
  /* in case of errorneous temperature reading
   * a temperature shift of 8 degrees means the reading is wrong or a ghost entered the room
   * either way, the temperature is not indicitive
   */
   if (abs(myTemp - prevTemp) >= 10 && prevTemp != 0) {
      myTemp = prevTemp;
  }
  
  // set the comparison variable for next cycle
  prevTemp = myTemp;
  
}

// Break the recieved data into usable parts
void foreignDataHandler(const char *event, const char *data) {
    
    String string = data;
    String eventName = event;
    String mydata[10];
    int end;
    
    for (int i = 0; i < 10; i++) {
        
        end = string.indexOf("~");
        
        if (end != -1) {
            mydata[i] = string.substring(0,end);
            Serial.println(eventName + "-" + String(i) + ": " + mydata[i]);
            string.remove(0,end + 1);
        }
        else {
            mydata[i] = string;
            Serial.println(eventName + "-" + String(i) + ": " + mydata[i]);
            goto here;
        }
    }
    
    here:
    
    forTemp = mydata[0].toInt();
    forDev = mydata[1].toInt();
        
    switch (mydata[2].toInt()) {
        case 0:
            forCall = false;
            break;
        case 1:
            forCall = true;
            break;
        default:
            break;
    }
    
    switch (mydata[3].toInt()) {
        case 0:
            forRun = false;
            break;
        case 1:
            forRun = true;
            break;
        default:
            break;
    }
    
    switch (mydata[4].toInt()) {
        case 0:
            forPriority = false;
            break;
        case 1:
            forPriority = true;
            break;
        default:
            break;
    }
    
    forReq = mydata[5].toInt();
    forAns = mydata[6].toInt();
    forTimer = mydata[7].toInt();
    
    foreignDataString = String(forTemp) + "~" + String(forDev) + "~" + String(forCall) + "~" + String(forRun) + "~" + String(forPriority) + "~" + String(forReq) + "~" + String(forAns) + "~" + String(forTimer);
    delay(500);
    
    // When foreign member requests to turn on
    if (forReq == 1) {
        if (myCall == false) {
            myAns = 1; // answer is yes, foreign house AC is not running
            myPriority = false;
            thirtyMinutes.stop();// local reliquishes priority
            goto skipAhead;
        }
        
        if (thirtyMinutes.isActive() == true) {
            myAns = 0; // the answer is no, to prevent rapid cycling
            myPriority = true; // local retains priority
        }
        else if (thirtyMinutes.isActive() == true && myCall == false) {
            myAns = 1;
            myPriority = false;
            thirtyMinutes.stop();
        }
        else {
            if (myDev > forDev) {
                myAns = 0; // the answer is no, my house is less comfortable than yours
                myPriority = true; // local retains priority
            }
            else {
                myAns = 1;
                myPriority = false;
            }
        }
    }
    else if (forReq == 0) {
        myAns = 0; // no need to answer a question that isn't being asked
    }
    skipAhead:
    
    // In the event that both decided that they have priority to run, this block clears that so they can start over
    if (myPriority == true && forPriority == true) {
        myPriority = false;
        myRun = false;
        digitalWrite(D7, LOW);
        thirtyMinutes.stop();
    }
    
}

void thirtyMinutesPassed() {
   
}

Credits

73cf782606956514a913809e5a7675ec
Andrew De Piante

I studying Mechanical Engineering at UNC Charlotte. I enjoy the performance automotive industry and quality-driven design.

Replications

Did you replicate this project? Share it!

I made one

Love this project? Think it could be improved? Tell us what you think!

Give feedback

Comments

Similar projects you might like

IoT Drone - Part 1 - Motor Control
Intermediate
  • 1,363
  • 27

Work in progress

After flying a friends drone I was hooked. However, I wanted to build my own, so this is part one of my drone project - motor control.

Bike Swarm
Intermediate
  • 611
  • 13

Work in progress

Cyclists will be able to connect seamlessly to form a synchronized swarm of riders to reduce traffic congestion and improving safety.

Bike Swarm

Team Bright Water Bottle

Smart Maid
Intermediate
  • 1,315
  • 21

Full instructions

Hack your existing iRobot Create with Particle and control it through Alexa.

Smart Maid

Team Yosnalab

"Internet-controlled" Nespresso Machine
Intermediate
  • 2,618
  • 8

Using a chatbot and a Nespresso machine to make coffee remotely.

IoT Smart Desk Drawer with Amazon DRS
Intermediate
  • 461
  • 4

Never run out of office supplies again. PostIt notes, stamps, paperclips, tape and paper replenished from the cloud.

Home Theater Accent Lights
Intermediate
  • 307
  • 1

Full instructions

Particle Photon and SmartThings controlled WS2812B.

Add projectSign up / Login
Respect project