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