]> gitweb.maison.local Git - pyowl.git/commitdiff
First commit
authorLionel <lionel@ra.enneade.fdn.org>
Fri, 24 Oct 2014 13:06:33 +0000 (15:06 +0200)
committerLionel <lionel@ra.enneade.fdn.org>
Fri, 24 Oct 2014 13:06:33 +0000 (15:06 +0200)
README [new file with mode: 0644]
cm160Server.py [new file with mode: 0755]
driver/Makefile [new file with mode: 0644]
driver/README [new file with mode: 0644]
driver/cp210x.c [new file with mode: 0644]

diff --git a/README b/README
new file mode 100644 (file)
index 0000000..7504097
--- /dev/null
+++ b/README
@@ -0,0 +1,3 @@
+### PyOWL
+# Py daemon storing OWL data on MySQL
+
diff --git a/cm160Server.py b/cm160Server.py
new file mode 100755 (executable)
index 0000000..347047e
--- /dev/null
@@ -0,0 +1,1145 @@
+#!/usr/bin/python
+# -*- coding: utf8 -*-
+#
+
+import serial
+import time
+import sys
+import struct
+import getopt
+import platform
+from   heapq import heappush, heappop
+from   threading import Thread, Lock
+import os
+import os.path
+import string
+import traceback
+import thread
+import datetime
+import getpass
+import locale
+
+# Lionel
+import MySQLdb
+
+class OWLError(Exception):
+    pass
+
+VERSION                          = 1.0
+CM160SERVER_CONFIG_FILE          = '.cm160server.cfg'
+PREVIOUS_DB_FILE                 = 'PREVIOUS_DB_DFILE'
+NON_COMPAT_MODE_TEXT_FILE_HEADER = "TIME     DATE       AMPS     COST PER KWH"
+
+#*********************** UserOutput *************************** 
+class UserOutput:
+  """Responsible for user output to stdout and to log files"""
+  
+  def __init__(self):
+    self.__logFile = None
+    self.__debug   = False
+    self.__fileLock = Lock()
+    self.__stdoutLock = Lock()
+    self.__progressChar='.'
+    self.__progressCharCount=0
+
+  def getDebug(self):
+    """Get the debug state"""
+    return self.__debug
+    
+  def getLogFilename(self):
+    """Return the full name of the log file"""
+    return self.__logFile
+    
+  def setLogFile(self, logFile):
+    """Set the name of the logfile to use"""
+    self.__logFile = logFile
+    
+  def setDebug(self, enabled):
+    """Set debug as True to enable the output of debug messages"""
+    self.__debug=enabled
+    
+  def logMessage(self, line):
+    """Add a line to the log file. The line should not contain any EOL
+       characters"""
+    self.__fileLock.acquire()
+    fd = None
+    try:    
+      self.__fileLocked = True
+      #If not logfile has been defined quit
+      if self.__logFile == None:
+        return     
+      #If the logfile already exists
+      if os.path.isfile(self.__logFile):
+        #Append to it  
+        fd = open(self.__logFile, "a")
+      else:
+        #Create it
+        fd = open(self.__logFile, "w")
+          
+      fd.write("%s\n" % (line) )
+    finally:
+      if fd != None:
+        fd.close()
+      self.__fileLock.release()
+
+  def __outputLine(self, line):
+    """Send a line of text to stdout and to the log file"""
+    self.logMessage(line)
+    self.__stdoutLock.acquire()
+    try:
+      print line
+    finally:
+      self.__stdoutLock.release()
+    
+  def info(self, line):
+    """Show an information message line to the user"""
+    iLine = 'INFO:  %s' % (line)
+    self.__outputLine(iLine)
+    
+  def warn(self, line):
+    """Show a warning message line to the user"""
+    iLine = 'WARN:  %s' % (line)
+    self.__outputLine(iLine)
+    
+  def debug(self, line):
+    """Show an debug message line to the user"""
+    if self.__debug:
+      dLine = 'DEBUG: %s' % (line)
+      self.__outputLine(dLine)
+    
+  def error(self, line):
+    """Show an error message line to the user"""
+    eLine = 'ERROR: %s' % (line)
+    self.__outputLine(eLine)
+
+  def debugException(self):
+    """
+       Method that runs on python 1.5.2 and python 2.2 to print a stack trace 
+       of the last exception. May be used for debug purposes.
+    """
+    lines = traceback.format_exception(sys.exc_info()[0],sys.exc_info()[1],sys.exc_info()[2])
+    self.debug(string.rstrip(string.join(lines, '')))
+    
+    
+    
+    
+    
+#*********************** CM160Data *************************** 
+
+class CM160Data:
+  """Responsible for holding the data received from the CM160"""
+
+  COMPATIBLE_MODE_HEADER = "#1000;AMPS;cm160;0;YEAR-MONTH-DAY HOUR:MIN:SEC"
+  INCOMPATIBLE_MODE_HEADER = "#HOUR:MIN:SEC DAY/MONTH/YEAR AMPS PENCE_PER_KWH"
+  LAST_VALID_MONTH = None
+  
+  @staticmethod
+  def GetTime(cm160Data):
+    try:
+      return time.strptime("%d %d %d %d %d %d" % (cm160Data.getYear(),\
+                                                cm160Data.getMonth(),\
+                                                cm160Data.getDay(),\
+                                                cm160Data.getHour(),\
+                                                cm160Data.getMin(),\
+                                                cm160Data.getSecond()) , "%Y %m %d %H %M %S")
+    except ValueError:
+      return None    
+  
+  def __init__(self, min, hour, day, month, year, cost, amps):
+    self.__sec        = 0       #The CM160 does not return seconds in it's data 
+                                #stream.
+    self.__min        = min
+    self.__hour       = hour
+    self.__day        = day
+    self.__month      = month&0xf # The CM160 server appear to set bits in the 
+                                  # most significant nibble. have seen 0x09 jump 
+                                  # to 0x49. Not sure why but I'll mask them out
+                                  # so that we don't get invalid month errors.
+    self.__year       = year
+    self.__costPerKWH = cost
+    self.__amps       = amps
+    
+    self.__realTime   = False  #Changes to True when realtime data is being read
+
+    self.__updateLastValidMonth()
+  #Gets
+  def getYear(self):
+    return self.__year
+    
+  def getMonth(self):
+    return self.__month
+    
+  def getDay(self):
+    return self.__day
+
+  def getHour(self):
+    return self.__hour
+
+  def getMin(self):
+    return self.__min
+
+  def getSecond(self):
+    return self.__sec
+
+  def getCostPerKWH(self):
+    return self.__costPerKWH
+    
+  def getAmps(self):
+    return self.__amps
+    
+  def getRealTime(self):
+    return self.__realTime
+  
+  #Sets
+  def setSecs(self, sec):
+    self.__sec = sec
+  
+  def setRealTime(self, realTime):
+    self.__realTime=realTime
+    
+  #Further methods
+  
+  def __updateLastValidMonth(self):
+    """Update the static variable that records the last valid month of any CM160Data seen"""
+    if self.__month >= 1 and self.__month <= 12:
+      CM160Data.LAST_VALID_MONTH = self.__month
+    else:
+      if CM160Data.LAST_VALID_MONTH != None:
+        #If not a valid month use the last onoe we saw that was
+        self.__month = CM160Data.LAST_VALID_MONTH
+      
+  def getCompatDateStr(self):
+    """Get the date as a string in the same format the original C# based
+       OWLServer used"""
+    return "%04d-%02d-%02d %02d:%02d:%02d" % (self.__year, self.__month, self.__day, self.__hour, self.__min, self.__sec)
+    
+  def getCompatStr(self):
+    """Get a string representing the CM160 data in a format that is compatible with the original OWLServer format"""
+    line = "1000;%f;cm160;0;%s" % (self.__amps, self.getCompatDateStr()) 
+    if self.__month >= 1 and self.__month <= 12:
+      #print 'Date ok'
+      return line
+    #The CM160 sometime returns a month greater than 12. As I don't know how to 
+    #interpret the month value we put a hash in front so it should get ignored
+    return "#%s (Invalid month)" % (line)
+      
+  def __str__(self):
+    """Return the object as a string"""
+    s="%s %f %f" % (self.getDateStr(), self.__amps, self.__costPerKWH)
+    if self.__month >= 1 and self.__month <= 12:
+      return s
+    #The CM160 sometime returns a month greater than 12. As I don't know how to 
+    #interpret the month value we put a hash in front so it should get ignored
+    return "#%s (Invalid month)" % (s)
+    
+  def getDatetime(self):
+    """Return the date+time as a python datetime object (naive - no tz)."""
+    return datetime.datetime(self.__year, self.__month, 
+            self.__day, self.__hour, self.__min, self.__sec)
+
+  def getDateStr(self):
+    return "%02d:%02d:%02d %02d/%02d/%02d" % \
+        (self.__hour,self.__min,self.__sec,self.__day,self.__month,self.__year)
+
+  def parse(self, line):
+    """Parse a line of text in the __str__() format and load the attributes of this object"""
+    loaded=False
+    
+    elems = line.split(" ")
+    if len(elems) == 4:
+      try:
+        self.__amps       = float(elems[2])
+        self.__costPerKWH = float(elems[3])
+        t                 = elems[0]
+        d                 = elems[1]
+        dateElems         = d.split("/")
+        if len(dateElems) == 3:
+          self.__day   = int(dateElems[0])
+          self.__month = int(dateElems[1])
+          self.__updateLastValidMonth()
+          self.__year  = int(dateElems[2])
+          timeElems = t.split(":")
+          if len(timeElems) == 3:
+            self.__hour = int(timeElems[0])
+            self.__min  = int(timeElems[1])
+            self.__sec  = int(timeElems[2])
+            loaded=True
+     
+      except ValueError:
+        pass
+    return loaded
+    
+  def sameMin(self, cm160Data):
+    """Return True if the cm160Data has the same year, day, month, hour and min 
+       as this object"""
+    if cm160Data == None:
+      return False
+    if self.__year   == cm160Data.getYear() and\
+       self.__month  == cm160Data.getMonth() and\
+       self.__day    == cm160Data.getDay() and\
+       self.__hour   == cm160Data.getHour() and\
+       self.__min    == cm160Data.getMin():
+      return True
+    return False
+    
+  def after(self, cm160Data):
+    """Return True if the data held in this (self) object has a later date/time
+       than the data held in the cm160Data object"""
+    #If we don't have a CM160Data object
+    if cm160Data == None:
+      #We can't say it's after
+      return False
+
+    selfTime = CM160Data.GetTime(self)
+    if selfTime == None:
+      return False
+      
+    cm160DataTime = CM160Data.GetTime(cm160Data)
+    if selfTime > cm160DataTime:
+      return True
+      
+    return False
+
+  def between(self, startTime, stopTime):
+    """Return True if the data held in this (self) object is between 
+       startTime and stopTime. 
+       Also returns true if equal to either startTime and stopTime. 
+    """
+    selfTime = CM160Data.GetTime(self)
+    if selfTime == None:
+      return False
+      
+    if selfTime >= startTime and selfTime <= stopTime:
+      return True
+      
+    return False
+
+
+
+
+
+#*********************** CM160 *************************** 
+
+class CM160:
+  """This class is responsible for 
+     - Connecting to the CM160 over a serial port
+     - Reading data from the CM160 unit
+     - Saving the data via a DataStore object
+     
+     When running the CM160 object has three threads 
+     - The main thread, responsible for reading data from the CM160 serial port 
+       and pushing the data received into a queue.
+     - The queue reader thread which read packets of data from the above queue 
+       and saves them via a DataStore object.
+  """
+  
+  DEFAULT_WINDOWS_LOGFILE      = "C:\\owl_server_log.txt"
+  DEFAULT_NON_WINDOWS_LOGFILE = "/var/log/owl_server_log.txt"
+
+  DEFAULT_SERVER_PORT = 12745
+  DEVICE_NAME = "OWL+USB (CM160)"
+  
+  MESSAGE_ID_0 = 0xa9
+  MESSAGE_ID_1 = 0x59
+  MESSAGE_ID_2 = 0x51
+  MESSAGE_ID_3 = 0x1d # Received once, not sure of the meaning in the protocol
+
+  ID_MSG      = "%cIDTCMV001%c" % (MESSAGE_ID_0,1)
+  WAIT_MSG    = "%cIDTWAITPCR" % (MESSAGE_ID_0)
+
+  @staticmethod
+  def IsWindows():
+    """Return True if windows platform, False if not"""
+    if platform.system() == "Windows":
+      return True
+    return False
+    
+  def __init__(self, uo, serverPort, dataStore, debug):
+    self.__uo             = uo
+    self.__serverPort     = serverPort
+    self.__dataStore      = dataStore
+    self.__debug          = debug
+
+    self.__ser            = None
+    self.__serialPortName = None
+    self.__serRXBuffer    = []
+    self.__queue          = []
+    
+    self.__queueReader    = None
+    self.__server         = None
+    
+    self.init()
+    
+    self.__firstRealTimeDataReceived = False
+          
+  def init(self):
+    if self.__queueReader != None:
+      self.__queueReader.waitStop()
+      
+    self.__queueReader = QueueReader(self.__queue, self.__dataStore, self.__uo)
+    self.__queueReader.start()
+      
+#    if self.__server != None:
+#      self.__server.waitStop()
+      
+#    self.__server = Server(self.__uo, self.__serverPort, self.__queueReader, self.__dataStore)
+#    self.__server.start()
+    
+  def __serialPortScan(self):
+    """scan for available serial ports. return a list of tuples [num, name]"""
+    available = []
+    s=None
+    for i in range(255):
+      try:
+        if CM160.IsWindows():
+          portname ="\\.\\COM%d" % (i)
+          s = serial.Serial(portname)
+        else:
+          portname = "/dev/ttyUSB" + `i` # bmcm-p1: force portname on linux
+          s = serial.Serial(portname)
+        s.close()
+        available.append( (i, s.portstr))
+      except serial.SerialException as e:
+        if e.__str__().find('Permission denied:') != -1:
+          self.__uo.warn("Try running as root to access %s" % (portname) )
+    return available
+    
+  def openSerialPort(self):
+    """Open a serial port with the required settings
+       Returns the seialPort object or None if an error occured opening the 
+       serial port.
+    """
+    #Ifthe serial port is open, close it
+    if self.__ser != None:
+       self.__ser.close()
+       self.__ser = None
+       
+    self.__ser = None
+    try:
+        if CM160.IsWindows():
+          self.__ser = serial.Serial(self.__serialPortName, 250000, bytesize=8, parity='N', stopbits=1, xonxoff=0, rtscts=0, dsrdtr = 0, timeout=30)   
+        else:
+          #Assume we have the custom cp210x driver loaded that maps the requested baud rate of 0 to a physical port speed of 250000 Bps required 
+          #for the CM160 device.
+          self.__ser = serial.Serial(self.__serialPortName, 0, bytesize=8, parity='N', stopbits=1, xonxoff=False, rtscts=False, dsrdtr = False, timeout=30)    
+          
+          sd = self.__ser.getSettingsDict()        
+          keys = sd.keys()
+          for k in keys:
+            self.__uo.debug("%s=%s" % (k, sd[k]) )
+        self.__uo.info("Opened serial port %s." % (self.__serialPortName) )
+        
+    except ValueError:
+      #If we fail to open the serial port with a ValueError then it is probably the baud rate that is the problem
+      raise OWLError("Failed to open %s at a baud rate of 250000 Bps" % (self.__serialPortName) )
+    
+    return self.__ser
+
+  def appendSerRXBuffer(self):
+    """Read any bytes waiting to be received into the serial RX buffer
+    """
+    bytesWaiting = self.__ser.inWaiting()
+    if bytesWaiting > 0:
+      buf = self.__ser.read(bytesWaiting)
+      self.__serRXBuffer = self.__serRXBuffer + list(buf)
+
+  def getSerialPortPacket(self, timeoutSeconds=60):
+      """Get the next 11 byte message an OWL+USB device
+         Returns a list object holding the message (string elements) or None 
+         if a timeout occured while waiting for a message.
+      """
+      messageLength=11
+      startTime=time.time()
+      while True:
+        self.appendSerRXBuffer()
+        
+        #If we have received at least one packet
+        if len(self.__serRXBuffer) >= 11:      
+          break
+        
+      if time.time() >= startTime+timeoutSeconds:
+        return None
+        
+      #Get this packet
+      packet=self.__serRXBuffer[:messageLength]
+      #Remove this message from the rxBuffer
+      self.__serRXBuffer=self.__serRXBuffer[messageLength:]
+      self.__uo.debug("RX PACKET: %s" % (str(packet)) )
+      return packet
+
+  def getString(self, byteList):
+    """Add a list of bytes to a string"""
+    strList=[]
+    for aByte in byteList:
+      strList.append(struct.pack('B' , aByte))
+    return "".join(strList)
+  
+  def serSend(self, byteList):
+    """Send a list of bytes to the serial port
+       openSerialPort() must have been successfully called before alling this method 
+    """
+    self.__ser.write( self.getString(byteList) )
+    for b in byteList:
+      self.__uo.debug("TX: %03d/0x%02x" % (b,b) )
+         
+  def processSerialPacket(self, packet, realTimeData):
+    """Process an 11 byte packet containing sensor data"""
+    if len(packet) != 11:
+      raise OWLError("Invalid data message length. 11 bytes expected, got %d" % (len(packet)) )
+    #Convert strings to list of numbers
+    frame=[]
+    checksum=0
+    byteCount=1
+    for s in packet:
+      v=ord(s)
+      if byteCount < 11:
+        checksum=checksum+v
+      frame.append(v)
+      byteCount=byteCount+1
+      
+    #It's only an 8 bit checksum
+    checksum=checksum&0xff
+    
+    #MESSAGE_ID_2 is used when the power has changed within the min update period
+    if frame[0] != CM160.MESSAGE_ID_1 and frame[0] != CM160.MESSAGE_ID_2:
+      raise OWLError("Invalid frame ID byte, expected 89, got %d" % (frame[0]) )
+      
+    #Check the checksum
+    if checksum != frame[10]:
+      raise OWLError("Invalid checksum, expected %d, got %d" % (frame[10], checksum) )
+
+    year = frame[1]+2000
+    month = frame[2]
+    day = frame[3]
+    hour = frame[4]
+    _min = frame[5]
+    costPerKWH = (frame[6]+(frame[7]<<8))/100.0 #Cost in pence per kWh
+    amps = (frame[8]+(frame[9]<<8))* 0.07       #Conversion factor to Amps
+    cm160Data = CM160Data(_min, hour, day, month, year, costPerKWH, amps)
+    #Set flag to indicate if this CM160Data was read in realtime or during a data download
+    cm160Data.setRealTime(realTimeData)
+    heappush(self.__queue, cm160Data) # read data from the queue by, cm160Data = heappop(self.__queue)
+
+  def readCM160(self, unitTimeout=60):
+    """Read CM160 device"""
+    self.__uo.info("Checking for %s device on %s" % (CM160.DEVICE_NAME, self.__serialPortName) )
+
+    displayedDownloadingDataMessage  = False
+    displayedDownloadCompleteMessage = False
+    downloadingData                  = True
+    detectedUnit                     = False
+    timeoutTime                      = time.time()+unitTimeout
+    lastRXDataTime                   = None
+    downloadStartTime                = None
+    try:
+      while True:
+        if len(self.__serRXBuffer) >= 11 or self.__ser.inWaiting() > 0:
+          packet = self.getSerialPortPacket() 
+          if ord(packet[0]) == CM160.MESSAGE_ID_0:
+            packetStr = "".join(packet)
+            if packetStr == CM160.ID_MSG:
+              detectedUnit=True
+              self.serSend([0x5a])
+              timeoutTime = time.time()+unitTimeout
+            elif packetStr == CM160.WAIT_MSG:
+              self.serSend([0xa5]) 
+              timeoutTime = time.time()+unitTimeout
+          else:
+            self.processSerialPacket(packet, displayedDownloadCompleteMessage)
+            timeoutTime = time.time()+unitTimeout
+            
+            if displayedDownloadCompleteMessage and not self.__firstRealTimeDataReceived:
+              self.__uo.info("Real time data is now being received.")
+              self.__firstRealTimeDataReceived=True
+
+            if not displayedDownloadingDataMessage and downloadingData:
+              self.__uo.info("Downloading historical data from the %s." % (CM160.DEVICE_NAME) )
+              self.__uo.info("This may take several minutes, please wait...")
+              displayedDownloadingDataMessage=True
+              downloadStartTime=time.time()
+              
+          lastRXDataTime=time.time()
+        else:
+          #If no cm160 unit detected and a timeout has occured
+          if not detectedUnit and time.time() > timeoutTime:
+            self.__uo.info("No %s device detected on %s (read error)" % (CM160.DEVICE_NAME, self.__serialPortName) )
+            return
+          if displayedDownloadingDataMessage and not displayedDownloadCompleteMessage and time.time() > lastRXDataTime+1:
+            self.__uo.info("Download complete.")
+            if downloadStartTime != None:
+              downloadTime = time.time()-downloadStartTime
+              self.__uo.info("Data download took %d seconds." % (int(downloadTime)) )
+            displayedDownloadCompleteMessage=True
+          time.sleep(0.1)
+    finally:       
+      self.shutdown()      
+      
+  def shutdown(self):
+    """Preform the actions required when exiting the application"""
+    
+    #Close the serial port
+    if self.__ser != None:
+      self.__ser.close()
+      self.__uo.info("%s closed" % (self.__serialPortName) )
+
+#    if self.__server != None:
+#      self.__server.stop()
+      
+    if self.__queueReader != None:
+      self.__queueReader.stop()    
+      
+  def scanSerialPorts(self):
+    self.__uo.info("Please connect the %s device" % (CM160.DEVICE_NAME) )
+    self.__uo.info("Checking for a %s device" % (CM160.DEVICE_NAME) )
+    while True:
+      try:
+        try:
+          availablePorts = self.__serialPortScan()
+          for availablePort in availablePorts:
+            self.__serialPortName = availablePort[1]
+            #If the serial port will open with the CM160 settings
+            self.openSerialPort()
+            if self.__ser != None:
+              self.readCM160()         
+        except:
+          uo.error(sys.exc_value)
+          if self.__serialPortName != None:
+            self.__uo.info("No %s device detected on %s" % (CM160.DEVICE_NAME, self.__serialPortName) )
+          #If in debug mode then throw the exception so that we see the 
+          #stack trace on the console
+          #if self.__debug:
+          #  raise
+        time.sleep(1)
+      finally:
+        if self.__ser != None:
+          try:
+            self.__ser.close()
+            self.init()
+            uo.info("Serial port closed")
+            time.sleep(5)
+          except:
+            pass
+          
+  def connectToSerialPort(self, serialPort):
+    self.__uo.info("The %s device must have been connected recently (serial port %d)" % (CM160.DEVICE_NAME, serialPort))
+    self.__uo.info("as the CM160 device will go to sleep if not detected shortly after it is connected.")
+    self.__serialPortName = None
+    
+    #We need to do things differently on windows :-(
+    if CM160.IsWindows():
+      self.__serialPortName="\\.\\COM%d" % (serialPort)
+    else:
+      self.__serialPortName = "/dev/ttyUSB%d" % (serialPort)
+     
+    #If the serial port will open with the CM160 settings
+    self.openSerialPort()
+    if self.__ser != None:
+      self.readCM160() 
+    else:
+      raise OWLError("Serial port %d/%s, not found." % (serialPort, self.__serialPortName))
+      
+
+#*********************** QueueReader *************************** 
+
+class QueueReader(Thread):
+  """This object is responsible for reading the queue (held in a CM160 object 
+     and writing the data to the log file in a format that is compatible with
+     the C# based OWLServer"""
+  def __init__ (self, queue, dataStore, uo):
+    Thread.__init__(self)
+    self.__queue          = queue
+    self.__dataStore      = dataStore
+    self.__uo             = uo
+    self.__reading        = False
+    self.__lastCM160Data  = None
+    self._running         = False
+    
+    self.setDaemon(True)
+       
+  def setCompatibleMode(self, enabled):
+    """Set compatible mode. 
+       If true then the log file is in a format compatible
+       the original OWLServer (C# based) OWLServer (this does not incde the cost
+       per KW read from the unit.
+       If False then the log file is saved in a format that includes the cost 
+       per KW read from the unit."
+    """
+    self.__compatibleMode = enabled
+    
+  def stop(self):
+    """Stop reading the queue and stop the thread
+       when the thread is active"""
+    self.__uo.debug("Stopping QueueReader")
+    self.__reading=False
+  def waitStop(self):
+    """stop the queue reader and block until it is stopped."""
+    self.stop()
+    self.__uo.debug("Waiting for QueueReader to stop")
+    while self._running:
+      time.sleep(0.1)
+    self.__uo.debug("QueueReader is now stopped")
+      
+  def run(self):
+    self.__reading=True
+    minStartTime = time.time()
+    sec=0
+    lastSec=-1
+    self._running=True
+    while self.__reading:
+      if len(self.__queue) > 0:
+        cm160Data = heappop(self.__queue)
+        self.__uo.debug("QUEUE DATA: <%s>" % (str(cm160Data)) ) 
+        
+        #As the CM160 does not return second granularity data, we have to
+        #compensate
+        #If we have a reading in the same min as the last
+        if cm160Data.sameMin(self.__lastCM160Data):
+          #Determine how many seconds have passed in this min
+          sec = int(time.time()-minStartTime)
+          #Ensure the seconds always increment within a min period
+          #When downloading data we will see changes within a min period
+          #but will not know how many seconds passed between readings
+          #As the min update period for the CM160 readings appears to be 
+          #about 5 seconds, we use this
+          if sec <= lastSec:
+            sec=lastSec+5
+          #Ensure the seconds never go to high (more than 12 updates a min
+          #This code is probably redundant as I have not seen this many updates 
+          #a min from the CM160. However, belt and braces.
+          if sec > 59:
+            sec = 59
+          #Set the seconds to this value
+          cm160Data.setSecs(sec)
+        else:
+          minStartTime = time.time()
+          sec=0
+          lastSec=sec
+          
+        self.__lastCM160Data = cm160Data
+        try:
+          self.store(cm160Data)
+        except ValueError:
+          self.__uo.error("Failed to store: %s" % (cm160Data) )
+          self.__uo.info("Attempting to read the next record")
+      else:
+        time.sleep(0.5)
+    self._running=False
+    
+  def store(self, cm160Data):
+    """Store the CM160Data object to the data store
+    """      
+    self.__dataStore.store(cm160Data)
+    self.__uo.debug(cm160Data)
+       
+  def getRealTimeSensorData(self):
+    """Get the sensor data as a String in Electric OWL compatible format
+       Returns None if the sensor data read was not read in real time.
+    """
+    if self.__lastCM160Data != None and\
+         self.__lastCM160Data.getCompatStr().find("#") != 0 and\
+         self.__lastCM160Data.getRealTime():
+      return self.__lastCM160Data.getCompatStr()
+    else:
+      return None
+    
+
+# ************* MySQL data store *********************************
+class MySQLDataStore(object):
+  """Responsible for the storage and retrieval of CM160 data using an MySQL
+     database to hold the data"""
+
+  DEFAULT_DBFILE = "cm160_data.db"
+
+  def __init__ (self):
+    self.__db             = "owl"
+    self.__StoreConn      = None
+    self.__StoreCursor    = None
+    self.__displayTime    = time.time()
+
+    self.__ensureStoreExists()
+
+  def __connect(self):
+    """Build a connection to the database and return the connection and cursor objects in a tuple"""
+    conn=MySQLdb.connect(user="owl",passwd="owl",db="owl")
+    cursor=conn.cursor()
+    return (conn, cursor)
+
+  def __ensureStoreExists(self):
+    """Ensure the MySQL database exists
+       This also creates a connection to the database used for storing CM160 data.
+    """
+    conn, cursor = self.__connect()
+    try:
+        cursor.execute("CREATE TABLE cm160data(ts timestamp, current float, tariff float, UNIQUE (ts))")
+        # Attempt to create table in case this is first time to run
+        # the server; but thereafter we expect an exception because the
+        # create will fail...
+    except MySQLdb.OperationalError as e:
+#        print "Error %d: %s" % (e.args[0], e.args[1])
+        if not(e.args[0] == 1050):
+            raise
+    conn.commit()
+    conn.close()
+    self.info("MySQL database OK: %s" % (self.__db) )
+
+  def store(self, cm160Data):
+    """Store the CM160 data in the MySQL database.
+       There may be duplicate timestamps in the CM160data.
+       If the data was added to the database return True, if 
+       not added to the database return False"""
+
+    #Create a connection if we don't already have one
+    if self.__StoreConn == None:
+      self.__StoreConn, self.__StoreCursor = self.__connect()
+
+    stored=False
+    try:
+        self.__StoreCursor.execute("""INSERT INTO cm160data VALUES (%s,%s,%s)""", (cm160Data.getDatetime(),cm160Data.getAmps(),cm160Data.getCostPerKWH(),))
+        stored=True
+
+    except MySQLdb.Error as e:
+        print("MySQL error : " + e.__str__())
+        if (e.args[0] == 1062):
+            # INSERT will fail this way if this is a duplicate
+            # record (as it commonly can be when receiving 30-day
+            # record from CM160 device!)
+            #self.info("Duplicate record discarded: %s" % (cm160Data) )
+            pass
+        else:
+            raise
+
+    #Display data every few seconds to let user know download is 
+    #in progress
+    if self.__displayTime < time.time():
+      self.info("stored record: %s" % cm160Data)
+      self.__displayTime=time.time()+2
+      #We used to commit every record this was very slow when 
+      #downloading 30 days of data aon slow (E.G raspberypi) 
+      #platforms. Now we only comit data as it is displayed.
+      self.__StoreConn.commit()
+
+    return stored
+
+# TODO : what is it ?
+  def getCM160List(self, startTime, stopTime):
+    """Return a list of CM160 data objects containing the data stored in the database between the 
+       start and stop times."""
+
+    # NB: As each socket connection is handled by a dedicated
+    # thread, and as sqlite objects can't be shared across
+    # threads, we must open a dedicated sqlite connection
+    # here - it can't be done (much) earlier and kept open
+    # e.g., in the enclosing Server context; and it's
+    # probably not a severe performance hit in this case
+    # anyway...
+    conn, cursor = self.__connect()
+
+    cursor.execute("SELECT * from cm160data WHERE \
+           (ts >= ?) AND (ts <= ?) ORDER BY ts", \
+           [startTime, stopTime])
+
+    # We have to retrieve all rows in order to reliably find out
+    # how many there are in total (which we need to be able to
+    # later give "percentage complete" messages). But if there
+    # are too many rows (i.e., if this takes too long) the EOWL
+    # client seems to get confused (closes the history window and
+    # starts wrongly showing the historical datapoints as part of
+    # the real time plot!). So we retrieve in blocks of at most
+    # 5000 records (which has just been very roughly determined
+    # by trial and error!) and send rather bogus "percentage"
+    # messages to the client as each block is retrieved, just to
+    # keep it awake and waiting...
+
+    rows = []
+    rowset = cursor.fetchmany(5000)
+    while (rowset):
+        rows += rowset
+        rowset = cursor.fetchmany()
+    conn.close()
+    # Could probably release the lock even before the
+    # fetchall() but delaying to after should be very safe...
+
+    cm160DataList=[]
+    rowCount = 0
+    for r in rows:
+      rowCount += 1 ;
+      (ts, current, tariff) = (r)
+      cm160Data = CM160Data(ts.minute, ts.hour, \
+                      ts.day, ts.month, ts.year, \
+                      tariff, current)
+      cm160DataList.append(cm160Data)
+      self.debug("Row: %s" % cm160Data.__str__())
+
+    return cm160DataList
+
+  def info(self, text):
+    """Display info text"""
+#    if self.__uo != None:
+#      self.__uo.info(text)
+
+
+
+######################## Presistent dict config ##########################
+
+def getUserHome():
+  """Get the user home path as this will be used to store config files"""
+  try:
+    return os.environ["HOME"]
+  except KeyError:
+    #If we don't have the HOME env variable set then attempt to use 
+    #the default windows path
+    homeDrive=os.environ["HOMEDRIVE"]
+    homePath=os.environ["HOMEPATH"]
+    return "%s%s" % (homeDrive, homePath) 
+    
+def getConfigFileHeader():
+  lines=[]
+  lines.append("#cm160Server.py config file.\n" )
+  lines.append("\n")
+  return "".join(lines)
+  
+def saveDict(config, cfgFile):
+  """Save the destClientConfig to a file"""
+  #Save the resultant dict
+  fd = open(cfgFile,'w')
+  fd.write(getConfigFileHeader() )
+  keys=config.keys()
+  keys.sort()
+  for k in keys:
+    t="%s=%s\n" % (k, config[k])
+    fd.write(t)
+  fd.close()
+  
+def getLinesFromFile(f):
+  """Get Lines from file"""
+  fd = open(f,"r")
+  lines = fd.readlines()
+  fd.close()
+  return lines
+  
+def _removeInvalidChars(line):
+  """Return a copy of line with each ASCII control character (0-31),
+  and each double quote, removed. (Vaguely private)"""
+  output = ''
+  for c in line:
+    if c >= ' ' and c != '"':
+      output=output+c
+  return output
+  
+def _addEntry(line,dict):
+  """Parse line into a key and value, adding the result to dict, as in
+  getDict."""
+  #check for a parameter
+  fields=line.split('=')
+  #if at least 2 fields exist
+  if len(fields) > 1:
+    #add the key,value pair to the dictionary
+    key=_removeInvalidChars(fields[0])
+    value=_removeInvalidChars(fields[1])
+    dict[key]=value
+    
+def getDict(filename):
+  """Load dict from file
+
+  Lines containing a hash sign as the first non-whitespace character are
+  ignored. Leading and trailing whitespace is ignored.
+
+  Lines not containing an equals sign are also silently ignored, for the
+  moment.
+
+  Lines not ignored are assumed to be in the form key=value, where key
+  does not contain an equals sign; value is assigned to dict[key].
+  Control characters and double quotes in both key and value are silently
+  discarded. value is also truncated just before the first whitespace or
+  equals sign it contains.
+  """
+  d = {}
+  lines = getLinesFromFile(filename)
+  for line in lines:
+    #strip leading and trailing whitespaces
+    line = line.strip()
+    #if a comment line then ignore
+    if line.find('#') == 0:
+      continue
+    #add an entry to the dict
+    _addEntry(line, d)
+  return d
+  
+def getConfigFile():
+  """Get the absolute path of the dest client config file"""
+  return os.path.join(getUserHome(), CM160SERVER_CONFIG_FILE)
+  
+######################## Command line interface ##########################
+  
+
+       
+def usage(uo):
+    uo.info("-p : Followed by the server port (default=%d)." % (CM160.DEFAULT_SERVER_PORT) )
+    uo.info("-s : Followed by the serial port to check for the")
+    uo.info("     %s device." % (CM160.DEVICE_NAME) )   
+    uo.info("     If not supplied then a search for a connected") 
+    uo.info("     %s device is performed." % (CM160.DEVICE_NAME))
+    uo.info("-r:  Read data from the database and display on stdout. If -i/-l")
+    uo.info("     arguments are not supplied then the entire database is displayed.")
+    uo.info("-i:  Followed by the inital date/time. Only used if the -r argument")
+    uo.info("     is provided.")
+    uo.info("     Format of date time may be")
+    uo.info("     dd/mm/yyyy or")
+    uo.info("     hh:mi:dd/mm/yyyy")
+    uo.info("-l:  Followed by the last date/time. Only used if the -r argument")
+    uo.info("     is provided (same format as used for the -i argument).")
+    uo.info("-t:  Forward the data from the sqlite database to a text file ")
+    uo.info("     named as per the sqlite DB, but with .txt extension")
+    uo.info("     added. This is provided to allow user scripts to parse the")
+    uo.info("     text file.")
+    uo.info("-o:  Display data (when -r or -t arguments are used) in old compatible")
+    uo.info("     mode format. This was the original output format for CM160")
+    uo.info("     data and is retained only because some users have developed")
+    uo.info("     scripts that parse data in this format.")
+    uo.info("-q : Quiet mode. No text is sent to stdout in this mode.")
+    uo.info("-d:  Debug on. Send extra debug text to stdout if not in quiet mode.")
+    uo.info("-h : Display this help text.")
+
+def getInput(prompt="", noEcho=False):
+  """Get input from user"""
+  p = "INPUT: %s: " % (prompt)
+  if noEcho:
+    return getpass.getpass(p, sys.stdout)
+  return raw_input(p)
+    
+def getDateTime(text):
+  """Return a datetime object from the given text
+     text may contain a datetime as either
+     dd/mm/yyyy or
+     hh:mi:dd/mm/yyyy 
+  """
+  
+  dt=None
+  
+  #if just the date has been entered
+  if text.find('/') != -1 and text.find(':') == -1:
+    dt=datetime.datetime.strptime(text,"%d/%m/%Y")
+    
+  elif text.find('/') != -1:  
+    dt=datetime.datetime.strptime(text,"%H:%M:%d/%m/%Y")
+    
+  else:
+    raise OWLError("%s is not a valid datetime" % (text) )
+
+  return dt
+
+# MAIN
+
+if __name__=='__main__':
+  uo = UserOutput()
+  
+  try:
+    #Set to the correct locale.
+    locale.setlocale(locale.LC_ALL, '')
+  except:
+    #Not all platforms allow locale setting.
+    uo.warn("Failed to set locale.")
+
+  #Load the saved config if present
+  cfgDict=None
+  cfgFilename = getConfigFile()
+  if os.path.isfile(cfgFilename):
+    cfgDict = getDict(cfgFilename)
+    
+  cm160 = None
+  debug      = False
+  try:
+    #Parse command line
+    opts, args = getopt.getopt(sys.argv[1:], 'toi:l:rf:p:qds:h', ["help"])
+
+    quietMode  = False
+    serverPort = CM160.DEFAULT_SERVER_PORT
+    serialPort = None
+    readDatabase=False
+    readStartTime=datetime.datetime.min
+    readStopTime=datetime.datetime.max
+    compatMode=False
+    forwardToTextFile=False
+    
+    for o, a in opts:
+      if o == '-f':
+        dbFile=a
+      elif o == '-p':
+        serverPort=int(a)
+      elif o == '-q':
+        quietMode=True
+      elif o == '-s':
+        serialPort=int(a)
+        
+        #Validate serial port
+        lowSerialPort=0
+        if CM160.IsWindows():
+          if serialPort == 0:
+            raise OWLError("Serial port numbering starts at 1, not 0 on Windows platforms.")
+          lowSerialPort=1
+          
+        if serialPort < lowSerialPort or serialPort > 255:
+          raise OWLError("Serial port out of range (%d - 255)." % (lowSerialPort) )
+          
+      elif o == "-d":
+        debug = True
+      elif o == "-r":
+        readDatabase = True
+      elif o == "-i":
+        readStartTime=getDateTime(a)
+      elif o == "-l":
+        readStopTime=getDateTime(a)
+      elif o == "-o":
+        compatMode=True
+      elif o == "-t":
+        forwardToTextFile=True
+      elif o == "-h":
+        usage(uo)
+        sys.exit(0)
+
+    #Don't really need to log stdout, uncomment if you really need to
+    #uo.setLogFile("c:\\cm160.uo.log")
+    
+    if not compatMode:
+      uo.info("CM160Server v %2.2f" % (VERSION) )
+
+    uo.setDebug(debug)
+    
+# Démarrage du datastore :
+
+    DataStore = MySQLDataStore()
+
+# Démarrage du collect data
+    cm160 = CM160(uo, serverPort, DataStore, debug)
+    
+# Connexion au serial port :
+    if serialPort == None:
+      cm160.scanSerialPorts()
+      
+    else:
+      cm160.connectToSerialPort(serialPort)
+
+# Puis ne fait plus qu'attendre une erreur, tenter de l'interpréter, et sortir "proprement"   
+  except getopt.GetoptError:
+    if debug:
+      uo.debugException()
+    else:
+      uo.error(sys.exc_value)  
+    usage(uo)
+    sys.exit(-1)
+    
+  except SystemExit:
+    pass
+  
+  except:
+    if debug:
+      raise
+      #uo.debugException()
+    else:
+      uo.error(sys.exc_value) 
+    sys.exit(-1)
+    
+
+
+
diff --git a/driver/Makefile b/driver/Makefile
new file mode 100644 (file)
index 0000000..05a3f1b
--- /dev/null
@@ -0,0 +1,33 @@
+obj-m = cp210x.o
+KVERSION = $(shell uname -r)
+
+all: clean build
+
+build:
+       echo "If the build fails you may need to install your kernel headers."
+       make -C /lib/modules/$(KVERSION)/build M=$(shell pwd) modules
+
+clean:
+       make -C /lib/modules/$(KVERSION)/build M=$(shell pwd) clean
+
+install:
+       #Check that the user is root
+       runner=`whoami` ; \
+       if test $$runner != "root" ; \
+       then \
+               echo "root access required to install kernel." && \
+               echo "try sudo make install"; exit 2; \
+       fi
+       #Check that the cp210x kernel module file exists
+       if [ ! -f cp210x.ko ]; \
+       then \
+           echo "cp210x.ko File not found. Please run 'make' before running sudo make install"; exit 2; \
+       fi
+       #Copy the kernel module to its correct location
+       cp -f cp210x.ko /lib/modules/$(KVERSION)/kernel/drivers/usb/serial
+       #Remove the current cp210x module (if present) and replace with the cm160 cp210x module
+#      sudo rmmod cp210x >/dev/null 2>&1; sudo modprobe cp210x
+       #Ensure the /etc/modules file will load the cp210x module
+       grep -q '^cp210x' /etc/modules || echo 'cp210x' >> /etc/modules
+       echo "cp210x kernel module installed."
+
diff --git a/driver/README b/driver/README
new file mode 100644 (file)
index 0000000..0fb0070
--- /dev/null
@@ -0,0 +1,14 @@
+Patched serial driver for kernel 3.16.2
+
+diff :
+
+407,408c407,408
+<     if( baud == 0)             baud = 250000; /* KLUDGE for Owl Energy Monitor, Use baud rate (B0 Hang up) that is unlikley to be of any use */
+<     else if (baud <= 300)      baud = 300;
+---
+>   if (baud <= 300)
+>     baud = 300;
+
+
+Warning : Makefile does not fully handle installation (just take a look at it).
+You have to manually copy/remove/delete stuff your self (mainly because I'm lazy, but also because I don't like scripts playing with things in /lib/modules).
diff --git a/driver/cp210x.c b/driver/cp210x.c
new file mode 100644 (file)
index 0000000..a982c0f
--- /dev/null
@@ -0,0 +1,885 @@
+/*
+ * Silicon Laboratories CP210x USB to RS232 serial adaptor driver
+ *
+ * Copyright (C) 2005 Craig Shelley (craig@microtron.org.uk)
+ *
+ *     This program is free software; you can redistribute it and/or
+ *     modify it under the terms of the GNU General Public License version
+ *     2 as published by the Free Software Foundation.
+ *
+ * Support to set flow control line levels using TIOCMGET and TIOCMSET
+ * thanks to Karl Hiramoto karl@hiramoto.org. RTSCTS hardware flow
+ * control thanks to Munir Nassar nassarmu@real-time.com
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/usb.h>
+#include <linux/uaccess.h>
+#include <linux/usb/serial.h>
+
+#define DRIVER_DESC "Silicon Labs CP210x RS232 serial adaptor driver"
+
+/*
+ * Function Prototypes
+ */
+static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *);
+static void cp210x_close(struct usb_serial_port *);
+static void cp210x_get_termios(struct tty_struct *, struct usb_serial_port *);
+static void cp210x_get_termios_port(struct usb_serial_port *port,
+       unsigned int *cflagp, unsigned int *baudp);
+static void cp210x_change_speed(struct tty_struct *, struct usb_serial_port *,
+                                                       struct ktermios *);
+static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *,
+                                                       struct ktermios*);
+static int cp210x_tiocmget(struct tty_struct *);
+static int cp210x_tiocmset(struct tty_struct *, unsigned int, unsigned int);
+static int cp210x_tiocmset_port(struct usb_serial_port *port,
+               unsigned int, unsigned int);
+static void cp210x_break_ctl(struct tty_struct *, int);
+static int cp210x_startup(struct usb_serial *);
+static void cp210x_release(struct usb_serial *);
+static void cp210x_dtr_rts(struct usb_serial_port *p, int on);
+
+static const struct usb_device_id id_table[] = {
+       { USB_DEVICE(0x045B, 0x0053) }, /* Renesas RX610 RX-Stick */
+       { USB_DEVICE(0x0471, 0x066A) }, /* AKTAKOM ACE-1001 cable */
+       { USB_DEVICE(0x0489, 0xE000) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */
+       { USB_DEVICE(0x0489, 0xE003) }, /* Pirelli Broadband S.p.A, DP-L10 SIP/GSM Mobile */
+       { USB_DEVICE(0x0745, 0x1000) }, /* CipherLab USB CCD Barcode Scanner 1000 */
+       { USB_DEVICE(0x0846, 0x1100) }, /* NetGear Managed Switch M4100 series, M5300 series, M7100 series */
+       { USB_DEVICE(0x08e6, 0x5501) }, /* Gemalto Prox-PU/CU contactless smartcard reader */
+       { USB_DEVICE(0x08FD, 0x000A) }, /* Digianswer A/S , ZigBee/802.15.4 MAC Device */
+       { USB_DEVICE(0x0BED, 0x1100) }, /* MEI (TM) Cashflow-SC Bill/Voucher Acceptor */
+       { USB_DEVICE(0x0BED, 0x1101) }, /* MEI series 2000 Combo Acceptor */
+       { USB_DEVICE(0x0FCF, 0x1003) }, /* Dynastream ANT development board */
+       { USB_DEVICE(0x0FCF, 0x1004) }, /* Dynastream ANT2USB */
+       { USB_DEVICE(0x0FCF, 0x1006) }, /* Dynastream ANT development board */
+       { USB_DEVICE(0x0FDE, 0xCA05) }, /* OWL Wireless Electricity Monitor CM-160 */
+       { USB_DEVICE(0x10A6, 0xAA26) }, /* Knock-off DCU-11 cable */
+       { USB_DEVICE(0x10AB, 0x10C5) }, /* Siemens MC60 Cable */
+       { USB_DEVICE(0x10B5, 0xAC70) }, /* Nokia CA-42 USB */
+       { USB_DEVICE(0x10C4, 0x0F91) }, /* Vstabi */
+       { USB_DEVICE(0x10C4, 0x1101) }, /* Arkham Technology DS101 Bus Monitor */
+       { USB_DEVICE(0x10C4, 0x1601) }, /* Arkham Technology DS101 Adapter */
+       { USB_DEVICE(0x10C4, 0x800A) }, /* SPORTident BSM7-D-USB main station */
+       { USB_DEVICE(0x10C4, 0x803B) }, /* Pololu USB-serial converter */
+       { USB_DEVICE(0x10C4, 0x8044) }, /* Cygnal Debug Adapter */
+       { USB_DEVICE(0x10C4, 0x804E) }, /* Software Bisque Paramount ME build-in converter */
+       { USB_DEVICE(0x10C4, 0x8053) }, /* Enfora EDG1228 */
+       { USB_DEVICE(0x10C4, 0x8054) }, /* Enfora GSM2228 */
+       { USB_DEVICE(0x10C4, 0x8066) }, /* Argussoft In-System Programmer */
+       { USB_DEVICE(0x10C4, 0x806F) }, /* IMS USB to RS422 Converter Cable */
+       { USB_DEVICE(0x10C4, 0x807A) }, /* Crumb128 board */
+       { USB_DEVICE(0x10C4, 0x80C4) }, /* Cygnal Integrated Products, Inc., Optris infrared thermometer */
+       { USB_DEVICE(0x10C4, 0x80CA) }, /* Degree Controls Inc */
+       { USB_DEVICE(0x10C4, 0x80DD) }, /* Tracient RFID */
+       { USB_DEVICE(0x10C4, 0x80F6) }, /* Suunto sports instrument */
+       { USB_DEVICE(0x10C4, 0x8115) }, /* Arygon NFC/Mifare Reader */
+       { USB_DEVICE(0x10C4, 0x813D) }, /* Burnside Telecom Deskmobile */
+       { USB_DEVICE(0x10C4, 0x813F) }, /* Tams Master Easy Control */
+       { USB_DEVICE(0x10C4, 0x814A) }, /* West Mountain Radio RIGblaster P&P */
+       { USB_DEVICE(0x10C4, 0x814B) }, /* West Mountain Radio RIGtalk */
+       { USB_DEVICE(0x2405, 0x0003) }, /* West Mountain Radio RIGblaster Advantage */
+       { USB_DEVICE(0x10C4, 0x8156) }, /* B&G H3000 link cable */
+       { USB_DEVICE(0x10C4, 0x815E) }, /* Helicomm IP-Link 1220-DVM */
+       { USB_DEVICE(0x10C4, 0x815F) }, /* Timewave HamLinkUSB */
+       { USB_DEVICE(0x10C4, 0x818B) }, /* AVIT Research USB to TTL */
+       { USB_DEVICE(0x10C4, 0x819F) }, /* MJS USB Toslink Switcher */
+       { USB_DEVICE(0x10C4, 0x81A6) }, /* ThinkOptics WavIt */
+       { USB_DEVICE(0x10C4, 0x81A9) }, /* Multiplex RC Interface */
+       { USB_DEVICE(0x10C4, 0x81AC) }, /* MSD Dash Hawk */
+       { USB_DEVICE(0x10C4, 0x81AD) }, /* INSYS USB Modem */
+       { USB_DEVICE(0x10C4, 0x81C8) }, /* Lipowsky Industrie Elektronik GmbH, Baby-JTAG */
+       { USB_DEVICE(0x10C4, 0x81E2) }, /* Lipowsky Industrie Elektronik GmbH, Baby-LIN */
+       { USB_DEVICE(0x10C4, 0x81E7) }, /* Aerocomm Radio */
+       { USB_DEVICE(0x10C4, 0x81E8) }, /* Zephyr Bioharness */
+       { USB_DEVICE(0x10C4, 0x81F2) }, /* C1007 HF band RFID controller */
+       { USB_DEVICE(0x10C4, 0x8218) }, /* Lipowsky Industrie Elektronik GmbH, HARP-1 */
+       { USB_DEVICE(0x10C4, 0x822B) }, /* Modem EDGE(GSM) Comander 2 */
+       { USB_DEVICE(0x10C4, 0x826B) }, /* Cygnal Integrated Products, Inc., Fasttrax GPS demonstration module */
+       { USB_DEVICE(0x10C4, 0x8281) }, /* Nanotec Plug & Drive */
+       { USB_DEVICE(0x10C4, 0x8293) }, /* Telegesis ETRX2USB */
+       { USB_DEVICE(0x10C4, 0x82F9) }, /* Procyon AVS */
+       { USB_DEVICE(0x10C4, 0x8341) }, /* Siemens MC35PU GPRS Modem */
+       { USB_DEVICE(0x10C4, 0x8382) }, /* Cygnal Integrated Products, Inc. */
+       { USB_DEVICE(0x10C4, 0x83A8) }, /* Amber Wireless AMB2560 */
+       { USB_DEVICE(0x10C4, 0x83D8) }, /* DekTec DTA Plus VHF/UHF Booster/Attenuator */
+       { USB_DEVICE(0x10C4, 0x8411) }, /* Kyocera GPS Module */
+       { USB_DEVICE(0x10C4, 0x8418) }, /* IRZ Automation Teleport SG-10 GSM/GPRS Modem */
+       { USB_DEVICE(0x10C4, 0x846E) }, /* BEI USB Sensor Interface (VCP) */
+       { USB_DEVICE(0x10C4, 0x8477) }, /* Balluff RFID */
+       { USB_DEVICE(0x10C4, 0x85EA) }, /* AC-Services IBUS-IF */
+       { USB_DEVICE(0x10C4, 0x85EB) }, /* AC-Services CIS-IBUS */
+       { USB_DEVICE(0x10C4, 0x85F8) }, /* Virtenio Preon32 */
+       { USB_DEVICE(0x10C4, 0x8664) }, /* AC-Services CAN-IF */
+       { USB_DEVICE(0x10C4, 0x8665) }, /* AC-Services OBD-IF */
+       { USB_DEVICE(0x10C4, 0x88A4) }, /* MMB Networks ZigBee USB Device */
+       { USB_DEVICE(0x10C4, 0x88A5) }, /* Planet Innovation Ingeni ZigBee USB Device */
+       { USB_DEVICE(0x10C4, 0xEA60) }, /* Silicon Labs factory default */
+       { USB_DEVICE(0x10C4, 0xEA61) }, /* Silicon Labs factory default */
+       { USB_DEVICE(0x10C4, 0xEA70) }, /* Silicon Labs factory default */
+       { USB_DEVICE(0x10C4, 0xEA80) }, /* Silicon Labs factory default */
+       { USB_DEVICE(0x10C4, 0xEA71) }, /* Infinity GPS-MIC-1 Radio Monophone */
+       { USB_DEVICE(0x10C4, 0xF001) }, /* Elan Digital Systems USBscope50 */
+       { USB_DEVICE(0x10C4, 0xF002) }, /* Elan Digital Systems USBwave12 */
+       { USB_DEVICE(0x10C4, 0xF003) }, /* Elan Digital Systems USBpulse100 */
+       { USB_DEVICE(0x10C4, 0xF004) }, /* Elan Digital Systems USBcount50 */
+       { USB_DEVICE(0x10C5, 0xEA61) }, /* Silicon Labs MobiData GPRS USB Modem */
+       { USB_DEVICE(0x10CE, 0xEA6A) }, /* Silicon Labs MobiData GPRS USB Modem 100EU */
+       { USB_DEVICE(0x13AD, 0x9999) }, /* Baltech card reader */
+       { USB_DEVICE(0x1555, 0x0004) }, /* Owen AC4 USB-RS485 Converter */
+       { USB_DEVICE(0x166A, 0x0201) }, /* Clipsal 5500PACA C-Bus Pascal Automation Controller */
+       { USB_DEVICE(0x166A, 0x0301) }, /* Clipsal 5800PC C-Bus Wireless PC Interface */
+       { USB_DEVICE(0x166A, 0x0303) }, /* Clipsal 5500PCU C-Bus USB interface */
+       { USB_DEVICE(0x166A, 0x0304) }, /* Clipsal 5000CT2 C-Bus Black and White Touchscreen */
+       { USB_DEVICE(0x166A, 0x0305) }, /* Clipsal C-5000CT2 C-Bus Spectrum Colour Touchscreen */
+       { USB_DEVICE(0x166A, 0x0401) }, /* Clipsal L51xx C-Bus Architectural Dimmer */
+       { USB_DEVICE(0x166A, 0x0101) }, /* Clipsal 5560884 C-Bus Multi-room Audio Matrix Switcher */
+       { USB_DEVICE(0x16D6, 0x0001) }, /* Jablotron serial interface */
+       { USB_DEVICE(0x16DC, 0x0010) }, /* W-IE-NE-R Plein & Baus GmbH PL512 Power Supply */
+       { USB_DEVICE(0x16DC, 0x0011) }, /* W-IE-NE-R Plein & Baus GmbH RCM Remote Control for MARATON Power Supply */
+       { USB_DEVICE(0x16DC, 0x0012) }, /* W-IE-NE-R Plein & Baus GmbH MPOD Multi Channel Power Supply */
+       { USB_DEVICE(0x16DC, 0x0015) }, /* W-IE-NE-R Plein & Baus GmbH CML Control, Monitoring and Data Logger */
+       { USB_DEVICE(0x17A8, 0x0001) }, /* Kamstrup Optical Eye/3-wire */
+       { USB_DEVICE(0x17A8, 0x0005) }, /* Kamstrup M-Bus Master MultiPort 250D */
+       { USB_DEVICE(0x17F4, 0xAAAA) }, /* Wavesense Jazz blood glucose meter */
+       { USB_DEVICE(0x1843, 0x0200) }, /* Vaisala USB Instrument Cable */
+       { USB_DEVICE(0x18EF, 0xE00F) }, /* ELV USB-I2C-Interface */
+       { USB_DEVICE(0x1ADB, 0x0001) }, /* Schweitzer Engineering C662 Cable */
+       { USB_DEVICE(0x1B1C, 0x1C00) }, /* Corsair USB Dongle */
+       { USB_DEVICE(0x1BE3, 0x07A6) }, /* WAGO 750-923 USB Service Cable */
+       { USB_DEVICE(0x1E29, 0x0102) }, /* Festo CPX-USB */
+       { USB_DEVICE(0x1E29, 0x0501) }, /* Festo CMSP */
+       { USB_DEVICE(0x1FB9, 0x0100) }, /* Lake Shore Model 121 Current Source */
+       { USB_DEVICE(0x1FB9, 0x0200) }, /* Lake Shore Model 218A Temperature Monitor */
+       { USB_DEVICE(0x1FB9, 0x0201) }, /* Lake Shore Model 219 Temperature Monitor */
+       { USB_DEVICE(0x1FB9, 0x0202) }, /* Lake Shore Model 233 Temperature Transmitter */
+       { USB_DEVICE(0x1FB9, 0x0203) }, /* Lake Shore Model 235 Temperature Transmitter */
+       { USB_DEVICE(0x1FB9, 0x0300) }, /* Lake Shore Model 335 Temperature Controller */
+       { USB_DEVICE(0x1FB9, 0x0301) }, /* Lake Shore Model 336 Temperature Controller */
+       { USB_DEVICE(0x1FB9, 0x0302) }, /* Lake Shore Model 350 Temperature Controller */
+       { USB_DEVICE(0x1FB9, 0x0303) }, /* Lake Shore Model 371 AC Bridge */
+       { USB_DEVICE(0x1FB9, 0x0400) }, /* Lake Shore Model 411 Handheld Gaussmeter */
+       { USB_DEVICE(0x1FB9, 0x0401) }, /* Lake Shore Model 425 Gaussmeter */
+       { USB_DEVICE(0x1FB9, 0x0402) }, /* Lake Shore Model 455A Gaussmeter */
+       { USB_DEVICE(0x1FB9, 0x0403) }, /* Lake Shore Model 475A Gaussmeter */
+       { USB_DEVICE(0x1FB9, 0x0404) }, /* Lake Shore Model 465 Three Axis Gaussmeter */
+       { USB_DEVICE(0x1FB9, 0x0600) }, /* Lake Shore Model 625A Superconducting MPS */
+       { USB_DEVICE(0x1FB9, 0x0601) }, /* Lake Shore Model 642A Magnet Power Supply */
+       { USB_DEVICE(0x1FB9, 0x0602) }, /* Lake Shore Model 648 Magnet Power Supply */
+       { USB_DEVICE(0x1FB9, 0x0700) }, /* Lake Shore Model 737 VSM Controller */
+       { USB_DEVICE(0x1FB9, 0x0701) }, /* Lake Shore Model 776 Hall Matrix */
+       { USB_DEVICE(0x3195, 0xF190) }, /* Link Instruments MSO-19 */
+       { USB_DEVICE(0x3195, 0xF280) }, /* Link Instruments MSO-28 */
+       { USB_DEVICE(0x3195, 0xF281) }, /* Link Instruments MSO-28 */
+       { USB_DEVICE(0x413C, 0x9500) }, /* DW700 GPS USB interface */
+       { } /* Terminating Entry */
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct cp210x_serial_private {
+       __u8                    bInterfaceNumber;
+};
+
+static struct usb_serial_driver cp210x_device = {
+       .driver = {
+               .owner =        THIS_MODULE,
+               .name =         "cp210x",
+       },
+       .id_table               = id_table,
+       .num_ports              = 1,
+       .bulk_in_size           = 256,
+       .bulk_out_size          = 256,
+       .open                   = cp210x_open,
+       .close                  = cp210x_close,
+       .break_ctl              = cp210x_break_ctl,
+       .set_termios            = cp210x_set_termios,
+       .tiocmget               = cp210x_tiocmget,
+       .tiocmset               = cp210x_tiocmset,
+       .attach                 = cp210x_startup,
+       .release                = cp210x_release,
+       .dtr_rts                = cp210x_dtr_rts
+};
+
+static struct usb_serial_driver * const serial_drivers[] = {
+       &cp210x_device, NULL
+};
+
+/* Config request types */
+#define REQTYPE_HOST_TO_INTERFACE      0x41
+#define REQTYPE_INTERFACE_TO_HOST      0xc1
+#define REQTYPE_HOST_TO_DEVICE 0x40
+#define REQTYPE_DEVICE_TO_HOST 0xc0
+
+/* Config request codes */
+#define CP210X_IFC_ENABLE      0x00
+#define CP210X_SET_BAUDDIV     0x01
+#define CP210X_GET_BAUDDIV     0x02
+#define CP210X_SET_LINE_CTL    0x03
+#define CP210X_GET_LINE_CTL    0x04
+#define CP210X_SET_BREAK       0x05
+#define CP210X_IMM_CHAR                0x06
+#define CP210X_SET_MHS         0x07
+#define CP210X_GET_MDMSTS      0x08
+#define CP210X_SET_XON         0x09
+#define CP210X_SET_XOFF                0x0A
+#define CP210X_SET_EVENTMASK   0x0B
+#define CP210X_GET_EVENTMASK   0x0C
+#define CP210X_SET_CHAR                0x0D
+#define CP210X_GET_CHARS       0x0E
+#define CP210X_GET_PROPS       0x0F
+#define CP210X_GET_COMM_STATUS 0x10
+#define CP210X_RESET           0x11
+#define CP210X_PURGE           0x12
+#define CP210X_SET_FLOW                0x13
+#define CP210X_GET_FLOW                0x14
+#define CP210X_EMBED_EVENTS    0x15
+#define CP210X_GET_EVENTSTATE  0x16
+#define CP210X_SET_CHARS       0x19
+#define CP210X_GET_BAUDRATE    0x1D
+#define CP210X_SET_BAUDRATE    0x1E
+
+/* CP210X_IFC_ENABLE */
+#define UART_ENABLE            0x0001
+#define UART_DISABLE           0x0000
+
+/* CP210X_(SET|GET)_BAUDDIV */
+#define BAUD_RATE_GEN_FREQ     0x384000
+
+/* CP210X_(SET|GET)_LINE_CTL */
+#define BITS_DATA_MASK         0X0f00
+#define BITS_DATA_5            0X0500
+#define BITS_DATA_6            0X0600
+#define BITS_DATA_7            0X0700
+#define BITS_DATA_8            0X0800
+#define BITS_DATA_9            0X0900
+
+#define BITS_PARITY_MASK       0x00f0
+#define BITS_PARITY_NONE       0x0000
+#define BITS_PARITY_ODD                0x0010
+#define BITS_PARITY_EVEN       0x0020
+#define BITS_PARITY_MARK       0x0030
+#define BITS_PARITY_SPACE      0x0040
+
+#define BITS_STOP_MASK         0x000f
+#define BITS_STOP_1            0x0000
+#define BITS_STOP_1_5          0x0001
+#define BITS_STOP_2            0x0002
+
+/* CP210X_SET_BREAK */
+#define BREAK_ON               0x0001
+#define BREAK_OFF              0x0000
+
+/* CP210X_(SET_MHS|GET_MDMSTS) */
+#define CONTROL_DTR            0x0001
+#define CONTROL_RTS            0x0002
+#define CONTROL_CTS            0x0010
+#define CONTROL_DSR            0x0020
+#define CONTROL_RING           0x0040
+#define CONTROL_DCD            0x0080
+#define CONTROL_WRITE_DTR      0x0100
+#define CONTROL_WRITE_RTS      0x0200
+
+/*
+ * cp210x_get_config
+ * Reads from the CP210x configuration registers
+ * 'size' is specified in bytes.
+ * 'data' is a pointer to a pre-allocated array of integers large
+ * enough to hold 'size' bytes (with 4 bytes to each integer)
+ */
+static int cp210x_get_config(struct usb_serial_port *port, u8 request,
+               unsigned int *data, int size)
+{
+       struct usb_serial *serial = port->serial;
+       struct cp210x_serial_private *spriv = usb_get_serial_data(serial);
+       __le32 *buf;
+       int result, i, length;
+
+       /* Number of integers required to contain the array */
+       length = (((size - 1) | 3) + 1) / 4;
+
+       buf = kcalloc(length, sizeof(__le32), GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       /* Issue the request, attempting to read 'size' bytes */
+       result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+                               request, REQTYPE_INTERFACE_TO_HOST, 0x0000,
+                               spriv->bInterfaceNumber, buf, size,
+                               USB_CTRL_GET_TIMEOUT);
+
+       /* Convert data into an array of integers */
+       for (i = 0; i < length; i++)
+               data[i] = le32_to_cpu(buf[i]);
+
+       kfree(buf);
+
+       if (result != size) {
+               dev_dbg(&port->dev, "%s - Unable to send config request, request=0x%x size=%d result=%d\n",
+                       __func__, request, size, result);
+               if (result > 0)
+                       result = -EPROTO;
+
+               return result;
+       }
+
+       return 0;
+}
+
+/*
+ * cp210x_set_config
+ * Writes to the CP210x configuration registers
+ * Values less than 16 bits wide are sent directly
+ * 'size' is specified in bytes.
+ */
+static int cp210x_set_config(struct usb_serial_port *port, u8 request,
+               unsigned int *data, int size)
+{
+       struct usb_serial *serial = port->serial;
+       struct cp210x_serial_private *spriv = usb_get_serial_data(serial);
+       __le32 *buf;
+       int result, i, length;
+
+       /* Number of integers required to contain the array */
+       length = (((size - 1) | 3) + 1) / 4;
+
+       buf = kmalloc(length * sizeof(__le32), GFP_KERNEL);
+       if (!buf)
+               return -ENOMEM;
+
+       /* Array of integers into bytes */
+       for (i = 0; i < length; i++)
+               buf[i] = cpu_to_le32(data[i]);
+
+       if (size > 2) {
+               result = usb_control_msg(serial->dev,
+                               usb_sndctrlpipe(serial->dev, 0),
+                               request, REQTYPE_HOST_TO_INTERFACE, 0x0000,
+                               spriv->bInterfaceNumber, buf, size,
+                               USB_CTRL_SET_TIMEOUT);
+       } else {
+               result = usb_control_msg(serial->dev,
+                               usb_sndctrlpipe(serial->dev, 0),
+                               request, REQTYPE_HOST_TO_INTERFACE, data[0],
+                               spriv->bInterfaceNumber, NULL, 0,
+                               USB_CTRL_SET_TIMEOUT);
+       }
+
+       kfree(buf);
+
+       if ((size > 2 && result != size) || result < 0) {
+               dev_dbg(&port->dev, "%s - Unable to send request, request=0x%x size=%d result=%d\n",
+                       __func__, request, size, result);
+               if (result > 0)
+                       result = -EPROTO;
+
+               return result;
+       }
+
+       return 0;
+}
+
+/*
+ * cp210x_set_config_single
+ * Convenience function for calling cp210x_set_config on single data values
+ * without requiring an integer pointer
+ */
+static inline int cp210x_set_config_single(struct usb_serial_port *port,
+               u8 request, unsigned int data)
+{
+       return cp210x_set_config(port, request, &data, 2);
+}
+
+/*
+ * cp210x_quantise_baudrate
+ * Quantises the baud rate as per AN205 Table 1
+ */
+static unsigned int cp210x_quantise_baudrate(unsigned int baud)
+{
+    if( baud == 0)             baud = 250000; /* KLUDGE for Owl Energy Monitor, Use baud rate (B0 Hang up) that is unlikley to be of any use */
+    else if (baud <= 300)      baud = 300;
+       else if (baud <= 600)      baud = 600;
+       else if (baud <= 1200)     baud = 1200;
+       else if (baud <= 1800)     baud = 1800;
+       else if (baud <= 2400)     baud = 2400;
+       else if (baud <= 4000)     baud = 4000;
+       else if (baud <= 4803)     baud = 4800;
+       else if (baud <= 7207)     baud = 7200;
+       else if (baud <= 9612)     baud = 9600;
+       else if (baud <= 14428)    baud = 14400;
+       else if (baud <= 16062)    baud = 16000;
+       else if (baud <= 19250)    baud = 19200;
+       else if (baud <= 28912)    baud = 28800;
+       else if (baud <= 38601)    baud = 38400;
+       else if (baud <= 51558)    baud = 51200;
+       else if (baud <= 56280)    baud = 56000;
+       else if (baud <= 58053)    baud = 57600;
+       else if (baud <= 64111)    baud = 64000;
+       else if (baud <= 77608)    baud = 76800;
+       else if (baud <= 117028)   baud = 115200;
+       else if (baud <= 129347)   baud = 128000;
+       else if (baud <= 156868)   baud = 153600;
+       else if (baud <= 237832)   baud = 230400;
+       else if (baud <= 254234)   baud = 250000;
+       else if (baud <= 273066)   baud = 256000;
+       else if (baud <= 491520)   baud = 460800;
+       else if (baud <= 567138)   baud = 500000;
+       else if (baud <= 670254)   baud = 576000;
+       else if (baud < 1000000)
+               baud = 921600;
+       else if (baud > 2000000)
+               baud = 2000000;
+       return baud;
+}
+
+static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+       int result;
+
+       result = cp210x_set_config_single(port, CP210X_IFC_ENABLE,
+                                                               UART_ENABLE);
+       if (result) {
+               dev_err(&port->dev, "%s - Unable to enable UART\n", __func__);
+               return result;
+       }
+
+       /* Configure the termios structure */
+       cp210x_get_termios(tty, port);
+
+       /* The baud rate must be initialised on cp2104 */
+       if (tty)
+               cp210x_change_speed(tty, port, NULL);
+
+       return usb_serial_generic_open(tty, port);
+}
+
+static void cp210x_close(struct usb_serial_port *port)
+{
+       usb_serial_generic_close(port);
+       cp210x_set_config_single(port, CP210X_IFC_ENABLE, UART_DISABLE);
+}
+
+/*
+ * cp210x_get_termios
+ * Reads the baud rate, data bits, parity, stop bits and flow control mode
+ * from the device, corrects any unsupported values, and configures the
+ * termios structure to reflect the state of the device
+ */
+static void cp210x_get_termios(struct tty_struct *tty,
+       struct usb_serial_port *port)
+{
+       unsigned int baud;
+
+       if (tty) {
+               cp210x_get_termios_port(tty->driver_data,
+                       &tty->termios.c_cflag, &baud);
+               tty_encode_baud_rate(tty, baud, baud);
+       } else {
+               unsigned int cflag;
+               cflag = 0;
+               cp210x_get_termios_port(port, &cflag, &baud);
+       }
+}
+
+/*
+ * cp210x_get_termios_port
+ * This is the heart of cp210x_get_termios which always uses a &usb_serial_port.
+ */
+static void cp210x_get_termios_port(struct usb_serial_port *port,
+       unsigned int *cflagp, unsigned int *baudp)
+{
+       struct device *dev = &port->dev;
+       unsigned int cflag, modem_ctl[4];
+       unsigned int baud;
+       unsigned int bits;
+
+       cp210x_get_config(port, CP210X_GET_BAUDRATE, &baud, 4);
+
+       dev_dbg(dev, "%s - baud rate = %d\n", __func__, baud);
+       *baudp = baud;
+
+       cflag = *cflagp;
+
+       cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
+       cflag &= ~CSIZE;
+       switch (bits & BITS_DATA_MASK) {
+       case BITS_DATA_5:
+               dev_dbg(dev, "%s - data bits = 5\n", __func__);
+               cflag |= CS5;
+               break;
+       case BITS_DATA_6:
+               dev_dbg(dev, "%s - data bits = 6\n", __func__);
+               cflag |= CS6;
+               break;
+       case BITS_DATA_7:
+               dev_dbg(dev, "%s - data bits = 7\n", __func__);
+               cflag |= CS7;
+               break;
+       case BITS_DATA_8:
+               dev_dbg(dev, "%s - data bits = 8\n", __func__);
+               cflag |= CS8;
+               break;
+       case BITS_DATA_9:
+               dev_dbg(dev, "%s - data bits = 9 (not supported, using 8 data bits)\n", __func__);
+               cflag |= CS8;
+               bits &= ~BITS_DATA_MASK;
+               bits |= BITS_DATA_8;
+               cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+               break;
+       default:
+               dev_dbg(dev, "%s - Unknown number of data bits, using 8\n", __func__);
+               cflag |= CS8;
+               bits &= ~BITS_DATA_MASK;
+               bits |= BITS_DATA_8;
+               cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+               break;
+       }
+
+       switch (bits & BITS_PARITY_MASK) {
+       case BITS_PARITY_NONE:
+               dev_dbg(dev, "%s - parity = NONE\n", __func__);
+               cflag &= ~PARENB;
+               break;
+       case BITS_PARITY_ODD:
+               dev_dbg(dev, "%s - parity = ODD\n", __func__);
+               cflag |= (PARENB|PARODD);
+               break;
+       case BITS_PARITY_EVEN:
+               dev_dbg(dev, "%s - parity = EVEN\n", __func__);
+               cflag &= ~PARODD;
+               cflag |= PARENB;
+               break;
+       case BITS_PARITY_MARK:
+               dev_dbg(dev, "%s - parity = MARK\n", __func__);
+               cflag |= (PARENB|PARODD|CMSPAR);
+               break;
+       case BITS_PARITY_SPACE:
+               dev_dbg(dev, "%s - parity = SPACE\n", __func__);
+               cflag &= ~PARODD;
+               cflag |= (PARENB|CMSPAR);
+               break;
+       default:
+               dev_dbg(dev, "%s - Unknown parity mode, disabling parity\n", __func__);
+               cflag &= ~PARENB;
+               bits &= ~BITS_PARITY_MASK;
+               cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+               break;
+       }
+
+       cflag &= ~CSTOPB;
+       switch (bits & BITS_STOP_MASK) {
+       case BITS_STOP_1:
+               dev_dbg(dev, "%s - stop bits = 1\n", __func__);
+               break;
+       case BITS_STOP_1_5:
+               dev_dbg(dev, "%s - stop bits = 1.5 (not supported, using 1 stop bit)\n", __func__);
+               bits &= ~BITS_STOP_MASK;
+               cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+               break;
+       case BITS_STOP_2:
+               dev_dbg(dev, "%s - stop bits = 2\n", __func__);
+               cflag |= CSTOPB;
+               break;
+       default:
+               dev_dbg(dev, "%s - Unknown number of stop bits, using 1 stop bit\n", __func__);
+               bits &= ~BITS_STOP_MASK;
+               cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2);
+               break;
+       }
+
+       cp210x_get_config(port, CP210X_GET_FLOW, modem_ctl, 16);
+       if (modem_ctl[0] & 0x0008) {
+               dev_dbg(dev, "%s - flow control = CRTSCTS\n", __func__);
+               cflag |= CRTSCTS;
+       } else {
+               dev_dbg(dev, "%s - flow control = NONE\n", __func__);
+               cflag &= ~CRTSCTS;
+       }
+
+       *cflagp = cflag;
+}
+
+/*
+ * CP2101 supports the following baud rates:
+ *
+ *     300, 600, 1200, 1800, 2400, 4800, 7200, 9600, 14400, 19200, 28800,
+ *     38400, 56000, 57600, 115200, 128000, 230400, 460800, 921600
+ *
+ * CP2102 and CP2103 support the following additional rates:
+ *
+ *     4000, 16000, 51200, 64000, 76800, 153600, 250000, 256000, 500000,
+ *     576000
+ *
+ * The device will map a requested rate to a supported one, but the result
+ * of requests for rates greater than 1053257 is undefined (see AN205).
+ *
+ * CP2104, CP2105 and CP2110 support most rates up to 2M, 921k and 1M baud,
+ * respectively, with an error less than 1%. The actual rates are determined
+ * by
+ *
+ *     div = round(freq / (2 x prescale x request))
+ *     actual = freq / (2 x prescale x div)
+ *
+ * For CP2104 and CP2105 freq is 48Mhz and prescale is 4 for request <= 365bps
+ * or 1 otherwise.
+ * For CP2110 freq is 24Mhz and prescale is 4 for request <= 300bps or 1
+ * otherwise.
+ */
+static void cp210x_change_speed(struct tty_struct *tty,
+               struct usb_serial_port *port, struct ktermios *old_termios)
+{
+       u32 baud;
+
+       baud = tty->termios.c_ospeed;
+
+       /* This maps the requested rate to a rate valid on cp2102 or cp2103,
+        * or to an arbitrary rate in [1M,2M].
+        *
+        * NOTE: B0 is not implemented.
+        */
+       baud = cp210x_quantise_baudrate(baud);
+
+       dev_dbg(&port->dev, "%s - setting baud rate to %u\n", __func__, baud);
+       if (cp210x_set_config(port, CP210X_SET_BAUDRATE, &baud,
+                                                       sizeof(baud))) {
+               dev_warn(&port->dev, "failed to set baud rate to %u\n", baud);
+               if (old_termios)
+                       baud = old_termios->c_ospeed;
+               else
+                       baud = 9600;
+       }
+
+       tty_encode_baud_rate(tty, baud, baud);
+}
+
+static void cp210x_set_termios(struct tty_struct *tty,
+               struct usb_serial_port *port, struct ktermios *old_termios)
+{
+       struct device *dev = &port->dev;
+       unsigned int cflag, old_cflag;
+       unsigned int bits;
+       unsigned int modem_ctl[4];
+
+       cflag = tty->termios.c_cflag;
+       old_cflag = old_termios->c_cflag;
+
+       if (tty->termios.c_ospeed != old_termios->c_ospeed)
+               cp210x_change_speed(tty, port, old_termios);
+
+       /* If the number of data bits is to be updated */
+       if ((cflag & CSIZE) != (old_cflag & CSIZE)) {
+               cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
+               bits &= ~BITS_DATA_MASK;
+               switch (cflag & CSIZE) {
+               case CS5:
+                       bits |= BITS_DATA_5;
+                       dev_dbg(dev, "%s - data bits = 5\n", __func__);
+                       break;
+               case CS6:
+                       bits |= BITS_DATA_6;
+                       dev_dbg(dev, "%s - data bits = 6\n", __func__);
+                       break;
+               case CS7:
+                       bits |= BITS_DATA_7;
+                       dev_dbg(dev, "%s - data bits = 7\n", __func__);
+                       break;
+               case CS8:
+                       bits |= BITS_DATA_8;
+                       dev_dbg(dev, "%s - data bits = 8\n", __func__);
+                       break;
+               /*case CS9:
+                       bits |= BITS_DATA_9;
+                       dev_dbg(dev, "%s - data bits = 9\n", __func__);
+                       break;*/
+               default:
+                       dev_dbg(dev, "cp210x driver does not support the number of bits requested, using 8 bit mode\n");
+                       bits |= BITS_DATA_8;
+                       break;
+               }
+               if (cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2))
+                       dev_dbg(dev, "Number of data bits requested not supported by device\n");
+       }
+
+       if ((cflag     & (PARENB|PARODD|CMSPAR)) !=
+           (old_cflag & (PARENB|PARODD|CMSPAR))) {
+               cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
+               bits &= ~BITS_PARITY_MASK;
+               if (cflag & PARENB) {
+                       if (cflag & CMSPAR) {
+                               if (cflag & PARODD) {
+                                       bits |= BITS_PARITY_MARK;
+                                       dev_dbg(dev, "%s - parity = MARK\n", __func__);
+                               } else {
+                                       bits |= BITS_PARITY_SPACE;
+                                       dev_dbg(dev, "%s - parity = SPACE\n", __func__);
+                               }
+                       } else {
+                               if (cflag & PARODD) {
+                                       bits |= BITS_PARITY_ODD;
+                                       dev_dbg(dev, "%s - parity = ODD\n", __func__);
+                               } else {
+                                       bits |= BITS_PARITY_EVEN;
+                                       dev_dbg(dev, "%s - parity = EVEN\n", __func__);
+                               }
+                       }
+               }
+               if (cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2))
+                       dev_dbg(dev, "Parity mode not supported by device\n");
+       }
+
+       if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) {
+               cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
+               bits &= ~BITS_STOP_MASK;
+               if (cflag & CSTOPB) {
+                       bits |= BITS_STOP_2;
+                       dev_dbg(dev, "%s - stop bits = 2\n", __func__);
+               } else {
+                       bits |= BITS_STOP_1;
+                       dev_dbg(dev, "%s - stop bits = 1\n", __func__);
+               }
+               if (cp210x_set_config(port, CP210X_SET_LINE_CTL, &bits, 2))
+                       dev_dbg(dev, "Number of stop bits requested not supported by device\n");
+       }
+
+       if ((cflag & CRTSCTS) != (old_cflag & CRTSCTS)) {
+               cp210x_get_config(port, CP210X_GET_FLOW, modem_ctl, 16);
+               dev_dbg(dev, "%s - read modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x\n",
+                       __func__, modem_ctl[0], modem_ctl[1],
+                       modem_ctl[2], modem_ctl[3]);
+
+               if (cflag & CRTSCTS) {
+                       modem_ctl[0] &= ~0x7B;
+                       modem_ctl[0] |= 0x09;
+                       modem_ctl[1] = 0x80;
+                       dev_dbg(dev, "%s - flow control = CRTSCTS\n", __func__);
+               } else {
+                       modem_ctl[0] &= ~0x7B;
+                       modem_ctl[0] |= 0x01;
+                       modem_ctl[1] |= 0x40;
+                       dev_dbg(dev, "%s - flow control = NONE\n", __func__);
+               }
+
+               dev_dbg(dev, "%s - write modem controls = 0x%.4x 0x%.4x 0x%.4x 0x%.4x\n",
+                       __func__, modem_ctl[0], modem_ctl[1],
+                       modem_ctl[2], modem_ctl[3]);
+               cp210x_set_config(port, CP210X_SET_FLOW, modem_ctl, 16);
+       }
+
+}
+
+static int cp210x_tiocmset(struct tty_struct *tty,
+               unsigned int set, unsigned int clear)
+{
+       struct usb_serial_port *port = tty->driver_data;
+       return cp210x_tiocmset_port(port, set, clear);
+}
+
+static int cp210x_tiocmset_port(struct usb_serial_port *port,
+               unsigned int set, unsigned int clear)
+{
+       unsigned int control = 0;
+
+       if (set & TIOCM_RTS) {
+               control |= CONTROL_RTS;
+               control |= CONTROL_WRITE_RTS;
+       }
+       if (set & TIOCM_DTR) {
+               control |= CONTROL_DTR;
+               control |= CONTROL_WRITE_DTR;
+       }
+       if (clear & TIOCM_RTS) {
+               control &= ~CONTROL_RTS;
+               control |= CONTROL_WRITE_RTS;
+       }
+       if (clear & TIOCM_DTR) {
+               control &= ~CONTROL_DTR;
+               control |= CONTROL_WRITE_DTR;
+       }
+
+       dev_dbg(&port->dev, "%s - control = 0x%.4x\n", __func__, control);
+
+       return cp210x_set_config(port, CP210X_SET_MHS, &control, 2);
+}
+
+static void cp210x_dtr_rts(struct usb_serial_port *p, int on)
+{
+       if (on)
+               cp210x_tiocmset_port(p, TIOCM_DTR|TIOCM_RTS, 0);
+       else
+               cp210x_tiocmset_port(p, 0, TIOCM_DTR|TIOCM_RTS);
+}
+
+static int cp210x_tiocmget(struct tty_struct *tty)
+{
+       struct usb_serial_port *port = tty->driver_data;
+       unsigned int control;
+       int result;
+
+       cp210x_get_config(port, CP210X_GET_MDMSTS, &control, 1);
+
+       result = ((control & CONTROL_DTR) ? TIOCM_DTR : 0)
+               |((control & CONTROL_RTS) ? TIOCM_RTS : 0)
+               |((control & CONTROL_CTS) ? TIOCM_CTS : 0)
+               |((control & CONTROL_DSR) ? TIOCM_DSR : 0)
+               |((control & CONTROL_RING)? TIOCM_RI  : 0)
+               |((control & CONTROL_DCD) ? TIOCM_CD  : 0);
+
+       dev_dbg(&port->dev, "%s - control = 0x%.2x\n", __func__, control);
+
+       return result;
+}
+
+static void cp210x_break_ctl(struct tty_struct *tty, int break_state)
+{
+       struct usb_serial_port *port = tty->driver_data;
+       unsigned int state;
+
+       if (break_state == 0)
+               state = BREAK_OFF;
+       else
+               state = BREAK_ON;
+       dev_dbg(&port->dev, "%s - turning break %s\n", __func__,
+               state == BREAK_OFF ? "off" : "on");
+       cp210x_set_config(port, CP210X_SET_BREAK, &state, 2);
+}
+
+static int cp210x_startup(struct usb_serial *serial)
+{
+       struct usb_host_interface *cur_altsetting;
+       struct cp210x_serial_private *spriv;
+
+       /* cp210x buffers behave strangely unless device is reset */
+       usb_reset_device(serial->dev);
+
+       spriv = kzalloc(sizeof(*spriv), GFP_KERNEL);
+       if (!spriv)
+               return -ENOMEM;
+
+       cur_altsetting = serial->interface->cur_altsetting;
+       spriv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber;
+
+       usb_set_serial_data(serial, spriv);
+
+       return 0;
+}
+
+static void cp210x_release(struct usb_serial *serial)
+{
+       struct cp210x_serial_private *spriv;
+
+       spriv = usb_get_serial_data(serial);
+       kfree(spriv);
+}
+
+module_usb_serial_driver(serial_drivers, id_table);
+
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");