AceInfinity
Emeritus, Contributor
Preview:
I have a few Philips hue devices in my house and I got tired of looking at all of the limited and buggy apps that require further payment to make use of the features that the devices themselves already have.
I looked through the documentation for the API and read quite a bit about it. Supposedly these lights use the ZigBee protocol (which I am very familiar with) and create a mesh for communication with one another, and from my understanding back to the hub. The hub itself is the only device that I can see from a network scan so it seems to be the only device that uses IP communication. The hub acts as the central point for keeping/storing data, and pushing and polling it to and from the devices themselves.
I wrote some C code in a couple hours to communicate with the RESTful web API built into the hub over TCP sockets on the default HTTP port (80). From the API, I don't have anything in my code that currently pulls data about a device, since that would require me to incorporate a JSON library in with my existing code and I'm already too tired to really see my computer screen, but for changing device data, my code works for most of the visual changes you'd see. I still have to add in code that changes device name and such, but I'll get there when I have some more free time...
Supposedly Philips uses FreeRTOS and lwTCP (Lightweight TCP/IP stack)...
main.c
hue.h
hue_config.h
socks.h
NOTE: I have 3 hue light bulbs, and 1 LED light strip, so I enumerate and update 4 devices to the settings defined. What you see in main() is meant to be example code to demonstrate usage of what I've written. You can keep the winsock initialization and destroy however.
Lastly, I do not have code to automatically identify the hub on the network. That is probably going to be the very next thing I do if I continue to work on this. You'll have to do a network scan and find the hub yourself.. On Windows 8.1, while on the same network as the hub it just shows up in the network locations on my computer with the IP address, so that may be a convenient way for you to figure it out if you don't have a scanner.
Enjoy :)
:thumbsup2:
I have a few Philips hue devices in my house and I got tired of looking at all of the limited and buggy apps that require further payment to make use of the features that the devices themselves already have.
I looked through the documentation for the API and read quite a bit about it. Supposedly these lights use the ZigBee protocol (which I am very familiar with) and create a mesh for communication with one another, and from my understanding back to the hub. The hub itself is the only device that I can see from a network scan so it seems to be the only device that uses IP communication. The hub acts as the central point for keeping/storing data, and pushing and polling it to and from the devices themselves.
I wrote some C code in a couple hours to communicate with the RESTful web API built into the hub over TCP sockets on the default HTTP port (80). From the API, I don't have anything in my code that currently pulls data about a device, since that would require me to incorporate a JSON library in with my existing code and I'm already too tired to really see my computer screen, but for changing device data, my code works for most of the visual changes you'd see. I still have to add in code that changes device name and such, but I'll get there when I have some more free time...
Supposedly Philips uses FreeRTOS and lwTCP (Lightweight TCP/IP stack)...
main.c
Code:
[NO-PARSE]/*
* Author: AceInfinity (c) 2015
* Date: 2015-01-06 19:34:23
* Filename: main.c
* Last Modified time: 2015-01-23 23:32:51
*/
#define HUE_HOST "192.168.1.35" // need to detect this automatically ideally
// #define HUE_PORT 80
#include "hue.h"
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
int main(void)
{
if (ws_init() != 0)
{
fprintf(stderr, "Failed to initialize Winsock.\n");
exit(1);
}
uint8_t bitflags = SET_ON | SET_BRIGHTNESS | SET_SATURATION | SET_HUE;
struct hue_config_t config =
{
.on = false,
.brightness = 0xFE,
.saturation = 0xFF,
.hue = HUE_RED,
.color_temp = 0,
.alert = "lselect",
.effect = "none",
.transitiontime = 10
};
// enumerate all light id's and set properties to all
const int n_devices = 4;
for (int id = 1; id <= n_devices; ++id)
{
char url[256];
sprintf(url, "/api/newdeveloper/lights/%d/state", id);
set_hue_light_opt(url, bitflags, &config);
}
if (ws_destroy() != 0)
fprintf(stderr, "Failed to cleanup Winsock.\n");
}[/NO-PARSE]
hue.h
Code:
[NO-PARSE]/*
* Author: AceInfinity (c) 2015
* Date: 2015-01-23 22:56:52
* File: hue.h
* Last Modified time: 2015-01-23 23:32:48
*/
#ifndef __HUE_HTTP_H__
#define __HUE_HTTP_H__
#include "socks.h"
#include "hue_config.h"
#include <stdbool.h>
#include <stdint.h>
bool send_put_request(const char *url, const char *data, const size_t send_max_len, char *recv_buf, const size_t recv_max_len)
{
SOCKET fd; // socket descriptor
if ((fd = ws_socket_create(TCP)) == INVALID_SOCKET)
{
int err = WSAGetLastError();
if (err == WSAETIMEDOUT)
fprintf(stderr, "[Socket Create] Connection timed out.\n");
else
fprintf(stderr, "Failed to create socket: %d\n", err);
return false;
}
if (ws_socket_connect(fd, HUE_HOST, HUE_PORT) != 0)
{
int err = WSAGetLastError();
if (err == WSAETIMEDOUT)
fprintf(stderr, "[Socket Connect] Connection timed out.\n");
else
fprintf(stderr, "Failed to connect socket: %d\n", err);
return false;
}
char send_buf[send_max_len];
memset(send_buf, 0, send_max_len);
sprintf(send_buf, "PUT %s HTTP/1.1\r\nContent-Length:%u\r\n\r\n%s", url, strlen(data), data);
if (ws_socket_send(fd, send_buf, strlen(send_buf)) == SOCKET_ERROR)
{
fprintf(stderr, "send() error: %d\n", WSAGetLastError());
return false;
}
if (ws_socket_recv(fd, recv_buf, recv_max_len) == SOCKET_ERROR)
{
fprintf(stderr, "recv() error: %d\n", WSAGetLastError());
return false;
}
return true;
}
void set_hue_light_opt(const char *url, uint8_t flags, const struct hue_config_t *config)
{
char data[MAX_BUF], recv_buf[MAX_BUF];
/* Transition Time (100ms multiple variant) */
if (flags & SET_TRANSITIONTIME)
{
sprintf(data, "{\"transitiontime\":%u}", config->transitiontime);
send_put_request(url, data, MAX_BUF, recv_buf, MAX_BUF);
}
/* ON(/OFF) */
if (flags & SET_ON)
{
sprintf(data, "{\"on\":%s}", config->on ? "true" : "false");
send_put_request(url, data, MAX_BUF, recv_buf, MAX_BUF);
}
/* Hue */
if (flags & SET_HUE)
{
sprintf(data, "{\"hue\":%u}", config->hue);
send_put_request(url, data, MAX_BUF, recv_buf, MAX_BUF);
}
/* Brightness */
if (flags & SET_BRIGHTNESS)
{
sprintf(data, "{\"bri\":%u}", config->brightness);
send_put_request(url, data, MAX_BUF, recv_buf, MAX_BUF);
}
/* Saturation */
if (flags & SET_SATURATION)
{
sprintf(data, "{\"sat\":%u}", config->saturation);
send_put_request(url, data, MAX_BUF, recv_buf, MAX_BUF);
}
/* Color Temp */
if (flags & SET_COLOR_TEMP)
{
sprintf(data, "{\"ct\":%u}", config->color_temp);
send_put_request(url, data, MAX_BUF, recv_buf, MAX_BUF);
}
/* Alert */
if (flags & SET_ALERT)
{
sprintf(data, "{\"alert\":\"%s\"}", config->alert);
send_put_request(url, data, MAX_BUF, recv_buf, MAX_BUF);
}
/* Effect */
if (flags & SET_EFFECT)
{
sprintf(data, "{\"effect\":\"%s\"}", config->effect);
send_put_request(url, data, MAX_BUF, recv_buf, MAX_BUF);
}
}
#endif // __HUE_HTTP_H__[/NO-PARSE]
hue_config.h
Code:
[NO-PARSE]/*
* Author: AceInfinity (c) 2015
* Date: 2015-01-23 22:53:23
* File: hue_config.h
* Last Modified time: 2015-01-23 23:32:43
*/
#ifndef __PHILIPS_HUE_H__
#define __PHILIPS_HUE_H__
#ifndef HUE_HOST
#error HUE_HOST is not defined (this must be determined automatically in future revisions)
#endif
#ifndef HUE_PORT
#define HUE_PORT 80 // use default http port (80)
#endif
#include <stdbool.h>
#include <stdint.h>
#define LIGHT_ID(id) #id
// predefined hue numeric values
#define HUE_RED 0
#define HUE_YELLOW 12750
#define HUE_GREEN 25500
#define HUE_BLUE 46920
#define HUE_MAGENTA 56100
// set option flags
#define SET_NONE 0
#define SET_ON 1
#define SET_BRIGHTNESS 2
#define SET_SATURATION 4
#define SET_HUE 8
#define SET_COLOR_TEMP 16
#define SET_ALERT 32
#define SET_EFFECT 64
#define SET_TRANSITIONTIME 128
struct hue_config_t
{
bool on;
uint8_t brightness; // 1 - 254 (8 bit unsigned)
uint8_t saturation; // 0 - 255 (8 bit unsigned)
uint16_t hue; // 0 - 65535 (16 bit unsigned)
// Mired color temp of the light
// 2012 connected lights are capable of 153 (6500K) to 500 (2000K).
// note - LED light strip does not support this state property
uint16_t color_temp;
char alert[8]; // "none", "select", "lselect"
char effect[10]; // "none", "colorloop"
// duration of the transition from the light’s current state to the new state.
// This is given as a multiple of 100ms and defaults to 4 (400ms).
// For example, setting transistiontime:10 will make the transition last 1 second. (10 * 100ms = 1sec)
uint16_t transitiontime;
// [reserved for future use]
// currently always returns true (will be updated in a future patch of the hue software)
bool reachable;
};
#endif // __PHILIPS_HUE_H__[/NO-PARSE]
socks.h
Code:
[NO-PARSE]/*
* Author: AceInfinity (c) 2015
* Date: 2015-01-23 19:52:22
* File: socks.h
* Last Modified time: 2015-01-23 23:32:38
*/
#ifndef __SOCKS_H__
#define __SOCKS_H__
#include <stdio.h>
#include <winsock2.h>
#define MAX_BUF 1024
enum WS_PROTO_TYPE { UDP, TCP };
int ws_init()
{
static WSADATA wsa_data;
return WSAStartup(WINSOCK_VERSION, &wsa_data);
}
int ws_destroy() { return WSACleanup(); }
int ws_socket_close(SOCKET fd)
{
shutdown(fd, SD_BOTH);
int ret = closesocket(fd);
if (ret == SOCKET_ERROR)
fprintf(stderr, "closesocket() failed: %d\n", WSAGetLastError());
return ret;
}
int tcp_keep_alive(SOCKET fd)
{
int keep_alive = 1;
if (setsockopt(fd , SOL_SOCKET , SO_KEEPALIVE
, (void *)&keep_alive, sizeof(keep_alive)) == SOCKET_ERROR)
{
fprintf(stderr, "Set sockets option SO_KEEPALIVE failed: %d\n", WSAGetLastError());
return -1;
}
/* When large blocks of data (for example, 3-4 MB) are sent over a
* blocking socket, send eventually fails with error 10055, WSAENOBUFS.
* (https://support.microsoft.com/kb/201213?wa=wsignin1.0)
*/
int sock_opt = 0;
if (setsockopt(fd , SOL_SOCKET , SO_SNDBUF, (void *)&sock_opt, sizeof(sock_opt)) == SOCKET_ERROR)
{
fprintf(stderr, "Set sockets option SO_SNDBUF failed: %d\n", WSAGetLastError());
return -1;
}
// reuse sockets, only effective for bind() function call
BOOL sock_reuse = TRUE;
if (setsockopt(fd , SOL_SOCKET , SO_REUSEADDR, (void *)&sock_reuse, sizeof(sock_reuse)) == SOCKET_ERROR)
{
fprintf(stderr, "Set sockets option SO_REUSEADDR failed: %d\n", WSAGetLastError());
return -1;
}
return 0;
}
int netaddr_set(char *name, struct sockaddr_in *addr)
{
if ((addr->sin_addr.s_addr = inet_addr(name)) == INADDR_NONE)
return -1;
struct hostent *host;
host = gethostbyname(name);
if (host)
addr->sin_addr.s_addr = *(size_t *) host->h_addr_list[0];
else
fprintf(stderr, "Failed to resolve IP address\n");
return 1;
}
SOCKET ws_socket_create(enum WS_PROTO_TYPE protocol)
{
SOCKET fd = 0;
switch (protocol)
{
case TCP:
fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
tcp_keep_alive(fd);
break;
case UDP:
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
break;
default:
return INVALID_SOCKET;
}
return fd;
}
int ws_socket_connect(SOCKET fd, char *ip, unsigned short port)
{
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
netaddr_set(ip, &addr);
addr.sin_port = htons(port);
if (connect(fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) < 0)
{
return -1;
}
return 0;
}
int ws_socket_send(SOCKET fd, const char *buf, size_t len)
{ return send(fd, buf, len, 0); }
int ws_socket_recv(SOCKET fd, char *buf, size_t len)
{ return recv(fd, buf, len, 0); }
#endif[/NO-PARSE]
NOTE: I have 3 hue light bulbs, and 1 LED light strip, so I enumerate and update 4 devices to the settings defined. What you see in main() is meant to be example code to demonstrate usage of what I've written. You can keep the winsock initialization and destroy however.
Lastly, I do not have code to automatically identify the hub on the network. That is probably going to be the very next thing I do if I continue to work on this. You'll have to do a network scan and find the hub yourself.. On Windows 8.1, while on the same network as the hub it just shows up in the network locations on my computer with the IP address, so that may be a convenient way for you to figure it out if you don't have a scanner.
Enjoy :)
:thumbsup2:
Last edited: