# HG changeset patch # User Daniel O'Connor # Date 1511077223 -37800 # Node ID 1f3c12ba927d34444e2d9a94494bc4a684a3123f Rework code for USB interface diff -r 000000000000 -r 1f3c12ba927d .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Sun Nov 19 18:10:23 2017 +1030 @@ -0,0 +1,1 @@ +.*\.pyc diff -r 000000000000 -r 1f3c12ba927d __init__.py diff -r 000000000000 -r 1f3c12ba927d docs/CRC.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/CRC.c Sun Nov 19 18:10:23 2017 +1030 @@ -0,0 +1,53 @@ +INT16U cal_crc_half(INT8U far *pin, INT8U len) +{ + + INT16U crc; + + INT8U da; + INT8U far *ptr; + INT8U bCRCHign; + INT8U bCRCLow; + + INT16U crc_ta[16]= + { + 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, + + 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef + }; + ptr=pin; + crc=0; + + while(len--!=0) + { + da=((INT8U)(crc>>8))>>4; + + crc<<=4; + + crc^=crc_ta[da^(*ptr>>4)]; + + da=((INT8U)(crc>>8))>>4; + + crc<<=4; + + crc^=crc_ta[da^(*ptr&0x0f)]; + + ptr++; + } + bCRCLow = crc; + + bCRCHign= (INT8U)(crc>>8); + + if(bCRCLow==0x28||bCRCLow==0x0d||bCRCLow==0x0a) + + { + bCRCLow++; + } + if(bCRCHign==0x28||bCRCHign==0x0d||bCRCHign==0x0a) + + { + bCRCHign++; + } + crc = ((INT16U)bCRCHign)<<8; + crc += bCRCLow; + return(crc); +} diff -r 000000000000 -r 1f3c12ba927d docs/CRC_README.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/CRC_README.txt Sun Nov 19 18:10:23 2017 +1030 @@ -0,0 +1,7 @@ +QPI CRC value is: 0xBEAC + +QPI: 0x51 0x50 0x49 + +Return: 0x0D + +The QPI command (HEX value) sent to unit is: 515049BEAC0D \ No newline at end of file diff -r 000000000000 -r 1f3c12ba927d docs/HS_MS_MSX RS232 Protocol 20140822 (after current upgrade).pdf Binary file docs/HS_MS_MSX RS232 Protocol 20140822 (after current upgrade).pdf has changed diff -r 000000000000 -r 1f3c12ba927d giant.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/giant.py Sun Nov 19 18:10:23 2017 +1030 @@ -0,0 +1,163 @@ +#!/usr/bin/env python + +# pip install --user pyusb pycrc +import crc16 +import exceptions +import os +import usb.core, usb.util, usb.control # https://github.com/pyusb/pyusb + +# USB details +# Borrowed from http://allican.be/blog/2017/01/28/reverse-engineering-cypress-serial-usb.html +vendorId = 0x0665 +productId = 0x5161 +interface = 0 + +# Have to add one to these CRCs values before sending +badvals = [0x28, 0x0d, 0x0a] + +class Timeout(exceptions.BaseException): + pass + +class FramingError(exceptions.BaseException): + pass + +class CRCError(exceptions.BaseException): + pass + +class GiantIPS(object): + PIWS = ['Reserved', 'InverterFault', 'BusOver', 'BusUnder', 'BusSoftFail', 'LineFail', 'OPVShort', 'InverterVoltsLow', + 'InverterVoltsHigh', 'OverTemp', 'FanLocked', 'BattVoltsHigh', 'BattLowAlarm', 'Reserved(Overcharge)', + 'BatterySHutdown', 'Reserved(BattDerate)', 'Overload', 'EEPROM', 'InverterOverCurrent', 'SelfTest', + 'OPDCVoltsOver', 'BattOpen', 'CurrentSenseFail', 'BatteryShort', 'PowerLimit', 'PVVoltsHigh1', + 'MPPTOverload', 'MPPTOverloadWarn', 'BattTooLowChrg', 'PVVoltsHigh2', 'MPPTOverload2', 'MPPTOverloadWarn2', + 'BattTooLowChrg2', 'PVVoltsHigh3', 'MPPTOverload3', 'MPPTOverloadWarn3', 'BattTooLowChrg3'] + def __init__(self): + dev = usb.core.find(idVendor = vendorId, idProduct = productId) + if dev.is_kernel_driver_active(interface): + dev.detach_kernel_driver(interface) + dev.set_interface_altsetting(0, 0) + self.dev = dev + + def compose_msg(self, data): + crc = crc16.crc16xmodem(data) + crclow = crc & 0xff + crchigh = crc >> 8 + if crclow in badvals: + crclow += 1 + if crchigh in badvals: + crchigh += 1 + data = data + chr(crchigh) + chr(crclow) + chr(0x0d) + while len(data) < 8: + data = data + b'\0' + return data + + def tx_msg(self, data): + self.dev.ctrl_transfer(0x21, 0x9, 0x200, 0, self.compose_msg(data)) + + def rx_msg(self): + res = '' + tries = 200 + while tries > 0 and '\r' not in res: + try: + d = self.dev.read(0x81, 8, 10) + res += ''.join([chr(i) for i in d if i != 0x00]) + except usb.core.USBError as e: + if e.errno == 110: # timeout + tries -= 1 + pass + else: + raise + + + if tries == 0: + raise Timeout() + if res[0] != '(' or res[-1] != '\r': + raise FramingError() + crc = crc16.crc16xmodem(res[0:-3]) + crclow = crc & 0xff + crchigh = crc >> 8 + if ord(res[-3]) != crchigh or ord(res[-2]) != crclow: + #raise CRCError() + print('CRC error') + + return res[1:-3] + + def cmd(self, cmd): + self.tx_msg(cmd) + return self.rx_msg() + + def getStatus(self): + d = self.cmd('QPIGS').split() + status = {} + if d[16][0] == '1': + status['SBUPrio'] = True + else: + status['SBUPrio'] = False + if d[16][1] == '1': + status['ConfigChg'] = True + else: + status['ConfigChg'] = False + if d[16][2] == '1': + status['BattVoltSteady'] = True + else: + status['BattVoltSteady'] = False + if d[16][3] == '1': + status['Charging'] = True + else: + status['Charging'] = False + if d[16][4] == '1': + status['SCC1Charging'] = True + else: + status['SCC1Charging'] = False + if d[16][5:] == '110': + status['ChargeType'] = 'SCC1' + elif d[16][5:] == '101': + status['ChargeType'] = 'AC' + else: + status['ChargeType'] = 'Both' + if d[20][0] == '1': + status['FloatCharge'] = True + else: + status['FloatCharge'] = False + if d[20][1] == '1': + status['Switch'] = True + else: + status['Switch'] = False + return { + 'GridVolts' : float(d[0]), + 'GridFreq' : float(d[1]), + 'ACVolts' : float(d[2]), + 'ACFreq' : float(d[3]), + 'ACAppPower' : float(d[4]), + 'ACActPower' : float(d[5]), + 'LoadPct' : float(d[6]), + 'BusVolts' : float(d[7]), + 'BattVolts' : float(d[8]), + 'BattChrCurr' : float(d[9]), + 'BattCap' : float(d[10]), + 'HSTemp' : float(d[11]), + 'PVCurr1' : float(d[12]), + 'PVVolt1' : float(d[13]), + 'SCC1Volt' : float(d[14]), + 'BattDisCurr' : float(d[15]), + 'Status' : status, + 'BattVoltOfs' : float(d[17]) / 0.01, # 10mV + 'PVChrgPow1' : float(d[19]), + } + + def getAlarms(self): + d = self.cmd('QPIWS') + res = {} + for i in range(min(len(d), len(self.PIWS))): + if d[i] == '1': + res[self.PIWS[i]] = True + else: + res[self.PIWS[i]] = False + return res + +def main(): + ips = GiantIPS() + print(ips.cmd('QPI')) + +if __name__ == '__main__': + main()