Brennon, wow…you are really moving on this…I am more like a turtle, haha. That ph value would be a dream come true and I would definitely add another stenner pump to automate the acid. My pool continually takes acid.
I am still working at the photon level…I’ve just figured out a bare minimum as to how to communicate with it (curl statements). What I will eventually do beyond that…I don’t know yet but will definitely look into your “hubitat”. I did glance at Samsung Smartthings prior and was a bit concerned about recent direction that seemed to limit the DIY community. Seems like some of that has been worked out but apparently you have moved on…
I’ll post my serial pump control…it’s simple code, probably nothing new for anyone except for me. If only I hadn’t had that indexing error I would have had it working about a week and a half ago, haha.
It is a standalone program and I had the photon out at my pool pad for a few days to run my pump. I controlled/monitored it using the following commands.
- To send a new pump rpm command (this case 30% max):
curl https://api.particle.io/v1/devices/{xxxxx_your_device_ID_here_xxxxx}/rpm_pct -d access_token={xxxx_your_access_token_here_xxxxx} -d “args=30”
- To get the latest pump status returned (along with some other variables)
curl “https://api.particle.io/v1/devices/{xxxxx_your_device_ID_here_xxxxx}/pmp_stat?access_token={xxxx_your_access_token_here_xxxxx}”
#include "Particle.h"
STARTUP(WiFi.selectAntenna(ANT_AUTO)); // continually switches at high speed between antennas
String Pump_Status; // variable exposed to the cloud that provides most recent pump status
int changePumpSpeed(String RPM_pct); // function called from the cloud to provide the new pump RPM percentage
//uint8_t pumpCommand[10] = {0x10, 0x02, 0x0C, 0x01, 0x00, rpm%, xsumh, xsuml, 0x10, 0x03}; //pump command pattern
// for Hayward Ecostar, rpm is calculated as: rpm = (rpm% * 100/3450), rpm% is a number from 0-100
// for Hayward Ecostar, rpm% from 1-19 are taken by the pump to be 20...which is the minimum pump rpm (besides 0)
// xsuml = byte0(0x10)+byte1(0x02)+byte2(0x0C)+byte3(0x01)+byte4(0x00)+byte5(rpm%), xsumh = 0x00 for pump command
// thanks to http://www.desert-home.com/2014/07/controlling-hayward-ecostar-pump.html for work on this
// according to that site, pump commands sent that are invalid (ex: bad xsum) will shut down the pump
//EX uint8_t pumpCommand[10] = {0x10, 0x02, 0x0C, 0x01, 0x00, 0x64, 0x00, 0x83, 0x10, 0x03}; //100% RPM
//EX uint8_t pumpCommand[10] = {0x10, 0x02, 0x0C, 0x01, 0x00, 0x50, 0x00, 0x6f, 0x10, 0x03}; //80% RPM
//EX uint8_t pumpCommand[10] = {0x10, 0x02, 0x0C, 0x01, 0x00, 0x2d, 0x00, 0x4c, 0x10, 0x03}; //45% RPM
uint8_t pumpCommand[10] = {0x10, 0x02, 0x0C, 0x01, 0x00, 0x00, 0x00, 0x1f, 0x10, 0x03}; //0% RPM, OFF initially
//ecostar pump responds with a status sequence when a pump command is sent to it. Pattern is as follows (13 bytes)
//uint8_t pumpStatus[13] = {0x10, 0x02, 0x00, 0x0c, 0x00, 0x00, rpm%, wattshi_bcd, wattslo_bcd, xsumh, xsuml, 0x10, 0x03}
uint8_t pumpStatus[13]; // buffer used to read pump status from serial port 1 and to check it
uint8_t latestPumpStatus[13]; // contains the latest successful read of the pump status
uint16_t pumpStatusXsum;
uint16_t InvalidPumpStatus = 0;
uint8_t pump_rpm_pct = 0x00; // variable pushed in from the cloud to change rpm of the pump
unsigned long nextPumpRefresh = 0;
unsigned long timetoGetPumpStatus = 0;
int latestPumpWatts = 0;
//SYSTEM_MODE(SEMI_AUTOMATIC); //Device runs without immediate connection
void setup()
{
Serial.begin(9600); // this serial port was used for debug only
Serial1.blockOnOverrun(false);
Serial1.begin(19200, SERIAL_8N2); // 1 stop bit and 2 stop bits both seemed to work, I settled on 2
Particle.function("rpm_pct", changePumpSpeed);
Particle.variable("pmp_stat", Pump_Status);
}
void loop()
{
int rx; //receive buffer pointer & counter
// Following is the code to send the pump a new rpm
// First, the pump command 10 byte packet is built, only 2 bytes are changed for a new pump rpm.
// Byte 5 of pump command contains the new pump rpm percentage, calculation: rpm = pump_rpm_pct*100/3450
// Byte 7 is low byte of checksum, byte 6 is the high byte but it will always be 00h for valid pump commands.
// in this demo, pump_rpm_pct is a variable "pushed" into the photon through a Particle cloud function call
// The Hayward Ecostar requires a pump command refresh about every second+ or so...or it shuts down.
pumpCommand[5] = pump_rpm_pct; // pump_rpm_pct is pushed in from the cloud in this demo
pumpCommand[7] = pumpCommand[0] + pumpCommand[1] + pumpCommand[2] + pumpCommand[3] + pumpCommand[4] + pumpCommand[5]; //xsum
if (nextPumpRefresh < millis()) //send command to ecostar and there after every .5 seconds (500ms) to keep pump operating
{
Serial1.write(pumpCommand, 10); // write the command to the serial1 port
nextPumpRefresh = millis() + 500; // setup the trigger for the next pump refresh time: .5 seconds later
// setup the the trigger to get the pump status. The pump responds to every command with a status.
timetoGetPumpStatus = millis() + 50; //my experiments show pump command to return status about 15ms...
};
// Following reads the pump status response from Serial1, it is NOT a general serial read function.
// My pool pump is the ONLY equipment connected to this serial port and so this code
// looks ONLY for the status from the pump.
// If an invalid pump status is obtained, it is thrown out and the code looks for the next one...a new
// one comes every 1/2 second. If one is "thrown out", an error count is incremented because I want to
// see if (and how often) this condition occurs.
// If a chlorinator (or other device) is added to the RS485 bus later, this code would HAVE to change
if (timetoGetPumpStatus < millis()) // get the pump status
{
rx = 0; // pump status byte pointer & counter
if (Serial1.available() == 13 && Serial1.peek() == 0x10) // read pump status only if it starts right & has 13 bytes
{
pumpStatus[rx] = Serial1.read();
pumpStatusXsum = pumpStatus[rx++]; // start the checksum for byte0-byte8
while (rx < 13) //read the next 12 bytes to fill in the pump status
{
pumpStatus[rx] = Serial1.read();
if (rx < 9) pumpStatusXsum = pumpStatusXsum + pumpStatus[rx]; // xsum remaining bytes of command
rx++;
}
if (pumpStatusXsum == (pumpStatus[9] << 8) | pumpStatus[10]) // make sure the checksum matches
{
rx = 0;
while (rx < 13) latestPumpStatus[rx++] = pumpStatus[rx]; // update the latest valid pump status received
latestPumpWatts = ((pumpStatus[7] & 0xF0) >> 4) * 1000 + ((pumpStatus[7] & 0x0F ) * 100) +
((pumpStatus[8] & 0xF0) >> 4) * 10 + (pumpStatus[8] & 0x0F );
PrintHex83(pumpStatus, 13); // print the latest pump status in hex, via the cloud
}
else rx = 0; // fail condition...xsum did not match
};
if (rx == 0) // failure to get valid pump status
{
while (Serial1.available() > 0) Serial1.read(); // empty the serial receive buffer if anything in it
InvalidPumpStatus = InvalidPumpStatus + 1; // error in pump status, log it
};
timetoGetPumpStatus = millis() + 500; // get next pump status sometime AFTER the next pump command is sent
};
}
// this function automatically gets called upon a matching POST request from the CLOUD
// The rpm for the ecostar can be changed from 20% Max (Max=3450) to 100% Max
// This function insures that the rpm sent from the CLOUD to the pump is either 0% (stop pump)
// or between 20%-100%.
int changePumpSpeed(String RPM_pct)
{
if (RPM_pct.toInt() <= 100 && RPM_pct.toInt() >= 20) pump_rpm_pct = RPM_pct.toInt();
else pump_rpm_pct = 0;
}
// I found this routine on an arduino site...written by Rob Tillaart
// It converts an integer array to hexadecimal characters. It can then be sent to a Serial
// port or up to the cloud for debug. Great for looking at serial port transmissions and
// receptions in hex.
// The only line I added was the last, which sends the data to the cloud via a Particle cloud variable
void PrintHex83(uint8_t *data, uint8_t length) // converts 8-bit data to printable hex - Rob Tillaart
{
char tmp[length*2+1];
byte first ;
int j=0;
for (uint8_t i=0; i<length; i++)
{
first = (data[i] >> 4) | 48;
if (first > 57) tmp[j] = first + (byte)39;
else tmp[j] = first ;
j++;
first = (data[i] & 0x0F) | 48;
if (first > 57) tmp[j] = first + (byte)39;
else tmp[j] = first;
j++;
}
tmp[length*2] = 0;
// Serial.println(tmp); // could print the hex output here if wanted
// This next line sends the pump status to the cloud along with some other stuff I was monitoring
// Pump_Status is the string variable that can be moitored from the cloud
Pump_Status = tmp + String(" ") + String(latestPumpWatts) + String(" ") + String(InvalidPumpStatus) + String(" ") + String(pumpStatusXsum);
}