Tying an MCP23008 to Particle


#1

Hi, I am trying to build a pool controller based off of one bscuderi13 built to tie into SmartThings. Now his setup uses only 4 outputs to control two valves using relays, with 1 relay for open, and the other for the close.

I plan on driving 4 valves, for a total of 8 relays. I wish to use an MCP23008 to unload the Photon of directly driving the relays.

How would I address those pins using rickkas7 MCP23008 library and integrating that into bscuderi13’s code below?

How would they be defined by #define? How would you reference say GP7 on MCP23008 to be driven low to actuate a relay on a SainSmart 8 relay board?

My code also will be using the I2C pins D0 and D1 for reading other sensors and future chemistry control.

   // This #include statement was automatically added by the Particle IDE.
#include <OneWire.h>  //For Temp Sensor

STARTUP(WiFi.selectAntenna(ANT_EXTERNAL)); // Use external antenna

// define pin names for easier reference
#define pPumpRelay1     D4
#define pPumpRelay2     D5
#define pPumpRelay3     D6
#define aeratoronrelay  D0
#define aeratoroffrelay D1
#define cleaneronrelay  D2
#define cleaneroffrelay D3
#define poollight       D7
#define tempPower       A0
#define tempGround      A1

// Pump Speed Variables
int pumpSetting = 0;
int pumpSpeed = 0;
int pumpPowerConsumption = 0;
int totalKWH = 0;

//** temp variables
OneWire ds = OneWire(A2);  //** 1-wire signal on pin A2
unsigned long lastUpdate = 0;
float lastTemp;
int tLast = -1;
int ioat = -777;

// Pool Temp & Level Variables
double poolTemp = 0.0;
int poolLevel = 0;
int checkinTime =  -1;

// Valve Flag Variables
char aFlag = 0;
char cFlag = 0;

// Variables to track valve position
char cleaners = 0;

//  Variables for Pool Light
char ccFlag = 0;


void setup() {
// Set Timezone
  Time.zone(-7);
// Set Pump Pins to Outputs
  pinMode(pPumpRelay1, OUTPUT); 
  pinMode(pPumpRelay2, OUTPUT); 
  pinMode(pPumpRelay3, OUTPUT); 
// Set Valve Pins to Outputs
  pinMode(aeratoronrelay, OUTPUT);
  pinMode(aeratoroffrelay, OUTPUT);
  pinMode(cleaneronrelay, OUTPUT);
  pinMode(cleaneroffrelay, OUTPUT);
  pinMode(poollight, OUTPUT);
// Set valve and light relays to defualt state
  digitalWrite(aeratoronrelay, HIGH);
  digitalWrite(aeratoroffrelay, HIGH);
  digitalWrite(cleaneronrelay, HIGH);
  digitalWrite(cleaneroffrelay, HIGH); 
  digitalWrite(poollight, HIGH);
// Default Pump Relays to Off
  digitalWrite(pPumpRelay1, HIGH);
  digitalWrite(pPumpRelay2, HIGH);
  digitalWrite(pPumpRelay3, HIGH);
// Register Cloud Functions for Pump
  Particle.function("PumpSpeed", SetSpeed);
  Particle.variable("PumpRPM", pumpSpeed);
  Particle.variable("PowerUse", pumpPowerConsumption);
  Particle.variable("TotalKWH", totalKWH);
// Register Cloud Functions for valves
  Particle.function("aerator", aeratorfunc);
  Particle.function("floorcleaner", cleanerfunc);
// subscribe to and publish temp & levelchanges
  Particle.subscribe("CurrentTemperature", tempHandler, MY_DEVICES);
  Particle.subscribe("waterlevel", levelHandler, MY_DEVICES);
  Particle.variable("PoolTemp", poolTemp);
  Particle.variable("PoolLevel", poolLevel);
//** register cloud functions for temp  
  Particle.variable("OAT", ioat);
// Cloud Functions For Pool Light
  Particle.function("poollight", poollightfunc);
  Particle.function("colorchange", colorchangefunc);
//** temp pin setup
  pinMode(tempPower, OUTPUT); //** power temp sensor
  pinMode(tempGround, OUTPUT); //** ground temp sensor
  digitalWrite(tempPower, HIGH);
  digitalWrite(tempGround, LOW);

// start valve position at cleaner on so easier to track  
  cleanerOn(); // go on first to hit valve limit switch
  cleanerOff(); // then go back to default to off
  aeratorOff(); // start default aerator off
  Particle.publish("cleaners", "off", PRIVATE); // let smartthings know to update valve position
}

void loop() {
  //** temp loop code checks the time and decides if we need to check outside temp
  int secNow = Time.local() % 86400;  // whats the current second of the day
  int tSince = (secNow - tLast);
  if (tSince < 0)                      // correct for 24 hour time clock
  {
    tSince = ((86400 - tLast) + secNow);
  }
  if (tSince >= 600 or tLast == -1)   // run every ten minutes or first boot
  {
    getOAT();
  }
  
  // Valve loop Code checks for valve flags set from the cloud function and runs the appropriate function to avoid cloud response delays
  if (aFlag == 1)
  {
    aeratorOn();
  }
  if (aFlag == 2)
  {
    aeratorOff();
  }
  if (cFlag == 1)
  {
    cleanerOn();
  }
  if (cFlag == 2)
  {
    cleanerOff();
  }
  
  // Pool Light Loop Code
  if (ccFlag == 1)
  {
    colorChange();
  }
  if (ccFlag == 2)
  {
    redLight();
  }
  if (ccFlag == 3)
  {
    greenLight();
  }
  if (ccFlag == 4)
  {
    blueLight();
  }
  if (ccFlag == 5)
  {
    yellowLight();
  }
  if (ccFlag == 6)
  {
    skyblueLight();
  }
  if (ccFlag == 7)
  {
    magentaLight();
  }
  if (ccFlag == 8)
  {
    whiteLight();
  }
}

// get a temp reading
void getOAT() {
byte i;
  byte present = 0;
  byte type_s;
  byte data[12];
  byte addr[8];
  float celsius, fahrenheit;
  if ( !ds.search(addr)) {
    ds.reset_search();
    delay(250);
    return;
  }
  switch (addr[0]) {
    case 0x10:
      type_s = 1;
      break;
    case 0x28:
      type_s = 0;
      break;
    case 0x22:
      type_s = 0;
      break;
    case 0x26:
      type_s = 2;
      break;
    default:
      return;
  }
  ds.reset();               
  ds.select(addr);          
  ds.write(0x44, 0);        
  delay(1000);     
  present = ds.reset();
  ds.select(addr);
  ds.write(0xB8,0);         
  ds.write(0x00,0);         
  present = ds.reset();
  ds.select(addr);
  ds.write(0xBE,0);         
  if (type_s == 2) {
    ds.write(0x00,0);       
  }
  for ( i = 0; i < 9; i++) {           
    data[i] = ds.read();
    }
  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; 
      if (data[7] == 0x10) {
        raw = (raw & 0xFFF0) + 12 - data[6];
      }
      celsius = (float)raw * 0.0625;
      break;
    case 0:
      
      if (cfg == 0x00) raw = raw & ~7;
      if (cfg == 0x20) raw = raw & ~3;
      if (cfg == 0x40) raw = raw & ~1;
      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);
      }
  }
  if((((celsius <= 0 && celsius > -1) && lastTemp > 5)) || celsius > 125) {
      celsius = lastTemp;
  }
  fahrenheit = celsius * 1.8 + 32.0;
  lastTemp = celsius;
  String oat = String(fahrenheit);
  Particle.publish("OAT", oat, PRIVATE);
  ioat = atoi(oat);
  tLast = Time.local() % 86400;
}

// aerator on function
void aeratorOn() {
    aFlag = 0;
    digitalWrite(aeratoroffrelay, HIGH);
    delay(1000);
    digitalWrite(aeratoronrelay, LOW);
    delay(8000);
    digitalWrite(aeratoronrelay, HIGH);

}

// aerator off function
void aeratorOff() {
    aFlag = 0;
    digitalWrite(aeratoronrelay, HIGH);
    delay(1000);
    digitalWrite(aeratoroffrelay, LOW);
    delay(8000);
    digitalWrite(aeratoroffrelay, HIGH);

}

// cleaner on function
void cleanerOn() {
  cFlag = 0;                           // reset flag to stop repeats
  if (cleaners != 1)                   // doesnt run if the valve position is already set
  {
    digitalWrite(cleaneroffrelay, HIGH); // first make sure the reverse direction relay is set to off
    delay(1000);                         // wait for motor to spool down before in case its reversing direction
    digitalWrite(cleaneronrelay, LOW);   // activate the relay
    delay(20000);                        // 20 secondes to make sure it hits limit switch
    cleaners = 1;                        // track valve position
    digitalWrite(cleaneronrelay, HIGH);  // Stop Relay
    wattchange();                        // Recalculate Power Consumption for valve position
  }
}

// cleaner off function sets the valve back to the middle returns with just timing
void cleanerOff() {
  cFlag = 0;                          // set the flag back to 0 to avoid looping
  if (cleaners != 0)                  // doesnt run if the valve position is already set
  {
    digitalWrite(cleaneronrelay, HIGH); // first make sure the reverse direction relay is set to off
    delay(1000);                         // wait for motor to spool down before in case its reversing direction
    digitalWrite(cleaneroffrelay, LOW);   // activate the relay
    delay(17500);                       // half of a full cycle timing to get valve into the center position since there is no limit switch set in the center
    digitalWrite(cleaneroffrelay, HIGH); // shutoff relay
    cleaners = 0;
    wattchange();                        // Recalculate Power Consumption for valve position
  }
}

// Pool Color Change
void colorChange() {
    ccFlag = 0;
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    Particle.publish("plight", "on", PRIVATE);
}

// Pool Red Light
void redLight() {
    ccFlag = 0;
    int light = digitalRead(poollight); // check if its on if it is turn it off and wait to reset first
    if (light == LOW) 
    {
        digitalWrite(poollight, HIGH);
        delay(5000);
    }
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    Particle.publish("plight", "on", PRIVATE);
}

// Pool Green Light
void greenLight() {
    ccFlag = 0;
    int light = digitalRead(poollight); // check if its on if it is turn it off and wait to reset first
    if (light == LOW) 
    {
        digitalWrite(poollight, HIGH);
        delay(5000);
    }
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    Particle.publish("plight", "on", PRIVATE);
}

//Pool Blue Light
void blueLight() {
    ccFlag = 0;
    int light = digitalRead(poollight); // check if its on if it is turn it off and wait to reset first
    if (light == LOW) 
    {
        digitalWrite(poollight, HIGH);
        delay(5000);
    }
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    Particle.publish("plight", "on", PRIVATE);
}

// Pool Yellow Light
void yellowLight() {
    ccFlag = 0;
    int light = digitalRead(poollight); // check if its on if it is turn it off and wait to reset first
    if (light == LOW) 
    {
        digitalWrite(poollight, HIGH);
        delay(5000);
    }
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    Particle.publish("plight", "on", PRIVATE);
}

// Pool Skyblue Light
void skyblueLight() {
    ccFlag = 0;
    int light = digitalRead(poollight); // check if its on if it is turn it off and wait to reset first
    if (light == LOW) 
    {
        digitalWrite(poollight, HIGH);
        delay(5000);
    }
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    Particle.publish("plight", "on", PRIVATE);
}

// Pool Magenta Light
void magentaLight() {
    ccFlag = 0;
    int light = digitalRead(poollight); // check if its on if it is turn it off and wait to reset first
    if (light == LOW) 
    {
        digitalWrite(poollight, HIGH);
        delay(5000);
    }
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    Particle.publish("plight", "on", PRIVATE);
}

// Pool White Light
void whiteLight() {
    ccFlag = 0;
    int light = digitalRead(poollight); // check if its on if it is turn it off and wait to reset first
    if (light == LOW) 
    {
        digitalWrite(poollight, HIGH);
        delay(5000);
    }
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    delay(250);
    digitalWrite(poollight, HIGH);
    delay(250);
    digitalWrite(poollight, LOW);
    Particle.publish("plight", "on", PRIVATE);
}

// runs if valve changes to update pump power use
void wattchange() {
    if (pumpSetting == 6)
    {
      if (cleaners == 0)  
      {
        pumpPowerConsumption = 972;
      }
      else
      {
        pumpPowerConsumption = 890; 
      }
    }
    if (pumpSetting == 7)
    {
      if (cleaners == 0)
      {
        pumpPowerConsumption = 1472;
      }
      else
      {
        pumpPowerConsumption = 1370; 
      }
    }
    Particle.publish("wattchange", "true", PRIVATE);  
}

int SetSpeed(String command)
{
// cloud trigger for pool speed
  if (command == "0") 
    {   
      digitalWrite(pPumpRelay1, HIGH);
      digitalWrite(pPumpRelay2, HIGH);
      digitalWrite(pPumpRelay3, HIGH);
      pumpSetting = 0;
      pumpSpeed = 0;
      pumpPowerConsumption = 0;
      return 0;
    }
  if (command == "1")
   {
      digitalWrite(pPumpRelay1, LOW);
      digitalWrite(pPumpRelay2, HIGH);
      digitalWrite(pPumpRelay3, HIGH);
      pumpSetting = 1;
      pumpSpeed = 1000;
      pumpPowerConsumption = 58;
      return 1;
   }
  if (command == "2")
   {
      digitalWrite(pPumpRelay1, HIGH);
      digitalWrite(pPumpRelay2, LOW);
      digitalWrite(pPumpRelay3, HIGH);
      pumpSetting = 2;
      pumpSpeed = 1250;
      pumpPowerConsumption = 93;
      return 2;
   }
   if (command == "3")
   {
      digitalWrite(pPumpRelay1, LOW);
      digitalWrite(pPumpRelay2, LOW);
      digitalWrite(pPumpRelay3, HIGH);
      pumpSetting = 3;
      pumpSpeed = 1500;
      pumpPowerConsumption = 145;
      return 3;
   }
   if (command == "4")
   {
      digitalWrite(pPumpRelay1, HIGH);
      digitalWrite(pPumpRelay2, HIGH);
      digitalWrite(pPumpRelay3, LOW);
      pumpSetting = 4;
      pumpSpeed = 2000;
      pumpPowerConsumption = 310;
       return 4;
   }
   if (command == "5")
   {
      digitalWrite(pPumpRelay1, LOW);
      digitalWrite(pPumpRelay2, HIGH);
      digitalWrite(pPumpRelay3, LOW);
      pumpSetting = 5;
      pumpSpeed = 2500;
      pumpPowerConsumption = 570;
      return 5;
   }
   if (command == "6")
   {
      digitalWrite(pPumpRelay1, HIGH);
      digitalWrite(pPumpRelay2, LOW);
      digitalWrite(pPumpRelay3, LOW);
      pumpSetting = 6;
      pumpSpeed = 3000;
      if (cleaners == 0)   // cleaners off power consumption is higher ill only use cleaners at 3000 oor 3450 rpm
      {
        pumpPowerConsumption = 972;
      }
      else
      {
        pumpPowerConsumption = 890; 
      }
      return 6;
   }
   if (command == "7")
   {
      digitalWrite(pPumpRelay1, LOW);
      digitalWrite(pPumpRelay2, LOW);
      digitalWrite(pPumpRelay3, LOW);
      pumpSetting = 7;
      pumpSpeed = 3450;
      if (cleaners == 0)   // cleaners off power consumption is higher ill only use cleaners at 3000 oor 3450 rpm
      {
        pumpPowerConsumption = 1472;
      }
      else
      {
        pumpPowerConsumption = 1370; 
      }
      return 7;
   }
   else  // bad command shutoff pump return 9
    {               
      digitalWrite(pPumpRelay1, HIGH);
      digitalWrite(pPumpRelay2, HIGH);
      digitalWrite(pPumpRelay3, HIGH);
      pumpSetting = 0;
      pumpSpeed = 0;
      pumpPowerConsumption = 0;
      return 9;
    }
}

//  Cloud Function for Aerator
int aeratorfunc(String command)
{
// cloud trigger for clockwise valve aerator on relay sets a flag in loop to avoid timing out the sending of the status return
  if (command == "1") 
    {   
      aFlag = 1;
      return 1;
    }
// cloud trigger for counterclosckwise valve aerator off relay sets a flag in loop to avoid timing out the sending of the status return
  else 
    {               
      aFlag = 2;
      return 0;
    }
}

int cleanerfunc(String command)
{
// cloud trigger for floor cleaner valve full on sets a flag in loop to avoid timing out the sending of the status return
  if (command == "1") 
    {   
      cFlag = 1;
      return 1;
    }
// cloud trigger for valve state to all returns open sets a flag in loop to avoid timing out the sending of the status return
  else 
    {               
      cFlag = 2;
      return 0;
    }
}
// Pool Light Cloud Functions
int poollightfunc(String command)
{
// cloud trigger for pool light on
int light = digitalRead(poollight); // check state
  if (command == "1")
    {
      if (light == HIGH){
      digitalWrite(poollight, LOW);
      }
      if (light == LOW){
      }
      return 1;
    }
 
// cloud command light off  
  else if (command == "0")
    {
      if (light == LOW){
      digitalWrite(poollight, HIGH);
      }
      if (light == HIGH){
      }
      return 0;
    }
// bad command
  else 
    {               
      return 666;
    }
}
int colorchangefunc(String command)
{
// cloud trigger for color change
  if (command == "1") 
    {   
      ccFlag = 1;
      return 1;
    }
  if (command == "2") 
    {   
      ccFlag = 2;
      return 2;
    }
  if (command == "3") 
    {   
      ccFlag = 3;
      return 3;
    }
  if (command == "4") 
    {   
      ccFlag = 4;
      return 4;
    }
  if (command == "5") 
    {   
      ccFlag = 5;
      return 5;
    }
  if (command == "6") 
    {   
      ccFlag = 6;
      return 6;
    }
  if (command == "7") 
    {   
      ccFlag = 7;
      return 7;
    }
  if (command == "8") 
    {   
      ccFlag = 8;
      return 8;
    }
// cloud trigger for pool light off
  else 
    {               
      ccFlag= 0;
      return 0;
    }
}


// Temp Handler when subscribed event happens
void tempHandler(const char *event, const char *data)
{
poolTemp = atof(data);
checkinTime = Time.local() % 86400;
}

// Level Handler when subscribed event happens
void levelHandler(const char *event, const char *data)
{
poolLevel = atoi(data);
}

#2

Thanks for the edit. I’m still trying to figure out the forum’s formatting.


#3

Have a look here

And about the MCP23008, why not have a look at the samples in the dedicated library?https://build.particle.io/libs/Adafruit_MCP23008/1.0.6/tab/example/i2cledtest.ino

Developing software is not only about putting lines of code - found somewhere else - together but about reading these lines, understanding what they do and why and then use that knowledge to adapt the code to fit your own needs.
If you have problems understanding the what and why, you can aske specific questions about that, but just asking: “What do I have to change to make it work?”, is not really the way we tend approach things here in this community.

Just one thing about

I would not. I would use arrays and enums and stay away from #define whenever possible.
And I would also try to reduce all these copy-pasted code blocks and try to condense their intended behaviours into reusable functions.
Having a quick look at that code I’d detect potential to reduce the length of that code to a third/quater or even less of what it currently is.


#4

Did you see the instructions page for the MCP23008-RK library?

The API is designed to work almost exactly like the built-in functions, digitalRead, digitalWrite, and pinMode, except operate on the GPIO lines on the MCP23008.


#5

I saw your instructions, so instead of the following for calling out relays that were located on the

digitalWrite(poollight, HIGH) 

for a typical pin on the Photon,
I’d just say

gpio.digitalWrite(0,HIGH)

only after the global object to initialize and having void begin(); in setup to start the I2C interface.

In the setup itself for the pool control, I only need to say

gpio.pinMode(0, OUTPUT);

to define the pin and the mode, then later, anywhere else, I should be able have the program to call out pin 0 on the MCP23008 to HIG, just as I would a regular pin on the photon, as shown in your example? I will experiment with your examples once my MCP23008 comes in.

ScruffR, no doubt, I’m going to clean this thing up, but more likely, rewrite it entirely. I only posted it as the example of what I’m trying to do and make sure I understand the MCP library correctly.

Thank you both.


#6

Yes, that’s how to do it!