From b4e25fea404ea3eb4c4c41df088eec6a0ecab20a Mon Sep 17 00:00:00 2001
From: Micah Elizabeth Scott <micah@scanlime.org>
Date: Mon, 4 Nov 2013 19:06:23 -0800
Subject: [PATCH] Implement the list_connected_devices JSON message

---
 server/foo.html                | 50 ++++++++++++++++++++++++++++++++
 server/src/enttecdmxdevice.cpp |  6 ++++
 server/src/enttecdmxdevice.h   |  1 +
 server/src/fcdevice.cpp        | 19 +++++++++----
 server/src/fcdevice.h          |  3 ++
 server/src/fcserver.cpp        | 39 +++++++++++++++++++++++--
 server/src/fcserver.h          |  7 ++++-
 server/src/netserver.cpp       | 52 ++++++++++++++++++++++++++++------
 server/src/netserver.h         | 12 ++++++--
 server/src/usbdevice.h         |  4 +++
 10 files changed, 174 insertions(+), 19 deletions(-)
 create mode 100644 server/foo.html

diff --git a/server/foo.html b/server/foo.html
new file mode 100644
index 0000000..9b0cd89
--- /dev/null
+++ b/server/foo.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8" /> 
+  <title>WebSocket Test</title>
+</head>
+<body>
+<script language="javascript" type="text/javascript">
+
+var websocket = new WebSocket("ws://127.0.0.1:7890/")
+var headerSize = 4;
+var numPixels = 16;
+var packet = new Uint8Array(headerSize + numPixels * 3);
+
+frameCallback = function() {
+  if (websocket.readyState != 1) {
+    return;
+  }
+
+  var t = new Date().getTime();
+
+  for (var i = 0; i < numPixels; i++) {
+    var l = 0x80 + 0x70 * Math.sin(i * 0.2 + t * 0.001);
+
+    packet[headerSize + i*3 + 0] = l * 0.2;
+    packet[headerSize + i*3 + 1] = l * 1.0;
+    packet[headerSize + i*3 + 2] = l * 0.4;
+  }
+
+  websocket.send(packet.buffer);
+
+  websocket.send(JSON.stringify({
+    "type": "list_connected_devices"
+  }));
+
+  setTimeout(frameCallback, 100);
+}
+
+websocket.onopen = function(evt) {
+  console.log("Connected");
+  frameCallback();
+}
+
+websocket.onmessage = function(evt) {
+  console.log(evt.data);
+}
+
+</script>
+</body>
+</html>
diff --git a/server/src/enttecdmxdevice.cpp b/server/src/enttecdmxdevice.cpp
index 08e82dd..729eac1 100644
--- a/server/src/enttecdmxdevice.cpp
+++ b/server/src/enttecdmxdevice.cpp
@@ -169,6 +169,12 @@ std::string EnttecDMXDevice::getName()
     return s.str();
 }
 
+void EnttecDMXDevice::describe(rapidjson::Value &object, Allocator &alloc)
+{
+    object.AddMember("type", "Enttec DMX USB Pro", alloc);
+    object.AddMember("serial", mSerial, alloc);
+}
+
 void EnttecDMXDevice::setChannel(unsigned n, uint8_t value)
 {
     if (n >= 1 && n <= 512) {
diff --git a/server/src/enttecdmxdevice.h b/server/src/enttecdmxdevice.h
index b08ff5e..a408d54 100644
--- a/server/src/enttecdmxdevice.h
+++ b/server/src/enttecdmxdevice.h
@@ -41,6 +41,7 @@ public:
     virtual void writeMessage(const OPC::Message &msg);
     virtual std::string getName();
     virtual void flush();
+    virtual void describe(rapidjson::Value &object, Allocator &alloc);
 
     void writeDMXPacket();
     void setChannel(unsigned n, uint8_t value);
diff --git a/server/src/fcdevice.cpp b/server/src/fcdevice.cpp
index 7cbc320..2a47194 100644
--- a/server/src/fcdevice.cpp
+++ b/server/src/fcdevice.cpp
@@ -109,6 +109,10 @@ int FCDevice::open()
         return r;
     }
 
+    unsigned major = mDD.bcdDevice >> 8;
+    unsigned minor = mDD.bcdDevice & 0xFF;
+    snprintf(mVersionString, sizeof mVersionString, "%x.%02x", major, minor);
+
     return libusb_get_string_descriptor_ascii(mHandle, mDD.iSerialNumber, (uint8_t*)mSerial, sizeof mSerial);
 }
 
@@ -532,12 +536,15 @@ std::string FCDevice::getName()
     std::ostringstream s;
     s << "Fadecandy";
     if (mSerial[0]) {
-        unsigned major = mDD.bcdDevice >> 8;
-        unsigned minor = mDD.bcdDevice & 0xFF;
-        char version[10];
-        snprintf(version, sizeof version, "%x.%02x", major, minor);
-
-        s << " (Serial# " << mSerial << ", Version " << version << ")";
+        s << " (Serial# " << mSerial << ", Version " << mVersionString << ")";
     }
     return s.str();
 }
+
+void FCDevice::describe(rapidjson::Value &object, Allocator &alloc)
+{
+    object.AddMember("type", "Fadecandy", alloc);
+    object.AddMember("serial", mSerial, alloc);
+    object.AddMember("version", mVersionString, alloc);
+    object.AddMember("bcd_version", mDD.bcdDevice, alloc);
+}
diff --git a/server/src/fcdevice.h b/server/src/fcdevice.h
index cdf9bab..56b2f5b 100644
--- a/server/src/fcdevice.h
+++ b/server/src/fcdevice.h
@@ -41,6 +41,7 @@ public:
     virtual void writeColorCorrection(const Value &color);
     virtual std::string getName();
     virtual void flush();
+    virtual void describe(rapidjson::Value &object, Allocator &alloc);
 
     static const unsigned NUM_PIXELS = 512;
 
@@ -97,6 +98,8 @@ private:
     bool mFrameWaitingForSubmit;
 
     char mSerial[256];
+    char mVersionString[10];
+
     libusb_device_descriptor mDD;
     Packet mFramebuffer[FRAMEBUFFER_PACKETS];
     Packet mColorLUT[LUT_PACKETS];
diff --git a/server/src/fcserver.cpp b/server/src/fcserver.cpp
index c805707..ce58cb5 100644
--- a/server/src/fcserver.cpp
+++ b/server/src/fcserver.cpp
@@ -34,7 +34,7 @@ FCServer::FCServer(rapidjson::Document &config)
       mColor(config["color"]),
       mDevices(config["devices"]),
       mVerbose(config["verbose"].IsTrue()),
-      mNetServer(cbMessage, this, mVerbose),
+      mNetServer(cbOpcMessage, cbJsonMessage, this, mVerbose),
       mUSBHotplugThread(0),
       mUSB(0)
 {
@@ -100,7 +100,7 @@ bool FCServer::startUSB(libusb_context *usb)
     return true;
 }
 
-void FCServer::cbMessage(OPC::Message &msg, void *context)
+void FCServer::cbOpcMessage(OPC::Message &msg, void *context)
 {
     /*
      * Broadcast the OPC message to all configured devices.
@@ -311,3 +311,38 @@ void FCServer::usbHotplugThreadFunc(void *arg)
         tthread::this_thread::sleep_for(tthread::chrono::seconds(1));
     }
 }
+
+void FCServer::cbJsonMessage(libwebsocket *wsi, rapidjson::Document &message, void *context)
+{
+    // Received a JSON message from a WebSockets client.
+    // Replies are formed by modifying the original message.
+
+    FCServer *self = (FCServer*) context;
+
+    const Value &vtype = message["type"];
+    if (!vtype.IsString()) {
+        lwsl_notice("NOTICE: Received JSON is missing mandatory \"type\" string\n");
+        return;
+    }
+    const char *type = vtype.GetString();
+
+    if (!strcmp(type, "list_connected_devices")) {
+        self->jsonListConectedDevices(message);
+    } else {
+        message.AddMember("error", "Unknown message type", message.GetAllocator());
+    }
+
+    self->mNetServer.jsonReply(wsi, message);
+}
+
+void FCServer::jsonListConectedDevices(rapidjson::Document &message)
+{
+    message.AddMember("devices", rapidjson::kArrayType, message.GetAllocator());
+    Value &list = message["devices"];
+
+    for (unsigned i = 0; i != mUSBDevices.size(); i++) {
+        USBDevice *usbDev = mUSBDevices[i];
+        list.PushBack(rapidjson::kObjectType, message.GetAllocator());
+        mUSBDevices[i]->describe(list[i], message.GetAllocator());
+    }
+}
diff --git a/server/src/fcserver.h b/server/src/fcserver.h
index 438be78..92e5ce3 100644
--- a/server/src/fcserver.h
+++ b/server/src/fcserver.h
@@ -60,7 +60,9 @@ private:
     std::vector<USBDevice*> mUSBDevices;
     struct libusb_context *mUSB;
 
-    static void cbMessage(OPC::Message &msg, void *context);
+    static void cbOpcMessage(OPC::Message &msg, void *context);
+    static void cbJsonMessage(libwebsocket *wsi, rapidjson::Document &message, void *context);
+
     static LIBUSB_CALL int cbHotplug(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void *user_data);
 
     bool startUSB(libusb_context *usb);
@@ -70,4 +72,7 @@ private:
     bool usbHotplugPoll();
 
     static void usbHotplugThreadFunc(void *arg);
+
+    // JSON message handlers
+    void jsonListConectedDevices(rapidjson::Document &message);
 };
diff --git a/server/src/netserver.cpp b/server/src/netserver.cpp
index 2e47c1f..a774354 100644
--- a/server/src/netserver.cpp
+++ b/server/src/netserver.cpp
@@ -28,12 +28,16 @@
 #include "netserver.h"
 #include "version.h"
 #include "libwebsockets.h"
+#include "rapidjson/stringbuffer.h"
+#include "rapidjson/writer.h"
 #include <iostream>
 #include <algorithm>
 
 
-NetServer::NetServer(OPC::callback_t messageCallback, void *context, bool verbose)
-    : mMessageCallback(messageCallback), mUserContext(context), mThread(0), mVerbose(verbose)
+NetServer::NetServer(OPC::callback_t opcCallback, jsonCallback_t jsonCallback,
+    void *context, bool verbose)
+    : mOpcCallback(opcCallback), mJsonCallback(jsonCallback),
+      mUserContext(context), mThread(0), mVerbose(verbose)
 {}
 
 bool NetServer::start(const char *host, int port)
@@ -245,7 +249,7 @@ int NetServer::opcRead(libwebsocket_context *context, libwebsocket *wsi,
         }
 
         // Complete packet.
-        mMessageCallback(*msg, mUserContext);
+        mOpcCallback(*msg, mUserContext);
 
         buffer += msgLength;
         bufferLength -= msgLength;
@@ -347,8 +351,7 @@ int NetServer::httpWrite(libwebsocket_context *context, libwebsocket *wsi, Clien
 
 int NetServer::wsRead(libwebsocket_context *context, libwebsocket *wsi, Client &client, uint8_t *in, size_t len)
 {
-    // WebSockets data! Binary frames are in OPC format, text frames are JSON.
-
+    // If this frame is binary, it's an OPC message. Does it parse?
     if (lws_frame_is_binary(wsi)) {
         OPC::Message *msg = (OPC::Message*) in;
 
@@ -367,13 +370,46 @@ int NetServer::wsRead(libwebsocket_context *context, libwebsocket *wsi, Client &
         }
 
         msg->setLength(len - OPC::HEADER_BYTES);
-        mMessageCallback(*msg, mUserContext);
+        mOpcCallback(*msg, mUserContext);
+
+        return 0;
+    }
 
+    // Text frames are JSON encoded. Does that parse?
+    rapidjson::Document message;
+    message.ParseInsitu<0>((char*) in);
+
+    if (message.HasParseError()) {
+        lwsl_notice("NOTICE: Parse error in received JSON, character %d: %s\n",
+            int(message.GetErrorOffset()), message.GetParseError());
         return 0;
     }
 
-    // XXX: Implement JSON messages
-    lwsl_notice("NOTICE: Received text frame over WebSockets. Not yet implemented!\n");
+    if (!message.IsObject()) {
+        lwsl_notice("NOTICE: Received JSON is not an object {}\n");
+        return 0;
+    }
 
+    mJsonCallback(wsi, message, mUserContext);
     return 0;
 }
+
+int NetServer::jsonReply(libwebsocket *wsi, rapidjson::Document &message)
+{
+    rapidjson::GenericStringBuffer<rapidjson::UTF8<>> buffer;
+
+    // Pre-packet padding
+    rapidjson::PutN<>(buffer, 0, LWS_SEND_BUFFER_PRE_PADDING);
+
+    // Write serialized message
+    rapidjson::Writer<rapidjson::GenericStringBuffer<rapidjson::UTF8<>>> writer(buffer);
+    message.Accept(writer);
+
+    // Post-packet padding
+    rapidjson::PutN<>(buffer, 0, LWS_SEND_BUFFER_POST_PADDING);
+
+    const char *string = buffer.GetString() + LWS_SEND_BUFFER_PRE_PADDING;
+    size_t len = buffer.Size() - LWS_SEND_BUFFER_PRE_PADDING - LWS_SEND_BUFFER_POST_PADDING;
+
+    return libwebsocket_write(wsi, (unsigned char *) string, len, LWS_WRITE_TEXT);
+}
diff --git a/server/src/netserver.h b/server/src/netserver.h
index 4abc8c9..7c2f034 100644
--- a/server/src/netserver.h
+++ b/server/src/netserver.h
@@ -24,6 +24,7 @@
 #pragma once
 #include <stdint.h>
 #include <list>
+#include "rapidjson/document.h"
 #include "tinythread.h"
 #include "libwebsockets.h"
 #include "opc.h"
@@ -31,11 +32,17 @@
 
 class NetServer {
 public:
-    NetServer(OPC::callback_t messageCallback, void *context, bool verbose = false);
+    typedef void (*jsonCallback_t)(libwebsocket *wsi, rapidjson::Document &message, void *context);
+
+    NetServer(OPC::callback_t opcCallback, jsonCallback_t jsonCallback,
+        void *context, bool verbose = false);
 
     // Start the event loop on a separate thread
     bool start(const char *host, int port);
 
+    // Reply callback, for use only on the NetServer thread. Call this inside jsonCallback.
+    int jsonReply(libwebsocket *wsi, rapidjson::Document &message);
+
 private:
     enum ClientState {
         CLIENT_STATE_PROTOCOL_DETECT = 0,
@@ -67,7 +74,8 @@ private:
         OPCBuffer *opcBuffer;
     };
 
-    OPC::callback_t mMessageCallback;
+    OPC::callback_t mOpcCallback;
+    jsonCallback_t mJsonCallback;
     void *mUserContext;
     tthread::thread *mThread;
     bool mVerbose;
diff --git a/server/src/usbdevice.h b/server/src/usbdevice.h
index 631111d..5e2716f 100644
--- a/server/src/usbdevice.h
+++ b/server/src/usbdevice.h
@@ -33,6 +33,7 @@ class USBDevice
 {
 public:
     typedef rapidjson::Value Value;
+    typedef rapidjson::MemoryPoolAllocator<> Allocator;
 
     USBDevice(libusb_device *device, bool verbose);
     virtual ~USBDevice();
@@ -58,6 +59,9 @@ public:
     virtual std::string getName() = 0;
     libusb_device *getDevice() { return mDevice; };
 
+    // Describe this device by adding keys to a JSON object
+    virtual void describe(rapidjson::Value &object, Allocator &alloc) = 0;
+
 protected:
     libusb_device *mDevice;
     libusb_device_handle *mHandle;
-- 
GitLab