We're glad you chose Woopsa!

Really! We think that once you've learned how to use Woopsa, you won't ever look back. But because our mission is to get Woopsa on as many devices as possible, we need you to tell us what you want to do.

I want to make a server in:

- or -

I want to make a client in:

Using the C# / .NET server

Introduction

Making a Woopsa server is extremely simple and requires just one line of code if you're using our reflector! Just give the Woopsa server a class and it will do the rest, automagically!

The C# / .NET server is included in the Woopsa assembly. We created the Woopsa library with Mono in mind, trying to use only the most standard .NET components for maximum compatibility.

Getting the assembly and using it

Start off by downloading Woopsa and get the Woopsa.dll assembly inside the DotNet folder. Add this assembly as a reference in your Visual Studio project and add the following line to the top of your source file:

using Woopsa;
	

That's it! You're now ready to create your server.

Creating your first server

Note: By default, the Woopsa server resides on port 80 and has a base path of /woopsa, meaning your first server will be available from http://localhost/woopsa

To create a simple server, you first need some data to publish to the world. You can set your Woopsa server to handle any C# object, but for example's sake, this is the class we are going to use:

public class WeatherStation
{
  public double Temperature { get; private set; }

  public double Sensitivity { get; set; }

  public string GetWeatherAtDate(DateTime date)
  {
    switch (date.DayOfWeek)
    {
      case DayOfWeek.Monday:
        return "cloudy";
      default:
        return "sunny";
    }
  }
}
	

Once you've written your class, you can simply create the new Woopsa server and give it your root object:

WeatherStation station = new WeatherStation();
WoopsaServer server = new WoopsaServer(station);
	

That's it! Your Woopsa server is now available on http://localhost/woopsa. The server starts in its own thread and uses a thread pool to serve multiple clients at once. This means you really don't have to worry about anything ... just work with your WeatherStation like you would with any other object, and Woopsa takes care of everything for you. Including publish/subscribe events!

Note: Woopsa is completely web-based. This means you can see your data simply with a browser! Go on http://localhost/woopsa/meta/ too see the metadata of your root object, or http://localhost/woopsa/read/Temperature to read a piece of data!

Going deeper with the server

Publishing only part of your model

If, for any reason, you don't want to publish your entire object and want to choose exactly what to publish, you can! There are two metadata attributes you can use in C# to accomplish this.

  • [WoopsaVisible(false)] Applies to properties, fields and methods. This tells the reflector to not publish this item.
  • [WoopsaVisibility(WoopsaVisibility.None)] Applies to classes. Just put this attribute on your class. All content on your instances will not be published

In summary, this is what your new class should look like:

[WoopsaVisibility(WoopsaVisibility.None)]
public class WeatherStation
{
  [WoopsaVisible(false)]
  public double Temperature { get; private set; }

  [WoopsaVisible(true)]
  public double Sensitivity { get; set; }

  [WoopsaVisible(false)]
  public string GetWeatherAtDate(DateTime date)
  {
    switch (date.DayOfWeek)
    {
      case DayOfWeek.Monday:
        return "cloudy";
      default:
        return "sunny";
    }
  }
}
	

Note: By default, the entire model is published

Working with a complete hierarchy

The reflector works very well, even with nested objects. The object tree is explored on creation and if you change the embedded objects in your classes, the new data would be right available.

Here's an example of using embedded objects:

public class SmokeDetector
{
  public bool SmokeDetected { get; private set; }
}

public class VentilationSystem
{  
  public VentilationSystem()
  {
    SmokeDetector = new SmokeDetector();
  }
  
  public bool Power { get; private set; }

  public SmokeDetector SmokeDetector { get; set; }
}

...

VentilationSystem system = new VentilationSystem();
WoopsaServer server = new WoopsaServer(system);

...

system.SmokeDetector = new SmokeDetector(); // This would update the server cache!!

	

That's it! If you want to know more about how to use the server, please take a look at some of the examples available in the source code. You can also read the full specifications of the Woopsa protocol to get a better understanding of how everything works!

Using SSL/TLS

Because Woopsa is based on HTTP, adding SSL/TLS is a breeze! You just need to specify port 443, add a certificate and you're good-to-go. The internal WebServer (Woopsa's own lightweight HTTP server) has a concept called Pre-route Processing which allows it to manipulate the data stream before the actual HTTP route handling is done. This makes adding SSL/TLS as easy as adding a TlsProcessor to the WebServer's PreRouteProcessors:

WeatherStation station = new WeatherStation();
WoopsaServer server = new WoopsaServer(station, 443);
server.WebServer.PreRouteProcessors.Add(new TlsProcessor("{path-to-your-certificate.p12}","{private-key-password}"));
	

Note: Internally, this processor uses .NET's SslStream and X509Certificate2 coupled with AuthenticateAsServer(). This means that the certificate you pass to the processor must be a PKCS12 package with a password-protected private key embedded in it. Creating certificates is not related to Woopsa and is not covered in this guide.

Using authentication

Because Woopsa is based on HTTP, authentication is done with the simple WWW-Authenticate mechanism. This means that clients wishing to connect to your authentication-enabled Woopsa server will need to provide the Authorization header with proper Base64-encoded credentials.

To enable authentication on the server, you simply need to give the server a SimpleAuthenticator through the Authenticator property. You can then check against the username and password as you wish, and return true on success or false on failure.

To disable authentication on the server, simply set Authenticator to null (which is the case by default).

private void authenticate(object sender, AuthenticationCheckEventArgs e)
{
	e.IsAuthenticated = e.Username == "admin" && e.Password == "1";
}
	
...	
	
WeatherStation root = new WeatherStation();
WoopsaServer woopsaServer = new WoopsaServer(root, 80);
woopsaServer.Authenticator = new SimpleAuthenticator("Woopsa server", authenticate);
	

Of course, SSL/TLS and authentication work together seamlessly and thus allow full server and client-side authentication. That's the magic of using standards!

Using the Node.js server

Introduction

Making a Woopsa server in Node.js is extremely simple and requires just one line of code if you're using the default options. The Woopsa server uses express in the background, which means it can integrate with your already-existing app if you so desire!

The Node.js server is included in the woopsa npm package. Woopsa for Node.js embraces the asynchronous nature of JavaScript, allowing you to plug it into any type of application and very quickly create a RESTful interface to your application.

Getting the package

Because the Woopsa Node.js server is an npm package, installing the library is as simple as:

npm install woopsa

Of course, you can also add it as a dependency to your package.json file, but this isn't covered in this tutorial. Once the package has been added to your project, you can simply require it using requirejs:

var woopsa = require('woopsa');

And that's it! You are now ready to use the Woopsa Node.js library!

Creating your first server

Note: By default, the Woopsa server resides on port 80 and has a base path of /woopsa, meaning your first server will be available from http://localhost/woopsa

To create a simple server, you first need some data to publish to the world. So we'll just construct a JavaScript object using the standard JSON syntax:

var weatherStation = {
  Temperature: 24.2,
  IsRaining: false,
  Sensitivity: 0.5,
  Altitude: 430,
  City: "Geneva",
  Time: new Date(),
  GetWeatherAtDate: function (date){
    var date = new Date(date);
    if ( date.getDay() === 1 )
      return "rainy";
    else
      return "sunny";
  },
  Thermostat: {
    SetPoint: 24.0
  }
}

Once you've written your data, you can simply create the new Woopsa server and give it your root object:

var woopsaServer = new woopsa.Server(weatherStation);

That's it! Your Woopsa server is now available on http://localhost/woopsa. The server creates a new express app and uses its inner routing mechanism to provide the Woopsa functionality. This means you really don't have to worry about anything ... just work with your weatherStation like you would with any other object, and Woopsa takes care of everything for you. Including publish/subscribe events (by polling the value of your object).

Note: Woopsa is completely web-based. This means you can see your data simply with a browser! Go on http://localhost/woopsa/meta/ too see the metadata of your root object, or http://localhost/woopsa/read/Temperature to read a piece of data!

Going deeper with the server

Force a different type with the typer function

You will have noticed in our example GetWeatherAtDate function, we first have to parse the date (as illustrated by var date = new Date(date)), because it's given to us as a string. This is because JavaScript is a completely dynamic language, and as such we cannot know in advance what type of argument our function needs. Thus, when you are using the reflector, all function arguments and return types are set to "Text". This is also the case for properties whose values evaluate to a string.

This is where the Typer function comes into play. When you create your Woopsa server, you can pass it a function that will be called every time the type needs to be known. This function is called a Typer function and is called with two arguments: the path of the Woopsa element we need a type for, and the inferred type. The inferred type is what the Woopsa library *thinks* is the correct type for this element. Your Typer function then simply needs to return a Woopsa type. You can either return the inferred type, or the Woopsa type of your choice based on the path.

To set it up, simply set the typer property in the options to a function that accepts two parameters. For example, if we want to do this for the date argument of our GetWeatherAtDate function, here's how we would do it:

var woopsaServer = new woopsa.Server(weatherStation, {
  typer: function (path, inferredType){
    if ( path === "/GetWeatherAtDate/date" )
      return "DateTime";
    else
      return inferredType;
  }
});
	

Now that we've done this, Woopsa will automatically parse values as a JavaScript Date object before passing it to our function. Thus, we can change the declaration of our GetWeatherAtDate function and make it a little bit shorter:

var weatherStation = {
  ...
  GetWeatherAtDate: function (date){
    if ( date.getDay() === 1 )
      return "rainy";
    else
      return "sunny";
  }
  ...
}
	

This also has the advantage of letting the Woopsa server do some type sanity checks and throw the appropriate errors in case the client tries doing something nonsensical with types.

Using asynchronous functions in your model

JavaScript, especially when used in Node.js applications, is asynchronous in nature. So what do you do when you need to read/write a file or write to a socket in a function? And how do you send that delayed result through Woopsa?

Under the hood, the Woopsa server is fully asynchronous for read, write and invoke operations. The reflector just hides that complexity from you. When it detects a function in your object, it creates a WoopsaMethod calling your function. The reflector, however, was made to be extended with additional manual functions.

The WoopsaServer object publishes an object called element to which you can add additional functions. To do this, simply create a new WoopsaMethodAsync object, passing it a function that accepts a callback function as last parameter. When you have your result, you can simply call the callback function with your return value. For example, we can make a function that returns "Hello world" after a certain amount of time.

...
var helloAfterNSeconds = new woopsa.Types.WoopsaMethodAsync(
  "HelloAfterNSeconds", // name of the method
  "Text", // return type of your method
  function (waitTime, done){ // The actual function that will be called upon invoke
    setTimeout(function (){
      done("Hello world"); // calling done "closes the loop" and allows the Woopsa server to respond
    }, waitTime * 1000);
  },
  [{"waitTime": "Integer"}] // an array of key-value pairs for the types
);
woopsaServer.element.addMethod(helloAfterNSeconds);
	

The Woopsa server will then publish a method called HelloAfterNSeconds that is fully asynchronous. This has the advantage of making your server 100% non-blocking, just like JavaScript was meant to be!

Note: Your function does not need to return a value. If you throw an exception asynchronously, it will crash your server. In the case of an asynchronous error, you should instead call the done callback that way:
done(null, new woopsa.Exceptions.WoopsaException("Your error goes here."));
The first argument doesn't necessarly have to be null, but it will be ignored either way, as the server will return your error to the client instead of the value.

Integrating Woopsa into your existing Express app

The Woopsa server uses express under the hood. If you already have a web server running in your application and want the Woopsa server to reside on the same port, you can! Just specify an expressApp property in the WoopsaServer constructor. The Woopsa server will then just add its own routes alongside your own, making sure both your webapp and Woopsa can live side-by-side.

var app = express(); // Or however your own app is initialized/called
...
// Your app's code goes here
...
var woopsaServer = new woopsa.Server(weatherStation, {
  pathPrefix: '/woopsa/', // The Woopsa server will be reacheable on /woopsa/. This is the default
  expressApp: app // When expressApp is not null, Woopsa and express will live side-by-side
});
	

Note: You'll have to be careful in your app not to add middleware that interferes with Woopsa. For example, setting a global middleware function that adds some headers might break Woopsa functionality in some situations. The Woopsa server will never pollute your own middleware chain, as everything is prefixed.

Using SSL/TLS

Because Woopsa uses express and Node.js's http module under the hood, using SSL/TLS with express is out of the scope of this guide. We recommend following this tutorial which covers the basics of serving your content through https with express.

Once you have an express server listening on https, you can simply read the section above to learn how to pass an already existing express app to the Woopsa server.

Using authentication

Because Woopsa is based on HTTP, authentication is done with the simple WWW-Authenticate mechanism. This means that clients wishing to connect to your authentication-enabled Woopsa server will need to provide the Authorization header. This is already handled for you natively in all Woospa clients.

To enable authentication on the server, you simply need to set the checkAuthenticate option in the Woopsa server constructor to a function (username, password) which the Woopsa server will call upon every request. You can then check against this username and password as you wish, and return true on success or false on failure.

var woopsaServer = new WoopsaServer(weatherStation, {
  checkAuthenticate: function (username, password){
    if ( username === 'admin' && password === 'secret' ){
      return true;
    }else{
      return false;
    }
  }
});
	

Of course, SSL/TLS and authentication work together seamlessly and thus allow full server and client-side authentication. That's the magic of using standards!

Using the Embedded server

Introduction

Embedded devices such as 8-bit microcontrollers like the Arduino can run Woopsa too! The Woopsa Embedded library was created with strong memory constrains in mind and only uses about 2-3 kiloBytes of RAM. This library is designed to be 100% cross-platform and only requires your microcontroller to support basic string functions and the C language.

Getting the code

Start off by downloading Woopsa and get the 3 Woopsa source files (woopsa-config.h, woopsa-server.h and woopsa-server.c) in the Embedded folder. Add these files to your project and include woopsa-server.h at the top of your source file:

#include "woopsa-server.h"
	

Depending on which platform you're trying to port Woopsa, you might have to make some adjustments to woopsa-config.h. For example, the version that is provided assumes that the standard string processing C libraries are available. This means that functions such as snprintf, strstr, strlen, atoi or tolower are available.

The Woopsa Embedded server uses macros defined in that file to convert strings to integers, count the length of strings, etc. In case your system does not support these functions, you can always write replacement functions adapted to your platform.

Note: You can also disable strings and remote method invocation alltogether if you do not need these features in your embedded Woopsa server. Just comment the #defines called WOOPSA_ENABLE_STRINGS and WOOPSA_ENABLE_METHODS.

How to use Woopsa Embedded

The Woopsa Embedded server is fully cross-platform because it is not tied to any specific network implementation. This means you will have to write the networking code yourself. You must thus write whatever code is necessary to aquire a buffer of bytes send from a client (which will be an HTTP request), then pass this buffer to the WoopsaHandleRequest function along with a response buffer. This function will analyze the request and build a response in the buffer. You can then send this buffer back to the client using whatever code you have.

To publish data using the Woopsa Embedded server, you use the simple macros defined in woopsa-server.h to specify properties and methods to publish.

float Temperature = 24.2;
char IsRaining = 1;
int Altitude = 430;
float Sensitivity = 0.5;
char City[20] = "Geneva";
float TimeSinceLastRain = 11;


char weatherBuffer[20];
char* GetWeather() {
  sprintf(weatherBuffer, "sunny");
  return weatherBuffer;
}

WOOPSA_BEGIN(woopsaEntries)
  WOOPSA_PROPERTY_READONLY(Temperature, WOOPSA_TYPE_REAL)
  WOOPSA_PROPERTY(IsRaining, WOOPSA_TYPE_LOGICAL)
  WOOPSA_PROPERTY(Altitude, WOOPSA_TYPE_INTEGER)
  WOOPSA_PROPERTY(Sensitivity, WOOPSA_TYPE_REAL)
  WOOPSA_PROPERTY(City, WOOPSA_TYPE_TEXT)
  WOOPSA_PROPERTY(TimeSinceLastRain, WOOPSA_TYPE_TIME_SPAN)
  WOOPSA_METHOD(GetWeather, WOOPSA_TYPE_TEXT)
WOOPSA_END;
	

This particular piece of code will create a new dictionary of Woopsa entries called woopsaEntries. The macro takes care of creating the dictionary so you do not need to "initialize" the variable.

Before you can pass data to Woopsa Embedded, you must first initialize a Woopsa server. To do this, just call the WoopsaServerInit function and pass it a pointer to a WoopsaServer, a path prefix, a dictionary of entries and an optional request handling method (see Serving other content).

WoopsaServer server;
..
WoopsaServerInit(&server, "/woopsa/", woopsaEntries, NULL);
	

That's it! You're now ready to pass data to Woopsa Embedded. It's up to you to write code to listen on a specific port or IP using the library of your choice (whatever is available on your embedded target). Because Woopsa was designed to be used by browsers directly, we recommend listening on TCP port 80, but nothing prevents you from passing Woopsa through a serial port, for example.

Because requests made through HTTP can be fragmented across multiple TCP packets, use the WoopsaCheckRequestComplete function, passing it your buffer, to check if you need to aquire more bytes from the client. This function will return WOOPSA_REQUEST_COMLETE when the full HTTP request has been received (including POST data), or WOOPSA_REQUEST_MORE_DATA_NEEDED if you need to get more bytes.

Once the request is complete, you can simply call the WoopsaHandleRequest function, passing it your input and output buffers. This function will generate a Woopsa response and returns WOOPSA_SUCCESS when everything went well. In cases where the client makes a malformed request or requests an element that doesn't exist, WOOPSA_CLIENT_REQUEST_ERROR will be returned and the buffer will contain a 404 or 500 HTTP error, which you must send back to the client.

You can then send the output buffer as a response to the client, and happily wait until the next request!

char buffer[2048]; // A buffer to store requests and responses
WoopsaUInt16 responseLength; // An integer that will be used to store the response length
...
// Accept an incoming connection and fill the buffer with the request
if (WoopsaCheckRequestComplete(&server, buffer, sizeof buffer) != WOOPSA_REQUEST_COMLETE) {
  // Get more bytes, this request is not finished
} else {
  if (WoopsaCheckRequestComplete(&server, buffer, sizeof buffer, buffer, sizeof buffer, &responseLength) >= WOOPSA_SUCCESS) {
    // Send the buffer back to the client
  }
}
	

Note: Woopsa Embedded was designed with high portability and low memory in mind. This means you can safely pass the same buffer for request and response, limiting your memory usage greatly.

Creating your first server

Arduino Ethernet Shield 2 example

A sample is available in the source files of Woopsa that runs on the Arduino Mega with an Ethernet Shield 2. Networking on the Arduino Ethernet Shield is easy and a shorter version of the code is given below to help you get started:

#include <Ethernet2.h>
#include "woopsa-server.h"

byte mac[] = {
  0x90, 0xA2, 0xDA, 0x10, 0x32, 0x0B
};
IPAddress ip(192, 168, 42, 3);
EthernetServer server(80);

WoopsaServer woopsaServer;

float Temperature;
int Altitude;

WOOPSA_BEGIN(woopsaEntries)
  WOOPSA_PROPERTY(Temperature, WOOPSA_TYPE_REAL)
  WOOPSA_PROPERTY(Altitude, WOOPSA_TYPE_INTEGER)
WOOPSA_END

void setup() {
  // Open serial communications and wait for port to open:
  Serial.begin(9600);
  while (!Serial); // wait for serial port to connect. Needed for native USB port only

  // start the Ethernet connection and the server:
  Ethernet.begin(mac, ip);
  server.begin();
  
  WoopsaServerInit(&woopsaServer, "/woopsa/", woopsaEntries, NULL);
}

void loop() {
  int bufferAt = 0;
  char dataBuffer[2048];
  short unsigned int responseLength;
  memset(dataBuffer, 0, sizeof(dataBuffer));
  // Listen for incoming clients
  EthernetClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        // We _append_ data to our buffer when it is received
        bufferAt += client.read((unsigned char*)(dataBuffer + bufferAt), sizeof(dataBuffer));
        
        // When we get a request from a client, we need
        // to make sure it's complete before we pass it
        // to the Woopsa server. This allows us to handle
        // cases where packets are fragmented.
        if (WoopsaCheckRequestComplete(&woopsaServer, dataBuffer, sizeof(dataBuffer)) != WOOPSA_REQUEST_COMLETE) {
          continue;
        }
        
        if ( WoopsaHandleRequest(&woopsaServer, dataBuffer, sizeof(dataBuffer), dataBuffer, sizeof(dataBuffer), &responseLength) >= WOOPSA_SUCCESS ) {
          client.print(dataBuffer);
		}
        break;
      }
    }
  }
  client.stop(); 
}
	

Standard sockets example

A sample is available in the source files of Woopsa that should run on any flavor of Linux/UNIX-like system. A shorter version of the code is given below to help you get started quickly:

float Temperature = 24.2;
int Altitude = 430;

WOOPSA_BEGIN(woopsaEntries)
  WOOPSA_PROPERTY(Temperature, WOOPSA_TYPE_REAL)
  WOOPSA_PROPERTY(Altitude, WOOPSA_TYPE_INTEGER)
WOOPSA_END;

int main(int argc, char argv[]) {
  SOCKET sock, clientSock;
  struct sockaddr_in addr, clientAddr;
  char buffer[BUFFER_SIZE];
  int clientAddrSize = 0, readBytes = 0;
  WoopsaServer server;
  WoopsaUInt16 responseLength;

  memset(buffer, 0, sizeof(buffer));
  WoopsaServerInit(&server, "/woopsa/", woopsaEntries, NULL);

  sock = socket(AF_INET, SOCK_STREAM, 0);

  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons(WOOPSA_PORT);

  bind(sock, (struct sockaddr *)&addr, sizeof(addr));
  listen(sock, 5);

  while (1) {
    clientAddrSize = sizeof(struct sockaddr_in);
    clientSock = accept(sock, (struct sockaddr *)&clientAddr, (socklen_t*)&clientAddrSize);

    while (1) {
      readBytes = recv(clientSock, buffer + readBytes, sizeof(buffer), NULL);

      if (readBytes == 0) 
        break;

      if (WoopsaCheckRequestComplete(&server, buffer, sizeof(buffer)) != WOOPSA_REQUEST_COMLETE) {
        // If the request is not complete, it means more data needs 
        // to be -added- to the buffer
        continue;
      }

      if (WoopsaHandleRequest(&server, buffer, sizeof(buffer), buffer, sizeof(buffer), &responseLength) >= WOOPSA_SUCCESS) {
        send(clientSock, buffer, responseLength, NULL);
      }
      readBytes = 0;
      memset(buffer, 0, sizeof(buffer));
    }
  }
}
	

Serving other content

Upon Woopsa server initialization, you can optionally pass a function pointer to handle requests which are not in the woopsa path prefix. This means you can also serve some HTML or other data directly on your embedded system!

WoopsaUInt16 ServeHTML(WoopsaChar8 path[], WoopsaUInt8 isPost, WoopsaChar8 dataBuffer[], WoopsaUInt16 dataBufferSize) {
  strcpy(dataBuffer, "Hello world!");
  return strlen("Hello world!");
}
..
WoopsaServerInit(&server, "/woopsa/", woopsaEntries, ServeHTML);
	

Note: You can do whatever you want in this function, and don't even necessarily have to copy data to the data buffer. The only mandatory step is to return the length of the content you will be serving, so Woopsa can prepare the proper Content-Length headers. When your function is called, the WoopsaHandleRequest function returns WOOPSA_OTHER_RESPONSE, allowing you to write something to your client directly. This is useful if you wish to serve some really long piece of data from program memory without being constrained by your RAM.

Limitations

Because Woopsa Embedded was designed with extreme portability and low memory usage in mind, it does not support subscriptions (publish/subscribe) or multiple requests. Also, the DateTime type is not handled in any way, as a large date-handling library would be necessary. Finally, Woopsa methods can not have any arguments: only return values are allowed. However, this should be enough to cover most embedded use cases. If you need more, you're welcome to use an embedded system running an OS like Linux and use the .NET library with Mono.

Using the JavaScript client

Introduction

Our official JavaScript client is the easiest way to get started using Woopsa in a web application. Contrary to most other protocols out there, you do not need a gateway server. Indeed, because Woopsa is based on HTTP, browsers can directly connect to Woopsa servers. This is great in many aspects, because it means there are less possible points of failure which makes for a much simpler infrastructure!

Getting the code

Note: The Woopsa JavaScript library depends on jQuery for making asynchronous requests.

Using a content delivery network

Woopsa will soon be available on a CDN. In the meantime, we suggest you host it yourself.

Hosting it yourself

If you'd prefer to host the Woopsa client code yourself, simply download Woopsa and get the minified (compressed) JavaScript in the JavaScript folder. Include it before the closing </body> tag of your page.

<script type="text/javascript" src="{your-javascript-folder}/woopsa-client.min.js"/>
  	

Creating your first client

Note: Woopsa is connection-less. This means that creating a client does not initiate any data exchange until requests are actually made.

Creating a new Woopsa client is easy. You just need to know the URL of the server, and pass jQuery as a dependency. Then you're ready to make your first requests! Because we want to help you get started as quickly as possible, we host a free sample Woopsa server on http://demo.woopsa.org/woopsa. To tell Woopsa to communicate with this server, use the following code to create a new client:

var client = new WoopsaClient("http://demo.woopsa.org/woopsa", jQuery);
	

That's it! You're now ready to make your first requests.

Basic usage

Note: our JavaScript library implements the Promise interface, which means done, fail and always are available for you!

Woopsa has 4 basic verbs that allow you to do everything you need! The three verbs are presented below, along with the code examples that go with them:

Exploring the object tree with meta

Because Woopsa is object-oriented, you can freely explore the data that's available on the server. This allows you to write very flexible clients that can fetch data based on what they're presented with. Fetching the object hierarchy is as simple as specifying the path and a callback method:

client.meta("/", function (tree){
  console.log(tree);
});
	
{
  "Name": "WeatherStation",
  "Items": [
    "SubscriptionService",
    "Thermostat"
  ],
  "Properties": [
    {
      "Name": "Temperature",
      "Type": "Real",
      "ReadOnly": true
    },
    {
      "Name": "IsRaining",
      "Type": "Logical",
      "ReadOnly": false
    },
    {
      "Name": "Altitude",
      "Type": "Integer",
      "ReadOnly": false
    },
    {
      "Name": "Sensitivity",
      "Type": "Real",
      "ReadOnly": false
    },
    {
      "Name": "City",
      "Type": "Text",
      "ReadOnly": false
    },
    {
      "Name": "Time",
      "Type": "DateTime",
      "ReadOnly": false
    },
    {
      "Name": "TimeSinceLastRain",
      "Type": "TimeSpan",
      "ReadOnly": false
    }
  ],
  "Methods": [
    {
      "Name": "MultiRequest",
      "ReturnType": "JsonData",
      "ArgumentInfos": [
        {
          "Name": "Requests",
          "Type": "JsonData"
        }
      ]
    },
    {
      "Name": "GetWeatherAtDate",
      "ReturnType": "Text",
      "ArgumentInfos": [
        {
          "Name": "date",
          "Type": "DateTime"
        }
      ]
    },
    {
      "Name": "ToString",
      "ReturnType": "Text",
      "ArgumentInfos": []
    },
    {
      "Name": "GetHashCode",
      "ReturnType": "Integer",
      "ArgumentInfos": []
    }
  ]
}
	

If you want to learn more about the meaning of all this stuff, read the full specifications to the Woopsa protocol!

Reading a property with read

You can make a request to read a property by using the simple read function provided to you in the library. This method only requires 2 parameters: the path of the property and a callback method for when the value is returned by the server.

client.read("/Temperature", function (value){
  console.log("The temperature is " + value);
});
	
The temperature is 24.2
	

Writing to a property with write

Writing properties is as easy as reading them. Watch out though, because properties have a type and may be read-only! The write method requires 3 parameters: the path of the property, the value to write and a callback function (optional) for when the value is writting to the server.

client.write("/Sensitivity", 0.5, function (response){
  if ( response == true ){
    console.log("The value was written successfully!");
  }else{
    console.log("The value was not written successfully :(");
  }
})
	
The value was written successfully!
	

Calling a method/function with invoke

Methods in Woopsa allow you to send commands to the server in a more intuitive way. Again, this is very easily done with Woopsa. The invoke method requires 3 parameters: the path of the method, the arguments and a callback for when the method has been called on the server.

client.invoke("/GetWeatherAtDate", new Date(2015, 9, 6), function (response){
  console.log(response);
})
	
partly cloudy
	

Listening for value changes (publish/subscribe)

On servers that support it, a SubscriptionService object is available at the root. This object has various methods to subscribe to value changes. However, the library abstracts this away for you! An easy-to-use onChange method allows you to bind a callback to the change of a property. All you need to provide it is a path and a callback, as well as a monitorInterval and a publishInterval.

var newSubscription;
client.onChange("/Temperature", function (value){
  console.log(value);
}, 0.03, 0.03, function (subscription){
  newSubscription = subscription;
}
...
// When you wish to unregister this subscription to stop receiving notifications for this property...
newSubscription.unregister(function (){
  console.log("Subscription removed");
});
	
24.2
..
24.3
..
24.1
..
	

If you wish to have more control over the rate at which events are raised, or just want to learn a bit more about how the publish/subscribe pattern is implemented in Woopsa, go read the full specifications!

Using SSL/TLS

Under the hood, the Woopsa JavaScript client uses the standard XmlHttpRequest. This means that using it with an SSL/TLS enabled server is just a matter of using https:// instead of http:// when specifying the server URL.

var client = new WoopsaClient("https://demo.woopsa.org/woopsa");
	

Using authentication

If the Woopsa server you are communicating with requires client authentication with a username and password, simply set the username and password properties of your client and all future requests will be authenticated.

var client = new WoopsaClient("http://demo.woopsa.org/woopsa", jQuery);
client.username = "admin";
client.password = "password";
	

Using the C# / .NET client

Introduction

The C# / .NET clients are included in the Woopsa assembly. We created the Woopsa library with Mono in mind, trying to use only the most standard .NET components for maximum compatibility. Like the JavaScript client, you do not need a gateway server to relay information. Just directly connect to your Woopsa server! There is a "standard" and a dynamic client (available only in .NET versions that support reflection). This tutorial covers the standard version of the client and finishes with an example on how to use the dynamic client.

Getting the assembly and using it

Start off by downloading Woopsa and get the Woopsa.dll assembly inside the DotNet folder. Add this assembly as a reference in your Visual Studio project and add the following line to the top of your source file:

using Woopsa;
	

That's it! You're now ready to create your client.

Creating your first client

Note: Woopsa is connection-less. This means that creating a client does not initiate any data exchange until requests are actually made.

Creating a new Woopsa client is easy. You just need to know the URL of the server. Then you're ready to make your first requests! Because we want to help you get started as quickly as possible, we host a free sample Woopsa server on http://demo.woopsa.org/woopsa. To tell Woopsa to communicate with this server, use the following code to create a new client:

WoopsaClient client = new WoopsaClient("http://demo.woopsa.org/woopsa");
	

That's it! You're now ready to work with your server.

Basic usage

The C# / .NET library abstracts away a lot of the workings of Woopsa. It works differently from the JavaScript implementation, making use of all the powerful features that the C# language and .NET platform have to offer.

Exploring the object tree

The method Meta("path") returns a WoopsaMetaResult object containing all the information on the object located at the given path parameter.

WoopsaMetaResult meta = client.ClientProtocol.Meta("/");
foreach (var property in meta.Properties)
{
	Console.WriteLine("Property {0} : {1} ({2})", property.Name, property.Type, property.IsReadOnly);
}
foreach (var method in meta.Methods)
{
	Console.WriteLine("Method {0} : {1}", method.Name, method.ReturnType);
	foreach (WoopsaMethodArgumentInfoMeta argument in method.ArgumentInfos)
	{
		Console.WriteLine("Argument {0} : {1}", argument.Name, argument.Type);
	}
}
foreach (var obj in meta.Items)
{
	Console.WriteLine("Item {0}", obj);
}
	
Property Temperature : Real (True)
Property IsRaining : Logical (False)
Property Altitude : Integer (False)
Property Sensitivity : Real (False)
Property City : Text (False)
Property Time : DateTime (False)
Property TimeSinceLastRain : TimeSpan (False)
Method MultiRequest : JsonData
Argument Requests : JsonData
Method GetWeatherAtDate : Text
Argument date : DateTime
Item SubscriptionService
Item Thermostat

Reading a property

Because they implement the IWoopsaProperty interface, reading a value is as easy as using the Read method! The library takes care of most cases of typecasting.

Console.WriteLine("Temperature = {0}", client.ClientProtocol.Read("Temperature"));
	
Temperature = 24.2
	

Writing to a property

When you wish to write a property, you need to know its type in order to assign it a WoopsaValue. Once you know this, writing is as simple as assigning something with the Write method.

client.ClientProtocol.Write("Sensitivity", new WoopsaValue(0.5));
	

Note: The Woopsa .NET library comes with tons of premade WoopsaValue constructors that accept just about any type you can think of, making your life easier when dealing with WoopsaValues.

Calling a method/function

When you have your method, simply call the Invoke method and passing it a collection of NameValueCollections for the arguments.

NameValueCollection arguments =  new NameValueCollection();
arguments.Add("date",new WoopsaValue(DateTime.Now).AsText);
string weather = client.ClientProtocol.Invoke("GetWeatherAtDate", arguments);
Console.WriteLine(weather);
	
cloudy
	

Note: While the JavaScript implementation of invoke has named parameters, this is not the case in the C# version. This means that the order in which you specify arguments is important!

Listening for value changes

WoopsaClient has a useful Subscribe method via the SubscriptionChannel property that you can use with a path and a EventHandler for the arguments.

client.SubscriptionChannel.Subscribe("Sensitivity", Sensitivity_Changed);
...
private void Sensitivity_Changed(object sender, WoopsaNotificationEventArgs e)
{
	Console.WriteLine(e.Notification.Value.ToString());
}
	
0.6
..
0.7
..
0.8
..
	

Bound API

A root object which implements the IWoopsaObject interface can be obtained from the Woopsa client method, CreateBoundRoot. This object is filled on-demand with all the published properties, methods and items on the server as IEnumerable properties. This means that you can simply iterate through these properties to know exactly what's on the server, without any additional logic!

WoopsaBoundClientObject root = client.CreateBoundRoot();
root.Properties.ByName("IsRaining").Value = true;
foreach (WoopsaProperty property in root.Properties)
{
	Console.WriteLine("{0} : {1} = {2}", property.Name, property.Type, property.Value);
}
	
Temperature : Real = 24.2
IsRaining : logical = true
Altitude : Integer = 1
Sensitivity : Real = 16 
City : Text = Yverdon-les-Bains
Time : DateTime = 2016-02-27T15:13:52.9134737Z
TimeSinceLastRain : TimeSpan = 259200
	

Unbound API

A root object which implements the IWoopsaObject interface can be created from the Woopsa client method, CreateUnboundRoot. This object is empty and will be filled manually with de methods GetPropertyByPath, GetUnboundItem and GetMethod. All this actions can also be done when the server is offline. For writing or reading properties the server must naturally be online.

// server offline
WoopsaUnboundClientObject root = client.CreateUnboundRoot("root");
WoopsaProperty altitude = root.GetPropertyByPath("Altitude", WoopsaValueType.Integer, false);
...
// server online
altitude.Value = 1512;
Console.WriteLine("{0} : {1} = {2}", altitude.Name, altitude.Type, altitude.Value);
	
Altitude : Integer = 1512
	

Even simpler: using the dynamic client

The steps presented above allow you to write strict object-oriented code with strong typing and compilation-time checking. However, the notation can be somewhat tedious at times. This is why we also created a dynamic client, which allows you to talk to a Woopsa server as if it were a regular object!

dynamic client = new WoopsaDynamicClient("http://demo.woopsa.org/woopsa");
//Reading a property
Console.WriteLine("Temperature = {0}", client.Temperature);
//Writing a property
client.Sensitivity = 0.5;
//Invoking a method
Console.WriteLine("Weather = {0}", client.GetWeatherAtDate(DateTime.Now));
	
Temperature = 24.2
Weather = cloudy
	

Using SSL/TLS

Under the hood, the Woopsa C# client uses HttpWebRequest. This means that using it with an SSL/TLS enabled server is just a matter of using https:// instead of http:// when specifying the server URL.

WoopsaClient client = new WoopsaClient("https://localhost/woopsa");
	

Note: Certificate validation will be handled by the .NET platform's own internals. This is out of the scope of this guide, however, a "quick and dirty" way to avoid errors when testing out on local servers with self-signed certificates is to add a RemoteCertificateValidationCallback to the ServicePointManager:

ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true;

Warning: This will override all the certificate validation chain and won't authentify the server! It's always recommended to get a proper certificate for your server instead.

Using authentication

If the Woopsa server you are communicating with requires client authentication with a username and password, simply set the Username and Password properties of your client (works on the dynamic client as well) and all future requests will be authenticated.

WoopsaClient client = new WoopsaClient("http://demo.woopsa.org/woopsa");
client.Username = "admin";
client.Password = "password";