Generic Serial/Arduino Controller

The PowerHome Generic Serial / Arduino controller is intended for DIY home automation hardware primarily based upon the Arduino platform but can be utilized with any serial port controlled device provided that it can be programmed to support the protocol. The protocol that this controller uses is a simple, lightweight, two-way query and control protocol detailed below.

Configuration of the controller within the Setup section of the PH Explorer is quite simple. Pressing the "Settings" button for the controller opens a window requiring only 2 fields. The COM port that the hardware is connected to on the PowerHome machine and the COM settings for the hardware, typically "19200,N,8,1" for 19200 BPS, No parity, 8 data bits, and 1 stop bit.

The controller supports a number of devices within PowerHome. On the Digital IO screen: Input, Output, IOT Input, and IOT Output device types are all supported. On the Analog IO screen: Input, Output, IOT Input, and IOT Output device types are also supported. For both the Digital IO and Analog IO screens, the Unit column must be set to 0 and the Point column can be set to any number between 0 and 512 inclusive.

The controller protocol consists of 4 basic command types depending upon the Digital or Analog IO device type that you're controlling or querying. These basic command types are:

The DI command is used when controlling/querying Input and IOT Input device types on the Digital IO screen. The DO command is used for Output and IOT Output on the Digital IO screen. The AI command is used for controlling/querying Input and IOT Input devices on the Analog IO screen and the AO command handles the Output and IOT Output devices on the Analog IO screen.

The commands sent from PowerHome to the connected controller use the 4 basic commands above with either an "R" or a "W" appended to them to specify whether the command is a "Read" (query the controller for current status) or a "Write" (command the controller to change the status). The controller will respond as appropriate with an echo of the command without the "R" or "W". Any command that is initiated by PowerHome is expected to be acknowledged by the controller. It is this acknowledgement that updates the actual device status within PowerHome. Every command (in both directions) starts with an asterisk "*" character and ends with a carriage return / linefeed combination. After the "*" character comes the basic command. If PowerHome is sending the command, then either an "R" or "W" will also be appended. After the command comes a colon ":" followed by the "Point" number that you defined within PowerHome. If the command is a read command (DIR, DOR, AIR, or AOR) from PowerHome, nothing more is needed and the command is terminated with cr/lf. If the command is a write command from PowerHome (DIW, DOW, AIW, or AOW), then the point value is followed by a comma followed by the actual data you wish to write. Commands sent from the controller to PowerHome start with an "*" followed by the command (DI, DO, AI, and AO), followed by a ":", followed by the point value, followed by a comma, followed by the status of the device and ending with the cr/lf pair. Sample commands can be seen below:

The controller is not limited to only query and response communications and fully supports communications initiated from the controller itself such as the reporting of a change of status.

PowerHome also provides a controller command (9990) that allows you to send any string you like to the connected controller through the serial port. This string can be an actual command adhering to the standard protocol or can be something else entirely if you wished to extend the protocol and have added support for it in your external device. Use the ph_ctlrcmd ( ) function to send the controller command. The string to send to the serial port should be in Parm3. An example of sending an infrared command using the Pronto format might look like this: ph_ctlrcmd(5,"ARDUINO",9990,0,0,"*IR:0000 0071 0022 0002 0149 00A7 0014 003F 0014 0014 0014 003F 0014 0014 0014 0014 0014 0014 0014 0014 0014 003F 0014 003F 0014 0014 0014 003F 0014 003F 0014 0014 0014 003F 0014 003F 0014 003F 0014 0014 0014 003F 0014 003F 0014 0014 0014 0014 0014 0014 0014 0014 0014 0014 0014 003F 0014 0014 0014 0014 0014 003F 0014 003F 0014 003F 0014 003F 0014 003F 0014 05A4 0149 0053 0014 05A4~r~n","")

A sample of actual Arduino code for control and query of several points can be seen below and can be modified to suit your own creations:

//CDS AI 1
//Temp AI 2
//Humd AI 3
//Ultrasonic AI 4
//PIR DI 1
//Pink Jack DI 2
//Yellow Jack DO 3

#include <SimpleDHT.h>

#define DHTPIN 5
#define CDSPIN 4
#define PIRPIN 2
#define UTrigPin 7
#define UEchoPin 8
#define PINKPIN 3
#define YELLOWPIN 4

#undef abs

SimpleDHT22 dht22(DHTPIN);

int CDSval = 0;
int Temp = 0;
int Humd = 0;
int PIRval = -1;
int Udistance = 0;
int Pinkval = -1;
int Yellowval = -1;
unsigned long DHTmilli = 0;

char serialBuf[64];

void setup()
{
  pinMode(PIRPIN,INPUT);
  pinMode(UTrigPin,OUTPUT);
  pinMode(UEchoPin,INPUT);
  pinMode(PINKPIN,INPUT_PULLUP);
  pinMode(YELLOWPIN,OUTPUT);

  digitalWrite(YELLOWPIN,LOW);
 
  Serial.begin(19200);
}

void loop()
{
  ReadDigital("I:1",false,PIRPIN,&PIRval);
  ReadDigital("I:2",true,PINKPIN,&Pinkval);
  ReadDigital("O:3",false,YELLOWPIN,&Yellowval);

  ReadAnalog("I:1",0.05,CDSPIN,&CDSval);

  ReadDHT22(2,3,&Temp,&Humd,2,10,&DHTmilli);

  ReadDistance(4,UTrigPin,UEchoPin,&Udistance);

  ReadSerial();
}

void ReadDigital(const char *type,boolean invert,int pin,int *value)
{
  int temp;

  temp = digitalRead(pin);

  if(*value != temp)
  {
    *value = temp;
    Serial.print("*D");
    Serial.print(type);
    Serial.print(",");

    if(invert)
    {
      Serial.println((*value == HIGH ? 0 : 1));
    }
    else
    {
      Serial.println(*value);
    }
  }
}

void ReadAnalog(const char *type,float percentchg,int pin,int *value)
{
  int temp;

  temp = analogRead(pin);

  if(abs(*value - temp) > (int)round(*value * percentchg))
  {
    *value = temp;
    Serial.print("*A");
    Serial.print(type);
    Serial.print(",");
    Serial.println(*value);
  }
}

void ReadDHT22(int tempvpin,int humdvpin,int *tempvar,int *humdvar,int tempchg,int humdchg,unsigned long *millisec)
{
  float TempRaw;
  float HumdRaw;
  int Temptemp;
  int Humdtemp;
  unsigned long millitemp = millis();
 
  if(abs(*millisec - millitemp) > 2000)
  {
    dht22.read2(&TempRaw, &HumdRaw, NULL);
    Temptemp = round((TempRaw * 9 / 5 + 32) * 10);
    Humdtemp = round(HumdRaw * 10);

    if(abs(*tempvar - Temptemp) > tempchg)
    {
      *tempvar = Temptemp;
      Serial.print("*AI:");
      Serial.print(tempvpin);
      Serial.print(",");
      Serial.println(*tempvar);
    }

    if(abs(*humdvar - Humdtemp) > 10)
    {
      *humdvar = Humdtemp;
      Serial.print("*AI:");
      Serial.print(humdvpin);
      Serial.print(",");
      Serial.println(*humdvar);
    }

    *millisec = millis();
  }
}

void ReadDistance(int vpin,int trigpin,int echopin,int *distance)
{
  int temp;
  unsigned long duration = 0;

  digitalWrite(trigpin,LOW);
  delayMicroseconds(2);
  digitalWrite(trigpin,HIGH);
  delayMicroseconds(10);
  digitalWrite(trigpin,LOW);
  duration = pulseIn(echopin,HIGH);
  temp = duration / 74 / 2;
  temp = *distance - (*distance / 3) + (temp / 3);
  if(abs(*distance - temp) > (int)round(*distance * 0.10))
  {
    *distance = temp;
    Serial.print("*AI:");
    Serial.print(vpin);
    Serial.print(",");
    Serial.println(*distance);
  }
}

void ReadSerial()
{
  static int ndx = 0;
  static int cmd = 0;
  static int point = 0;
  static int value = 0;
  char rc;

  while (Serial.available() > 0)
  {
    rc = Serial.read();

    switch(rc)
    {
      case '*':
        ndx = 1;
        cmd = 0;
        point = 0;
        value = 0;
        break;
      case 'A':
      case 'D':
        if(ndx == 1)
        {
          cmd = rc - 64;
          ndx ++;
        }
        else
          ndx = 0;
        break;
      case 'I':
      case 'O':
        if(ndx == 2)
        {
          cmd = cmd * 10 + rc - 72;
          ndx ++;
        }
        else
          ndx = 0;
        break;
      case 'R':
      case 'W':
        if(ndx == 3)
        {
          cmd = cmd * 10 + rc - 81;
          ndx ++;
        }
        else
          ndx = 0;
        break;
      case ':':
        if(ndx == 4)
        {
          point = 0;
          value = 0;
          ndx ++;
        }
        else
          ndx = 0;
        break;
      case '0':
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
        if(ndx == 5)
        {
          point = point * 10 + rc - 48;
        }
        else if(ndx == 6)
        {
          value = value * 10 + rc - 48;
        }
        else
          ndx = 0;
        break;
      case ',':
        if(ndx == 5 && point > 0)
          ndx ++;
        else
          ndx = 0;
        break;
      case '\r':
        break;
      case '\n':
        if(ndx == 5 || ndx == 6)
          ndx = 7;
        else
          ndx = 0;
        break;
    }

    if(ndx == 7)
    {
      switch(cmd)
      {
        case 111: //AIR
          switch(point)
          {
            case 1:
              CDSval = 0;
              break;
            case 2:
              Temp = 0;
              break;
            case 3:
              Humd = 0;
              break;
            case 4:
              Udistance = 0;
              break;
          }
          break;
        case 411: //DIR
          switch(point)
          {
            case 1:
              PIRval = -1;
              break;
            case 2:
              Pinkval = -1;
              break;
          }
          break;
        case 471: //DOR
          switch(point)
          {
            case 3:
              Yellowval = -1;
              break;
          }
          break;
        case 476: //DOW
          switch(point)
          {
            case 3:
              if(value == 0)
                digitalWrite(YELLOWPIN,LOW);
              else
                digitalWrite(YELLOWPIN,HIGH);
              Yellowval = -1;
              break;
          }
          break;
      }

      ndx = 0;
      cmd = 0;
      point = 0;
      value = 0;
    }
  }
}