Advertisement
Guest User

cms.py

a guest
Feb 18th, 2011
134
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.11 KB | None | 0 0
  1. # Copyright (c) 2010-2011 Edmund Tse
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a copy of
  4. # this software and associated documentation files (the "Software"), to deal in
  5. # the Software without restriction, including without limitation the rights to
  6. # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
  7. # of the Software, and to permit persons to whom the Software is furnished to do
  8. # so, subject to the following conditions:
  9. #
  10. # The above copyright notice and this permission notice shall be included in all
  11. # copies or substantial portions of the Software.
  12. #
  13. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  19. # SOFTWARE.
  20. #
  21.  
  22. import struct
  23. import pv
  24.  
  25. def bin2hex(data):
  26.     """
  27.     Converts binary data to hex
  28.     """
  29.     return data.encode('hex_codec')
  30.  
  31. def checksum(data):
  32.     """
  33.     (bytes) -> (2 bytes)
  34.     Computes the checksum for the given data.
  35.     """
  36.     return struct.pack('!H', sum(map(ord, data)))
  37.  
  38. def parse_frame(data):
  39.     """
  40.     Converts a frame in binary format into a Frame object
  41.     """
  42.     if len(data) < 11# 2B sync 2B src 2B dst 2B cmd 1B len 0B data 2B checksum
  43.         raise ValueError("Frame too short (%d B)" % len(data))
  44.  
  45.     if not checksum(data[0:-2]) == data[-2:]:
  46.         raise ValueError("Bad checksum")
  47.  
  48.     (preamble, src, dst, cmd, size) = struct.unpack('!HHHHB', data[0:9])
  49.     if preamble != Frame.SYNC:
  50.         raise ValueError("Bad preamble")
  51.  
  52.     payload = data[9:-2]
  53.     if len(payload) != size:
  54.         raise ValueError("Bad payload size: expected %d, actual %d" % (size, len(payload)))
  55.  
  56.     return Frame(cmd, payload, dst, src)
  57.  
  58. def interpret_data(data, layout, dictionary):
  59.     try:
  60.         numbers = struct.unpack('!' + 'H'*len(layout), data)
  61.     except struct.error as e:
  62.         print "Error unpacking data:", e
  63.         return None
  64.  
  65.     values = dict(zip(layout, numbers))
  66.     return [(name, reduce(lambda x,y:(x<<16) + y, map(values.get, code)) / divisor)
  67.             for name, (code, divisor) in dictionary.items()
  68.             if reduce(lambda x,y: x and y, map(values.has_key, code))]
  69.  
  70. class Frame(object):
  71.     """
  72.     <sync> <src> <dst> <cmd> <len> <payload> <checksum>
  73.       2B    2B    2B    2B    1B     len B       2B
  74.     """
  75.     MAX_SIZE =      256         # Arbitrary max frame size
  76.     SYNC =          0xaaaa      # 2 sync bytes preamble "1010101010101010"
  77.     ADDR_DEFAULT =  0x0000      # Broadcast address
  78.     ADDR_HOST =     0x0100      # Default host address
  79.     ADDR_DEV =      0x0001      # Default device address
  80.     # Commands
  81.     CMD_DSC =   0x0000      # Computer discovers devices
  82.     CMD_DSC_R = 0x0080      # Inverter advertises its serial number
  83.     CMD_REG =   0x0001      # Address registration
  84.     CMD_REG_R = 0x0081      # Acknowledge the assigned address
  85.     CMD_RMV =   0x0002      # Disconnect
  86.     CMD_RMV_R = 0x0082      # Disconnect ACK
  87.     CMD_RCT =   0x0003      # Reconnect all devices
  88.     CMD_RST =   0x0004      # Reset communications
  89.     CMD_STL =   0x0100      # Status frame structure request
  90.     CMD_STL_R = 0x0180      # Status frame structure reply
  91.     CMD_PRL =   0x0101      # Parameter frame structure request
  92.     CMD_PRL_R = 0x0181      # Parameter frame structure reply
  93.     CMD_STA =   0x0102      # Status request
  94.     CMD_STA_R = 0x0182      # Status reply
  95.     CMD_VER =   0x0103      # Version string request
  96.     CMD_VER_R = 0x0183      # Version string reply
  97.     CMD_PRM =   0x0104      # Parameter request
  98.     CMD_PRM_R = 0x0184      # Parameters reply
  99.     CMD_SP0 =   0x0200      # Set Vpv-Start
  100.     CMD_SP0_R = 0x0280      # Set Vpv-Start ACK
  101.     CMD_SP1 =   0x0201      # Set T-Start
  102.     CMD_SP1_R = 0x0281      # Set T-Start ACK
  103.     CMD_SP2 =   0x0204      # Set Vac-Min
  104.     CMD_SP2_R = 0x0284      # Set Vac-Min ACK
  105.     CMD_SP3 =   0x0205      # Set Vac-Max
  106.     CMD_SP3_R = 0x0285      # Set Vac-Max ACK
  107.     CMD_SP4 =   0x0206      # Set Fac-Max
  108.     CMD_SP4_R = 0x0286      # Set Fac-Max ACK
  109.     CMD_SP5 =   0x0207      # Set Fac-Min
  110.     CMD_SP5_R = 0x0287      # Set Fac-Min ACK
  111.     CMD_SP6 =   0x0208      # Set DZac-Max
  112.     CMD_SP6_R = 0x0288      # Set DZac-Max ACK
  113.     CMD_SP7 =   0x0209      # Set DZac
  114.     CMD_SP7_R = 0x0289      # Set DZac ACK
  115.     CMD_ZRO =   0x0300      # Reset inverter E-Total and h-Total
  116.     CMD_ZRO_R = 0x0380      # Reset inverter E-Total and h-Total ACK
  117.  
  118.     def __init__(self, cmd, payload='', dst=ADDR_DEFAULT, src=ADDR_DEFAULT):
  119.         assert(type(src) == int)
  120.         assert(type(cmd) == int)
  121.         assert(type(payload) == str)
  122.         assert(type(dst) == int)
  123.         assert(type(src) == int)
  124.         (self.src, self.dst, self.cmd, self.payload) = (src, dst, cmd, payload)
  125.  
  126.     def __repr__(self):
  127.         return bin2hex(self.bytes())
  128.  
  129.     def colorize(self):
  130.         """
  131.         Returns ANSI coloured hex representation of the frame
  132.         """
  133.         string = str(self)
  134.         if len(string) < 22:
  135.             return string
  136.         return '\033[90m' + string[0:4] + '\033[93m' + string[4:8] + \
  137.                 '\033[94m' + string[8:12] + '\033[97m' + string[12:16] + \
  138.                 '\033[91m' + string[16:18] + '\033[00m' + string[18:-4] + \
  139.                 '\033[92m' + string[-4:] + '\033[00m'
  140.  
  141.     def bytes(self):
  142.         """
  143.         Returns the bytes of this frame including preamble and checksum.
  144.         """
  145.         data = struct.pack('!HHHHB', Frame.SYNC, self.src, self.dst, self.cmd, len(self.payload)) + self.payload
  146.         return data + checksum(data)
  147.  
  148. class Device:
  149.     """
  150.     Device is a base class that provides physical and link layer operations
  151.     """
  152.     STATUS = {
  153.             # Field     Code            Divisor
  154.             'Temp-inv': ('\x00',        10.0),      # Inverter internal temperature (deg C)
  155.             'Vpv1':     ('\x01',        10.0),      # PV1 Voltage (V)
  156.             'Vpv2':     ('\x02',        10.0),      # PV2 Voltage (V)
  157.             'Vpv3':     ('\x03',        10.0),      # PV3 Voltage (V)
  158.             'Ipv1':     ('\x04',        10.0),      # PV1 Current (A)
  159.             'Ipv2':     ('\x05',        10.0),      # PV2 Current (A)
  160.             'Ipv3':     ('\x06',        10.0),      # PV3 Current (A)
  161.             'Vpv':      ('\x40',        10.0),      # PV Voltage (V)
  162.             'Iac':      ('\x41',        10.0),      # Current to grid (A)
  163.             'Vac':      ('\x42',        10.0),      # Grid voltage (V)
  164.             'Fac':      ('\x43',        100.0),     # Grid frequency (Hz)
  165.             'Pac':      ('\x44',        1),         # Power to grid (W)
  166.             'Zac':      ('\x45',        1),         # Grid impedance (mOhm)
  167.             'E-Total'('\x47\x48',    10.0),      # Total energy to grid (kWh)
  168.             'h-Total'('\x49\x4a',    1),         # Total Operation hours (Hr)
  169.             'Mode':     ('\x4c',        1),         # Operation mode
  170.             'Error':    ('\x7e\x7f',    1)          # Error
  171.             }
  172.     PARAM = {
  173.             'Vpc-start':    ('\x40',    10.0),  # PV Start-up voltage (V)
  174.             'T-start':      ('\x41',    1),     # Time to connect grid (Sec)
  175.             'Vac-Min':      ('\x44',    10.0),  # Minimum operational grid voltage
  176.             'Vac-Max':      ('\x45',    10.0),  # Maximum operational grid voltage
  177.             'Fac-Min':      ('\x46',    100.0), # Minimum operational frequency
  178.             'Fac-Max':      ('\x47',    100.0), # Maximum operational frequency
  179.             'Zac-Max':      ('\x48',    1),     # Maximum operational grid impedance
  180.             'DZac':         ('\x49',    1),     # Allowable Delta Zac of operation
  181.             }
  182.     MODE = {0:'Wait', 1:'Normal', 2:'Fault', 3:'Permenant Fault'}
  183.     ERROR = {       # The 2 error bytes are bit fields, e.g. ERROR[16] = 0x0100
  184.              0: ('The GFCI detection circucit is abnormal', 'GFCI ckt fails'),
  185.              1: ('The DC output sensor is abnormal', 'DC sensor fails'),
  186.              2: ('The 2.5V reference inside is abnormal', 'Ref 2.5V fails'),
  187.              3: ('Different measurements between Master and Slave for output DC current', 'DC inj. differs for M-S'),
  188.              4: ('Different measurements between Master and Slave for GFCI', 'Ground I differs for M-S'),
  189.              5: ('DC Bus voltage is too low', 'Bus-Low-Fail'),
  190.              6: ('DC Bus voltage is too High', 'Bus-High-Fail'),
  191.              7: ('Device Fault', 'Device-Fault'),
  192.              8: ('Delta GridZ is too high', 'Delta Z high'),
  193.              9: ('No grid voltage detected', 'No-Utility'),
  194.             10: ('Ground current is too high', 'Ground I high'),
  195.             11: ('DC bus is not correct', 'DC BUS fails'),
  196.             12: ('Master and Slave firmware version is unmatch', 'M-S Version Fail'),
  197.             13: ('Internal temperature is high', 'Temperature high'),
  198.             14: ('AutoTest failed', 'Test Fail'),
  199.             15: ('PV voltage is too high', 'Vpv high'),
  200.             16: ('Fan Lock', 'FanLock-Warning'),
  201.             17: ('The measured AC voltage is out of tolerable range', 'Vac out of range'),
  202.             18: ('Isulation resistance of PV to earth is too low', 'PV insulation low'),
  203.             19: ('The DC injection to grid is too high', 'DC injection high'),
  204.             20: ('Different measurements between Master and Slave for dl, Fac, Uac or Zac', 'Fac,Zac,Vac differs for M-S'),
  205.             21: ('Different measurements between Master and Slave for grid impedance', 'Zac differs for M-S'),
  206.             22: ('Different measurements between Master and Slave for grid frequency', 'Fac differs for M-S'),
  207.             23: ('Different measurements between Master and Slave for grid voltage', 'Vac differs for M-S'),
  208.             24: ('Memory space is full', 'MemFull-Warning'),
  209.             25: ('Test of output AC relay fails', 'AC relay fails'),
  210.             26: ('The slave impedance is out of tolerable range', 'Zac-Slave out of range'),
  211.             27: ('The measured AC impedance is out of range', 'Zac-Master out of range'),
  212.             28: ('The slave frequency is out of tolerable range', 'Fac-Slave out of range'),
  213.             29: ('The master frequency is out of tolerable range', 'Fac-Master out of range'),
  214.             30: ('EEPROM reading or writing error', 'EEPROM fails'),
  215.             31: ('Communication between microcontrollers fails', 'Comm fails between M-S'),
  216.             }
  217.  
  218.     def __init__(self, port, addr):
  219.         self.addr = addr
  220.         self.port = port
  221.  
  222.     def send(self, frm, use_frame_src=False):
  223.         """
  224.         Writes a frame to the port
  225.         """
  226.         if not use_frame_src:
  227.             frm.src = self.addr
  228.         if pv._DEBUG:
  229.             if pv._ANSI_COLOR:
  230.                 print "\033[96mSEND\033[00m ->", frm.colorize()
  231.             else:
  232.                 print "SEND ->", frm
  233.  
  234.         self.port.write(frm.bytes())
  235.  
  236.     def receive(self):
  237.         """
  238.         Returns a valid incoming frame, or None if no valid frames received
  239.         within the timeout period
  240.         """
  241.         sync = struct.pack('!H', Frame.SYNC)
  242.         frm = None
  243.  
  244.         while frm is None:
  245.             byte = self.port.read()
  246.             if len(byte) != 1: break
  247.             if byte != sync[0]: continue
  248.  
  249.             byte = self.port.read()
  250.             if len(byte) != 1: break
  251.             if byte != sync[1]: continue
  252.  
  253.             src_dst_cmd = self.port.read(6)
  254.             if len(src_dst_cmd) != 6: break
  255.  
  256.             length = self.port.read()
  257.             if len(length) != 1: break
  258.  
  259.             payload_checksum = self.port.read(ord(length) + 2)
  260.             if len(payload_checksum) != ord(length) + 2: break
  261.  
  262.             buf = sync + src_dst_cmd + length + payload_checksum
  263.             if pv._DEBUG:
  264.                 if pv._ANSI_COLOR:
  265.                     print "\033[95mRECV\033[00m <-", bin2hex(buf),
  266.                 else:
  267.                     print "RECV <-", bin2hex(buf),
  268.  
  269.             try:
  270.                 frm = parse_frame(buf)
  271.                 if pv._DEBUG:
  272.                     print "OK"
  273.             except ValueError as e:
  274.                 if pv._DEBUG:
  275.                     print e
  276.         return frm
  277.  
  278. class Inverter(Device):
  279.     """
  280.     Contains methods to interact with a PV inverter
  281.     """
  282.     def __init__(self, port, my_addr=Frame.ADDR_HOST):
  283.         Device.__init__(self, port, my_addr)
  284.  
  285.     def reset(self):
  286.         """
  287.         Resets the communication protocol
  288.         """
  289.         self.send(Frame(Frame.CMD_RST))
  290.  
  291.     def discover(self):
  292.         """
  293.         Looks for an available inverter
  294.         Returns the inverter serial number as string, or None if offline
  295.         """
  296.         self.send(Frame(Frame.CMD_DSC))
  297.         frm = self.receive()
  298.         return frm.payload if frm is not None and frm.cmd == Frame.CMD_DSC_R else None
  299.  
  300.     def register(self, sn, addr=Frame.ADDR_DEV):
  301.         """
  302.         Registers an inverter for direct communication
  303.         Returns True if the inverter acknowledges registration
  304.         """
  305.         self.send(Frame(Frame.CMD_REG, sn + struct.pack('!H', addr)))
  306.         frm = self.receive()
  307.         return True if frm is not None and frm.cmd == Frame.CMD_REG_R else False
  308.  
  309.     def version(self, dst=Frame.ADDR_DEV):
  310.         """
  311.         Queries an inverter for an extended identification string
  312.         Returns the device identification string, or None
  313.         """
  314.         self.send(Frame(Frame.CMD_VER, dst=dst))
  315.         frm = self.receive()
  316.         return frm.payload if frm is not None and frm.cmd == Frame.CMD_VER_R else None
  317.  
  318.     def status_layout(self, dst=Frame.ADDR_DEV):
  319.         """
  320.         Queries an inverter for the layout of its status frame
  321.         Returns a string of bytes representing the status layout, or None
  322.         """
  323.         self.send(Frame(Frame.CMD_STL, dst=dst))
  324.         frm = self.receive()
  325.         return frm.payload if frm is not None and frm.cmd == Frame.CMD_STL_R else None
  326.  
  327.     def param_layout(self, dst=Frame.ADDR_DEV):
  328.         """
  329.         Queries an inverter for the layout of its parameters frame
  330.         Returns a string of bytes representing the parameters layout, or None
  331.         """
  332.         self.send(Frame(Frame.CMD_PRL, dst=dst))
  333.         frm = self.receive()
  334.         return frm.payload if frm is not None and frm.cmd == Frame.CMD_PRL_R else None
  335.  
  336.     def parameters(self, layout, dst=Frame.ADDR_DEV):
  337.         """
  338.         Queries an inverter for its parameters
  339.         Returns a list of device parameters, or None
  340.         """
  341.         self.send(Frame(Frame.CMD_PRM, dst=dst))
  342.         frm = self.receive()
  343.         return interpret_data(frm.payload, layout, Device.PARAM) if \
  344.                 frm is not None and frm.cmd == Frame.CMD_PRM_R else None
  345.  
  346.     def status(self, layout, dst=Frame.ADDR_DEV):
  347.         """
  348.         Queries an inverter for its status, given the layout of response frame
  349.         Returns a list of device status, or None
  350.         """
  351.         self.send(Frame(Frame.CMD_STA, dst=dst))
  352.         frm = self.receive()
  353.         return interpret_data(frm.payload, layout, Device.STATUS) if \
  354.                 frm is not None and frm.cmd == Frame.CMD_STA_R else None
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement