diff --git a/server/foo.html b/server/foo.html new file mode 100644 index 0000000000000000000000000000000000000000..9b0cd8964e0bad53d271d093d009defe0d77db48 --- /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 08e82dd291d4462dfe2da68c293ea39f93d06a21..729eac12d4d63c506e811f04b43aa870e2964082 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 b08ff5ed92d84ae8d8b1ab489c7870ab6bb38e1d..a408d546998bdf35e381b19754aa6620b228b3e7 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 7cbc320a4dc5ef163b71ef486614e4f9ec845b04..2a47194921ea8288313187f3e58d7102be16e154 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 cdf9bab0b5ba1163887ebbca57185e94dab305cd..56b2f5b0d5ab6b2b0c066927dc353eb305e37df4 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 c805707b134c874b25b12f73880ccb245cd1d2f4..ce58cb5f416538140433ba8e13fd0f8b865c2f83 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 438be7848d41f50bab54e16801643dfc573d5d02..92e5ce330787ef68527af6d01f4ed8bde01f2ec3 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 2e47c1ffd45fd8f85b8abda7c06271f0d26213f8..a7743542647f34ae3af0ad2de103a825e56651bb 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 4abc8c93468afd439a0ba5a033b04e82172321d7..7c2f034ab4d409e3a7553928015db4093ffc5f28 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 631111d86b9ade9159ddd274e6e8e9537e688867..5e2716f6ded8741865b2eb4d704fcea7e99c938b 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;