/*
 * i2c_usb.c - test application for the i2c-tiny-usb interface
 *             http://www.harbaum.org/till/i2c_tiny_usb
 *
 *
 */

// #define NO_USB

/* Includes */
#include <hildon/hildon-program.h>

#include <gtk/gtkmain.h>
#include <gtk/gtklabel.h>

#include <glib.h>
#include <errno.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <usb.h>

/* ds1621 chip address (A0-A2 tied low) */
#define DS1621_ADDR  0x48

/* pcf8574 chip address (A0-A2 tied low) */
#define PCF8574_ADDR  0x20

#define LOOPS 100

#define USB_CTRL_IN    (USB_TYPE_CLASS | USB_ENDPOINT_IN)
#define USB_CTRL_OUT   (USB_TYPE_CLASS)

/* the vendor and product id was donated by ftdi ... many thanks!*/
#define I2C_TINY_USB_VID  0x0403
#define I2C_TINY_USB_PID  0xc631

#define I2C_M_RD                0x01

/* commands via USB, must e.g. match command ids firmware */
#define CMD_ECHO       0
#define CMD_GET_FUNC   1
#define CMD_SET_DELAY  2
#define CMD_GET_STATUS 3
#define CMD_I2C_IO     4
#define CMD_I2C_BEGIN  1  // flag to I2C_IO
#define CMD_I2C_END    2  // flag to I2C_IO

#define STATUS_IDLE          0
#define STATUS_ADDRESS_ACK   1
#define STATUS_ADDRESS_NAK   2

usb_dev_handle      *handle = NULL;
gboolean pcf8574_present = 0;
gboolean ds1621_present = 0;

/* global reference to main window */
HildonWindow *window;
void error(char *msg, char *parm);

/* write a set of bytes to the i2c_tiny_usb device */
int i2c_tiny_usb_write(int request, int value, int index) {
  if(usb_control_msg(handle, USB_CTRL_OUT, request, 
                      value, index, NULL, 0, 1000) < 0) {
    error("USB error: %s", usb_strerror());
    return -1;
  }
  return 1;
}

/* read a set of bytes from the i2c_tiny_usb device */
int i2c_tiny_usb_read(unsigned char cmd, void *data, int len) {
  int                 nBytes;

  /* send control request and accept return value */
  nBytes = usb_control_msg(handle, 
           USB_CTRL_IN, 
           cmd, 0, 0, data, len, 1000);

  if(nBytes < 0) {
    error("USB error: %s", usb_strerror());
    return nBytes;
  }

  return 0;
}

/* get i2c usb interface firmware version */
void i2c_tiny_usb_get_func(GtkWidget *vbox) {
  unsigned long func;
  
  if(i2c_tiny_usb_read(CMD_GET_FUNC, &func, sizeof(func)) == 0) {
    char str[64];

    sprintf(str, "Functionality = %lx\n", func);  
    gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(str), FALSE, FALSE, 0);
  } else
    gtk_box_pack_start(GTK_BOX(vbox), 
                       gtk_label_new("Unable to read functionality"), 
                       FALSE, FALSE, 0);
}

/* set a value in the I2C_USB interface */
void i2c_tiny_usb_set(unsigned char cmd, int value) {
  if(usb_control_msg(handle, 
             USB_TYPE_VENDOR, cmd, value, 0, 
             NULL, 0, 1000) < 0) {
    error("USB error: %s", usb_strerror());
  }
}

/* get the current transaction status from the i2c_tiny_usb interface */
int i2c_tiny_usb_get_status(void) {
  int i;
  unsigned char status;
  
  if((i=i2c_tiny_usb_read(CMD_GET_STATUS, &status, sizeof(status))) < 0) {
    error("Error reading status", NULL);
    return i;
  }

  return status;
}

/* write command and read an 8 or 16 bit value from the given chip */
int i2c_read_with_cmd(unsigned char addr, char cmd, int length) {
  unsigned char result[2];

  if((length < 0) || (length > sizeof(result))) {
    error("Request exceeds buffer size", NULL);
    return -1;
  } 

  /* write one byte register address to chip */
  if(usb_control_msg(handle, USB_CTRL_OUT, 
                     CMD_I2C_IO + CMD_I2C_BEGIN
                     + ((!length)?CMD_I2C_END:0),
                     0, addr, &cmd, 1, 
                     1000) < 1) {
    error("USB error: %s", usb_strerror());
    return -1;
  } 

  if(i2c_tiny_usb_get_status() != STATUS_ADDRESS_ACK) {
    error("Write command status failed!", NULL);
    return -1;
  }

  // just a test? return ok
  if(!length) return 0;

  if(usb_control_msg(handle, 
                     USB_CTRL_IN, 
                     CMD_I2C_IO + CMD_I2C_END,
                     I2C_M_RD, addr, (char*)result, length, 
                     1000) < 1) {
    error("USB error: %s", usb_strerror());
    return -1;
  } 

  if(i2c_tiny_usb_get_status() != STATUS_ADDRESS_ACK) {
    error("read data status failed", NULL);
    return -1;
  }

  // return 16 bit result
  if(length == 2)
    return 256*result[0] + result[1];

  // return 8 bit result
  return result[0];  
}

/* write a single byte to the i2c client */
int i2c_write_byte(unsigned char addr, char data) {

  /* write one byte register address to chip */
  if(usb_control_msg(handle, USB_CTRL_OUT, 
                     CMD_I2C_IO + CMD_I2C_BEGIN + CMD_I2C_END,
                     0, addr, &data, 1, 
                     1000) < 1) {
    error("USB error: %s", usb_strerror());
    return -1;
  } 

  if(i2c_tiny_usb_get_status() != STATUS_ADDRESS_ACK) {
    error("Write command status failed", NULL);
    return -1;
  }

  return 0;  
}

/* write a command byte and a single byte to the i2c client */
int i2c_write_cmd_and_byte(unsigned char addr, char cmd, char data) {
  char msg[2];

  msg[0] = cmd;
  msg[1] = data;

  /* write one byte register address to chip */
  if(usb_control_msg(handle, USB_CTRL_OUT, 
                     CMD_I2C_IO + CMD_I2C_BEGIN + CMD_I2C_END,
                     0, addr, msg, 2, 
                     1000) < 1) {
    error("USB error: %s", usb_strerror());
    return -1;
  } 

  if(i2c_tiny_usb_get_status() != STATUS_ADDRESS_ACK) {
    error("Write command status failed", NULL);
    return -1;
  }

  return 0;  
}

/* write a command byte and a 16 bit value to the i2c client */
int i2c_write_cmd_and_word(unsigned char addr, char cmd, int data) {
  char msg[3];

  msg[0] = cmd;
  msg[1] = data >> 8;
  msg[2] = data & 0xff;

  /* write one byte register address to chip */
  if(usb_control_msg(handle, USB_CTRL_OUT, 
                     CMD_I2C_IO + CMD_I2C_BEGIN + CMD_I2C_END,
                     0, addr, msg, 3, 
                     1000) < 1) {
    error("USB error: %s", usb_strerror());
    return -1;
  } 

  if(i2c_tiny_usb_get_status() != STATUS_ADDRESS_ACK) {
    error("Write command status failed", NULL);
    return -1;
  }

  return 0;  
}

/* read ds1621 control register */
void ds1621_read_control(void) {
  int result;

  do {
    result = i2c_read_with_cmd(DS1621_ADDR, 0xac, 1);
  } while(!(result & 0x80));
}

/************************** GUI related code ************************/

static void
button_clicked (GtkButton* button, gpointer data) {
  GtkWidget **checkBits = (GtkWidget**)data;
  int i, value = 0;

  for(i=0;i<8;i++) {
    value <<= 1;

    if(GTK_WIDGET_STATE(checkBits[i]))
      value |= 1;
  }

#ifndef NO_USB
  if(pcf8574_present)
    i2c_write_byte(PCF8574_ADDR, value);
#endif
}

static gboolean
update_temperature(gpointer data) {
  GtkLabel *label = (GtkLabel*)data;
  char str[32];
  int temp, counter, slope;

  /* just write command 0xee to start conversion */
  if(i2c_read_with_cmd(DS1621_ADDR, 0xee, 0) < 0)
    return 0;
      
  ds1621_read_control();
      
  temp = i2c_read_with_cmd(DS1621_ADDR, 0xaa, 2);
  if(temp < 0) return 0;
    
  /* read counter and slope values */
  counter = i2c_read_with_cmd(DS1621_ADDR, 0xa8, 1);
  slope = i2c_read_with_cmd(DS1621_ADDR, 0xa9, 1);
    
  /* use counter and slope to adjust temperature (see ds1621 datasheet) */
  temp = (temp & 0xff00) - 256/4;
  temp += 256 * (slope - counter) / slope;
      
  sprintf(str, "%d.%03d °C", temp>>8, 1000 * (temp & 0xff) / 256);
  gtk_label_set_text(label, str);

  return 1;
}

void error(char *msg, char *parm) {
  GtkWidget *dialog;

  dialog = gtk_message_dialog_new(GTK_WINDOW(window),
                                  GTK_DIALOG_DESTROY_WITH_PARENT,
                                  GTK_MESSAGE_ERROR,
                                  GTK_BUTTONS_CLOSE,
                                  msg, parm);
  gtk_dialog_run(GTK_DIALOG(dialog));
  gtk_widget_destroy(dialog);
}

int main(int argc, char *argv[]) {
  /* Create needed variables */
  HildonProgram *program;
  int i;

  /* pcf8574 parallel port interface */
  GtkWidget *vbox;
  GtkWidget *ds1621Frame;
  GtkWidget *tempLabel;
  GtkWidget *pcf8574Frame;
  GtkWidget *hbox, *buttonBox;
  GtkWidget *checkBits[8];
  GSource *temp_timer_src;

  struct usb_bus      *bus;
  struct usb_device   *dev;
  int ret;
  
  /* Initialize the GTK. */
  gtk_init(&argc, &argv);
  
  /* Create the hildon program and setup the title */
  program = HILDON_PROGRAM(hildon_program_get_instance());
  g_set_application_name("I²C-Tiny-USB Demo");
  
  /* Create HildonWindow and set it to HildonProgram */
  window = HILDON_WINDOW(hildon_window_new());
  hildon_program_add_window(program, window);

  /************* main view **************/
  
  gtk_container_add(GTK_CONTAINER(window),
                    GTK_WIDGET(vbox = gtk_vbox_new(FALSE, 0)));

  /************* hardware initialization **************/
#ifndef NO_USB
  usb_init();
  
  usb_find_busses();
  usb_find_devices();

  for(bus = usb_get_busses(); bus; bus = bus->next) {
    for(dev = bus->devices; dev; dev = dev->next) {
      if((dev->descriptor.idVendor == I2C_TINY_USB_VID) && 
         (dev->descriptor.idProduct == I2C_TINY_USB_PID)) {
        char str[128];
        
        sprintf(str, "\nI²C-Tiny-USB device on bus %s device %s", 
                bus->dirname, dev->filename);
        
        gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new(str), FALSE, FALSE, 0);

        /* open device */
        if(!(handle = usb_open(dev))) {
          error("Cannot open the device: %s", usb_strerror());
          exit(EIO);
        }
        break;
      }
    }
  }
  
  if(!handle) {
    error("No i2c_tiny_usb device attached", NULL);
    exit(ENODEV);
  }

  /* Get exclusive access to interface 0 */
  ret = usb_claim_interface(handle, 0);
  if (ret != 0) {
    error("USB error: %s", usb_strerror());
    exit(EPERM);
  }

  /* do some testing */
  i2c_tiny_usb_get_func(vbox);

  /* try to set i2c clock to 100kHz (10us), will actually result in ~50kHz */
  /* since the software generated i2c clock isn't too exact. in fact setting */
  /* it to 10us doesn't do anything at all since this already is the default */
  i2c_tiny_usb_set(CMD_SET_DELAY, 10);
#else
  gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new("\nNO_USB option set"), 
                     FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), gtk_label_new("No functions available\n"), 
                     FALSE, FALSE, 0);
#endif

  /************* create ds1621 frame **************/

  gtk_container_add(GTK_CONTAINER(vbox), 
                    GTK_WIDGET(ds1621Frame = gtk_frame_new(" ds1621 ")));

  gtk_container_add(GTK_CONTAINER(ds1621Frame),
                    GTK_WIDGET(hbox = gtk_hbox_new(FALSE, 0)));

  gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("Temperature:"),
                     FALSE, FALSE, 32);

  gtk_box_pack_start(GTK_BOX(hbox), tempLabel = gtk_label_new("--- °C"),
                     FALSE, FALSE, 0);

#ifndef NO_USB
  /* try to access ds1621 at address DS1621_ADDR */
  if(usb_control_msg(handle, USB_CTRL_IN, 
                     CMD_I2C_IO + CMD_I2C_BEGIN + CMD_I2C_END,
                     0, DS1621_ADDR, NULL, 0, 
                     1000) < 0) {
    error("USB error: %s", usb_strerror());
    goto quit;
  } 
  
  if(i2c_tiny_usb_get_status() == STATUS_ADDRESS_ACK) {
    ds1621_present = 1;

    /* activate one shot mode */
    if(i2c_write_cmd_and_byte(DS1621_ADDR, 0xac, 0x01) < 0)
      goto quit;
    
    /* wait 10ms */
    usleep(10000);

    /* build an update timer for the temperature display */
    temp_timer_src = g_timeout_source_new(1000);
    g_source_set_callback(temp_timer_src, update_temperature, tempLabel, NULL); 
    g_source_attach (temp_timer_src, NULL);
    g_source_unref(temp_timer_src);
  } else
    gtk_frame_set_label((GtkFrame*)ds1621Frame, " ds1621 - not found ");  
#endif

  /************* create pcf8574 frame **************/

  /* create a frame for the pcf8574 elements */
  gtk_container_add(GTK_CONTAINER(vbox),
                    GTK_WIDGET(pcf8574Frame = gtk_frame_new(" pcf8574 ")));
  
  gtk_container_add(GTK_CONTAINER(pcf8574Frame),
                    GTK_WIDGET(hbox = gtk_hbox_new(FALSE, 0)));

  gtk_box_pack_start(GTK_BOX(hbox), gtk_label_new("Output bits:"),
                     FALSE, FALSE, 32);

  /* create a button box for the eight buttons */
  gtk_box_pack_start(GTK_BOX(hbox), buttonBox = gtk_hbutton_box_new(),
                     FALSE, FALSE, 0);

  gtk_button_box_set_child_size((GtkButtonBox*)buttonBox, 0, 0);

  /* add the eight buttons */
  for(i=0;i<8;i++) {
    gtk_container_add(GTK_CONTAINER(buttonBox),
                      GTK_WIDGET(checkBits[i] = gtk_check_button_new()));

    g_signal_connect(G_OBJECT(checkBits[i]), "clicked",
                     G_CALLBACK(button_clicked), checkBits);
  }

#ifndef NO_USB
  /* try to access pcf8574 at address PCF8574_ADDR */
  if(usb_control_msg(handle, USB_CTRL_IN, 
                     CMD_I2C_IO + CMD_I2C_BEGIN + CMD_I2C_END,
                     0, PCF8574_ADDR, NULL, 0, 
                     1000) < 0) {
    
    error("USB error: %s", usb_strerror());
    goto quit;
  } 
  
  if(i2c_tiny_usb_get_status() == STATUS_ADDRESS_ACK) {
    pcf8574_present = 1;
    i2c_write_byte(PCF8574_ADDR, 0x00);  /* default value */
  } else
    gtk_frame_set_label((GtkFrame*)pcf8574Frame, " pcf8574 - not found ");  
#endif

  /***************************         *************************/

  gtk_box_pack_start(GTK_BOX(vbox), 
             gtk_label_new("\nhttp://www.harbaum.org/till/i2c_tiny_usb\n"),
                     FALSE, FALSE, 0);

  /* begin the main application */
  gtk_widget_show_all(GTK_WIDGET(window));
  
  /* Connect signal to X in the upper corner */
  g_signal_connect(G_OBJECT(window), "delete_event",
                   G_CALLBACK(gtk_main_quit), NULL);

  gtk_main();

 quit:
  ret = usb_release_interface(handle, 0);
  if (ret)
    error("USB error: %s\n", usb_strerror());
  
  usb_close(handle);
  
  return 0;
}