/*
 * uDMX controller daemon - Chris Fallin <chris@cfallin.org>
 * based on the commandline utility, with the copyright notice below:
 */

/* Name: powerSwitch.c
 * Project: PowerSwitch based on AVR USB driver
 * Author: Christian Starkjohann
 * Creation Date: 2005-01-16
 * Tabsize: 4
 * Copyright: (c) 2005 by OBJECTIVE DEVELOPMENT Software GmbH
 * License: Proprietary, free under certain conditions. See Documentation.
 * This Revision: $Id: uDMX.c,v 1.1.1.1 2006/02/15 17:55:06 cvs Exp $
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <usb.h>    /* this is libusb, see http://libusb.sourceforge.net/ */

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <signal.h>
#include <pthread.h>

#define USBDEV_SHARED_VENDOR    0x16C0  /* VOTI */
#define USBDEV_SHARED_PRODUCT   0x05DC  /* Obdev's free shared PID */
/* Use obdev's generic shared VID/PID pair and follow the rules outlined
 * in firmware/usbdrv/USBID-License.txt.
 */

#define cmd_SetChannelRange 2

static int  usbGetStringAscii(usb_dev_handle *dev, int index, int langid, char *buf, int buflen)
{
char    buffer[256];
int     rval, i;

    if((rval = usb_control_msg(dev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + index, langid, buffer, sizeof(buffer), 1000)) < 0)
        return rval;
    if(buffer[1] != USB_DT_STRING)
        return 0;
    if((unsigned char)buffer[0] < rval)
        rval = (unsigned char)buffer[0];
    rval /= 2;
    /* lossy conversion to ISO Latin1 */
    for(i=1;i<rval;i++){
        if(i > buflen)  /* destination buffer overflow */
            break;
        buf[i-1] = buffer[2 * i];
        if(buffer[2 * i + 1] != 0)  /* outside of ISO Latin1 range */
            buf[i-1] = '?';
    }
    buf[i-1] = 0;
    return i-1;
}


/* PowerSwitch uses the free shared default VID/PID. If you want to see an
 * example device lookup where an individually reserved PID is used, see our
 * RemoteSensor reference implementation.
 */
static usb_dev_handle   *findDevice(void)
{
struct usb_bus      *bus;
struct usb_device   *dev;
usb_dev_handle      *handle = 0;

    usb_find_busses();
    usb_find_devices();
    for(bus=usb_busses; bus; bus=bus->next){
        for(dev=bus->devices; dev; dev=dev->next){
            if(dev->descriptor.idVendor == USBDEV_SHARED_VENDOR && dev->descriptor.idProduct == USBDEV_SHARED_PRODUCT){
                char    string[256];
                int     len;
                handle = usb_open(dev); /* we need to open the device in order to query strings */
                if(!handle){
                    fprintf(stderr, "Warning: cannot open USB device: %s\n", usb_strerror());
                    continue;
                }
                /* now find out whether the device actually is obdev's Remote Sensor: */
                len = usbGetStringAscii(handle, dev->descriptor.iManufacturer, 0x0409, string, sizeof(string));
                if(len < 0){
                    fprintf(stderr, "warning: cannot query manufacturer for device: %s\n", usb_strerror());
                    goto skipDevice;
                }
                /* fprintf(stderr, "seen device from vendor ->%s<-\n", string); */
                if(strcmp(string, "www.anyma.ch") != 0)
                    goto skipDevice;
                len = usbGetStringAscii(handle, dev->descriptor.iProduct, 0x0409, string, sizeof(string));
                if(len < 0){
                    fprintf(stderr, "warning: cannot query product for device: %s\n", usb_strerror());
                    goto skipDevice;
                }
                /* fprintf(stderr, "seen product ->%s<-\n", string); */
                if(strcmp(string, "uDMX") == 0)
                    break;
skipDevice:
                usb_close(handle);
                handle = NULL;
            }
        }
        if(handle)
            break;
    }
    if(!handle)
        fprintf(stderr, "Could not find USB device www.anyma.ch/uDMX\n");
    return handle;
}

static int udmx_open(usb_dev_handle **handle)
{
  *handle = NULL;
  
  usb_init();
  usb_set_debug(1);
  if((*handle = findDevice()) == NULL)
    return -1;
    
  return 0;
}

// data starts at channel 0
static int udmx_send(usb_dev_handle *handle, char *data, int count)
{
  int n;
  char buf[512];
  
  memset(buf, 0, 512);
  memcpy(buf, data, (count < 512) ? count : 512);
  
  n = usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE |
      USB_ENDPOINT_OUT, cmd_SetChannelRange, count, 0, buf, count, 5000);
  if(n < 0)
  {
    fprintf(stderr, "USB error: %s\n", usb_strerror());
    return 1;
  }
    
  return 0;
}

static int udmx_close(usb_dev_handle *handle)
{
  usb_close(handle);
}

// p is the 512-byte live DMX buffer
void *update_thread_body(void *p)
{
  int err = 0;
  char *buf = (char *)p;
  
  usb_dev_handle *handle = NULL;

  while(1)
  {
  retry:
    if(!handle)
    {
      if(udmx_open(&handle))
      {
        fprintf(stderr, "Error opening USB device.\n");
        return 0;
      }
    }

    if(udmx_send(handle, buf, 512))
    {
      err++;
      fprintf(stderr, "uDMX update error (count: %d)\n", err);
    }

    // refresh rate of 30 Hz
    usleep(1000000 / 30);
  }
}

// so that a Ctrl-C removes our control FIFO properly
void sig_handler(int sig)
{
  unlink("/tmp/udmx-control");
  exit(0);
}

int main(int argc, char **argv)
{
  usb_dev_handle *handle = NULL;
  int control_fd;
  
  int data;
  int channel;
  
  char buf[512];
  char live_buf[512];

  pthread_t update_thread;
  
  signal(SIGTERM, sig_handler);
  signal(SIGINT, sig_handler);
  
  if(mkfifo("/tmp/udmx-control", 0666) == -1)
  {
    fprintf(stderr, "Error creating control FIFO.\n");
    goto out;
  }
  
  memset(buf, 0, 512);
  memset(live_buf, 0, 512);
  channel = 0;
  data = 0;
  
  control_fd = -1;

  // start the update thread
  if(pthread_create(&update_thread, 0, update_thread_body, live_buf))
  {
    fprintf(stderr, "Error creating update thread.\n");
    goto out_all;
  }
  
  while(1)
  {
    // read bytes from control FIFO
    // top three bits determine a byte's role:
    //    000    - data low     (bits 0 - 4)
    //    001    - data high    (bits 5 - 7)
    //    010    - channel low  (bits 0 - 4)
    //    011    - channel high (bits 5 - 8)
    //    100    - send (updates live DMX buffer in device)
    //
    // the control stream feeds us channel first, then data, low first, then
    // high - so once we get a channel high byte, we set a channel pointer,
    // and once we get a data high byte, we send the specified data to the
    // channel indicated by the channel pointer.
    
    char c;
    
    if((control_fd == -1) || (read(control_fd, &c, 1) < 1))
    {
      // EOF, or simply FIFO not open (initial condition): (re-)open FIFO
      if((control_fd = open("/tmp/udmx-control", O_RDONLY)) < 0)
      {
        fprintf(stderr, "Error: could no open FIFO.\n");
        goto out_fifo;
      }
    }
    
    switch(c & 0xe0)
    {
    case 0x00:
      data = c & 0x1f;
      break;
    case 0x20:
      data |= ( (c & 0x07) << 5 );
      buf[channel] = data;
      break;
    case 0x40:
      channel = c & 0x1f;
      break;
    case 0x60:
      channel |= ( (c & 0x0f) << 5 );
      assert(channel < 512); // should always be the case because of arithmetic
      break;
    case 0x80:
      // update to live buffer
      memcpy(live_buf, buf, 512);
      break;
    }
  }
  
out_all:
out_fifo:
  unlink("/tmp/udmx-control");
out:
  return 0;
}
