From 5dcec198694e4bbe1b90aa8509e62dcc9a6bd134 Mon Sep 17 00:00:00 2001
From: Micah Elizabeth Scott <micah@scanlime.org>
Date: Wed, 24 Jul 2013 09:58:00 -0700
Subject: [PATCH] Device management and hotplug

---
 server/fcdevice.cpp |  61 ++++++++++++++++++++++
 server/fcdevice.h   |  19 ++++++-
 server/fcserver.cpp | 122 ++++++++++++++++++++++++++++++++++++++++++--
 server/fcserver.h   |   5 ++
 4 files changed, 203 insertions(+), 4 deletions(-)

diff --git a/server/fcdevice.cpp b/server/fcdevice.cpp
index 6bcbddf..069c34f 100644
--- a/server/fcdevice.cpp
+++ b/server/fcdevice.cpp
@@ -22,3 +22,64 @@
  */
 
 #include "fcdevice.h"
+
+
+FCDevice::FCDevice(libusb_device *device, bool verbose)
+	: mVerbose(verbose),
+	  mDevice(libusb_ref_device(device)),
+	  mHandle(0),
+	  mConfig(0)
+{
+	mSerial[0] = '\0';
+}
+
+FCDevice::~FCDevice()
+{
+	if (mHandle) {
+		libusb_close(mHandle);
+	}
+	if (mDevice) {
+		libusb_unref_device(mDevice);
+	}
+}
+
+bool FCDevice::isFadecandy()
+{
+	libusb_device_descriptor dd;
+
+	if (libusb_get_device_descriptor(mDevice, &dd) < 0) {
+		// Can't access descriptor?
+		return false;
+	}
+
+	return dd.idVendor == 0x1d50 && dd.idProduct == 0x607a;
+}
+
+int FCDevice::open()
+{
+	int r = libusb_open(mDevice, &mHandle);
+	if (r < 0) {
+		return r;
+	}
+
+	libusb_device_descriptor dd;
+	r = libusb_get_device_descriptor(mDevice, &dd);
+	if (r < 0) {
+		return r;
+	}
+
+	return libusb_get_string_descriptor_ascii(mHandle, dd.iSerialNumber, (uint8_t*)mSerial, sizeof mSerial);
+}
+
+void FCDevice::setConfiguration(const Value *config)
+{
+	mConfig = config;
+}
+
+void FCDevice::writeColorCorrection(const Value &color)
+{
+}
+
+void FCDevice::writeMessage(const OPCSink::Message &msg)
+{
+}
diff --git a/server/fcdevice.h b/server/fcdevice.h
index 85368b0..283e8b3 100644
--- a/server/fcdevice.h
+++ b/server/fcdevice.h
@@ -22,16 +22,33 @@
  */
 
 #pragma once
+#include "rapidjson/document.h"
+#include "opcsink.h"
 #include <libusb.h>
 
 
 class FCDevice {
 public:
-    FCDevice(libusb_device *device);
+	typedef rapidjson::Value Value;
 
+    FCDevice(libusb_device *device, bool verbose = false);
+    ~FCDevice();
+
+    libusb_device *getDevice() { return mDevice; };
+    bool isFadecandy();
     int open();
 
+    // Valid after open():
+
+    const char *getSerial() { return mSerial; }
+    void setConfiguration(const Value *config);
+    void writeColorCorrection(const Value &color);
+    void writeMessage(const OPCSink::Message &msg);
+
 private:
+	bool mVerbose;
     libusb_device *mDevice;
     libusb_device_handle *mHandle;
+    const Value *mConfig;
+    char mSerial[256];
 };
diff --git a/server/fcserver.cpp b/server/fcserver.cpp
index f44867f..3f760e7 100644
--- a/server/fcserver.cpp
+++ b/server/fcserver.cpp
@@ -23,6 +23,7 @@
 
 #include "util.h"
 #include "fcserver.h"
+#include "fcdevice.h"
 #include <netdb.h>
 #include <ctype.h>
 #include <iostream>
@@ -70,6 +71,14 @@ FCServer::FCServer(rapidjson::Document &config)
 	} else {
 		mError << "The required 'listen' configuration key must be a [host, port] list.\n";
 	}
+
+	/*
+	 * Minimal validation on 'devices'
+	 */
+
+	if (!mDevices.IsArray()) {
+		mError << "The required 'devices' configuration key must be an array.\n";
+	}
 }
 
 FCServer::~FCServer()
@@ -108,9 +117,16 @@ void FCServer::startUSB(struct ev_loop *loop)
 
 void FCServer::cbMessage(OPCSink::Message &msg, void *context)
 {
+	/*
+	 * Broadcast the OPC message to all configured devices.
+	 */
+
 	FCServer *self = static_cast<FCServer*>(context);
 
-	printf("Msg %d bytes\n", msg.length());
+	for (std::vector<FCDevice*>::iterator i = self->mFCDevices.begin(), e = self->mFCDevices.end(); i != e; ++i) {
+		FCDevice *fcd = *i;
+		fcd->writeMessage(msg);
+	}
 }
 
 int FCServer::cbHotplug(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void *user_data)
@@ -129,10 +145,110 @@ int FCServer::cbHotplug(libusb_context *ctx, libusb_device *device, libusb_hotpl
 
 void FCServer::usbDeviceArrived(libusb_device *device)
 {
-	// New USB device. Is this a device we recognize?
+	/*
+	 * New USB device. Is this a device we recognize?
+     *
+	 * Right now we're only looking for FCDevices, but in the future
+	 * we can look for other types of USB devices here too.
+	 */
+
+	FCDevice *fcd = new FCDevice(device);
+	if (!fcd->isFadecandy()) {
+		// Not a recognized device.
+		delete fcd;
+		return;
+	}
+
+	int r = fcd->open();
+	if (r < 0) {
+		if (mVerbose) {
+			std::clog << "Error opening Fadecandy USB device: " << libusb_strerror(libusb_error(r)) << "\n";
+		}
+		delete fcd;
+		return;
+	}
+
+	// Look for a matching device in the JSON
+	const Value *fcjson = matchFCDevice(fcd->getSerial());
+
+	if (mVerbose) {
+		std::clog << "USB Fadecandy attached, serial: \"" << fcd->getSerial() << "\"";
+		if (fcjson) {
+			std::clog << " (configuration found)\n";
+		} else {
+			std::clog << " (not matched in config file)\n";
+		}
+	}
+
+	if (fcjson) {
+		// Store the configuration, use it for future messages
+		fcd->setConfiguration(fcjson);
+	} else {
+		delete fcd;
+		return;
+	}
+
+	// Send the default color lookup table
+	fcd->writeColorCorrection(mColor);
+
+	// Remember this device for later. It's now active, and we should broadcast messages to it.
+	mFCDevices.push_back(fcd);
 }
 
 void FCServer::usbDeviceLeft(libusb_device *device)
 {
-	
+	/*
+	 * Is this a device we recognize? If so, delete it.
+	 */
+
+	for (std::vector<FCDevice*>::iterator i = mFCDevices.begin(), e = mFCDevices.end(); i != e; ++i) {
+		FCDevice *fcd = *i;
+		if (fcd->getDevice() == device) {
+			if (mVerbose) {
+				std::clog << "USB Fadecandy removed, serial: \"" << fcd->getSerial() << "\"\n";
+			}
+			mFCDevices.erase(i);
+			delete fcd;
+			break;
+		}
+	}
+}
+
+const FCServer::Value *FCServer::matchFCDevice(const char *serial)
+{
+	/*
+	 * Look for a record in mDevices that matches a Fadecandy board with the given serial number.
+	 * Returns 0 if nothing matches.
+	 */
+
+	for (unsigned i = 0; i < mDevices.Size(); ++i) {
+		const Value &v = mDevices[i];
+		const Value &vtype = v["type"];
+		const Value &vserial = v["serial"];
+
+		if (!vtype.IsString() || strcmp(vtype.GetString(), "fadecandy")) {
+			// Wrong type
+			continue;
+		}
+
+		if (!vserial.IsNull()) {
+			// Not a wildcard serial number?
+			// If a serial was not specified, it matches any device.
+
+			if (!vserial.IsString()) {
+				// Non-string serial number. Bad form.
+				continue;
+			}
+
+			if (strcmp(vserial.GetString(), serial)) {
+				// Not a match
+				continue;
+			}
+		}
+
+		// Match
+		return &v;
+	}
+
+	return 0;
 }
diff --git a/server/fcserver.h b/server/fcserver.h
index 3a7178b..1a6921a 100644
--- a/server/fcserver.h
+++ b/server/fcserver.h
@@ -24,9 +24,11 @@
 #pragma once
 #include "rapidjson/document.h"
 #include "opcsink.h"
+#include "fcdevice.h"
 #include "libusbev.h"
 #include <libusb.h>
 #include <sstream>
+#include <vector>
 #include <ev.h>
 #include <netinet/in.h>
 #include <netdb.h>
@@ -58,10 +60,13 @@ private:
     libusb_context *mUSB;
     LibUSBEventBridge mUSBEvent;
 
+    std::vector<FCDevice*> mFCDevices;
+
     static void cbMessage(OPCSink::Message &msg, void *context);
     static int cbHotplug(libusb_context *ctx, libusb_device *device, libusb_hotplug_event event, void *user_data);
 
     void startUSB(struct ev_loop *loop);
     void usbDeviceArrived(libusb_device *device);
     void usbDeviceLeft(libusb_device *device);
+    const Value *matchFCDevice(const char *serial);
 };
-- 
GitLab