Просмотр исходного кода

U421 PIR monitoring (or at least the better part of it)

Zachary Kotlarek 14 лет назад
Родитель
Сommit
0e7616e383
5 измененных файлов с 1268 добавлено и 0 удалено
  1. 3 0
      motion/.gitignore
  2. 829 0
      motion/USBMicroX.c
  3. 350 0
      motion/USBMicroX.h
  4. 73 0
      motion/main.c
  5. 13 0
      motion/makefile

+ 3 - 0
motion/.gitignore

@@ -0,0 +1,3 @@
+*.o
+MOTION
+USBMicroX

+ 829 - 0
motion/USBMicroX.c

@@ -0,0 +1,829 @@
+#include "USBMicroX.h"
+#define UNLESS(S) if(!(S))
+
+#define USBMX_ID_PRODUCT 0x01A5
+#define USBMX_ID_VENDOR 0x0DE7
+
+static long dict_get_long(CFDictionaryRef properties, CFStringRef key)
+{
+	long number;
+	CFTypeRef value = CFDictionaryGetValue(properties, key);
+	if (!value) return 0;
+	CFTypeID type = CFGetTypeID(value);
+	if (type != CFNumberGetTypeID()) return 0 ;
+	CFNumberGetValue((CFNumberRef)value, kCFNumberLongType, (void *)&number);
+	return number;
+}
+
+static char* dict_get_string(CFDictionaryRef properties, CFStringRef key, CFStringEncoding encoding)
+{
+	static char cstring[512];
+	CFTypeRef value = CFDictionaryGetValue(properties, key);
+	if (!value) return "";
+	CFTypeID type = CFGetTypeID(value) ;
+	if (type != CFStringGetTypeID()) return "";	
+	CFStringGetCString((CFStringRef)value, cstring, sizeof(cstring), encoding);
+	return cstring; // WARNING: be sure to copy string after return
+}
+
+static void nsp(int L) {
+	int i = 0;
+	while(i++ < L) printf("    ");
+}
+
+static void cftype_dump(CFTypeRef o, int L);
+
+static void cftype_dump_arrayapply(const void *value, void *context) {
+	int L = (int)context;
+	nsp(L); printf("0x%08x\n", value);
+	cftype_dump((CFTypeRef)value, L+1);
+}
+
+static void cftype_dump_dictapply (const void *key, const void *value, void *context) {
+	int L = (int)context;
+	nsp(L); printf("\"%s\" => 0x%08x\n",
+		CFStringGetCStringPtr(key, CFStringGetSystemEncoding()), value);
+	cftype_dump((CFTypeRef)value, L+1);
+}
+
+static void cftype_dump(CFTypeRef o, int L)
+{
+	if(CFGetTypeID(o) == CFArrayGetTypeID()) {
+		nsp(L); printf("[\n");
+		CFArrayApplyFunction(
+			o,
+			CFRangeMake(0, CFArrayGetCount(o)),
+			cftype_dump_arrayapply,
+			(void*)(L+1));
+		nsp(L); printf("]\n");
+	} else if(CFGetTypeID(o) == CFDictionaryGetTypeID()) {
+		nsp(L); printf("{\n");
+		CFDictionaryApplyFunction(
+			o,
+			cftype_dump_dictapply,
+			(void*)(L+1));
+		nsp(L); printf("}\n");
+	} else if(CFGetTypeID(o) == CFNumberGetTypeID()) {
+		long number;
+		CFNumberGetValue((CFNumberRef)o, kCFNumberLongType, (void*)&number);
+		nsp(L); printf("number: 0x%08x\n", number);
+	} else if(CFGetTypeID(o) == CFStringGetTypeID()) {
+		const char *s = CFStringGetCStringPtr(o, CFStringGetSystemEncoding());
+		nsp(L); printf("string: \"%s\"\n", s);
+	} else if(CFGetTypeID(o) == CFBooleanGetTypeID()) {
+		nsp(L); printf("boolean: %s\n", CFBooleanGetValue(o)?"TRUE":"FALSE");		
+	} else {
+		nsp(L); printf("??? unknown type\n");
+	}
+}
+
+int USBmX_DeviceAddedHandler(
+			USBmX_ContextRef ctx, 
+			USBmX_DeviceAddedProc proc, 
+			void *cookie)
+{
+	ctx->deviceAdded = proc;
+	ctx->deviceAdded_cookie = cookie;
+	return 0;
+}
+
+int USBmX_DeviceRemovedHandler(
+			USBmX_ContextRef ctx, 
+			USBmX_DeviceRemovedProc proc, 
+			void *cookie)
+{
+	ctx->deviceRemoved = proc;
+	ctx->deviceRemoved_cookie = cookie;
+	return 0;	
+}
+
+int USBmX_DeviceInfo(USBmX_DeviceRef dev)
+{
+	if(!dev) goto err;
+	cftype_dump(dev->properties, 0);
+	return 0;
+err:
+	return -1;
+}
+
+static void DumpBuffer(const u_char buf[], unsigned int buf_size)
+{
+	const u_char *p = buf;
+	while(buf_size--) printf("0x%02x ", *p++);
+	printf("\n");
+}
+
+void USBmX_DumpCommand(u_char buf[])
+{
+	DumpBuffer(buf, 8);
+}
+
+void DeviceAddedHandler(void *refCon, io_iterator_t iterator)
+{
+	#if USBMX_DEBUG_LOG
+	USBMX_LOG("Device added notification");
+	#endif
+	io_object_t o;
+	USBmX_ContextRef ctx;
+	// Must iterate in order to re-arm notification
+	while(o = IOIteratorNext(iterator)) {
+		IOObjectRelease(o);
+	}
+	ctx = (USBmX_ContextRef)refCon;
+	#if USBMX_DEBUG_LOG
+	USBMX_LOG("ctx = %x", ctx);
+	#endif
+	USBmX_ScanForDevices(ctx);
+}
+
+void DestroyDeviceRef(USBmX_DeviceRef d)
+{
+	#if USBMX_DEBUG_LOG
+	USBMX_LOG("Destroying: %x %x %x %x", d->properties, d->serialNumber, d->interface, d);
+	#endif
+	CFRelease(d->properties);
+	free((void *)d->serialNumber);
+	free((void *)d->deviceName);
+	free((void *)d->manufacturer);
+	(*d->interface)->close(d->interface);
+	free(d);
+}
+
+void DeviceRemovedHandler(void *target, IOReturn result, void *refcon, void *sender)
+{
+	USBmX_DeviceRef d = (USBmX_DeviceRef)refcon;
+	USBmX_ContextRef ctx = d->ctx;
+	#if USBMX_DEBUG_LOG
+	USBMX_LOG("Device Removed (# %s)", d->serialNumber);
+	#endif
+		
+	USBmX_DeviceRef p = ctx->deviceList;
+	USBmX_DeviceRef pl = NULL;
+	while(p) {
+		if(p == d)
+		{
+			if(pl == NULL)	ctx->deviceList = d->next;
+			else			pl->next = d->next;
+			////////////////////////////
+			//USBMX_LOG("NEED TO CALL DEVICE REMOVED CALLBACK");
+			if(ctx->deviceRemoved)
+			{
+				(*ctx->deviceRemoved)(d, ctx->deviceRemoved_cookie);
+			}
+			////////////////////////////
+			DestroyDeviceRef(d);
+			break;
+		}
+		pl = p;
+		p = p->next;
+	}
+}
+
+static void InterruptReportHandler(
+	void *target, 
+	IOReturn result, 
+	void *refcon, 
+	void *sender, 
+	UInt32 bufferSize)
+{
+	USBmX_DeviceRef d = (USBmX_DeviceRef)target;
+
+	#if USBMX_DEBUG_LOG
+	printf("Response: ");
+	DumpBuffer(d->response_buffer, bufferSize);
+	#endif
+	
+	d->response_ready = 1;
+}
+
+static int SetReport(USBmX_DeviceRef d, const u_char *buf, unsigned int buf_size)
+{
+	#if USBMX_DEBUG_LOG
+	printf("Report: "); DumpBuffer(buf, buf_size);
+	#endif
+	d->response_ready = 0;
+	IOReturn rc = (*d->interface)->setReport(
+		d->interface, 
+		kIOHIDReportTypeFeature,
+		0, //reportID
+		(void *)buf,
+		buf_size,
+		1000, //timeout
+		NULL,
+		NULL,
+		NULL
+		);
+	while(!d->response_ready) {
+		SInt32 r = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.001, false);
+		#if USBMX_DEBUG_LOG
+		printf("wait r=%x\n", r);
+		#endif
+	}
+	if(rc != kIOReturnSuccess) {
+		USBMX_LOG("SetReport: (*interface)->setReport() Error %x", rc);
+		return -1;
+	}
+	return 0;
+}
+
+int USBmX_DeviceCount(USBmX_ContextRef ctx)
+{
+	if(!ctx) return 0;
+	USBmX_DeviceRef d = ctx->deviceList;
+	int count = 0;
+	while(d) {
+		++count;
+		d = d->next;
+	}
+	return count;
+}
+
+USBmX_DeviceRef USBmX_DefaultDevice(USBmX_ContextRef ctx)
+{
+	if(ctx) return ctx->deviceList;
+	return NULL;
+}
+
+USBmX_DeviceRef USBmX_DeviceWithSerial(USBmX_ContextRef ctx, const char *serial)
+{
+	if(!ctx || !serial) return NULL;
+	USBmX_DeviceRef dev = ctx->deviceList;
+	#if USBMX_DEBUG_LOG
+	USBMX_LOG("Searching for device...");
+	#endif
+	while(dev) {
+		if(!strcmp(serial, dev->serialNumber))
+		{
+			#if USBMX_DEBUG_LOG
+			USBMX_LOG("Device found! %x (# %s)", dev, dev->serialNumber);
+			#endif			
+			return dev;
+		}
+		dev = dev->next;
+	}
+	#if USBMX_DEBUG_LOG
+	USBMX_LOG("Device not found");
+	#endif
+	return NULL;
+}
+
+int USBmX_RawCommand(USBmX_DeviceRef dev, 
+	const u_char command[8], 
+	u_char response[8])
+{
+	if(!dev) goto err;
+	if(SetReport(dev, command, 8) < 0) goto err;
+	if(response) {
+		memcpy(response, dev->response_buffer, 8);
+	}
+	return 0;
+err:
+	return -1;
+}
+
+static int CreateDeviceRef(USBmX_ContextRef ctx, io_object_t dev, USBmX_DeviceRef *dev_out)
+{
+	IOReturn rc;
+	kern_return_t result;
+
+	USBmX_DeviceRef dr = USBMX_ALLOC(USBmX_Device);
+	dr->next = NULL;
+	dr->ctx = ctx;
+	dr->userData = NULL;
+	
+	// Get device properties
+	result = IORegistryEntryCreateCFProperties(
+		dev, &dr->properties,
+		kCFAllocatorDefault, kNilOptions);
+	if(result != KERN_SUCCESS || !dr->properties) {
+		USBMX_LOG("IORegistryEntryCreateCFProperties Error %x", result);			
+		goto err;
+	}
+
+	dr->serialNumber = strdup(
+		dict_get_string(
+			dr->properties,
+			CFSTR(kIOHIDSerialNumberKey), 
+			CFStringGetSystemEncoding()));
+	dr->deviceName = strdup(
+		dict_get_string(
+			dr->properties,
+			CFSTR(kIOHIDProductKey), 
+			CFStringGetSystemEncoding()));
+	dr->manufacturer = strdup(
+		dict_get_string(
+			dr->properties,
+			CFSTR(kIOHIDManufacturerKey), 
+			CFStringGetSystemEncoding()));
+
+	// Add device to head of list if we don't already have it
+	if(USBmX_DeviceWithSerial(ctx, dr->serialNumber) == NULL)
+	{
+		#if USBMX_DEBUG_LOG
+		USBMX_LOG("Adding device");
+		#endif
+		dr->next = ctx->deviceList;
+		ctx->deviceList = dr;
+	} else {
+		#if USBMX_DEBUG_LOG
+		USBMX_LOG("Already have device");
+		#endif
+		return 0;
+	}
+
+	#if USBMX_DEBUG_LOG
+	USBMX_LOG("Found Device (# %s)", dr->serialNumber);
+	USBMX_LOG("kIOHIDManufacturerKey: \"%s\"",
+		dict_get_string(dr->properties, 
+		CFSTR(kIOHIDManufacturerKey), CFStringGetSystemEncoding()));
+	#endif
+
+	{	// Set up the device
+		SInt32 score = 0;
+		IOCFPlugInInterface **plugIn;
+
+		if((rc = IOCreatePlugInInterfaceForService(
+			dev,
+			kIOHIDDeviceUserClientTypeID,
+			kIOCFPlugInInterfaceID,
+			&plugIn, &score)) != kIOReturnSuccess)
+		{
+				USBMX_LOG("IOCreatePlugInInterfaceForService Error %x", rc);
+				goto err;
+		}
+		
+		// Grab interface
+		IOHIDDeviceInterface **interface;
+		HRESULT prc;
+		prc = (*plugIn)->QueryInterface(plugIn,
+			CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID),
+			(LPVOID)&interface);
+		(*plugIn)->Release(plugIn);
+		if(prc != S_OK) {
+			USBMX_LOG("QueryInterface Error %x", prc);
+			goto err;
+		}
+		
+		dr->interface = interface;
+
+		// Set up removal callback
+		if((rc = (*interface)->setRemovalCallback(interface, 
+			DeviceRemovedHandler, NULL, (void *)dr)) != kIOReturnSuccess)
+		{
+			USBMX_LOG("(*interface)->setRemovalCallback() Error %x", rc);
+			goto err1;
+		}
+
+		if((rc = (*interface)->open(interface, 0)) != kIOReturnSuccess)
+		{
+			USBMX_LOG("(*interface)->open() Error %x", rc);
+			goto err1;
+        }
+
+		mach_port_t async_port;
+		if((rc = (*interface)->createAsyncPort(interface, &async_port)) != kIOReturnSuccess)
+		{
+			USBMX_LOG("(*interface)->createAsyncPort() Error %x", rc);
+			goto err1;
+        }
+		
+		CFRunLoopSourceRef rl_source;
+		if((rc = (*interface)->createAsyncEventSource(interface, &rl_source)) != kIOReturnSuccess)
+		{
+			USBMX_LOG("(*interface)->createAsyncEventSource() Error %x", rc);
+			goto err1;
+		}
+
+		CFRunLoopAddSource(
+			CFRunLoopGetCurrent(),
+			rl_source,
+			kCFRunLoopDefaultMode);
+		if((rc = ((IOHIDDeviceInterface122*)*interface)->setInterruptReportHandlerCallback(
+			interface,
+			dr->response_buffer,
+			USBMX_RESPONSE_BUFFER_SIZE,
+			InterruptReportHandler,
+			(void *)dr,
+			NULL)) != kIOReturnSuccess)
+		{
+			USBMX_LOG("(*interface)->setInterruptReportHandlerCallback() Error %x", rc);
+			goto err1;
+		}
+	}
+	
+	*dev_out = dr;
+	////////////////
+	//USBMX_LOG("NEED TO CALL DEVICE ADDED CALLBACK");
+	if(ctx->deviceAdded)
+	{
+		(*ctx->deviceAdded)(dr, ctx->deviceAdded_cookie);
+	}
+	////////////////
+	return 0;
+err1:
+	(*dr->interface)->Release(dr->interface);
+err:
+	IOObjectRelease(dev);
+	free(dr);
+	return -1;
+}
+
+int USBmX_ScanForDevices(USBmX_ContextRef ctx)
+{
+	IOReturn rc;	
+	io_iterator_t iterator;
+	io_object_t d;
+	int n_devices = 0;
+	
+	CFRetain(ctx->matchingDict);
+	
+	// Narrow it down to devices matching vendor and product IDs
+	if(rc = IOServiceGetMatchingServices(
+		kIOMasterPortDefault,
+		ctx->matchingDict,
+		&iterator) != kIOReturnSuccess)
+	{
+			USBMX_LOG("IOServiceGetMatchingServices Error %x", rc);
+			goto err;
+	}
+
+	// Iterate over each found device
+	while(d = IOIteratorNext(iterator))
+	{
+		USBmX_DeviceRef dev;
+		if(CreateDeviceRef(ctx, d, &dev) < 0) goto err1;
+		n_devices++;
+	}
+	
+	IOObjectRelease(iterator);
+
+noerr:
+	return n_devices;
+err1:
+	IOObjectRelease(iterator);
+err:
+	USBMX_LOG("USBmX_FindDevices Failed!");
+	return -1;
+}
+
+USBmX_ContextRef USBmX_Create(void)
+{
+	USBmX_ContextRef ctx = NULL;
+	if(ctx = USBMX_ALLOC(USBmX_Context))
+	{
+		ctx->deviceList = NULL;
+		ctx->matchingDict = NULL;
+		ctx->deviceAdded = NULL;
+		ctx->deviceRemoved = NULL;
+		ctx->deviceAdded_cookie = NULL;
+		ctx->deviceRemoved_cookie = NULL;
+	}
+	return ctx;
+}
+
+int USBmX_Begin(USBmX_ContextRef ctx)
+{
+//	USBmX_ContextRef ctx = USBMX_ALLOC(USBmX_Context);
+//	ctx->deviceList = NULL;
+//	ctx->matchingDict = NULL;
+	if(!ctx) return -1;
+
+	// Grab root IOKit HID services
+	UNLESS(ctx->matchingDict = IOServiceMatching(kIOHIDDeviceKey))
+	{
+		USBMX_LOG("IOServiceMatching(kIOHIDDeviceKey) Error");
+		goto err;
+	}
+
+	{	// Add product and vendor keys to matchingDict
+		SInt32 product = USBMX_ID_PRODUCT;
+		SInt32 vendor = USBMX_ID_VENDOR;
+		CFNumberRef num = NULL;
+		#define USBMX_DNA(NAME, VALUE) \
+			UNLESS(num = CFNumberCreate( \
+					kCFAllocatorDefault, \
+					kCFNumberSInt32Type, \
+					& VALUE )) { \
+				USBMX_LOG("CFNumber Error"); \
+				goto err; \
+			} \
+			CFDictionaryAddValue(ctx->matchingDict, CFSTR(NAME), num); \
+			CFRelease(num);
+		USBMX_DNA(kIOHIDVendorIDKey, vendor)
+		USBMX_DNA(kIOHIDProductIDKey, product)
+		#undef USBMX_DNA
+	}
+
+	{	// Set up onPlug handler
+		io_iterator_t plugIterator;
+		IONotificationPortRef plugPort;
+		//CFRunLoopSourceRef runLoopSource;
+		
+		//CFMutableDictionaryRef hd = IOServiceMatching(kIOHIDDeviceKey);
+		CFRetain(ctx->matchingDict);
+
+		UNLESS(plugPort = IONotificationPortCreate(kIOMasterPortDefault))
+		{
+			USBMX_LOG("IONotificationPortCreate error");		
+		}
+		
+		CFRunLoopAddSource(
+			CFRunLoopGetCurrent(),
+			IONotificationPortGetRunLoopSource(plugPort),
+			kCFRunLoopDefaultMode);
+		if(IOServiceAddMatchingNotification(
+			plugPort,
+			kIOFirstMatchNotification,
+			ctx->matchingDict,
+			DeviceAddedHandler,
+			(void *)ctx,
+			&plugIterator) != KERN_SUCCESS)
+		{
+			USBMX_LOG("IOServiceAddMatchingNotification error");		
+		}
+		DeviceAddedHandler((void *)ctx, plugIterator);
+	}
+
+	//if(USBmX_ScanForDevices(ctx) < 0) goto err;
+noerr:
+	//return ctx;
+	return 0;
+err:
+	USBMX_LOG("USBmX_Init Failed!");
+	free(ctx);
+	//return NULL;
+	return -1;
+}
+
+int USBmX_Destroy(USBmX_ContextRef ctx)
+{
+	if(!ctx) return -1;
+	free(ctx);
+	return 0;
+}
+
+int USBmX_InitPorts(USBmX_DeviceRef dev)
+{
+	int r;
+	u_char res[8];
+	static const u_char cmd[8] = {
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;
+}
+
+int USBmX_WriteA(USBmX_DeviceRef dev, u_char value)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x01,
+		value,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;	
+}
+
+int USBmX_WriteB(USBmX_DeviceRef dev, u_char value)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x02,
+		value,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;	
+}
+
+int USBmX_WriteABit(USBmX_DeviceRef dev, u_char v_and, u_char v_or)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x03,
+		v_and,
+		v_or,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;	
+}
+
+int USBmX_WriteBBit(USBmX_DeviceRef dev, u_char v_and, u_char v_or)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x04,
+		v_and,
+		v_or,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;	
+}
+
+int USBmX_ReadA(USBmX_DeviceRef dev, u_char *value_out)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x05,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(value_out) *value_out = res[1];
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;
+}
+
+int USBmX_ReadB(USBmX_DeviceRef dev, u_char *value_out)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x06,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(value_out) *value_out = res[1];
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;
+}
+
+int USBmX_SetBit(USBmX_DeviceRef dev, u_char line)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x07,
+		line,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;
+}
+
+int USBmX_ResetBit(USBmX_DeviceRef dev, u_char line)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x08,
+		line,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;
+}
+
+int USBmX_DirectionA(USBmX_DeviceRef dev, u_char dir0, u_char dir1)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x09,
+		dir0,
+		dir1,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;
+}
+
+int USBmX_DirectionB(USBmX_DeviceRef dev, u_char dir0, u_char dir1)
+{
+	int r;
+	u_char res[8];
+	u_char cmd[8] = {
+		0x0A,
+		dir0,
+		dir1,
+		0x00,
+		0x00,
+		0x00,
+		0x00,
+		0x00
+	};
+	r = USBmX_RawCommand(dev, cmd, res);
+	if(r < 0) return r;
+	if(res[0] != cmd[0]) return -1;
+	return 0;
+}
+
+int USBmX_StrobeWrite(USBmX_DeviceRef dev, 
+	u_char data, 
+	u_char port_select, 
+	u_char strobe_line,
+	u_char pulse_length)
+{
+	return -1;
+}
+
+int USBmX_StrobeRead(USBmX_DeviceRef dev, 
+	u_char *data_out, 
+	u_char port_select, 
+	u_char strobe_line,
+	u_char pulse_length)
+{
+	return -1;
+}
+
+int USBmX_StrobeWrites(USBmX_DeviceRef dev, 
+	u_char n_bytes, 
+	u_char bytes[])
+{
+	return -1;
+}
+
+int USBmX_InitLCD(USBmX_DeviceRef dev,
+	u_char rw_select,
+	u_char rs_select,
+	u_char port_select,
+	u_char e_select)
+{
+	return -1;
+}
+
+int USBmX_LCDCmd(USBmX_DeviceRef dev, u_char cmd)
+{
+	return -1;
+}
+
+int USBmX_LCDData(USBmX_DeviceRef dev, u_char data)
+{
+	return -1;
+}

+ 350 - 0
motion/USBMicroX.h

@@ -0,0 +1,350 @@
+#ifndef _USBMICROX_H_
+#define _USBMICROX_H_
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <libgen.h>
+#include <mach/mach.h>
+#include <sys/types.h>
+#include <CoreFoundation/CFNumber.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOKit/IOKitLib.h>
+#include <IOKit/IOCFPlugIn.h>
+#include <IOKit/usb/IOUSBLib.h>
+#include <IOKit/hid/IOHIDLib.h>
+#include <IOKit/hid/IOHIDKeys.h>
+
+#define USBMX_LOG(S, ...) \
+	({ fprintf(stderr, "/%s/%s/%d >>> " S "\n", basename(__FILE__) , \
+	__FUNCTION__ , __LINE__ , ## __VA_ARGS__ ); })
+#define USBMX_CMD(...) (u_char[8]){ __VA_ARGS__ }
+#define USBMX_ALLOC(TYPE) (TYPE*)malloc(sizeof(TYPE))
+
+#define USBMX_DEBUG_LOG 0
+#define USBMX_RESPONSE_BUFFER_SIZE 8
+
+struct _USBmX_Context;
+
+typedef struct _USBmX_Device {
+	struct _USBmX_Context *ctx;
+	IOHIDDeviceInterface **interface;
+	CFMutableDictionaryRef properties;
+	const char *serialNumber;
+	const char *deviceName;
+	const char *manufacturer;
+	int response_ready;
+	u_char response_buffer[USBMX_RESPONSE_BUFFER_SIZE];
+	struct _USBmX_Device *next;
+	void *userData;
+} USBmX_Device;
+typedef USBmX_Device* USBmX_DeviceRef;
+
+typedef void (*USBmX_DeviceAddedProc)(USBmX_DeviceRef dev, void *cookie);
+typedef void (*USBmX_DeviceRemovedProc)(USBmX_DeviceRef dev, void *cookie);
+
+typedef struct _USBmX_Context {
+	USBmX_DeviceRef deviceList;
+	CFMutableDictionaryRef matchingDict;
+	USBmX_DeviceAddedProc deviceAdded;
+	USBmX_DeviceRemovedProc deviceRemoved;
+	void *deviceAdded_cookie;
+	void *deviceRemoved_cookie;
+} USBmX_Context;
+typedef USBmX_Context* USBmX_ContextRef;
+
+/*
+ *	USBmX_Create:
+ *	Creates a USBmX_ContextRef used in all other
+ *	routines.  Should return a valid USBmX_ContextRef
+ *	even if no devices are conected.
+ */
+extern USBmX_ContextRef USBmX_Create(void);
+
+extern int USBmX_DeviceAddedHandler(
+			USBmX_ContextRef ctx, 
+			USBmX_DeviceAddedProc proc, 
+			void *cookie);
+extern int USBmX_DeviceRemovedHandler(
+			USBmX_ContextRef ctx, 
+			USBmX_DeviceRemovedProc proc, 
+			void *cookie);
+
+/*
+ *	USBmX_Begin:
+ *	You must call USBmX_Begin to init the USB
+ *	subsystem.  Call before sending any commands.
+ *	Returns < 0 on error.
+ */
+extern int USBmX_Begin(USBmX_ContextRef ctx);
+
+/*
+ *	USBmX_ScanForDevices:
+ *	Call to manually re-scan the USB bus for newly
+ *	connected devices.  You should never have to
+ *	call this yourself (there are internal
+ *	notifications that should call this automatically
+ *	when a device is plugged in.
+ *	Returns < 0 on error.
+ */
+extern int USBmX_ScanForDevices(USBmX_ContextRef ctx);
+
+/*
+ *	USBmX_Destroy:
+ *	Call when you are done messing with all devices.
+ */
+extern int USBmX_Destroy(USBmX_ContextRef ctx);
+
+/*
+ *	USBmX_DeviceCount:
+ *	Get the number of connected devices.
+ */
+extern int USBmX_DeviceCount(USBmX_ContextRef ctx);
+
+/*
+ *	USBmX_DefaultDevice:
+ *	Get the default devico (generally the last device
+ *	that was plugged in.  Useful if you only have 1
+ *	device plugged in and don't want to mess with the
+ *	serial number.  Returns NULL if there are no
+ *	devices connected.
+ */
+extern USBmX_DeviceRef USBmX_DefaultDevice(USBmX_ContextRef ctx);
+
+/*
+ *	USBmX_DeviceWithSerial:
+ *	Get the device matching the provided serial
+ *	number.  Returns NULL if the device cannot be
+ *	found.
+ */
+extern USBmX_DeviceRef USBmX_DeviceWithSerial(USBmX_ContextRef ctx, const char *serial);
+
+/*
+ *	USBmX_DumpCommand:
+ *	Small utility function that prints out an 8 byte
+ *	buffer in human readable form.  Useful for
+ *	printing commands and responses.
+ */
+extern void USBmX_DumpCommand(u_char buf[]);
+
+/*
+ *	USBmX_DeviceInfo:
+ *	Outputs as much infomation as possible on a
+ *	given device.
+ */
+extern int USBmX_DeviceInfo(USBmX_DeviceRef dev);
+
+
+/*
+*****************************************************
+*****************  Commands  ************************
+*****************************************************
+The following command functions return 0 on success
+and < 0 on error.
+
+Where the command demands that you chose a 'line'
+(A0..A7, or B0..B7), the table below may be helpful:
+Value	Line
+-----	----
+0x00	A0
+0x01	A1
+0x02	A2
+0x03	A3
+0x04	A4
+0x05	A5
+0x06	A6
+0x07	A7
+0x08	B0
+0x09	B1
+0x0A	B2
+0x0B	B3
+0x0C	B4
+0x0D	B5
+0x0E	B6
+0x0F	B7
+*/
+
+
+/*
+ *	USBmX_RawCommand:
+ *	Performs a raw command on the specifie device.
+ *	More info can be found here:
+ *	http://usbmicro.com/odn/index.html
+ *	under "Raw Command Summary"
+ */
+extern int USBmX_RawCommand(USBmX_DeviceRef dev, 
+	const u_char command[8], 
+	u_char response[8]);
+
+/*
+The initial state of the ports on power up is that all
+of the 16 lines are set to be inputs. This command 
+duplicates the power up conditions of setting all lines 
+to input.
+*/
+extern int USBmX_InitPorts(USBmX_DeviceRef dev);
+
+/*
+Write a byte value to specified port. The possible values 
+range from 0-255 (0x00..0xFF) (passed in value).
+*/
+extern int USBmX_WriteA(USBmX_DeviceRef dev, u_char value);
+extern int USBmX_WriteB(USBmX_DeviceRef dev, u_char value);
+
+/*
+Write masked values to specified port. The net result of 
+writing masked values is that only the specified bits will 
+be written. The resulting port condition is the logic 
+combination of the current port state ANDed with the 
+first term (v_and) and then ORed with the second (v_or). 
+This command can affect any number of lines on the port.
+*/
+extern int USBmX_WriteABit(USBmX_DeviceRef dev, u_char v_and, u_char v_or);
+extern int USBmX_WriteBBit(USBmX_DeviceRef dev, u_char v_and, u_char v_or);
+
+/*
+Read a byte value from specified port. The returned value
+(returned in *value_out) is the state of the port lines that 
+an external device has set, if the line is an input. 
+The lines that might be configured as outputs return the 
+output state.
+*/
+extern int USBmX_ReadA(USBmX_DeviceRef dev, u_char *value_out);
+extern int USBmX_ReadB(USBmX_DeviceRef dev, u_char *value_out);
+
+/*
+Set a single bit/line high. (0..15)
+*/
+extern int USBmX_SetBit(USBmX_DeviceRef dev, u_char line);
+
+/*
+Set a single bit/line low. (0..15)
+*/
+extern int USBmX_ResetBit(USBmX_DeviceRef dev, u_char line);
+
+/*
+Set the i/o direction of specified port.
+dir0 is written to the direction0 control register, dir1 
+is written to the direction1 control register appropriate 
+for the port.
+The individual pins of the ports can be set to input, or 
+output. Input is set when both bits associated with a 
+particular line are set to 0. There are different types 
+of outputs available for the board, but generally setting 
+both of the direction bits to 1 will suffice.
+High order bits of dir0 and dir1 correspond to high order
+lines.
+*/
+extern int USBmX_DirectionA(USBmX_DeviceRef dev, u_char dir0, u_char dir1);
+extern int USBmX_DirectionB(USBmX_DeviceRef dev, u_char dir0, u_char dir1);
+
+/*
+Strobe write of a byte value to a port. This command 
+selects port A (port_select=0x00) or B (port_select=0x01) 
+for the written byte, as well as a polarity (negative or 
+positive) and a line (A.0 - B.7 (strobe_line=0..15)) to 
+toggle. The byte is written and then the line toggled.
+pulse_length can be anywhere between 0x00..0xFF.
+Default is to have the strobe negative-going 
+(high->low->high).  Add 0x10 to strobe_line to have the
+pulse be positive-going (low->high->low).
+*/
+extern int USBmX_StrobeWrite(USBmX_DeviceRef dev, 
+	u_char data, 
+	u_char port_select, 
+	u_char strobe_line,
+	u_char pulse_length);
+
+/*
+Strobe read of a byte value from a port. This command 
+selects port A (port_select=0x00) or B (port_select=0x01) 
+for the read byte, as well as a polarity (negative or 
+positive) and a line (A.0 - B.7 (strobe_line=0..15)) to 
+toggle. The line is toggled and the byte is read.
+pulse_length can be anywhere between 0x00..0xFF.
+Default is to have the strobe negative-going 
+(high->low->high).  Add 0x10 to strobe_line to have the
+pulse be positive-going (low->high->low).
+*data_out is set to the byte read.
+*/
+extern int USBmX_StrobeRead(USBmX_DeviceRef dev, 
+	u_char *data_out, 
+	u_char port_select, 
+	u_char strobe_line,
+	u_char pulse_length);
+
+/*
+Strobe write of a 1 to 6 byte value to a port. This 
+command uses port A or B for the written byte, as well 
+as a polarity (negative or positive) and a line 
+(A.0 - B.7) to toggle based on the previously executed 
+USBmX_StrobeWrite or USBmX_StrobeRead.
+Control this command's port for the written byte, 
+polarity and strobe line by using the USBmX_StrobeWrite 
+or USBmX_StrobeRead. This command will then use the 
+same selected port, polarity, and strobe line to write 
+up to 6 bytes.
+n_bytes is the number of bytes to write.
+bytes[] is an array of bytes, the first n_bytes values
+will be written.  It must be at least of size n_bytes.
+*/
+extern int USBmX_StrobeWrites(USBmX_DeviceRef dev, 
+	u_char n_bytes, 
+	u_char bytes[]);
+
+/*
+Strobe read of a 1 to 6 byte value from a port. This 
+command uses port A or B for the read byte, as well 
+as a polarity (negative or positive) and a line 
+(A.0 - B.7) to toggle based on the previously executed 
+USBmX_StrobeWrite or USBmX_StrobeRead.
+Control this command's port for the written byte, 
+polarity and strobe line by using the USBmX_StrobeWrite 
+or USBmX_StrobeRead. This command will then use the 
+same selected port, polarity, and strobe line to read 
+up to 6 bytes.
+n_bytes is the number of bytes to read.
+bytes[] is an array of bytes that the read values will be 
+copied into.  It must be at least of size n_bytes.
+*/
+extern int USBmX_StrobeReads(USBmX_DeviceRef dev, 
+	u_char n_bytes, 
+	u_char bytes[]);
+
+/*
+Initialize LCD variables. This includes the selection 
+of the lines used for RW, RS, E and the port used for 
+data. RW, RS, and E are reset. These commands are 
+appropriate for HD44780 devices and devices that are 
+compatible.
+port_select = 0 for port A, or 1 for port B.
+See table (way) above for line->number mappings.
+*/
+extern int USBmX_InitLCD(USBmX_DeviceRef dev,
+	u_char rw_select,
+	u_char rs_select,
+	u_char port_select,
+	u_char e_select);
+	
+/*
+Write a command to the LCD. The RS, RW, E lines and the 
+data port are selected by the USBmX_InitLCD. The data 
+byte is written to the selected port and the control 
+lines are set appropriately. The E line is toggled for 
+five to eight microseconds.
+*/
+extern int USBmX_LCDCmd(USBmX_DeviceRef dev, u_char cmd);
+
+/*
+Write a character to the LCD. The RS, RW, E lines and 
+the data port are selected by the USBmX_InitLCD. The data 
+byte is written to the selected port and the control 
+lines are set appropriately. The E line is toggled for 
+five to eight microseconds.
+*/
+extern int USBmX_LCDData(USBmX_DeviceRef dev, u_char data);
+
+// TODO: SPI + Stepper
+
+#endif /* _USBMICROX_H_ */

+ 73 - 0
motion/main.c

@@ -0,0 +1,73 @@
+#include "USBMicroX.h"
+#include <fcntl.h>
+
+#define OUT_FILE "MOTION"
+#define SERIAL "101128160542"
+#define SLEEP_DELAY 250000
+#define TIMEOUT 2
+
+// Prototypes
+void timeout(int sig);
+
+int main(int argc, const char * argv[])
+{
+	int fd;
+	unsigned char data;
+	
+	// Grab a USBmicroX context
+	USBmX_ContextRef ctx = USBmX_Create();
+	USBmX_Begin(ctx);
+	
+	// Find the specified device
+	USBmX_DeviceRef device = USBmX_DeviceWithSerial(ctx, SERIAL);
+	if (!device) {
+		fprintf(stderr, "Device not found: %s\n", SERIAL);
+		exit(1);
+	}
+	
+	// Monitor for USB timeouts
+	signal(SIGALRM, timeout);
+
+	// Read forever
+	while (1) {
+		// Read from the device
+		alarm(TIMEOUT);
+		if (USBmX_ReadA(device, &data) != 0) {
+			fprintf(stderr, "Device read error\n");
+			exit(1);
+		}
+		alarm(0);
+		
+		// Pin A5 is high when we've got motion
+		if (data & 0x20) {
+			printf("Motion detected");
+			if (utimes(OUT_FILE, NULL) != 0) {
+				fprintf(stderr, "Creating outfile\n");
+				if (errno == ENOENT) {
+					fd = open(OUT_FILE, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+					if (fd <= 0 || close(fd) != 0) {
+						fprintf(stderr, "Error creating output file (%d): %s\n", errno, OUT_FILE);
+						exit(1);
+					}
+				} else {
+					fprintf(stderr, "Error touching output file (%d): %s\n", errno, OUT_FILE);
+				}
+			}
+		}
+		printf("\n");
+		
+		// Sleep and loop
+		usleep(SLEEP_DELAY);
+	}
+	
+	// Cleanup
+	USBmX_Destroy(ctx);
+    return 0;
+}
+
+// Catch the timeout
+void timeout(int sig) {
+	signal(sig, SIG_IGN);
+	fprintf(stderr, "Timeout waiting for USB read\n");
+	exit(1);
+}

+ 13 - 0
motion/makefile

@@ -0,0 +1,13 @@
+default: USBMicroX
+
+USBMicroX: USBMicroX.o main.o
+	gcc -framework CoreFoundation -framework IOKit -o USBMicroX USBMicroX.o main.o
+
+main.o: main.c
+	gcc -c main.c
+
+USBMicroX.o: USBMicroX.c USBMicroX.h
+	gcc -c USBMicroX.c
+
+clean:
+	rm -f main.o USBMicroX.o USBMicroX