From 32d75bb1e6d9733af4705e20711e55a0081af0c8 Mon Sep 17 00:00:00 2001 From: Lionel Date: Fri, 24 Oct 2014 15:06:33 +0200 Subject: [PATCH] First commit --- README | 3 + cm160Server.py | 1145 +++++++++++++++++++++++++++++++++++++++++++++++ driver/Makefile | 33 ++ driver/README | 14 + driver/cp210x.c | 885 ++++++++++++++++++++++++++++++++++++ 5 files changed, 2080 insertions(+) create mode 100644 README create mode 100755 cm160Server.py create mode 100644 driver/Makefile create mode 100644 driver/README create mode 100644 driver/cp210x.c diff --git a/README b/README new file mode 100644 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 index 0000000..347047e --- /dev/null +++ b/cm160Server.py @@ -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 index 0000000..05a3f1b --- /dev/null +++ b/driver/Makefile @@ -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 index 0000000..0fb0070 --- /dev/null +++ b/driver/README @@ -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 index 0000000..a982c0f --- /dev/null +++ b/driver/cp210x.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); -- 2.39.2