diff --git a/DeltaPVOutput.py b/DeltaPVOutput.py index 7bfee0c..9578f3b 100755 --- a/DeltaPVOutput.py +++ b/DeltaPVOutput.py @@ -1,78 +1,79 @@ -#A simple script to read values from a delta inverter and post them to -#PVoutput.org - -import time, subprocess,serial,argparse,sys -from deltaInv import DeltaInverter -from time import localtime, strftime - -#PVOutput.org API Values - UPDATE THESE TO YOURS! -SYSTEMID="" -APIKEY="" - -parser = argparse.ArgumentParser() - -if __name__ == '__main__': - - parser.add_argument('-v','--verbose',help='Enable verbose output to stderr',default=False,dest='verbose_mode',required=False,action='store_true') - parser_results = parser.parse_args() - - #Edit your serial connection as required!! - connection = serial.Serial('/dev/ttyUSB0',19200,timeout=0.2); - localtime = time.localtime(time.time()) - - t_date = 'd={0}'.format(strftime('%Y%m%d')) - t_time = 't={0}'.format(strftime('%H:%M')) - - inv1 = DeltaInverter(1) #init Inverter 1 - #Get the Daily Energy thus far - cmd = inv1.getCmdStringFor('Day Wh') - connection.write(cmd) - response = connection.read(100) - #if no response the inverter is asleep - if response: - value = inv1.getValueFromResponse(response) - t_energy = 'v1={0}'.format(value) - - #instanteous power - cmd = inv1.getCmdStringFor('AC Power') - connection.write(cmd) - response = connection.read(100) - value = inv1.getValueFromResponse(response) - t_power = 'v2={0}'.format(value) - - #AC Voltage - cmd = inv1.getCmdStringFor('AC Volts') - connection.write(cmd) - response = connection.read(100) - value = inv1.getValueFromResponse(response) - t_volts = 'v6={0}'.format(value) - - #Temp - this appears to be onboard somewhere not the heatsink - cmd = inv1.getCmdStringFor('DC Temp') - connection.write(cmd) - response = connection.read(100) - value = inv1.getValueFromResponse(response) - t_temp = 'v5={0}'.format(value) - - #if verbose mode - if parser_results.verbose_mode==True: - sys.stderr.write('Date: %s, Time: %s\n' %(t_date, t_time)) - sys.stderr.write('Energy Today: %sWh, Instantaneous Power: %sW\n' %(t_energy,t_power)) - sys.stderr.write('Volts: %s, Temp: %s oC\n' % (t_volts,t_temp)) - sys.stderr.flush() - - #Send it all off to PVOutput.org - cmd = ['/usr/bin/curl', - '-d', t_date, - '-d', t_time, - '-d', t_energy, - '-d', t_power, - '-d', t_volts, - '-d', t_temp, - '-H', 'X-Pvoutput-Apikey: ' + APIKEY, - '-H', 'X-Pvoutput-SystemId: ' + SYSTEMID, - 'http://pvoutput.org/service/r1/addstatus.jsp'] - ret = subprocess.call (cmd) - else: - print "No response from inverter - shutdown? No Data sent to PVOutput.org" - connection.close() +#A simple script to read values from a delta inverter and post them to +#PVoutput.org + +import time, subprocess,serial,argparse,sys +from deltaInv import DeltaInverter +from time import localtime, strftime + +#PVOutput.org API Values - UPDATE THESE TO YOURS! +SYSTEMID="" +APIKEY="" + +parser = argparse.ArgumentParser() + +if __name__ == '__main__': + + parser.add_argument('-v','--verbose',help='Enable verbose output to stderr',default=False,dest='verbose_mode',required=False,action='store_true') + parser_results = parser.parse_args() + + #Edit your serial connection as required!! + connection = serial.Serial('/dev/ttyUSB0',19200,timeout=0.2); + localtime = time.localtime(time.time()) + + t_date = 'd={0}'.format(strftime('%Y%m%d')) + t_time = 't={0}'.format(strftime('%H:%M')) + + inv1 = DeltaInverter(1) #init Inverter 1 + #Get the Daily Energy thus far + cmd = inv1.getCmdStringFor('Day Wh') + connection.write(cmd) + response = connection.read(100) + #if no response the inverter is asleep + if response: + value = inv1.getValueFromResponse(response) + t_energy = 'v1={0}'.format(value) + + #instanteous power + cmd = inv1.getCmdStringFor('AC Power') + connection.write(cmd) + response = connection.read(100) + value = inv1.getValueFromResponse(response) + t_power = 'v2={0}'.format(value) + + #AC Voltage + cmd = inv1.getCmdStringFor('AC Volts') + connection.write(cmd) + response = connection.read(100) + value = inv1.getValueFromResponse(response) + t_volts = 'v6={0}'.format(value) + + #Temp - this appears to be onboard somewhere not the heatsink + cmd = inv1.getCmdStringFor('DC Temp') + connection.write(cmd) + response = connection.read(100) + value = inv1.getValueFromResponse(response) + t_temp = 'v5={0}'.format(value) + + #if verbose mode + if parser_results.verbose_mode==True: + sys.stderr.write('Date: %s, Time: %s\n' %(t_date, t_time)) + sys.stderr.write('Energy Today: %sWh, Instantaneous Power: %sW\n' %(t_energy,t_power)) + sys.stderr.write('Volts: %s, Temp: %s oC\n' % (t_volts,t_temp)) + sys.stderr.flush() + + #Send it all off to PVOutput.org + cmd = ['/usr/bin/curl', + '-d', t_date, + '-d', t_time, + '-d', t_energy, + '-d', t_power, + '-d', t_volts, + '-d', t_temp, + '-H', 'X-Pvoutput-Apikey: ' + APIKEY, + '-H', 'X-Pvoutput-SystemId: ' + SYSTEMID, + 'http://pvoutput.org/service/r1/addstatus.jsp'] + ret = subprocess.call (cmd) + else: + print ("No response from inverter - shutdown? No Data sent to PVOutput.org") + connection.close() + diff --git a/crc.py b/crc.py index f95b797..edb3279 100755 --- a/crc.py +++ b/crc.py @@ -1,54 +1,55 @@ -class CRC16: - - INITIAL_MODBUS = 0xFFFF - INITIAL_DF1 = 0x0000 - - table = ( - 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, - 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, - 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, - 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, - 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, - 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, - 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, - 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, - 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, - 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, - 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, - 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, - 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, - 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, - 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, - 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, - 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, - 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, - 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, - 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, - 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, - 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, - 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, - 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, - 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, - 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, - 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, - 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, - 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, - 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, - 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, - 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 ) - - def calcByte(self, ch, crc): - """Given a new Byte and previous CRC, Calc a new CRC-16""" - if type(ch) == type("c"): - by = ord( ch) - else: - by = ch - crc = (crc >> 8) ^ self.table[(crc ^ by) & 0xFF] - return (crc & 0xFFFF) - - def calcString(self, st, crc=INITIAL_DF1): - """Given a bunary string and starting CRC, Calc a final CRC-16 """ - for ch in st: - crc = (crc >> 8) ^ self.table[(crc ^ ord(ch)) & 0xFF] - return crc - +class CRC16: + + INITIAL_MODBUS = 0xFFFF + INITIAL_DF1 = 0x0000 + + table = ( + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, + 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, + 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, + 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, + 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, + 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, + 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, + 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, + 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, + 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, + 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, + 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, + 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, + 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, + 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, + 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, + 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, + 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, + 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, + 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, + 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, + 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, + 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, + 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, + 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, + 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, + 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, + 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, + 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 ) + + def calcByte(self, byte, crc): + """Given a new Byte and previous CRC, Calc a new CRC-16""" + if type(ch) == type("c"): + by = ord( ch) + else: + by = ch + crc = (crc >> 8) ^ self.table[(crc ^ by) & 0xFF] + return (crc & 0xFFFF) + + def calcString(self, data, crc=INITIAL_DF1): + """Given a bunary string and starting CRC, Calc a final CRC-16 """ + for byte in data: + crc = (crc >> 8) ^ self.table[(crc ^ byte) & 0xFF] + return crc + + diff --git a/deltaInv.py b/deltaInv.py index 8497b7d..11f809c 100755 --- a/deltaInv.py +++ b/deltaInv.py @@ -1,139 +1,137 @@ -import crc,struct,sys -from crc import CRC16 -from struct import * -class DeltaInverter: - - inverterNum=0; - - #Known Commands - ##StrValue, Format, divisor, units - - cmds = {'\x10\x01': ('DC Cur1',0,10.0,'A'), - '\x10\x02': ('DC Volts1',0,1,'V'), - '\x10\x03': ('DC Pwr1',0,1,'W'), - '\x10\x04': ('DC Cur2',0,10.0,'A'), - '\x10\x05': ('DC Volts2',0,1,'V'), - '\x10\x06': ('DC Pwr2',0,1,'W'), - '\x10\x07': ('AC Current',0,10.0,'A'), - '\x10\x08': ('AC Volts',0,1,'V'), - '\x10\x09': ('AC Power',0,1,'W'), - '\x11\x07': ('AC I Avg',0,10.0,'A'), - '\x11\x08': ('AC V Avg',0,1,'V'), - '\x11\x09': ('AC P Avg',0,1,'W'), - '\x13\x03': ('Day Wh',0,1,'Wh'), - '\x13\x04': ('Uptime',0,1,'min'), - '\x00\x00': ('Inverter Type',9,0,''), - '\x00\x01': ('Serial',1,0,''), - '\x00\x08': ('Part',1,0,''), - '\x00\x40': ('FW Version',10,0,''), - '\x20\x05': ('AC Temp',0,1,'o'), - '\x21\x08': ('DC Temp',0,1,'o') - }; - - - #Constructor takes inverter number (incase you have more than 1) - def __init__(self,inverter=1): - self.inverterNum=inverter - self.crcCalc = CRC16() - - #private to do the binary packing of the protocol - def __buildCmd(self, cmd): - l = len(cmd) - crc = self.crcCalc.calcString( struct.pack('BBB%ds'%l,5,self.inverterNum,l,cmd)) - lo = crc & (0xff); - high = (crc>>8) & 0xff; - return struct.pack('BBBB%dsBBB' %len(cmd),2,5,self.inverterNum,len(cmd),cmd,lo,high,3); - - #retrieves the instruction for the given human readable command - def __findCmd(self,strValue): - for k,v in self.cmds.iteritems(): - if(v[0]==strValue): - return k - #unpacks the given command into an {Instruction} {Value} {Units} string - def __unpackFormatted(self,cmd): - if not self.isValidResponse(cmd): - return "Invalid Response" - cmdcontents = cmd[1:-3] - lendata = ord(cmdcontents[2])-2 - try: - stringName,fmt,divisor,unit = self.cmds[cmdcontents[3:5]] - if fmt==0: ##General Numbers - resp,invNum,size,instruction,value = struct.unpack('>BBB2sH',cmdcontents) - value = value / divisor - elif fmt==1: ##ascii string - resp,invNum,size,instruction,value = struct.unpack('>BBB2s%ds' %lendata,cmdcontents) - elif fmt==9: ##Model - resp,invNum,size,instruction,typeof,model,value = struct.unpack('>BBB2sBB%ds' % (lendata-2),cmdcontents) - return self.cmds[instruction][0]+": Type:" + str(typeof) + " Model:" +value - elif fmt==10: ##FWVersion # - resp,invNum,size,instruction,ver,major,minor = struct.unpack('>BBB2sBBB',cmdcontents) - return self.cmds[instruction][0]+": " + str(ver) +"." + str(major)+ "."+ str(minor) - else: - resp,invNum,size,instruction,value = struct.unpack('>BBB2s%ds' % lendata,cmdcontents) - return self.cmds[instruction][0] + ": " + str(value) + " "+unit - except: - return "Error parsing string, perhaps unknown instruction" - - #Returns the packed command to be sent over serail, - #Command includes STX, inverter number, CRC, ETX - def getCmdStringFor(self,cmd): - return self.__buildCmd(self.__findCmd(cmd)) - - #Returns a formatted human readble form of a response - def getFormattedResponse(self,cmd): - return self.__unpackFormatted(cmd) - - #Returns a raw value from a response - def getValueFromResponse(self,cmd): - return self.__unpackData(cmd) - - #prints out hex values of a command string and the related instruction - def debugRequestString(self,cmdString): - cmd = cmdString[4:6] - strCmd = self.cmds[cmd][0] - inverter = ord(cmdString[2]) - print "%s on inverter %d:" % (strCmd,inverter) - for ch in cmdString: - sys.stdout.write("%02X " % ord(ch)) - print "" - - #checks for a valid STX, ETX and CRC - def isValidResponse(self,cmd): - if ord(cmd[1])<> 0x06 or ord(cmd[0])!=0x02 or ord(cmd[len(cmd)-1])!=0x03: - return False - cmdcontents = cmd[1:-3] - crc = self.crcCalc.calcString(cmdcontents) - lo = crc & (0xff) - high = (crc>>8) & 0xff - crcByte = len(cmd)-3 - if ord(cmd[crcByte])!=lo or ord(cmd[crcByte+1])!=high: - return False - return True - - #Returns a raw value from a response - def __unpackData(self,cmd): - if not self.isValidResponse(cmd): - return "Invalid Response" - cmdcontents = cmd[1:-3] - lendata = ord(cmdcontents[2])-2 - try: - stringName,fmt,divisor,unit = self.cmds[cmdcontents[3:5]] - if fmt==0: ##General Numbers - resp,invNum,size,instruction,value = struct.unpack('>BBB2sH',cmdcontents) - value = value / divisor - elif fmt==1: ##ascii string - resp,invNum,size,instruction,value = struct.unpack('>BBB2s%ds' %lendata,cmdcontents) - # return self.cmds[instruction][0] + ": " + str(value) + " "+unit - elif fmt==9: ##Model - resp,invNum,size,instruction,typeof,model,value = struct.unpack('>BBB2sBB%ds' % (lendata-2),cmdcontents) - return ": Type:" + str(typeof) + " Model:" +value - elif fmt==10: ##FWVersion # - resp,invNum,size,instruction,ver,major,minor = struct.unpack('>BBB2sBBB',cmdcontents) - return str(ver) +"." + str(major)+ "."+ str(minor) - else: - resp,invNum,size,instruction,value = struct.unpack('>BBB2s%ds' % lendata,cmdcontents) - return str(value) - except: - return "Error parsing string, perhaps unknown instruction" - - +import struct,sys,re +from crc import CRC16 +from struct import * +class DeltaInverter: + + inverterNum=0; + + #Known Commands + ##StrValue, Format, divisor, units + + cmds = {'\x10\x01': ('DC Cur1',0,10.0,'A'), + '\x10\x02': ('DC Volts1',0,1,'V'), + '\x10\x03': ('DC Pwr1',0,1,'W'), + '\x10\x04': ('DC Cur2',0,10.0,'A'), + '\x10\x05': ('DC Volts2',0,1,'V'), + '\x10\x06': ('DC Pwr2',0,1,'W'), + '\x10\x07': ('AC Current',0,10.0,'A'), + '\x10\x08': ('AC Volts',0,1,'V'), + '\x10\x09': ('AC Power',0,1,'W'), + '\x11\x07': ('AC I Avg',0,10.0,'A'), + '\x11\x08': ('AC V Avg',0,1,'V'), + '\x11\x09': ('AC P Avg',0,1,'W'), + '\x13\x03': ('Day Wh',0,1,'Wh'), + '\x13\x04': ('Uptime',0,1,'min'), + '\x00\x00': ('Inverter Type',9,0,''), + '\x00\x01': ('Serial',1,0,''), + '\x00\x08': ('Part',1,0,''), + '\x00\x40': ('FW Version',10,0,''), + '\x20\x05': ('AC Temp',0,1,'o'), + '\x21\x06': ('DC Temp',0,1,'o') + }; + + + #Constructor takes inverter number (incase you have more than 1) + def __init__(self,inverter=1): + self.inverterNum=inverter + self.crcCalc = CRC16() + + #private to do the binary packing of the protocol + def __buildCmd(self, cmd): + l = len(cmd) + crc = self.crcCalc.calcString( struct.pack('BBB%ds'%l,5,self.inverterNum,l,bytes(cmd,'utf-8'))) + lo = crc & (0xff); + high = (crc>>8) & 0xff; + return struct.pack('BBBB%dsBBB' %len(cmd),2,5,self.inverterNum,len(cmd),bytes(cmd,'utf-8'),lo,high,3); + + #retrieves the instruction for the given human readable command + def __findCmd(self,strValue): + for k,v in self.cmds.items(): + if(v[0]==strValue): + return k + #unpacks the given command into an {Instruction} {Value} {Units} string + def __unpackFormatted(self,cmd): + if not self.isValidResponse(cmd): + return "Invalid Response" + cmdcontents = cmd[1:-3] + lendata = ord(cmdcontents[2])-2 + try: + stringName,fmt,divisor,unit = self.cmds[cmdcontents[3:5].decode('utf-8')] + if fmt==0: ##General Numbers + resp,invNum,size,instruction,value = struct.unpack('>BBB2sH',cmdcontents) + value = value / divisor + elif fmt==1: ##ascii string + resp,invNum,size,instruction,value = struct.unpack('>BBB2s%ds' %lendata,cmdcontents) + elif fmt==9: ##Model + resp,invNum,size,instruction,typeof,model,value = struct.unpack('>BBB2sBB%ds' % (lendata-2),cmdcontents) + return self.cmds[instruction][0]+": Type:" + str(typeof) + " Model:" +value + elif fmt==10: ##FWVersion # + resp,invNum,size,instruction,ver,major,minor = struct.unpack('>BBB2sBBB',cmdcontents) + return self.cmds[instruction][0]+": " + str(ver) +"." + str(major)+ "."+ str(minor) + else: + resp,invNum,size,instruction,value = struct.unpack('>BBB2s%ds' % lendata,cmdcontents) + return self.cmds[instruction][0] + ": " + str(value) + " "+unit + except: + return "Error parsing string, perhaps unknown instruction" + + #Returns the packed command to be sent over serial, + #Command includes STX, inverter number, CRC, ETX + def getCmdStringFor(self,cmd): + return self.__buildCmd(self.__findCmd(cmd)) + + #Returns a formatted human readble form of a response + def getFormattedResponse(self,cmd): + return self.__unpackFormatted(cmd) + + #Returns a raw value from a response + def getValueFromResponse(self,cmd): + return self.__unpackData(cmd) + + #prints out hex values of a command string and the related instruction + def debugRequestString(self,cmdString): + cmd = cmdString[4:6] + strCmd = self.cmds[cmd][0] + inverter = ord(cmdString[2]) + print(("%s on inverter %d:" % (strCmd,inverter))) + for ch in cmdString: + sys.stdout.write("%02X " % ord(ch)) + print ("") + + #checks for a valid STX, ETX and CRC + def isValidResponse(self,cmd): + if cmd[1]!= 0x06 or cmd[0]!=0x02 or cmd[len(cmd)-1]!=0x03: + return False + cmdcontents = cmd[1:-3] + crc = self.crcCalc.calcString(cmdcontents) + lo = crc & (0xff) + high = (crc>>8) & 0xff + crcByte = len(cmd)-3 + if cmd[crcByte]!=lo or cmd[crcByte+1]!=high: + return False + return True + + #Returns a raw value from a response + def __unpackData(self,cmd): + if not self.isValidResponse(cmd): + return "Invalid Response" + cmdcontents = cmd[1:-3] + lendata = cmdcontents[2]-2 + try: + stringName,fmt,divisor,unit = self.cmds[cmdcontents[3:5].decode('utf-8')] + if fmt==0: ##General Numbers + resp,invNum,size,instruction,value = struct.unpack('>BBB2sH',cmdcontents) + value = value / divisor + elif fmt==1: ##ascii string + resp,invNum,size,instruction,value = struct.unpack('>BBB2s%ds' %lendata,cmdcontents) + # return self.cmds[instruction][0] + ": " + str(value) + " "+unit + elif fmt==9: ##Model + resp,invNum,size,instruction,typeof,model,value = struct.unpack('>BBB2sBB%ds' % (lendata-2),cmdcontents) + return ": Type:" + str(typeof) + " Model:" +value + elif fmt==10: ##FWVersion # + resp,invNum,size,instruction,ver,major,minor = struct.unpack('>BBB2sBBB',cmdcontents) + return str(ver) +"." + str(major)+ "."+ str(minor) + else: + resp,invNum,size,instruction,value = struct.unpack('>BBB2s%ds' % lendata,cmdcontents) + return str(value) + except: + return "Error parsing string, perhaps unknown instruction"