Spark local Cloud on Synology Disk Station

All,

I saw already some of you mentioning to intend to install the local Cloud on a Synology Disk Station. Was anybody successful in doing this?
If yes, how?

Regards
Michael

2 Likes

Haven’t heard anyone have success but would love to hear from you once you attempt to do so!! :smiley:

Yes, I would also be interested. Sadly my Synology is one of the few where getting ipkg installed is next to impossible (IIRC it has something to do about the CPU arch) so I’m not going to be of any help.

Note to future Synology buyers: look into if IPKG can be installed before purchasing!

Heya @Sparky1,

Ooh, I’ve got a Synology at home and this sounds like a fun project. Mostly it would just be a matter of getting Nodejs running. Looks like there is a tutorial here that might help get you started - http://blog.tatablack.net/tag/synology/

Thanks!
David

There is official node.js package, at least on Intel Atom based devices (I have DS1512+, latest DSM version installed). The package is maintaned by Synology itself, so it should be as stable as node.js version itself. See Tools category in package manager.
Not going to do that myself though, I have dedicated Linux server if I would want to try.

1 Like

I have a DS214play (Atom based) on DSM 5.1. I get the following after using git to bring down a copy of spark-server and trying to install.

DiskStation> npm install
npm WARN package.json spark-server@0.1.1 No description
npm WARN package.json spark-server@0.1.1 No README data
\
> ursa@0.8.0 install /volume1/spark-server/spark-server/js/node_modules/ursa
> node-gyp configure build && node install.js

gyp ERR! build error 
gyp ERR! stack Error: not found: make
gyp ERR! stack     at F (/volume1/@appstore/Node.js/usr/lib/node_modules/npm/node_modules/which/which.js:43:28)
gyp ERR! stack     at E (/volume1/@appstore/Node.js/usr/lib/node_modules/npm/node_modules/which/which.js:46:29)
gyp ERR! stack     at /volume1/@appstore/Node.js/usr/lib/node_modules/npm/node_modules/which/which.js:57:16
gyp ERR! stack     at Object.oncomplete (evalmachine.<anonymous>:107:15)
gyp ERR! System Linux 3.2.40
gyp ERR! command "node" "/volume1/@appstore/Node.js/usr/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js" "configure" "build"
gyp ERR! cwd /volume1/spark-server/spark-server/js/node_modules/ursa
gyp ERR! node -v v0.10.33
gyp ERR! node-gyp -v v1.0.1
gyp ERR! not ok 

npm ERR! ursa@0.8.0 install: `node-gyp configure build && node install.js`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the ursa@0.8.0 install script.
npm ERR! This is most likely a problem with the ursa package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     node-gyp configure build && node install.js
npm ERR! You can get their info via:
npm ERR!     npm owner ls ursa
npm ERR! There is likely additional logging output above.
npm ERR! System Linux 3.2.40
npm ERR! command "/usr/bin/node" "/usr/bin/npm" "install"
npm ERR! cwd /volume1/spark-server/spark-server/js
npm ERR! node -v v0.10.33
npm ERR! npm -v 1.4.28
npm ERR! code ELIFECYCLE
npm ERR! not ok code 0

So I’m guessing I need to track down the Synology dev materials.

It looks like make is not installed.

Yep.

After looking at what it will take to get that working I’d just rather spend the time trying to get “the cloud” working on an Intel edison board.

Synology just keeps shutting down avenues of individual expansion in each new version of DSM. It seems I have to learn a whole new bag of tricks to do something I could do easily before (after each point release) and it has gotten old.

1 Like

Hi @JackANSI,

Hmm I hope that’s not the track Synology is on, I vaguely remember needing to install the build packages to get python working, but maybe I’m misremembering. In any case, running it on an Edison / pi should be very do-able. :slight_smile:

Thanks,
David

I was able to get spark-server running on an Edison running Ubilinux without any trouble at all. Didn’t try with Yocto.

Since I have an x86-based DS214play I decided to just tar up the spark-server folder on the Edison, copy it to the Synology, and give it a shot… Seems to run just fine. (also works with forever nicely ‘npm install -g forever’ then ‘forever main.js’) I have not made any attempts to get it to start up on its own from a reboot. I also have bash, ipkg, and a lot of stuff installed during 4.x in /opt that I have no idea if it helps out right now.

I didn’t bother with generating new keys so its just a simple ‘spark keys server default_key.pub.pem ~spark.local.cloud.ip.here~’ and a core is up and running on the synology. I also did not test the spark-cli fully (setup, etc) yet so I don’t know if any surprises will crop up there…

1 Like

All,

I just wanted to update you on this topic on how I finally solved this.
I came to the conclusion that installing the cloud just to control a LED strip is kind of an overkill. Instead I have made the core listen to a UDP port which receives UDP datagrams from a simple PHP page running on the synology.

This solves many problems at once:

  • nearly 0 latency
  • The PHP site can act as a web app, so no need to program something additional for mobile devices
  • As many functions / variables available as you like, because you can create you own simple “protocol” with a command and a data section.
  • received UDP package is pinged from the spark to the sever so you know that it was received.

Further more I have created a “I am alive” TCP call to the PHP page from the core, so the server knows which sparks a currently available

2 Likes

@Sparky1, sounds great! Will you be sharing code? :smile:

Hi Peekay,

sure. The code isn’t probably a masterpiece but it works for now. It is still under construction and features to be added. But as I said it does what I want:

Program:

    SYSTEM_MODE(MANUAL);
// This #include statement was automatically added by the Spark IDE.
#include "neopixel/neopixel.h"

// This #include statement was automatically added by the Spark IDE.
#include "my_helpers.h"

//#################################################################
//      BUGS to fix
//#################################################################

// 1. debug parameter isn't passed to the helpers class

//#################################################################
//      GLOBAL DEFINITIONS
//#################################################################

// Define the LED strip parameters
#define PIXEL_COUNT 70      // Number of Pixels
#define PIXEL_PIN D0        // Pin of the data connection to the strip
#define PIXEL_TYPE TM1829   // Strip controller type

// Define the UDP parameters
const int UDP_TX_PACKET_MAX_SIZE=1024;   //UDP package size
const unsigned int localPort = 8888;    //UDP Port used for two way communication

// Define the TCP call back  parameters
char home_server[] = "diskstation";     // diskstation
const int  home_server_port = 80;       // diskstation
const String name_device = "test";      // name of the spark core in the home automation system
const int beat_rate = 10000;           // heart beat rate sent to server in ms

// Switch debugging on/off
const int debug = 1;

//#################################################################
//      Needed Objects
//#################################################################

// An UDP instance to let us send and receive packets over UDP
UDP Udp;
// An TCP client instance to let us send and receive packets over TCP
TCPClient TCP_client;

// create some helping fuctions
my_helpers h = my_helpers(debug);

// construct the strip object
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
 
char packetBuffer[UDP_TX_PACKET_MAX_SIZE]; //buffer to hold incoming packet,

//#################################################################
//      PROGRAM LOGIC
//#################################################################


void setup() {

// connect to the hood
WiFi.on();
WiFi.connect();
while(!WiFi.ready());

// start the UDP
Udp.begin(localPort);

// Print your device IP Address via serial for debuging
if (debug == 1){Serial.begin(9600);}
if (debug == 1){delay (4000);}
if (debug == 1){Serial.println(WiFi.localIP());}

//Setup the strip & initialize all pixels to 'off'
strip.begin();
strip.show(); 

}

void loop() {

int packetSize = Udp.parsePacket();
memset(&packetBuffer[0], 0, sizeof(packetBuffer));


if(packetSize)
    {
        
    if (debug == 1){Serial.print("Received packet of size ");}
    if (debug == 1){Serial.println(packetSize);}
    if (debug == 1){Serial.print("From ");}
    
    IPAddress remote = Udp.remoteIP();
    int port = Udp.remotePort();
    
    if (debug == 1)
    {
        for (int i =0; i < 4; i++)
        {
            Serial.print(remote[i], DEC);
            if (i < 3)
                {
                Serial.print(".");
                }
        }   
    }
    
        if (debug == 1){Serial.print(", port ");}
        if (debug == 1){Serial.println(port);}

    // read the packet into packetBufffer
    Udp.read(packetBuffer,UDP_TX_PACKET_MAX_SIZE);
        if (debug == 1){Serial.println("Contents UDP datagram:");}
        if (debug == 1){Serial.println(packetBuffer);}

    Udp.stop();
    delay(1);
    Udp.begin(localPort);

    String datagram = String(packetBuffer);

    String command = h.dispatcher (datagram);
        if (debug == 1){Serial.print("Command: ");}
        if (debug == 1){Serial.println(command);}
    String data = h.data_load (datagram);
        if (debug == 1){Serial.print("Data: ");}
        if (debug == 1){Serial.println(data);}

// DO CHANGES TO THE LED STRIP
// ff = function number e.g. 01, 02 ...
// rrr, ggg, bbb RGB values 0 - 255
// ppp = pixel number 000 - 999
    if (command == "Off") // (0) Switch off the strip
    {
        colorAll(strip.Color(0, 0, 0), 0);
    }
    
    
    else if (command == "ColorWipe") // (1) change the strip pixel by pixel. data package: ffrrrgggbbb
    {
    int RGBValues[3];
    h.RGBFromString(RGBValues, data);
        if (debug == 1){Serial.println("ColorWipe");}
        if (debug == 1){Serial.println("Data: ");}
        if (debug == 1){Serial.println(data);}
        if (debug == 1){Serial.println("RGB Values: ");}
        if (debug == 1){Serial.println(RGBValues[0]);}
        if (debug == 1){Serial.println(RGBValues[1]);}
        if (debug == 1){Serial.println(RGBValues[2]);}        
    colorWipe(strip.Color(RGBValues[0], RGBValues[1], RGBValues[2]), 300);
    }
    else if (command == "ColorAll") // (2) change the strip at once. data package: ffrrrgggbbb
    {
    int RGBValues[3];
    h.RGBFromString(RGBValues, data);  
        if (debug == 1){Serial.println("ColorAll");}
        if (debug == 1){Serial.println("Data: ");}
        if (debug == 1){Serial.println(data);}
        if (debug == 1){Serial.println("RGB Values: ");}
        if (debug == 1){Serial.println(RGBValues[0]);}
        if (debug == 1){Serial.println(RGBValues[1]);}
        if (debug == 1){Serial.println(RGBValues[2]);}        
    colorAll(strip.Color(RGBValues[0], RGBValues[1], RGBValues[2]), 0);
    }
    
    else if (command == "Pixel") // (9) control one ore more pixel individually data package: ffppprrrgggbbbppprrrgggbbb...
    {
    int pixels;
    
    // determine the number of pixels we have to change. one pixel control package has always 12 characters in the data package (ppprrrgggbbb).
    // so the number of pixels is the length of the data package / 12
    pixels = data.length() / 12;
    
    // prepare the array
    int Pixel[4];
            if (debug == 1){Serial.println("Pixel");}
            if (debug == 1){Serial.println("Data: ");}
            if (debug == 1){Serial.println(data);}
    // fill the array from the data package
        for (int i=0; i < pixels; i++)
        {
            h.pixeldata (Pixel, data.substring(i * 12, (i * 12) + 12)); // cut the data package into the pixel control packages
            strip.setPixelColor(Pixel[0], strip.Color(Pixel[1], Pixel[2], Pixel[3]));
                if (debug == 1){Serial.println("Pixel Data: ");}
                if (debug == 1){Serial.println(Pixel[0]);}
                if (debug == 1){Serial.println(Pixel[1]);}
                if (debug == 1){Serial.println(Pixel[2]);}  
                if (debug == 1){Serial.println(Pixel[3]);}  
        }
    strip.show();
    }

    //echo back the UDP message
    Udp.beginPacket(remote, port);
    Udp.write(packetBuffer);
    Udp.endPacket();
    }

h.heart_beat(TCP_client, home_server, home_server_port, name_device, beat_rate);


} //end loop


//#################################################################
//      LED Strip Functions (to be implemented in the helpers later)
//#################################################################


// Set all pixels in the strip to a solid color, then wait (ms)
void colorAll(uint32_t c, uint8_t wait) {
  uint16_t i;
  
  for(i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
  }
  strip.show();
  delay(wait);
}

// Fill the dots one after the other with a color, wait (ms) after each one
void colorWipe(uint32_t c, uint8_t wait) {
  for(uint16_t i=0; i<strip.numPixels(); i++) {
    strip.setPixelColor(i, c);
    strip.show();
    delay(wait);
  }
}

void rainbow(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) {
    for(i=0; i<strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i+j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Slightly different, this makes the rainbow equally distributed throughout, then wait (ms)
void rainbowCycle(uint8_t wait) {
  uint16_t i, j;

  for(j=0; j<256; j++) { // 1 cycle of all colors on wheel
    for(i=0; i< strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
    }
    strip.show();
    delay(wait);
  }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) {
  if(WheelPos < 85) {
   return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } else if(WheelPos < 170) {
   WheelPos -= 85;
   return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } else {
   WheelPos -= 170;
   return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

Includes (CPP):

/*-------------------------------------------------------------------------

  -------------------------------------------------------------------------*/

#include "my_helpers.h"
#include "neopixel/neopixel.h"

//constructor
my_helpers::my_helpers(int debug_flag) {

debug = debug_flag;
request_sent = 0;
time_stamp = 0;


}


//sends a heart beat to the server together with the IP address & device name
void my_helpers::heart_beat(TCPClient client, char host[], int port, String my_name, int heart_rate) 
{

if (time_stamp < millis() && request_sent == 0)
    {
        if (debug == 1){Serial.println("trying to connect to:");}
        if (debug == 1){Serial.print(host);}
        if (debug == 1){Serial.print(":");}
        if (debug == 1){Serial.print(port);}
        if (client.connect(host, port))
        {
            if (debug == 1){Serial.println("  connected");}
            // send the heart beat
            client.print("GET /led_strip/controller.php?function=heartbeat&device=");
            client.print(my_name);
            client.print("&IP=");
            client.print(WiFi.localIP());
            client.println(" HTTP/1.0");
            client.println("Host: diskstation");
            client.println("Content-Length: 0");
            client.println();
            
            request_sent = 1;
            
            if (debug == 1){Serial.println("request sent");}
            
            time_stamp = millis() + heart_rate;
            
            if (debug == 1){Serial.println("timestamp:");}
            if (debug == 1){Serial.println(time_stamp);}
        }
        else
        {
            if (debug == 1){Serial.println("connection failed");}
            time_stamp = millis() + 2000;
            if (debug == 1){Serial.println("timestamp:");}
            if (debug == 1){Serial.println(time_stamp);}
        }
    }

while (request_sent == 1)
    {
        if (client.available())
        {
            char c = client.read();
            if (debug == 1){Serial.print(c);}
        }

        if (!client.connected())
        {
            if (debug == 1){Serial.println();}
            if (debug == 1){Serial.println("disconnecting.");}
            request_sent = 0;
            client.stop();
        }
    }


}

//parse the two digit function number from the UDP datagram and return the function name
String my_helpers::dispatcher (String &data)
{

    switch (data.substring(0,2).toInt())
    {
        case 0:
        return "Off";
        break;        
        
        case 1:
        return "ColorWipe";
        break;
        
        case 2:
        return "ColorAll";
        break;
        
        case 3:
        return "";
        break;
        
        case 4:
        return "";
        break;
        
        case 5:
        return "";
        break;
        
        case 9:
        return "Pixel";
        break;
        
        default:
        return "error";
        break;        
    }

}

//parse the data from the UDP datagram and retrun it
String my_helpers::data_load (String &data)
{

    return data.substring(2);

}

//parse the two digit function number from the UDP datagram and return the function name
void my_helpers::pixeldata (int PixelArray[], String data)
{
  PixelArray[0] = data.substring(0, 3).toInt();    // pixel number
  PixelArray[1] = data.substring(3, 6).toInt();    // red value
  PixelArray[2] = data.substring(6, 9).toInt();    // green value
  PixelArray[3] = data.substring(9, 12).toInt();    // blue value
}


// Splitt the string rrrgggbbb into seperate varaibles
void my_helpers::RGBFromString(int RGBArray[], String &RGBString) {
    
  RGBArray[0] = RGBString.substring(0, 3).toInt();  // red value
  RGBArray[1] = RGBString.substring(3, 6).toInt();  // green value
  RGBArray[2] = RGBString.substring(6, 9).toInt();  // blue value
}

Includes (.H):

#ifndef MY_HELPERS_H
#define MY_HELPERS_H

#include "application.h"
#include "neopixel/neopixel.h"


class my_helpers
{
 
public:
    // Constructor: no args needed
    my_helpers(int debug_flag);

    String dispatcher (String &data);                                                            //parse the two digit function number from the UDP datagram and return the function name
    String data_load (String &data);                                                             //parse the data from the UDP datagram and retrun it
    void pixeldata (int PixelArray[], String data);
    void heart_beat (TCPClient client,  char host[], int port, String my_name, int heart_rate); //sends a heart beat to the server together with the IP address & device name
    void RGBFromString(int RGBArray[], String &RGBString);                                       //Extracts seperate RGB values from this String "RRRGGGBBB"

private:

    int debug;
    int request_sent;
    unsigned long time_stamp;

};
#endif // HELPERS_H

Since it’s some while ago - has anyone successfully installed the Cloud on a Synology DS?