Martin Smaha
Published © GPL3+

Direct Photon to TP-Link Device Control

Add this small file to your project to control TP-Link bulbs and plugs directly with simple subroutine calls.

BeginnerFull instructions provided1 hour2,999
Direct Photon to TP-Link Device Control

Things used in this project

Story

Read more

Code

TPLink.cpp

C/C++
Add this file to your src folder.
/*
 * Project TPLink
 * Description: Routines to Communicate to TPLink products
 * Author: Martin Smaha
 * Date:3/27/17
 */
#include "Particle.h"
#include "TPLink.h"

 unsigned int localPort = 8888; //local port to use
 unsigned int TPLinkPort = 9999;//TPLink protocol port
 UDP Udp;
 char Buffer[512];

// LB100 / LB110 Bulb control parameters are
//{"smartlife.iot.smartbulb.lightingservice":{"transition_light_state":{"on_off":1,"brightness":70,"mode":"normal","ignore_default":1,"color_temp":2700,"transition_period":150}}}
// HS100 / HS110 / HS105 Plug control parameters are
//{system":{"set_relay_state":{"state":1}}}
// LB130 Bulb control parameters are
//{"smartlife.iot.smartbulb.lightingservice":{"transition_light_state":{"on_off":1,"mode":"normal","hue":120,"saturation":65,"color_temp":0,"brightness":100,"err_code":0}}}

// protocol encoder
 int encode(char * xData) { //encodes JSON text string passed in parameter
   int length = strlen(xData);
   char key = 171;
   for (int j = 0; j < length; j++) {
     char b = (key ^ xData[j]);
     key = b;
     xData[j] = b;
   }
   return length;
 }

// encode and send a UDP packet to the target TPLink device
void SendPacket(uint8_t * targetIP, char * xPacket) {
  IPAddress IPfromBytes;
  IPfromBytes = targetIP;
  int len = encode(xPacket);
  Udp.begin(localPort);
  Udp.sendPacket(xPacket, len, IPfromBytes, TPLinkPort);
  Udp.stop();
}
// light bulb control packet
// NOTE: you can only change brightness while bulb is ON
void TPLink_Bulb(uint8_t * lampIP, int state, int percent) {
  if (percent == 0) {
    state = 0;//force off
  }
  sprintf(Buffer,"{\"smartlife.iot.smartbulb.lightingservice\":{\"transition_light_state\":{\"on_off\":%d,\"brightness\":%d,\"mode\":\"normal\",\"ignore_default\":1,\"color_temp\":2700,\"transition_period\":0}}}",state,percent);
  SendPacket(lampIP, Buffer);
}
// color bulb control packet
void TPLink_LB130Bulb(uint8_t * lampIP, int state, int hue, int saturation, int percent) {
  sprintf(Buffer,"{\"smartlife.iot.smartbulb.lightingservice\":{\"transition_light_state\":{\"on_off\":%d,\"hue\":%d,\"saturation\":%d,\"brightness\":%d,\"mode\":\"normal\",\"ignore_default\":1,\"color_temp\":0,\"transition_period\":0}}}",state,hue,saturation,percent);
  SendPacket(lampIP, Buffer);
}
//alternate call
void TPLink_RGB_Bulb(uint8_t * lampIP, int state, int Red, int Green, int Blue) {
  if ((Red == 0 && Green == 0 && Blue == 0) || state == 0) {
    TPLink_LB130Bulb(lampIP,0,0,0,0);
  } else {
    int hsl[3];
    rgbToHsl(Red, Green, Blue, hsl);
    TPLink_LB130Bulb(lampIP,state,hsl[0],hsl[1],hsl[2]);
  }
}
// plug control packet
void TPLink_Plug(uint8_t * plugIP, int state) {
  sprintf(Buffer,"{\"system\":{\"set_relay_state\":{\"state\":%d}}}",state);
  SendPacket(plugIP, Buffer);
}
// RGB to HSL conversion
/**
 * Converts an RGB color value to HSL. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSL_color_space.
 * Assumes Red, Green, and Blue contained in the set [0, 255] and
 * returns h, s, and l as integers ready for TPLink
 *
 */

void rgbToHsl(int Red, int Green, int Blue, int * HSL) {
    float r = float(Red) / 255.0f;
    float g = float(Green) / 255.0f;
    float b = float(Blue) / 255.0f;

    float max = (r > g && r > b) ? r : (g > b) ? g : b;
    float min = (r < g && r < b) ? r : (g < b) ? g : b;

    float h, s, l;
    l = (max + min) / 2.0f;

    if (max == min) {
        h = s = 0.0f;
    } else {
        float d = max - min;
        s = (l > 0.5f) ? d / (2.0f - max - min) : d / (max + min);

        if (r > g && r > b)
            h = (g - b) / d + (g < b ? 6.0f : 0.0f);

        else if (g > b)
            h = (b - r) / d + 2.0f;

        else
            h = (r - g) / d + 4.0f;

        h /= 6.0f;
    }
    h *= 360;
    s *= 100;
    l *= 100;
    HSL[0] = h;
    HSL[1] = s;
    HSL[2] = l;
}

TPLink.h

C/C++
Add this file to your src folder
/*
 * Project TPLink
 * Description: Routines to Communicate to TPLink products
 * Author: Martin Smaha
 * Date:3/27/17
 */
 void TPLink_Bulb(uint8_t * lampIP, int state, int percent);
 void TPLink_LB130Bulb(uint8_t * lampIP, int state, int hue, int saturation, int percent);
 void TPLink_RGB_Bulb(uint8_t * lampIP, int state, int Red, int Green, int Blue); //LB130 alternate call
 void TPLink_Plug(uint8_t * plugIP, int state);
 void rgbToHsl(int Red, int Green, int Blue, int * HSL);//converion routine

TPLinkDemo

C/C++
Sample demo project to illustrate TPLink subroutine calls.
/*
 * Project TPLink
 * Description: Communicate to TPLink products
 * Author: Martin Smaha
 * Date:3/27/17
 */
#include "TPLink.h"

uint8_t LB100bulbIP[4] = {192,168,0,150};//change this to match your network bulb IP address
uint8_t HS105plugIP[4] = {192,168,0,158};//change this to match your network plug IP address
uint8_t LB130bulbIP[4] = {192,168,0,153};//change this to match your network bulb IP address

// setup() runs once, when the device is first turned on.
void setup() {
//no special setup needed for TPLink functions
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  TPLink_Bulb(LB100bulbIP, 1, 5); //light the bulb at last ON percent
  delay(1000); //bulbs be changed even faster than this
  TPLink_Bulb(LB100bulbIP, 1, 100);//change brightness to 100
  delay(1000);
  TPLink_Bulb(LB100bulbIP, 1, 5);//change brightness to 5
  delay(1000);
  TPLink_Bulb(LB100bulbIP, 0, 5);//turn bulb OFF
  delay(1000);
  TPLink_Plug(HS105plugIP, 1);//turn plug ON
  delay(3000); //plugs require a longer time between transitions
  TPLink_Plug(HS105plugIP, 0);//turn plug OFF
  delay(1000);
  TPLink_LB130Bulb(LB130bulbIP, 1, 120, 65, 100);//greenish
  delay(1000);
  TPLink_LB130Bulb(LB130bulbIP, 1, 240, 65, 100);//blueish
  delay(1000);
  TPLink_LB130Bulb(LB130bulbIP, 1, 330, 65, 100);//redish
  delay(1000);
  TPLink_RGB_Bulb(LB130bulbIP, 1, 0, 255, 0);//green
  delay(1000);
  TPLink_RGB_Bulb(LB130bulbIP, 1, 0, 0, 255);//blue
  delay(1000);
  TPLink_RGB_Bulb(LB130bulbIP, 1, 255, 0, 0);//red
}

TPLinkHub

C/C++
Sample demo project to illustrate IFTTT or web hook to Particle cloud connection
/*
 * Project TPLink Hub
 * Description: IFTTT to TPLink Photon hub
 * Author: Martin Smaha
 * Date:3/24/17
 */
#include "TPLink.h"

uint8_t bulbIP[4] = {192,168,0,150};//change this to match your network bulb IP address

// Cloud functions must return int and take one String
int FrontPorch(String extra) {
  TPLink_Bulb(bulbIP, 1, 100);//wake the bulb up and turn it ON
  TPLink_Bulb(bulbIP, 1, 100);//set the brightness to 100
  return 0;
}

// setup() runs once, when the device is first turned on.
void setup() {
  // register the cloud function
  Particle.function("FrontPorch", FrontPorch);
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  //nothing to do	
}

TPLinkFlicker

C/C++
Sample demo project to demonstrate candle light flicker effect.
/*
 * Project TPLink Flicker
 * Description: Flicker a TPLink bulb to simulate candle
 * Author: Martin Smaha
 * Date:3/24/17
 */
#include "TPLink.h"

uint8_t bulbIP[4] = {192,168,0,150};//change this to match your network bulb IP address

// setup() runs once, when the device is first turned on.
void setup() {
//no special setup needed for TPLink functions
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  int brightness = random(10, 25);//min and max brightness percent to flicker between
  int msec = random(100, 300);//min and max delay to next value. min should not be less than 100
  TPLink_Bulb(bulbIP, 1, brightness);
  delay(msec);
}

TPLinkVixen

C/C++
Sample Vixen Lights ACN protocol to TPLink bridge
/*
 * Project TPLink Vixen
 * Description: Vixen Lights sACN Controller to TP-Link bulbs
 * Author: Martin Smaha
 * Date:3/27/17
 */
#include "TPLink.h"
int LED = D7;//to show activity
uint8_t LB100IP[4] = {192,168,0,150};//change this to match your network bulb IP address
uint8_t LB130IP[4] = {192,168,0,153};//change this to match your network bulb IP address

UDP Multicast;
uint8_t packetBuffer[2048];

void DecodeDMX(uint8_t * xBuffer);

// setup() runs once, when the device is first turned on.
void setup() {
  //UDP multicast from Vixen
	int remotePort = 5568;
	IPAddress multicastAddress(239,255,0,1);//change the 1 to match the DMX universe assigned to this device
	Multicast.setBuffer(2048);//replace the 512 byte default buffer with one large enough for the entire packet
	Multicast.begin(remotePort);//open the port
	Multicast.joinMulticast(multicastAddress);//accept multicast packets
  // small blue LED on board
  pinMode(LED, OUTPUT);
  digitalWriteFast(LED, LOW);
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  int size = Multicast.parsePacket();
	if (size > 0) {
		size = Multicast.read(packetBuffer, 2048);//read all UDP packet data
		if (size >= 635) {//dmx packet
			digitalWriteFast(LED, HIGH);//show packet received activity
			DecodeDMX(packetBuffer);
			digitalWriteFast(LED, LOW);
		}
	}
}

void DecodeDMX(uint8_t * xBuffer) {
  //traditionally you would want to decode the ACN packet Preamble, Postamble, and PDU to find the correct offset
  //but since we know it for Vixen streaming ACN packets, and it does not change, we will just code it as const
	const int offset = 125;//just point to start of Vixen encoded DMX channel data

  if (xBuffer[offset] != 0) {//not the correct packet format
		return;
	}

	//define these hardware channels to match the channel patching you used in Vixen
  //my BulbTest.tim Vixen system and sequence uses these channel definitions
	//channels 1,2,3 are LB130 R,G,B
	int red = xBuffer[offset+1];
	int green = xBuffer[offset+2];
	int blue = xBuffer[offset+3];
  TPLink_RGB_Bulb(LB130IP, 1, red, green, blue);

	//channel 4 is LB100
	int percentBrightness = xBuffer[offset+4] * 100 / 255;
	TPLink_Bulb(LB100IP, 1, percentBrightness);

  //place additional channel definitions here

}

TPLinkVixen2

C/C++
Simple Vixen Lights BlinkyLinky protocol to TPLink bridge
/*
 * Project TPLink Vixen 2
 * Description: Vixen Lights Blinky Linky Controller to TP-Link bulbs
 * Author: Martin Smaha
 * Date:3/28/17
 */
#include "TPLink.h"
int LED = D7;//to show activity
uint8_t LB100IP[4] = {192,168,0,150};//change this to match your network bulb IP address
uint8_t LB130IP[4] = {192,168,0,153};//change this to match your network bulb IP address

TCPServer server = TCPServer(50000);//choose whatever port you want, just tell Vixen in controller config
TCPClient client;

uint8_t packetBuffer[1024];
uint8_t channels[256];//channel values
unsigned long lastMS;

void Decode(uint8_t * xBuffer);
void UpdateBulbs(void);

// setup() runs once, when the device is first turned on.
void setup() {
  memset(channels,0,256);
	// TCP stream from Vixen
	server.begin();
  // small blue LED on board
  pinMode(LED, OUTPUT);
  digitalWriteFast(LED, LOW);
  // millisecond timer
  lastMS = millis();
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
	if (client.connected()) {
	  // echo all available bytes back to the client
	  if (client.available()) {
		  client.read(packetBuffer,1024);
		  Decode(packetBuffer);
      lastMS = millis();
		} else {
      // use the timebase to determine when to update instead of just using delay()
      unsigned long newMS = millis();//curent cpu running time in milliseconds
      if (newMS > lastMS) {
        unsigned long diff = newMS - lastMS;
        if (diff >= 25) { //update at half the frame interval
          lastMS = newMS - (diff - 25);
          UpdateBulbs();//refresh bulbs because they are UDP packets that could get lost
    			digitalWriteFast(LED, LOW);
        }
      } else { //throw away one update every 49 days when the timebase wraps
        lastMS = newMS;
      }
		}
  } else {
	  // if no client is yet connected, check for a new connection
	  client = server.available();
  }
}

void Decode(uint8_t * xBuffer) {
  if (xBuffer[0] == 0xDE && xBuffer[1] == 0xAD && xBuffer[2] == 0xBE && xBuffer[3] == 0xEF) {
    digitalWriteFast(LED, HIGH);//show packet received activity
    int length = xBuffer[6] * 256 + xBuffer[7];
    int offset = 8;//point to first channel number
    for (int i = 0; i < length; i+=2) {//only changed channels are reported
      channels[xBuffer[offset]] = xBuffer[offset+1];
      offset += 2;
    }
    UpdateBulbs();
  }
}
void UpdateBulbs(void) {
  //refresh all connected devces
  const int offset = -1;//correct for local 0/1 offset
  //define these hardware channels to match the channel patching you used in Vixen
  //my BulbTest.tim Vixen system and sequence uses these channel definitions
	//channels 1,2,3 are LB130 R,G,B
	int red = channels[offset+1];
	int green = channels[offset+2];
	int blue = channels[offset+3];
  TPLink_RGB_Bulb(LB130IP, 1, red, green, blue);

	//channel 4 is LB100
	int percentBrightness = channels[offset+4] * 100 / 255;
	TPLink_Bulb(LB100IP, 1, percentBrightness);

  //place additional channel definitions here
}

Credits

Martin Smaha

Martin Smaha

4 projects • 9 followers
Designing hardware and software since 1975.

Comments