Skip to content

Instantly share code, notes, and snippets.

@kasperkamperman
Created March 11, 2016 14:19
Show Gist options
  • Save kasperkamperman/89f67f049a2ec45a5c9c to your computer and use it in GitHub Desktop.
Save kasperkamperman/89f67f049a2ec45a5c9c to your computer and use it in GitHub Desktop.
Example implementation to receive commands over TCP on your Photon running FastLED animations.
/*
Example implementation to receive commands over TCP on your Photon running FastLED animations.
FastLED is not implemented you have to do that yourself.
It makes use of this Serial cmd protocol concept:
https://gist.github.com/kasperkamperman/7e827bb481dfcdb4cb52
See this Processing sketch to send TCP Data. Modify the IPAddress to the local address of the Photon.
https://gist.github.com/kasperkamperman/1f185758519fa3131ea0
This setup will make it possible to run animations that won't break on cloud or WiFi activity.
We use a Timer interrupt to make sure our leds update with a fixed frequency.
The Particle won't break this block by background events.
Code based on my questions and the advices I got in this topic:
https://community.particle.io/t/control-system-thread-duration-or-keeping-a-really-fixed-time-loop/20757
Uses the SparkIntervalTimer libary by Paul Kourany and Daniel Gilbert
https://github.com/pkourany/SparkIntervalTimer
*/
// you need to add this when you compile offline.
#include "application.h"
#include "SparkIntervalTimer/SparkIntervalTimer.h"
//https://docs.particle.io/reference/firmware/photon/#system-events
SYSTEM_THREAD(ENABLED);
//https://docs.particle.io/support/troubleshooting/mode-switching/photon/#semi-automatic-mode
SYSTEM_MODE(SEMI_AUTOMATIC);
// pre-define
void fixedLoop();
void parseTCPCommands();
void setTime();
void printTime();
void sendReply();
volatile int counter = 0;
IntervalTimer loopTimer;
TCPServer server = TCPServer(12345);
TCPClient client;
boolean clientAlreadyConnected = false;
struct Cmd {
uint8_t type;
uint8_t typeId;
uint8_t cmdId;
uint8_t paddingByte; // unused
uint16_t cmdValue0;
uint16_t cmdValue1;
uint16_t cmdValue2;
uint16_t cmdValue3;
};
Cmd aCmd;
boolean cmdIsWrittenFlag = false;
boolean cmdNeedsProcessingFlag = false;
// for time measuring purposes
unsigned long t1;
unsigned long t2;
unsigned long t3 = 0;
void setup() {
Serial.begin(57600);
// in case you want the whole cloud
//Particle.connect();
//waitUntil(Particle.connected);
// in case you only want WiFi, we don't use the cloud
// in this example
WiFi.connect();
waitUntil(WiFi.ready);
// IP address only available after particle process has run
Particle.process();
Serial.println(WiFi.localIP());
delay(500);
server.begin();
// 400 fps loop
loopTimer.begin(fixedLoop, 2500, uSec);
}
void loop() {
// this is the normal loop
// here we do things that are not time critical.
// take in account that the Particle Photon runs it's own
// hidden loop (Particle.process()) to do things like the cloud
// connection.
parseTCPCommands();
}
void fixedLoop() {
/*
This is kind of dummy code. Just to show we code that almost
uses all of the 2500 avaible microseconds.
And code that uses 800 microseconds (for example the time to write out some leds).
*/
// https://docs.particle.io/reference/firmware/photon/#single_threaded_block-
SINGLE_THREADED_BLOCK() {
if(counter<5) {
delayMicroseconds(2450);
}
else {
delayMicroseconds(1000);
// here we do something with the received commands.
// in this case we just print out the received value.
// take in account that printing also takes time..
if(!cmdIsWrittenFlag && cmdNeedsProcessingFlag) {
Serial.println(aCmd.cmdValue0);
cmdNeedsProcessingFlag = false;
}
}
counter++;
if(counter>15) counter=0;
}
}
void parseTCPCommands() {
// routine partly based on
// http://www.forward.com.au/pfod/HomeAutomation/InlineWiFiPowerSwitch/InlinePowerSwitch.ino.txt
// see if a client is available
if (!client) {
// evaluates to false if no connection
client = server.available();
}
else
{ // have client
// check if that client isn't connected anymore
if (!client.connected()) {
// only disconnect if it was previously connected
if (clientAlreadyConnected) {
client.stop();
clientAlreadyConnected = false;
// check for new connection
client = server.available();
}
}
else {
//we are connected
if (!clientAlreadyConnected) {
clientAlreadyConnected = true;
//clear out the input buffer
client.flush();
// welcome if we were not already connected
// print the address to Serial
IPAddress clientIP = client.remoteIP();
Serial.print("Hi :");
Serial.println(clientIP);
}
// we use if() instead of while(). We only want to get one command from our buffer
// so we leave other commands in the client buffer.
// we only continue reading the buffer when the cmdNeedsProcessingFlag == false.
// we then know that we processed (that happens in our fixedLoop) the previous
// message.
if(client.available() > 0 && !cmdNeedsProcessingFlag) {
cmdIsWrittenFlag = true;
client.readBytes((char*) &aCmd,sizeof(Cmd));
cmdIsWrittenFlag = false;
cmdNeedsProcessingFlag = true;
}
}
}
}
// in case you want to debug time
void setTime() {
t1 = micros();
}
void printTime() {
t2 = micros()-t1;
if(t2>t3) t3 = t2;
Serial.print(F("time: "));
Serial.print(t3);
Serial.print(" ");
Serial.println(t2);
}
// not used right now
// probably takes longer then the available time
void sendReply() {
// test if we received the values
Serial.print(aCmd.type);
Serial.print(",");
Serial.print(aCmd.typeId);
Serial.print(",");
Serial.print(aCmd.cmdId);
Serial.print(",");
Serial.print(aCmd.cmdValue0);
Serial.print(",");
Serial.print(aCmd.cmdValue1);
Serial.print(",");
Serial.print(aCmd.cmdValue2);
Serial.print(",");
Serial.print(aCmd.cmdValue3);
Serial.println();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment