I'm trying to control Philips Hue lights through an Arduino Uno (with a WiFi shield Model R3). Both the shield and the Philips Hue Bridge are connected to the same network and I'm trying to send JSON commands from the Arduino to the Hue Bridge from the following code taken from here.
#include <SPI.h>
#include <WiFi.h>
#include <WiFiClient.h>
char ssid[] = "mynetwork"; // your network SSID (name)
char pass[] = "mypass"; // your network password
int status = WL_IDLE_STATUS; // the Wifi radio's status
//Hue constants
const char hueHubIP[] = "192.168.0.50"; //Hue Bridge IP
const char hueUsername[] = "myhue"; //Hue username
const int hueHubPort = 80;
IPAddress server(192,168,0,50);
WiFiClient client;
//Hue variables
boolean hueOn; // on/off
int hueBri; // brightness value
long hueHue; // hue value
String hueCmd; // Hue command
unsigned long buffer=0; //buffer for received data storage
unsigned long addr;
IPAddress ip;
void setup()
{
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// check for the presence of the shield:
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println("WiFi shield not present");
// don't continue:
while (true);
}
// attempt to connect to Wifi network:
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
// you're connected now, so print out the data:
Serial.print("You're connected to the network");
printCurrentNet();
printWifiData();
ip = WiFi.localIP();
Serial.println(ip);
}
void loop()
{
String command = "{\"on\": true,\"hue\": 50100,\"sat\":255,\"bri\":255,\"transitiontime\":"+String(random(15,25))+"}";
setHue(1,command);
command = "{\"on\": true,\"hue\": 65280,\"sat\":255,\"bri\":255,\"transitiontime\":"+String(random(15,25))+"}";
setHue(2,command);
command = "{\"hue\": 65280,\"sat\":255,\"bri\":255,\"transitiontime\":"+String(random(15,25))+"}";
setHue(1,command);
command = "{\"hue\": 50100,\"sat\":255,\"bri\":255,\"transitiontime\":"+String(random(15,25))+"}";
setHue(2,command);
}
/* setHue() is our main command function, which needs to be passed a light number and a
* properly formatted command string in JSON format (basically a Javascript style array of variables
* and values. It then makes a simple HTTP PUT request to the Bridge at the IP specified at the start.
*/
boolean setHue(int lightNum,String command)
{
if (client.connect(server, 80))
{
Serial.println(F("connected to server"));
while (client.connected()&& status == WL_CONNECTED)
{
client.print(F("PUT /api/"));
client.print(hueUsername);
client.print(F("/lights/"));
client.print(lightNum); // hueLight zero based, add 1
client.println(F("/state HTTP/1.1"));
client.println(F("keep-alive"));
client.print(F("Host: "));
client.println(hueHubIP);
client.print(F("Content-Length: "));
client.println(command.length());
client.println(F("Content-Type: text/plain;charset=UTF-8"));
client.println(); // blank line before body
client.println(command); // Hue command
}
client.stop();
return true; // command executed
}
else
return false; // command failed
}
//client.flush();
//client.stop();
/* A helper function in case your logic depends on the current state of the light.
* This sets a number of global variables which you can check to find out if a light is currently on or not
* and the hue etc. Not needed just to send out commands
*/
boolean getHue(int lightNum)
{
if (client.connect(server, 80))
{
client.print(F("GET /api/"));
client.print(hueUsername);
client.print(F("/lights/"));
client.print(lightNum);
client.println(F(" HTTP/1.1"));
client.print(F("Host: "));
client.println(hueHubIP);
client.println(F("Content-type: application/json"));
client.println(F("keep-alive"));
client.println();
while (client.connected()&& status == WL_CONNECTED)
{
if (client.available())
{
client.findUntil("\"on\":", "0円");
hueOn = (client.readStringUntil(',') == "true"); // if light is on, set variable to true
client.findUntil("\"bri\":", "0円");
hueBri = client.readStringUntil(',').toInt(); // set variable to brightness value
client.findUntil("\"hue\":", "0円");
hueHue = client.readStringUntil(',').toInt(); // set variable to hue value
break; // not capturing other light attributes yet
}
}
client.stop();
return true; // captured on,bri,hue
}
else
return false; // error reading on,bri,hue
}
void printWifiData() {
//WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);
}
void printCurrentNet() {
//SSID of the network you're attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());
}
}
The above code gets my Arduino connected to mynetwork
and I have also connected Hue Bridge to the same but it throws an error of No Socket Available
.
UPDATE (9th March 2016):
I implemented suggestions 1
and 4
given by slash-dev and was able to get rid of No Socket Available
error. Following is the updated code:
#include <SPI.h>
#include <WiFi.h>
#include <WiFiClient.h>
char ssid[] = "mynetwork"; // your network SSID (name)
char pass[] = "mypass"; // your network password
int status = WL_IDLE_STATUS; // the Wifi radio's status
//Hue constants
const char hueHubIP[] = "192.168.0.50"; //Hue hub IP
const char hueUsername[] = "myhue"; //Hue username
const int hueHubPort = 80;
IPAddress server(192,168,0,50);
WiFiClient client;
//Hue variables
boolean hueOn; // on/off
int hueBri; // brightness value
long hueHue; // hue value
String hueCmd; // Hue command
unsigned long buffer=0; //buffer for received data storage
unsigned long addr;
IPAddress ip;
void setup()
{
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// check for the presence of the shield:
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println(F("WiFi shield not present"));
// don't continue:
while (true);
}
// attempt to connect to Wifi network:
while (status != WL_CONNECTED) {
Serial.print(F("Attempting to connect to WPA SSID: "));
Serial.println(ssid);
// Connect to WPA/WPA2 network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
// you're connected now, so print out the data:
Serial.print(F("You're connected to the network"));
printCurrentNet();
printWifiData();
}
void loop()
{
String command = "{\"on\": true,\"hue\": 50100,\"sat\":255,\"bri\":255,\"transitiontime\":"+String(random(15,25))+"}";
setHue(1,command);
command = "{\"on\": true,\"hue\": 65280,\"sat\":255,\"bri\":255,\"transitiontime\":"+String(random(15,25))+"}";
setHue(2,command);
command = "{\"hue\": 65280,\"sat\":255,\"bri\":255,\"transitiontime\":"+String(random(15,25))+"}";
setHue(1,command);
command = "{\"hue\": 50100,\"sat\":255,\"bri\":255,\"transitiontime\":"+String(random(15,25))+"}";
setHue(2,command);
}
/* setHue() is our main command function, which needs to be passed a light number and a
* properly formatted command string in JSON format (basically a Javascript style array of variables
* and values. It then makes a simple HTTP PUT request to the Bridge at the IP specified at the start.
*/
boolean setHue(int lightNum,String command)
{
client.stop();
if (client.connect(server, 80))
{
Serial.println(F("connected to server"));
while (client.connected()&& status == WL_CONNECTED)
{
client.print(F("PUT /api/"));
client.print(hueUsername);
client.print(F("/lights/"));
client.print(lightNum); // hueLight zero based, add 1
client.println(F("/state HTTP/1.1"));
client.println(F("Connection: keep-alive"));
client.print(F("Host: "));
client.println(hueHubIP);
client.print(F("Content-Length: "));
client.println(command.length());
client.println(F("Content-Type: text/plain;charset=UTF-8"));
client.println(); // blank line before body
client.println(command); // Hue command
}
client.flush();
client.stop();
return true; // command executed
}
else
return false; // command failed
}
/* A helper function in case your logic depends on the current state of the light.
* This sets a number of global variables which you can check to find out if a light is currently on or not
* and the hue etc. Not needed just to send out commands
*/
boolean getHue(int lightNum)
{
client.stop();
if (client.connect(server, 80))
{
client.print(F("GET /api/"));
client.print(hueUsername);
client.print(F("/lights/"));
client.print(lightNum);
client.println(F(" HTTP/1.1"));
client.print(F("Host: "));
client.println(hueHubIP);
client.println(F("Content-type: application/json"));
client.println(F("Connection: keep-alive"));
client.println();
while (client.connected()&& status == WL_CONNECTED)
{
if (client.available())
{
client.findUntil("\"on\":", "0円");
hueOn = (client.readStringUntil(',') == "true"); // if light is on, set variable to true
client.findUntil("\"bri\":", "0円");
hueBri = client.readStringUntil(',').toInt(); // set variable to brightness value
client.findUntil("\"hue\":", "0円");
hueHue = client.readStringUntil(',').toInt(); // set variable to hue value
break; // not capturing other light attributes yet
}
}
client.flush();
client.stop();
return true; // captured on,bri,hue
}
else
return false; // error reading on,bri,hue
}
void printWifiData() {
//WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print(F("IP Address: "));
Serial.println(ip);
}
void printCurrentNet() {
//SSID of the network you're attached to:
Serial.print(F("SSID: "));
Serial.println(WiFi.SSID());
}
-
1See Arduino wifi shield - "No Socket available" thread in forum.arduino.ccJames Waldby - jwpat7– James Waldby - jwpat72016年03月01日 09:13:57 +00:00Commented Mar 1, 2016 at 9:13
-
@jwpat: I have already seen that solution and appended the stated lines to my code but it doesn't work for me.Hyperbola– Hyperbola2016年03月01日 09:52:35 +00:00Commented Mar 1, 2016 at 9:52
-
The error message seems to be coming from the network library code itself rather than anywhere in your sketch, which is a rather ugly practice by the library's author - what's to say that the Serial object is being used in a way that makes it an appropriate place to display errors? To debug this more, you may need to dig into the networking library and add your own temporary (this project only) output that is more specific about why no socket is available.Chris Stratton– Chris Stratton2016年03月07日 14:44:47 +00:00Commented Mar 7, 2016 at 14:44
4 Answers 4
I suspect a low RAM failure. Here are some suggestions that will dramatically lower your RAM usage:
1) You still have Serial.print
using double-quoted C string constants. Use F("C string")
for those, too. For example:
Serial.println( F("WiFi shield not present") );
Every double-quoted C string uses RAM in addition to FLASH program space. You have already saved a bunch, but you should get rid of the above uses.
2) Don't use
String
for setHue
.
TL;DR - it will use extra RAM, FLASH, CPU time, and cause inexplicable or random failures.
Instead, use C strings, aka char
arrays for variables, or double-quoted string constants. When the constants are wrapped with the F() macro, they don't use any RAM.
Here is an example of how to define and call a setHue
with C strings instead of String
:
--> See complete code below in UPDATE 3/9/2016 section <--
This just prints the C strings to Serial
, but I hope you can see how they could also be printed to the client
.
3) Don't use
String
for getHue
.
Here is an example of how to define and call a getHue
that uses C strings instead of String
:
--> See complete code below in UPDATE 3/9/2016 section <--
Just copy and paste the displayed JSON into the Serial Monitor "Send" field. The debug prints will display what gets parsed.
- NOTE: It's not clear if the JSON attributes always come back in the same order. This example expects the same order in which they were sent. Other techniques would have to be used to accommodate any order.
4) Be sure to call
client.flush()
before every client.stop()
.
Summary: I would guess that the combination of non-F() "C strings" wasting RAM, plus the
String
use of RAM (doubled or more), is leading to a low-RAM failure. The above approach will lower RAM usage dramatically, and also save more than 1.6kB in program space. After implementing these changes, almost all RAM will be available for the WiFi libraries.
UPDATE 3/9/2016
Here's my changes, merged into your second sketch.
#include <SPI.h>
#include <WiFi.h>
#include <WiFiClient.h>
char ssid[] = "mynetwork"; // your network SSID (name)
char pass[] = "mypass"; // your network password
int status = WL_IDLE_STATUS; // the Wifi radio's status
//Hue constants
const char hueHubIP[] = "192.168.0.50"; //Hue hub IP
const char hueUsername[] = "myhue"; //Hue username
const int hueHubPort = 80;
IPAddress server(192,168,0,50);
// Uncomment this to *really* use the Wi-Fi sheild.
// Comment this to read & write everything to the Serial Monitor for testing.
//#define REAL_WIFI
#ifdef REAL_WIFI
WiFiClient client;
#else
HardwareSerial & client = Serial; // redirect 'client' to the Serial Monitor
#endif
//Hue variables
boolean hueOn; // on/off
int hueBri; // brightness value
long hueHue; // hue value
String hueCmd; // Hue command
unsigned long buffer=0; //buffer for received data storage
unsigned long addr;
IPAddress ip;
void setup()
{
//Initialize serial and wait for port to open:
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
// check for the presence of the shield:
if (WiFi.status() == WL_NO_SHIELD) {
Serial.println(F("WiFi shield not present"));
// don't continue:
#ifdef REAL_WIFI
while (true);
#else
status = WL_CONNECTED;
#endif
}
// attempt to connect to Wifi network:
while (status != WL_CONNECTED) {
Serial.print(F("Attempting to connect to WPA SSID: "));
Serial.println(ssid);
// Connect to WPA/WPA2 network:
status = WiFi.begin(ssid, pass);
// wait 10 seconds for connection:
delay(10000);
}
// you're connected now, so print out the data:
Serial.print(F("You're connected to the network"));
printCurrentNet();
printWifiData();
}
void loop()
{
setHue
( 1,
F("{\"on\": true,\"hue\": 50100,\"sat\":255,\"bri\":255,\"transitiontime\":"),
random(15,25),
F("}")
);
delay( 10000 );
setHue
( 2,
F("{\"on\": true,\"hue\": 65280,\"sat\":255,\"bri\":255,\"transitiontime\":"),
random(15,25),
F("}")
);
delay( 10000 );
setHue
( 1,
F("{\"hue\": 65280,\"sat\":255,\"bri\":255,\"transitiontime\":"),
random(15,25),
F("}")
);
delay( 10000 );
setHue
( 2,
F("{\"hue\": 50100,\"sat\":255,\"bri\":255,\"transitiontime\":"),
random(15,25),
F("}")
);
delay( 10000 );
// getHue( 1 );
// getHue( 2 );
}
/* setHue() is our main command function, which needs to be passed a light number and a
* properly formatted command string in JSON format (basically a Javascript style array of variables
* and values. It then makes a simple HTTP PUT request to the Bridge at the IP specified at the start.
*/
boolean setHue
( int lightNum,
const __FlashStringHelper *header,
int parameter,
const __FlashStringHelper *trailer )
{
#ifdef REAL_WIFI
client.stop();
if (client.connect(server, 80))
#endif
{
Serial.println(F("connected to server"));
#ifdef REAL_WIFI
if (client.connected()&& status == WL_CONNECTED)
#endif
{
client.print(F("PUT /api/"));
client.print(hueUsername);
client.print(F("/lights/"));
client.print(lightNum); // hueLight zero based, add 1
client.println(F("/state HTTP/1.1"));
client.println(F("Connection: keep-alive"));
client.print(F("Host: "));
client.println(hueHubIP);
// Calculate total command length.
int cmdLength =
strlen_P( (const prog_char *)header ) +
1 + // Assume one digit for parameter...
strlen_P( (const prog_char *)trailer );
// ... and add room for more digits if necessary.
if (parameter > 9)
cmdLength++;
if (parameter > 99)
cmdLength++;
client.print(F("Content-Length: "));
client.println( cmdLength );
client.println(F("Content-Type: text/plain;charset=UTF-8"));
client.println(); // blank line before body
client.print( header ); // Hue command pieces
client.print( parameter );
client.println( trailer );
}
client.flush();
#ifdef REAL_WIFI
client.stop();
#endif
return true; // command executed
}
#ifdef REAL_WIFI
else
return false; // command failed
#endif
}
//---------------------------------------------------------------------
// Utility function to extract one `bool` value from the client stream.
// No buffers are used. Each character is handled as it is received.
// (you may want/need to add a timeout)
static bool clientParseBool( char delim )
{
bool val = false;
uint8_t count = 0; // number of chars we've read for the value
while (status == WL_CONNECTED) {
if (client.available()) {
char c = client.read();
if (c == delim)
break; // done!
if ((c == ' ') || (count++ > 0))
continue; // skip spaces and anything after the first non-space char
if (c == 't')
val = true;
}
}
// Debug prints
Serial.print( F("bool ") );
Serial.println( val );
return val;
}
//-------------------------------------------
// Utility function to extract one 'unsigned 16-bit int' value from the client stream.
// No buffers are used. Each character is handled as it is received.
// (you may want/need to add a timeout)
static uint16_t clientParseUint( char delim )
{
uint16_t val = 0;
while (status == WL_CONNECTED) {
if (client.available()) {
char c = client.read();
if (c == delim)
break; // done!
if (c == ' ')
continue; // skip spaces
if (isdigit(c))
val = val*10 + (c - '0'); // accumulate one digit of the bri value
}
}
// Debug prints
Serial.print( F("int ") );
Serial.println( val );
return val;
}
//-------------------------------------------
/* A helper function in case your logic depends on the current state of the light.
* This sets a number of global variables which you can check to find out if a light is currently on or not
* and the hue etc. Not needed just to send out commands
*/
boolean getHue(int lightNum)
{
bool gotHue = false;
#ifdef REAL_WIFI
client.stop();
if (client.connect(server, 80))
#endif
{
client.print(F("GET /api/"));
client.print(hueUsername);
client.print(F("/lights/"));
client.print(lightNum);
client.println(F(" HTTP/1.1"));
client.print(F("Host: "));
client.println(hueHubIP);
client.println(F("Content-type: application/json"));
client.println(F("Connection: keep-alive"));
client.println();
if (status == WL_CONNECTED)
{
client.findUntil("\"on\":", "0円");
hueOn = clientParseBool(',');
if (status == WL_CONNECTED)
{
client.findUntil("\"bri\":", "0円");
hueBri = clientParseUint(',');
if (status == WL_CONNECTED)
{
client.findUntil("\"hue\":", "0円");
hueHue = clientParseUint(',');
gotHue = true; // captured all three: on,bri,hue
}
}
}
client.flush();
#ifdef REAL_WIFI
client.stop();
#endif
}
return gotHue;
}
void printWifiData() {
//WiFi shield's IP address:
IPAddress ip = WiFi.localIP();
Serial.print(F("IP Address: "));
Serial.println(ip);
}
void printCurrentNet() {
//SSID of the network you're attached to:
Serial.print(F("SSID: "));
Serial.println(WiFi.SSID());
}
A few notes:
- I did not have the extra blank line in my original
setHue
, so I put it back in to match your sketch. - I added a
REAL_WIFI
define that I could comment out for testing onSerial
. When I run it, I get this output:WiFi shield not present
You're connected to the networkSSID:
IP Address: 0.0.0.0
connected to server
PUT /api/myhue/lights/1/state HTTP/1.1
Connection: keep-alive
Host: 192.168.0.50
Content-Length: 65
Content-Type: text/plain;charset=UTF-8
{"on": true,"hue": 50100,"sat":255,"bri":255,"transitiontime":22}
connected to server
PUT /api/myhue/lights/2/state HTTP/1.1
Connection: keep-alive
Host: 192.168.0.50
Content-Length: 65
Content-Type: text/plain;charset=UTF-8
{"on": true,"hue": 65280,"sat":255,"bri":255,"transitiontime":24} - You can copy and paste the above output if you want to add and test
getHue
with just the Serial Monitor (i.e., comment out#define REAL_WIFI
). - I added a 10-second delay between each
setHue
, just to pace the output, and to give the "transition-time" um... some time to work. The transition time is in seconds, soloop
will callsetHue
again, before the requested transition has completed. You won't see much change when you keep telling it to gradually change to the new color, but it only gets to transition a little bit. Without any delay, I don't think you'll see anything. This could be your current problem! With 10 seconds, at least it will get about half-way there. :-(
-
Thanks for the detailed answer. I'm not getting the
No Socket Available
error anymore but the code isn't doing what it is supposed to i.e. change the colors of the bulb. Previously, I have sent JSON commands from my computer browser by going to the IP of the Philips Hue Bridge and it works smoothly. Have a look here once: developers.meethue.com/documentation/getting-startedHyperbola– Hyperbola2016年03月08日 05:36:12 +00:00Commented Mar 8, 2016 at 5:36 -
@Hyperbola, I'm glad the
No Socket Available
error is gone, but I'll need to see "the code". :) You could edit your question and add an UPDATE section with the latest version. My examples won't do anything with the network, of course. They just use the Serial Monitor.slash-dev– slash-dev2016年03月08日 14:23:17 +00:00Commented Mar 8, 2016 at 14:23 -
I have implemented suggestions
#1
and#4
as#2
and#3
were throwing some errors. However, the sketch isn't doing anything irrespective of no errors. It's not able to change the color oft he bulb. Can you think of any possibilities of the same? Basically, we are just sending the same JSON commands from the arduino as we'd do from the browser: developers.meethue.com/documentation/getting-startedHyperbola– Hyperbola2016年03月09日 04:30:14 +00:00Commented Mar 9, 2016 at 4:30 -
@Hyperbola, new UPDATE section above. I suspect
transition-time
is doing what you told it to do... :-)slash-dev– slash-dev2016年03月09日 20:44:03 +00:00Commented Mar 9, 2016 at 20:44
I wonder if you are running out of RAM? Those sockets use RAM, and you have quite a few string constants, like this:
client.println("Content-Type: text/plain;charset=UTF-8");
Change that (and all similar ones) to use the F()
macro:
client.println(F("Content-Type: text/plain;charset=UTF-8"));
-
I put the F() macro and cut some unnecessary data printing (MAC ID etc.) but it's still throwing the same error. I have updated the question with the new code having the macro.Hyperbola– Hyperbola2016年03月07日 06:03:39 +00:00Commented Mar 7, 2016 at 6:03
-
Some of those Ethernet devices only support a limited number of sockets. I thought it might be 3 or 4, but can you find out how many for your WiFi shield? Or put it another way, what is the spec of the shield? (link)2016年03月07日 06:32:41 +00:00Commented Mar 7, 2016 at 6:32
-
I'm using the R3 Shield, exactly same as here. (arduino.cc/en/Main/ArduinoWiFiShield)Hyperbola– Hyperbola2016年03月07日 06:45:55 +00:00Commented Mar 7, 2016 at 6:45
With reference to this example, perhaps you should call client.stop()
right before you call client.connect()
at the start of setHue()
and getHue()
. You should also call not only client.stop()
, but also client.flush()
at the end of both functions, just in case: Flush first, then stop.
On a different note, correct your HTTP headers from client.println(F("keep-alive"));
to client.println(F("Connection: keep-alive"));
.
For anyone else that comes across this. The reason it takes about 10 seconds to run this, is because of the Original: while (client.connected())
or in the OP's Post above the while (client.connected()&& status == WL_CONNECTED)
. Either are located in the setHue.
I commented that while loop out (and the opening { and closing } brackets for that while loop or you'll get an error of course), and the hue light changed instantaneously.
If you put a print to serial line in that loop, you'll see that it runs that loop continuously, which causes the long delay.
If you come upon this post from the original https://www.makeuseof.com/tag/control-philips-hue-lights-arduino-and-motion-sensor/ note that you should change the main loop's if statements to:
if((digitalRead(pir) == 1) && (activated == false) )
and
if((digitalRead(pir) == 0) && (activated == true) )
That way the setHue only is called on a change of state.