class RenkeRsRaN01Jt(SitModbusDevice): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 1 DEFAULT_MODBUS_PORT = '/dev/ttyUSB0' DEFAULT_TARGET_MODE = SitModbusDevice.TARGET_MODE_RTU PARSER_DESCRIPTION = 'Actions with RS-RA-N01-JT from ali irradiance sensor' + SitConstants.DEFAULT_HELP_LICENSE_NOTICE # CLASS ATTRIBUTES _byte_order = Endian.Big _word_order = Endian.Big _substract_one_to_register_index = False _rtu_baudrate = 4800 _rtu_parity = 'N' _rtu_timeout = 10 #seconds # FUNCTIONS DEFINITION """ Initialize """ def __init__(self, a_slave_address=DEFAULT_SLAVE_ADDRESS, a_port=DEFAULT_MODBUS_PORT, an_ip_address=None): assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) try: self.init_arg_parse() assert self.valid_slave_address( a_slave_address ), 'a_slave_address parameter invalid:{}'.format(l_slave_address) l_slave_address = a_slave_address l_usb_port = self.DEFAULT_MODBUS_PORT if __name__ == '__main__': if (hasattr(self._args, 'slave_address') and self._args.slave_address): l_slave_address = self._args.slave_address if (hasattr(self._args, 'usb_port') and self._args.usb_port): l_usb_port = self._args.usb_port super().__init__(l_slave_address, self.DEFAULT_TARGET_MODE, a_port=l_usb_port, an_ip_address=self._args.host_ip) self._logger = SitLogger().new_logger(self.__class__.__name__, self._args.host_mac) self._init_sit_modbus_registers(l_slave_address) self.invariants() #self._logger.debug('init->' + self.out()) except OSError as l_e: self._logger.warning( "init-> OSError, probably rollingfileAppender" % (l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: print('Error in init: %s' % (l_e)) raise l_e #exit(1) def _init_sit_modbus_registers(self, a_slave_address): """ Initializes self._sit_modbus_registers """ assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) l_reg_list = OrderedDict() SitUtils.od_extend( l_reg_list, RegisterTypeInt16u( 'GHI', 'Total irradiation on the external irradiation sensor/pyranometer (W/m2)', 0x0, a_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int16u', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16u('GHIDev', 'Solar radiation deviation (0~1800)', 0x52, a_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int16u', an_is_metadata=False)) #SitUtils.od_extend(l_reg_list, RegisterTypeInt32u('WDigIo', 'Active power setpoint Digital I/O', 31235, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) self.append_modbus_registers(l_reg_list) # self.add_cc_only_sit_modbus_registers(1) # self.add_common_sit_modbus_registers(2) self.invariants() def _header_rows(self): return [['#Mn', 'renke'], ['#Md', 'rs-ra-n01-jt']] def sma_fix2(self, a_sit_modbus_register): """ """ l_new_val = a_sit_modbus_register.value / 100 self._logger.debug( 'sma_fix2->Setting new value-> old:{} new:{}'.format( a_sit_modbus_register.value, l_new_val)) a_sit_modbus_register.value = l_new_val def _W_event(self, a_sit_modbus_register): """ Called by modbus_device.call_sit_modbus_registers_events() """ self._logger.debug('_W_event-> register:{}'.format( a_sit_modbus_register.out_short())) l_short_desc = 'W' l_min_val = self.MIN_W_FOR_RAISE_EVENT_GENERATION l_val = a_sit_modbus_register.value l_sit_dt = SitDateTime() if a_sit_modbus_register.short_description == l_short_desc: l_start_time = time(8, 30) l_end_time = time(16, 30) l_is_day, l_valid_time = l_sit_dt.time_is_between( datetime.now().time(), l_start_time, l_end_time) # l_is_between_sunrise_sunset = l_sit_dt.now_is_into_sunrise_sunset_from_conf(self._sit_json_conf) # # self._logger.debug('read_sit_modbus_register-> l_is_day:{} l_valid_time:{} l_is_between_sunrise_sunset:{}'.format(l_is_day, l_valid_time, l_is_between_sunrise_sunset)) l_msg = '_W_event-> register_index:{} value ({}) '.format( a_sit_modbus_register.register_index, l_val) l_msg += ' between {} and {}'.format(l_start_time, l_end_time) self._logger.info( '_W_event-> DEVICE IS GENERATING {} kW'.format(l_val)) if (l_valid_time and l_val <= l_min_val): l_msg = '_W_event-> register_index:{} value ({} <= {}), valid_time:{}'.format( a_sit_modbus_register.register_index, l_val, l_min_val, l_valid_time) self._logger.warning(l_msg) # Create Dir l_dir = '/tmp/solarity_events' if not os.path.exists(l_dir): os.makedirs(l_dir) l_file = self.__class__.__name__ + '_event_{}_register_{}_slave_{}'.format( datetime.now().strftime("%Y%m%d_%H"), a_sit_modbus_register.register_index, a_sit_modbus_register.slave_address) l_file_abs_path = l_dir + '/' + l_file if not os.path.exists(l_file_abs_path): self._logger.info( '_W_event-> Event not sent, sending email file:{}'. format(l_file_abs_path)) # SEND MAIL l_subject = 'event with failure on $(hostname) slave:' + str( a_sit_modbus_register.slave_address ) + ' $(date +%Y%m%d_%H%M%S) W val:(' + str( l_val) + ' <= ' + str(l_min_val) + ')W ' l_body = [ 'event in slave:{} with failure on $(hostname) $(date +%Y%m%d_%H%M%S) review file {}' .format( a_sit_modbus_register.slave_address, self.csv_file_path( a_sit_modbus_register.slave_address)) ] l_body.append(' Between {} and {}'.format( l_start_time, l_end_time)) l_body.append('Register->out:{}'.format( a_sit_modbus_register.out_human_readable( a_with_description=self._args.long))) l_subject, l_body = self._setted_parts(l_subject, l_body) SitUtils.send_mail( self.events_mail_receivers(), l_subject, l_body, [ self.csv_file_path( a_sit_modbus_register.slave_address) ]) os.mknod(l_file_abs_path) else: self._logger.warning( '_W_event-> Event already sent, not sending email file:{}' .format(l_file_abs_path)) else: l_msg = '_W_event-> Event not raised register_index:{} value ({} > {}), valid_time:{}'.format( a_sit_modbus_register.register_index, l_val, l_min_val, l_valid_time) self._logger.debug(l_msg) # ACCESS # IMPLEMENTATION # EXECUTE ARGS """ Parsing arguments and calling corresponding functions """ def execute_corresponding_args(self): try: self.connect() if self._args.verbose: self._logger.setLevel(logging.DEBUG) else: self._logger.setLevel(logging.INFO) if self._args.store_values or self._args.display_all or self._args.test or self._args.raise_event: assert self.valid_slave_address( self._slave_address), 'Invalid slave address {}'.format( self._slave_address) self.read_all_sit_modbus_registers() if self._args.store_values: self.store_values_into_csv(self._sit_modbus_registers, self._slave_address) if self._args.display_all: print( self.out_human_readable( a_with_description=self._args.long)) if self._args.raise_event: assert len(self._sit_modbus_registers ) > 0, 'modbus_registers_not_empty' self.call_sit_modbus_registers_events() if self._args.test: self.test() # if self._args.manual_restart: # self.manual_restart() except Exception as l_e: self._logger.exception( "execute_corresponding_args-> Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) raise l_e finally: if self.is_connected(): self.disconnect() self.invariants() def add_arg_parse_modbus_device(self): super().add_arg_parse() def add_arg_parse(self): """ Override method """ self.add_arg_parse_modbus_device() self._parser.add_argument( '-e', '--raise_event', help='Raises the corresponding event if setted', action="store_true") #self._parser.add_argument('-r', '--manual_restart', help='Sends a manual restart to inverter manager', action="store_true") self._parser.add_argument('-c', '--slave_address', help='Slave address of modbus device', nargs='?') self._parser.add_argument('-p', '--usb_port', help='USB port', nargs='?') def add_required_named(self, a_required_named): pass def test(self): """ Test function """ try: self._logger.info("################# BEGIN #################") # l_sit_dt = SitDateTime() # l_is_between_sunrise_sunset = l_sit_dt.now_is_into_sunrise_sunset_from_conf(self._sit_json_conf) #self.call_sit_modbus_registers_events(l_slave) # self._logger.info("--> ************* device models *************: %s" % (l_d.models)) #Lists properties to be loaded with l_d.<property>.read() and then access them # self._logger.info("-->inverter ************* l_d.inverter.points *************: %s" % (l_d.inverter.points)) #Gives the inverter available properties # self._logger.info("-->inverter ************* common *************: %s" % (l_d.common)) # self._logger.info("-->inverter ************* common Serial Number *************: %s" % (l_d.common.SN)) self._logger.info("################# END #################") except Exception as l_e: self._logger.exception("Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) self._logger.error('Error: %s' % (l_e)) raise l_e def events_mail_receivers(self): return [ '*****@*****.**', '*****@*****.**' ] #return ['*****@*****.**'] #return ['*****@*****.**', '*****@*****.**'] def invariants_modbus_device(self): super().invariants() def invariants(self): self.invariants_modbus_device()
class SmartLogger1000a(SitModbusDevice): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 1 DEFAULT_MODBUS_PORT = 502 DEFAULT_TARGET_MODE = SitModbusDevice.TARGET_MODE_TCP MIN_W_FOR_RAISE_EVENT_GENERATION = 5000 PARSER_DESCRIPTION = 'Actions with Huawei smart logger 1000a device. ' + SitConstants.DEFAULT_HELP_LICENSE_NOTICE # CLASS ATTRIBUTES _byte_order = Endian.Big _word_order = Endian.Big _substract_one_to_register_index = False # FUNCTIONS DEFINITION """ Initialize """ def __init__(self, a_slave_address=DEFAULT_SLAVE_ADDRESS, a_port=DEFAULT_MODBUS_PORT, an_ip_address=None): assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) try: self.init_arg_parse() assert self.valid_slave_address( a_slave_address ), 'a_slave_address parameter invalid:{}'.format(l_slave_address) l_slave_address = a_slave_address if __name__ == '__main__': if (hasattr(self._args, 'slave_address') and self._args.slave_address): l_slave_address = self._args.slave_address super().__init__(l_slave_address, self.DEFAULT_TARGET_MODE, a_port=self.DEFAULT_MODBUS_PORT, an_ip_address=self._args.host_ip) self._logger = SitLogger().new_logger(self.__class__.__name__, self._args.host_mac) self._init_sit_modbus_registers(l_slave_address) self.invariants() #self._logger.debug('init->' + self.out()) except OSError as l_e: self._logger.warning( "init-> OSError, probably rollingfileAppender" % (l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: print('Error in init: %s' % (l_e)) raise l_e #exit(1) def _init_sit_modbus_registers(self, a_slave_address): """ Initializes self._sit_modbus_registers """ assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) self.add_common_sit_modbus_registers(1) self.invariants() def add_common_sit_modbus_registers(self, a_slave_address): """ Common devices registers """ assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) assert a_slave_address == 1 or a_slave_address >= 3, 'Dont ask for slave_address 2, the add_cc_only_sit_modbus_registers is done for that! addr:{}'.format( a_slave_address) l_reg_list = OrderedDict() l_slave_address = a_slave_address SitUtils.od_extend( l_reg_list, RegisterTypeStringVar(SitConstants.SS_REG_SHORT_ABB_SERIAL_NUMBER, 'ESN', 40713, 10, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'W', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32s(SitConstants.SS_REG_SHORT_ABB_AC_POWER, 'Total active output power of all inverters', 40525, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'W', an_is_metadata=False, an_event=SitModbusRegisterEvent(self._W_event))) SitUtils.od_extend( l_reg_list, RegisterTypeInt32s( SitConstants.SS_REG_SHORT_ABB_AC_S_REACTIVE_POWER, 'Reactive power', 40544, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'kVar', an_is_metadata=False)) # Active power control # SitUtils.od_extend(l_reg_list, RegisterTypeInt16u(SitConstants.SS_REG_SHORT_ABB_STATUS_OPERATING_STATE, 'Plant Status 1=Unlimited/2Limited/3Idle/4Fault/5Communication_interrupt', 40543, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Enum', an_is_metadata=False)) # Not working on tranque sante and maristas santamaria SitUtils.od_extend( l_reg_list, RegisterTypeInt16u('PlantSt2', 'Plant Status 2 0=ildle/1=on-grid/...', 40566, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Enum', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16u('ActPwrCtlMode', 'Active power control mode 0=no limit/other...', 40737, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Enum', an_is_metadata=False)) #Meter # UNABLE TO READ IT SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('WMeter', 'Active power of meter', 32278, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'W', an_is_metadata=False)) #Huawei specials SitUtils.od_extend( l_reg_list, RegisterTypeInt32s( SitConstants.SS_REG_SHORT_EXTRA_HUAWEI_ACT_POWER_ADJ, 'Active Power adjustment', 40426, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16u( SitConstants.SS_REG_SHORT_EXTRA_HUAWEI_ACT_POWER_ADJ_PCT, 'Active Power adjustment percentage', 40428, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u( 'LifeTimeKWHOut', 'Equals the total energy yield generatedby all inverters.', 40560, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'UInt', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u( 'TodaykWhOutput', 'Equals daily energy yield generated byall inverters.', 40562, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'UInt', an_is_metadata=False)) #SitUtils.od_extend(l_reg_list, RegisterTypeStrVar('Mn', 'Model', 30000, 15, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'String15', an_is_metadata=True)) # CLUSTER AND INVERTERS # SitUtils.od_extend(l_reg_list, RegisterTypeInt32u('Vr', 'Version number of the SMA Modbus profile', 30001, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True)) # # SitUtils.od_extend(l_reg_list, RegisterTypeInt32u('ID', 'SUSy ID (of the Cluster Controller)', 30003, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt32u('SN', 'Serial number (of the Cluster Controller)', 30005, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('NewData', 'Modbus data change: meter value is increased by the Cluster Controller if new data is available.', 30007, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=False)) # SitUtils.od_extend(l_reg_list, RegisterTypeSmaCCDeviceClass('DeviceClass', 'Device Class', 30051, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Enum', an_is_metadata=True)) # # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('W', 'Current active power on all line conductors (W), accumulated values of the inverters', 30775, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'W', an_is_metadata=False, an_event=SitModbusRegisterEvent(self._W_event))) # SitUtils.od_extend(l_reg_list, RegisterTypeInt64u('Wh', 'Total energy fed in across all line conductors, in Wh (accumulated values of the inverters) System param', 30513, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Wh', an_is_metadata=False)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('VAr', 'Reactive power on all line conductors (var), accumulated values of the inverters', 30805, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'VAr', an_is_metadata=False)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt64u('TotWhDay', 'Energy fed in on current day across all line conductors, in Wh (accumulated values of the inverters)', 30517, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Wh', an_is_metadata=False)) # self.append_modbus_registers(l_reg_list) def _W_event(self, a_sit_modbus_register): """ Called by modbus_device.call_sit_modbus_registers_events() """ self._logger.debug('_W_event-> register:{}'.format( a_sit_modbus_register.out_short())) l_short_desc = 'W' l_min_val = self.MIN_W_FOR_RAISE_EVENT_GENERATION l_val = a_sit_modbus_register.value l_sit_dt = SitDateTime() if a_sit_modbus_register.short_description == l_short_desc: l_start_time = time(8, 30) l_end_time = time(16, 30) l_is_day, l_valid_time = l_sit_dt.time_is_between( datetime.now().time(), l_start_time, l_end_time) # l_is_between_sunrise_sunset = l_sit_dt.now_is_into_sunrise_sunset_from_conf(self._sit_json_conf) # # self._logger.debug('read_sit_modbus_register-> l_is_day:{} l_valid_time:{} l_is_between_sunrise_sunset:{}'.format(l_is_day, l_valid_time, l_is_between_sunrise_sunset)) l_msg = '_W_event-> register_index:{} value ({}) '.format( a_sit_modbus_register.register_index, l_val) l_msg += ' between {} and {}'.format(l_start_time, l_end_time) self._logger.info( '_W_event-> DEVICE IS GENERATING {} kW'.format(l_val)) if (l_valid_time and l_val <= l_min_val): l_msg = '_W_event-> register_index:{} value ({} <= {}), valid_time:{}'.format( a_sit_modbus_register.register_index, l_val, l_min_val, l_valid_time) self._logger.warning(l_msg) # Create Dir l_dir = '/tmp/solarity_events' if not os.path.exists(l_dir): os.makedirs(l_dir) l_file = self.__class__.__name__ + '_event_{}_register_{}_slave_{}'.format( datetime.now().strftime("%Y%m%d_%H"), a_sit_modbus_register.register_index, a_sit_modbus_register.slave_address) l_file_abs_path = l_dir + '/' + l_file if not os.path.exists(l_file_abs_path): self._logger.info( '_W_event-> Event not sent, sending email file:{}'. format(l_file_abs_path)) # SEND MAIL l_subject = 'event with failure on $(hostname) slave:' + str( a_sit_modbus_register.slave_address ) + ' $(date +%Y%m%d_%H%M%S) W val:(' + str( l_val) + ' <= ' + str(l_min_val) + ')W ' l_body = [ 'event in slave:{} with failure on $(hostname) $(date +%Y%m%d_%H%M%S) review file {}' .format( a_sit_modbus_register.slave_address, self.csv_file_path( a_sit_modbus_register.slave_address)) ] l_body.append(' Between {} and {}'.format( l_start_time, l_end_time)) l_body.append('Register->out:{}'.format( a_sit_modbus_register.out_human_readable( a_with_description=self._args.long))) l_subject, l_body = self._setted_parts(l_subject, l_body) SitUtils.send_mail( self.events_mail_receivers(), l_subject, l_body, [ self.csv_file_path( a_sit_modbus_register.slave_address) ]) os.mknod(l_file_abs_path) else: self._logger.warning( '_W_event-> Event already sent, not sending email file:{}' .format(l_file_abs_path)) else: l_msg = '_W_event-> Event not raised register_index:{} value ({} > {}), valid_time:{}'.format( a_sit_modbus_register.register_index, l_val, l_min_val, l_valid_time) self._logger.debug(l_msg) def _setted_parts(self, a_subject, a_body): return a_subject, a_body def manual_restart(self): """ Manual restart documented on p.45 of doc """ l_res = 'test_res' self._logger.info('manual_restart-> NOW') #a_register_index, a_slave_address, a_value): l_res = self.write_register_value(0, 201, 1) self._logger.info('manual_restart-> result:{}'.format(l_res)) return l_res def read_all_sit_modbus_registers(self): """ Read inverters data """ super().read_all_sit_modbus_registers() # l_reg_index = 42109 # l_slave_address = 3 # self.read_inverter_data(l_slave_address) def read_inverter_data(self, a_slave_address): """ Was for test but not working """ assert False, 'deprecated' l_reg = RegisterTypeInt64u( 'Wh2', 'Total energy fed in across all line conductors, in Wh (accumulated values of the inverters) System param', 30513, SitModbusRegister.ACCESS_MODE_R, 'Wh', an_is_metadata=False, a_slave_address=a_slave_address) self.read_inverter_data_register(l_reg, a_slave_address) print(l_reg.out_human_readable(a_with_description=True)) # l_reg = RegisterTypeInt32u('SN', 'Serial Number', a_reg_index + 1, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True) # self.read_inverter_data_register(l_reg, a_slave_address) # # l_reg = RegisterTypeInt16u('UnitID', 'Unit ID', a_reg_index + 3, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True) # self.read_inverter_data_register(l_reg, a_slave_address) def read_inverter_data_register(self, a_register, a_slave_address): """ Reads given inverter data Was for test but not working """ assert False, 'deprecated' try: self.read_sit_modbus_register(a_register, a_slave_address) if self._args.store_values: pass # self.store_values_into_csv([l_reg], l_slave) if self._args.display_all: print('***************** INVERTER slave:{} ******************'. format(a_slave_address)) print( a_register.out_human_readable( a_with_description=self._args.long)) except ModbusException as l_e: self._logger.error( 'read_inverter_data-> error reading register {}'.format(l_e)) except Exception as l_e: raise l_e # ACCESS # IMPLEMENTATION # EXECUTE ARGS """ Parsing arguments and calling corresponding functions """ def execute_corresponding_args(self): try: self.connect() if self._args.verbose: self._logger.setLevel(logging.DEBUG) else: self._logger.setLevel(logging.INFO) if self._args.store_values or self._args.display_all or self._args.test or self._args.raise_event: assert self.valid_slave_address( self._slave_address), 'Invalid slave address {}'.format( self._slave_address) self.read_all_sit_modbus_registers() if self._args.store_values: self.store_values_into_csv(self._sit_modbus_registers, self._slave_address) if self._args.display_all: print( self.out_human_readable( a_with_description=self._args.long)) if self._args.raise_event: assert len(self._sit_modbus_registers ) > 0, 'modbus_registers_not_empty' self.call_sit_modbus_registers_events() if self._args.test: self.test() # if self._args.manual_restart: # self.manual_restart() except Exception as l_e: self._logger.exception( "execute_corresponding_args-> Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) raise l_e finally: if self.is_connected(): self.disconnect() self.invariants() def add_arg_parse_modbus_device(self): super().add_arg_parse() def add_arg_parse(self): """ Override method """ self.add_arg_parse_modbus_device() self._parser.add_argument( '-e', '--raise_event', help='Raises the corresponding event if setted', action="store_true") #self._parser.add_argument('-r', '--manual_restart', help='Sends a manual restart to inverter manager', action="store_true") self._parser.add_argument('-c', '--slave_address', help='Slave address of modbus device', nargs='?') def add_required_named(self, a_required_named): pass def test(self): """ Test function """ try: self._logger.info("################# BEGIN #################") # l_sit_dt = SitDateTime() # l_is_between_sunrise_sunset = l_sit_dt.now_is_into_sunrise_sunset_from_conf(self._sit_json_conf) #self.call_sit_modbus_registers_events(l_slave) # self._logger.info("--> ************* device models *************: %s" % (l_d.models)) #Lists properties to be loaded with l_d.<property>.read() and then access them # self._logger.info("-->inverter ************* l_d.inverter.points *************: %s" % (l_d.inverter.points)) #Gives the inverter available properties # self._logger.info("-->inverter ************* common *************: %s" % (l_d.common)) # self._logger.info("-->inverter ************* common Serial Number *************: %s" % (l_d.common.SN)) self._logger.info("################# END #################") except Exception as l_e: self._logger.exception("Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) self._logger.error('Error: %s' % (l_e)) raise l_e def events_mail_receivers(self): return [ '*****@*****.**', '*****@*****.**' ] #return ['*****@*****.**'] #return ['*****@*****.**', '*****@*****.**'] def invariants_modbus_device(self): super().invariants() def invariants(self): self.invariants_modbus_device()
class SunnyTripower60(InverterManager): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 126 MIN_KW_FOR_RAISE_EVENT_GENERATION = 5 # FUNCTIONS DEFINITION """ Initialize """ def __init__(self, a_slave_address=DEFAULT_SLAVE_ADDRESS): assert self.valid_slave_address( a_slave_address), 'init invalid slave address' try: self.init_arg_parse() l_slave_address = a_slave_address if (hasattr(self._args, 'slave_address') and self._args.slave_address): self._slave_addresses_list = SitUtils.args_to_list( self._args.slave_address) if self._slave_addresses_list is None: self._slave_addresses_list = [a_slave_address] assert self.valid_slave_address_list( self._slave_addresses_list ), 'Given script arguments are not valid, or could not be parsed:{}'.format( self._slave_addresses_list) assert self.valid_ip( self._args.host_ip), 'valid ip address:{}'.format( self._args.host_ip) #a_slave_address=DEFAULT_SLAVE_ADDRESS, a_port=DEFAULT_MODBUS_PORT, an_ip_address=None super().__init__(l_slave_address, a_port=self.DEFAULT_MODBUS_PORT, an_ip_address=self._args.host_ip) self._logger = SitLogger().new_logger(__name__, self._args.host_mac) #self._logger.debug('init->' + self.out()) except OSError as l_e: self._logger.warning( "init-> OSError, probably rollingfileAppender" % (l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: print('Error in init: %s' % (l_e)) raise l_e #exit(1) self.invariants() def _init_sit_modbus_registers(self, a_slave_address): """ Initializes self._sit_modbus_registers """ assert len(self._sit_modbus_registers) == 0 l_reg_list = OrderedDict() l_slave_address = a_slave_address self._add_common_registers(l_reg_list, l_slave_address) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor('A', 'AC Current sum of all inverters', 40188, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'A', a_scale_factor=40192)) self.append_modbus_registers(l_reg_list) #error self.add_modbus_register('ID', 'Model ID (ID): 120 = Sunspec nameplate model', 40238, SitModbusRegister.REGISTER_TYPE_INT_16_U, SitModbusRegister.ACCESS_MODE_R, 'uint16') #error self.add_modbus_register('VArPct_Mod', 'Mode of the percentile reactive power limitation: 1 = in % of WMax', 40365, SitModbusRegister.REGISTER_TYPE_ENUM_16, SitModbusRegister.ACCESS_MODE_R, 'enum16') #self.add_modbus_register('VArPct_Ena', 'Control of the percentile reactive power limitation,(SMA: Qext): 1 = activated', 40365, SitModbusRegister.REGISTER_TYPE_ENUM_16, SitModbusRegister.ACCESS_MODE_RW, 'enum16') self.invariants() """ Parsing arguments and calling corresponding functions """ def execute_corresponding_args(self): self.invariants() try: self.connect() if self._args.verbose: self._logger.setLevel(logging.DEBUG) else: self._logger.setLevel(logging.INFO) if (self._args.store_values or self._args.display_all or self._args.raise_event or self._args.test): assert self.valid_slave_address_list( self._slave_addresses_list ), 'Slave addresses list is invalid:{}'.format( self._slave_addresses_list) # FOR EACH SLAVE for l_slave in self._slave_addresses_list: self._sit_modbus_registers = OrderedDict() try: self._init_sit_modbus_registers(l_slave) assert len(self._sit_modbus_registers ) > 0, 'modbusregisters empty' self.read_all_sit_modbus_registers() if self._args.store_values: self.store_values_into_csv( self._sit_modbus_registers, l_slave) if self._args.display_all: print( self.out_human_readable( a_with_description=self._args.long)) if self._args.raise_event: self.call_sit_modbus_registers_events() if self._args.test: self.test() except ModbusException as l_e: self._logger.exception( 'execute_corresponding_args Exception:{}'.format( l_e), l_e) #pass it because of other slave addresses except Exception as l_e: self._logger.exception( 'execute_corresponding_args Exception:{}'.format( l_e), l_e) raise l_e except Exception as l_e: self._logger.exception( "execute_corresponding_args-> Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) raise l_e finally: if self.is_connected(): self.disconnect() self.invariants() def add_arg_parse(self): """ Override method """ self.add_arg_parse_modbus_device() self._parser.add_argument( '-e', '--raise_event', help='Raises the corresponding event if setted', action="store_true") self._parser.add_argument('-c', '--slave_address', help='Slave address of modbus device', nargs='?', required=True) """ Test function """ def test(self): self.invariants() try: print("################# BEGIN #################") # self._logger.info("--> ************* device models *************: %s" % (l_d.models)) #Lists properties to be loaded with l_d.<property>.read() and then access them # self._logger.info("-->inverter ************* l_d.inverter.points *************: %s" % (l_d.inverter.points)) #Gives the inverter available properties # self._logger.info("-->inverter ************* common *************: %s" % (l_d.common)) # self._logger.info("-->inverter ************* common Serial Number *************: %s" % (l_d.common.SN)) print("################# END #################") except Exception as l_e: self._logger.exception("Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) self._logger.error('Error: %s' % (l_e)) raise l_e self.invariants() def invariants(self): self.invariants_modbus_device()
class DataManager(SitModbusDevice): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 1 DEFAULT_MODBUS_PORT = 502 DEFAULT_TARGET_MODE = SitModbusDevice.TARGET_MODE_TCP MIN_W_FOR_RAISE_EVENT_GENERATION = 2000 PARSER_DESCRIPTION = 'Actions with sma data manager device. ' + SitConstants.DEFAULT_HELP_LICENSE_NOTICE # CLASS ATTRIBUTES _byte_order = Endian.Big _word_order = Endian.Big _substract_one_to_register_index = False # FUNCTIONS DEFINITION """ Initialize """ def __init__(self, a_slave_address=DEFAULT_SLAVE_ADDRESS, a_port=DEFAULT_MODBUS_PORT, an_ip_address=None): assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) try: self.init_arg_parse() assert self.valid_slave_address( a_slave_address ), 'a_slave_address parameter invalid:{}'.format(l_slave_address) l_slave_address = a_slave_address if __name__ == '__main__': if (hasattr(self._args, 'slave_address') and self._args.slave_address): l_slave_address = self._args.slave_address super().__init__(l_slave_address, self.DEFAULT_TARGET_MODE, a_port=self.DEFAULT_MODBUS_PORT, an_ip_address=self._args.host_ip) self._logger = SitLogger().new_logger(self.__class__.__name__, self._args.host_mac) self._init_sit_modbus_registers(l_slave_address) self.invariants() #self._logger.debug('init->' + self.out()) except OSError as l_e: self._logger.warning( "init-> OSError, probably rollingfileAppender" % (l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: print('Error in init: %s' % (l_e)) raise l_e #exit(1) def _init_sit_modbus_registers(self, a_slave_address): """ Initializes self._sit_modbus_registers """ assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) self.add_common_sit_modbus_registers(1) self.add_dm_only_sit_modbus_registers(2) self.invariants() def add_common_sit_modbus_registers(self, a_slave_address): """ Common devices registers """ assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) assert a_slave_address == 1 or a_slave_address >= 3, 'Dont ask for slave_address 2, the add_cc_only_sit_modbus_registers is done for that! addr:{}'.format( a_slave_address) l_reg_list = OrderedDict() l_slave_address = a_slave_address # CLUSTER AND INVERTERS SitUtils.od_extend( l_reg_list, RegisterTypeInt32u('Vr', 'Version number of the SMA Modbus profile', 30001, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u('ID', 'SUSy ID (of the Data manager)', 30003, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u('SN', 'Serial number (of the Cluster Controller)', 30005, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32s( 'NewData', 'Modbus data change: meter value is increased by the Cluster Controller if new data is available.', 30007, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeSmaCCDeviceClass('DeviceClass', 'Device Class', 30051, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Enum', an_is_metadata=True)) # Doc tells unitID=2?? p.16 SitUtils.od_extend( l_reg_list, RegisterTypeInt32s( 'W', 'Current active power on all line conductors (W), accumulated values of the inverters', 30775, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'W', an_is_metadata=False, an_event=SitModbusRegisterEvent(self._W_event))) SitUtils.od_extend( l_reg_list, RegisterTypeInt64u( 'Wh', 'Total energy fed in across all line conductors, in Wh (accumulated values of the inverters) System param', 30513, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Wh', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32s( 'VAr', 'Reactive power on all line conductors (var), accumulated values of the inverters', 30805, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'VAr', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeInt64u( 'TotWhDay', 'Energy fed in on current day across all line conductors, in Wh (accumulated values of the inverters)', 30517, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'Wh', an_is_metadata=False)) self.append_modbus_registers(l_reg_list) def add_dm_only_sit_modbus_registers(self, a_slave_address): """ Registers particular to cluster controller """ assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) assert a_slave_address == 2, 'add_cc_only_sit_modbus_registers->for this part slave_address should be =2 and is:{}'.format( a_slave_address) l_reg_list = OrderedDict() l_slave_address = a_slave_address # #PARAMETERS UNIT_ID = 2 (p.26 of doc) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u('WDigIo', 'Active power setpoint Digital I/O', 31235, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u('WAnalog', 'Active power setpoint Analog', 31237, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u('WSetPoint', 'Active power setpoint in %s', 31239, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32s( 'WSetPointDirMar', 'Active power setpoint in %s Specification Modbus Direct marketing', 31241, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u( 'ResSetPoint', 'Resulting setpoint (minimum value definition of all specifications)', 31243, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) #Strange SitUtils.od_extend( l_reg_list, RegisterTypeInt32s( 'WExport', 'Current utility grid export active power P in W (actual value of the active power fed in at the grid-connection point; measured with an external measuring device).', 31249, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32s( 'VArExport', 'Current utility grid export reactive power Q in VAr (actual value of the reactive power fed in at the grid- connection point; measured with an external measuring device).', 31251, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False)) #SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('AC_1', 'Analog current input 1 (mA)', 34637, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'mA', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('AC_2', 'Analog current input 2 (mA)', 34639, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'mA', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('AC_3', 'Analog current input 3 (mA)', 34641, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'mA', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('AC_4', 'Analog current input 4 (mA)', 34643, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'mA', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) # # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('InDCV_1', 'Analog voltage input 1 (V)', 34645, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('InDCV_2', 'Analog voltage input 2 (V)', 34647, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('InDCV_3', 'Analog voltage input 3 (V)', 34649, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) # SitUtils.od_extend(l_reg_list, RegisterTypeInt32s('InDCV_4', 'Analog voltage input 4 (V)', 34651, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16s( 'WSetPointDirTotal', 'Direct marketer: Active power setpoint P, in % of the maximum active power (PMAX) of the PV plant. -100-0=Load|0=No active power|0-100 generator', 40493, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u('WSetPointMan', 'Active power setpoint (manual specification)', 41167, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '%', an_is_metadata=False, a_post_set_value_call=self.sma_fix2)) # IRRADIATIONS # not working on sanbe, getting max_int, SitUtils.od_extend(l_reg_list, RegisterTypeInt32u('IrradiationSurfaceTot', 'Total irradiation on the sensor surface (W/m2)', 34613, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'W/m2', an_is_metadata=False)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32u( 'GHI', 'Total irradiation on the external irradiation sensor/pyranometer (W/m2)', 34623, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'W/m2', an_is_metadata=False)) self.append_modbus_registers(l_reg_list) def sma_fix2(self, a_sit_modbus_register): """ """ l_new_val = a_sit_modbus_register.value / 100 self._logger.debug( 'sma_fix2->Setting new value-> old:{} new:{}'.format( a_sit_modbus_register.value, l_new_val)) a_sit_modbus_register.value = l_new_val def _W_event(self, a_sit_modbus_register): """ Called by modbus_device.call_sit_modbus_registers_events() """ self._logger.debug('_W_event-> register:{}'.format( a_sit_modbus_register.out_short())) l_short_desc = 'W' l_min_val = self.MIN_W_FOR_RAISE_EVENT_GENERATION l_val = a_sit_modbus_register.value l_sit_dt = SitDateTime() if a_sit_modbus_register.short_description == l_short_desc: l_start_time = time(8, 30) l_end_time = time(16, 30) l_is_day, l_valid_time = l_sit_dt.time_is_between( datetime.now().time(), l_start_time, l_end_time) # l_is_between_sunrise_sunset = l_sit_dt.now_is_into_sunrise_sunset_from_conf(self._sit_json_conf) # # self._logger.debug('read_sit_modbus_register-> l_is_day:{} l_valid_time:{} l_is_between_sunrise_sunset:{}'.format(l_is_day, l_valid_time, l_is_between_sunrise_sunset)) l_msg = '_W_event-> register_index:{} value ({}) '.format( a_sit_modbus_register.register_index, l_val) l_msg += ' between {} and {}'.format(l_start_time, l_end_time) self._logger.info( '_W_event-> DEVICE IS GENERATING {} kW'.format(l_val)) if (l_valid_time and l_val <= l_min_val): l_msg = '_W_event-> register_index:{} value ({} <= {}), valid_time:{}'.format( a_sit_modbus_register.register_index, l_val, l_min_val, l_valid_time) self._logger.warning(l_msg) # Create Dir l_dir = '/tmp/solarity_events' if not os.path.exists(l_dir): os.makedirs(l_dir) l_file = self.__class__.__name__ + '_event_{}_register_{}_slave_{}'.format( datetime.now().strftime("%Y%m%d_%H"), a_sit_modbus_register.register_index, a_sit_modbus_register.slave_address) l_file_abs_path = l_dir + '/' + l_file if not os.path.exists(l_file_abs_path): self._logger.info( '_W_event-> Event not sent, sending email file:{}'. format(l_file_abs_path)) # SEND MAIL l_subject = 'event with failure on $(hostname) slave:' + str( a_sit_modbus_register.slave_address ) + ' $(date +%Y%m%d_%H%M%S) W val:(' + str( l_val) + ' <= ' + str(l_min_val) + ')W ' l_body = [ 'event in slave:{} with failure on $(hostname) $(date +%Y%m%d_%H%M%S) review file {}' .format( a_sit_modbus_register.slave_address, self.csv_file_path( a_sit_modbus_register.slave_address)) ] l_body.append(' Between {} and {}'.format( l_start_time, l_end_time)) l_body.append('Register->out:{}'.format( a_sit_modbus_register.out_human_readable( a_with_description=self._args.long))) l_subject, l_body = self._setted_parts(l_subject, l_body) SitUtils.send_mail( self.events_mail_receivers(), l_subject, l_body, [ self.csv_file_path( a_sit_modbus_register.slave_address) ]) os.mknod(l_file_abs_path) else: self._logger.warning( '_W_event-> Event already sent, not sending email file:{}' .format(l_file_abs_path)) else: l_msg = '_W_event-> Event not raised register_index:{} value ({} > {}), valid_time:{}'.format( a_sit_modbus_register.register_index, l_val, l_min_val, l_valid_time) self._logger.debug(l_msg) def _setted_parts(self, a_subject, a_body): return a_subject, a_body def manual_restart(self): """ Manual restart documented on p.45 of doc """ l_res = 'test_res' self._logger.info('manual_restart-> NOW') #a_register_index, a_slave_address, a_value): l_res = self.write_register_value(0, 201, 1) self._logger.info('manual_restart-> result:{}'.format(l_res)) return l_res def read_all_sit_modbus_registers(self): """ Read inverters data """ super().read_all_sit_modbus_registers() # l_reg_index = 42109 # l_slave_address = 3 # self.read_inverter_data(l_slave_address) def read_inverter_data(self, a_slave_address): """ Was for test but not working """ assert False, 'deprecated' l_reg = RegisterTypeInt64u( 'Wh2', 'Total energy fed in across all line conductors, in Wh (accumulated values of the inverters) System param', 30513, SitModbusRegister.ACCESS_MODE_R, 'Wh', an_is_metadata=False, a_slave_address=a_slave_address) self.read_inverter_data_register(l_reg, a_slave_address) print(l_reg.out_human_readable(a_with_description=True)) # l_reg = RegisterTypeInt32u('SN', 'Serial Number', a_reg_index + 1, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True) # self.read_inverter_data_register(l_reg, a_slave_address) # # l_reg = RegisterTypeInt16u('UnitID', 'Unit ID', a_reg_index + 3, SitModbusRegister.ACCESS_MODE_R, 'Int32u', an_is_metadata=True) # self.read_inverter_data_register(l_reg, a_slave_address) def read_inverter_data_register(self, a_register, a_slave_address): """ Reads given inverter data Was for test but not working """ assert False, 'deprecated' try: self.read_sit_modbus_register(a_register, a_slave_address) if self._args.store_values: pass # self.store_values_into_csv([l_reg], l_slave) if self._args.display_all: print('***************** INVERTER slave:{} ******************'. format(a_slave_address)) print( a_register.out_human_readable( a_with_description=self._args.long)) except ModbusException as l_e: self._logger.error( 'read_inverter_data-> error reading register {}'.format(l_e)) except Exception as l_e: raise l_e # ACCESS # IMPLEMENTATION # EXECUTE ARGS """ Parsing arguments and calling corresponding functions """ def execute_corresponding_args(self): try: self.connect() if self._args.verbose: self._logger.setLevel(logging.DEBUG) else: self._logger.setLevel(logging.INFO) if self._args.store_values or self._args.display_all or self._args.test or self._args.raise_event: assert self.valid_slave_address( self._slave_address), 'Invalid slave address {}'.format( self._slave_address) self.read_all_sit_modbus_registers() if self._args.store_values: self.store_values_into_csv(self._sit_modbus_registers, self._slave_address) if self._args.display_all: print( self.out_human_readable( a_with_description=self._args.long)) if self._args.raise_event: assert len(self._sit_modbus_registers ) > 0, 'modbus_registers_not_empty' self.call_sit_modbus_registers_events() if self._args.test: self.test() # if self._args.manual_restart: # self.manual_restart() except Exception as l_e: self._logger.exception( "execute_corresponding_args-> Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) raise l_e finally: if self.is_connected(): self.disconnect() self.invariants() def add_arg_parse_modbus_device(self): super().add_arg_parse() def add_arg_parse(self): """ Override method """ self.add_arg_parse_modbus_device() self._parser.add_argument( '-e', '--raise_event', help='Raises the corresponding event if setted', action="store_true") #self._parser.add_argument('-r', '--manual_restart', help='Sends a manual restart to inverter manager', action="store_true") self._parser.add_argument('-c', '--slave_address', help='Slave address of modbus device', nargs='?') def add_required_named(self, a_required_named): pass def test(self): """ Test function """ try: self._logger.info("################# BEGIN #################") # l_sit_dt = SitDateTime() # l_is_between_sunrise_sunset = l_sit_dt.now_is_into_sunrise_sunset_from_conf(self._sit_json_conf) #self.call_sit_modbus_registers_events(l_slave) # self._logger.info("--> ************* device models *************: %s" % (l_d.models)) #Lists properties to be loaded with l_d.<property>.read() and then access them # self._logger.info("-->inverter ************* l_d.inverter.points *************: %s" % (l_d.inverter.points)) #Gives the inverter available properties # self._logger.info("-->inverter ************* common *************: %s" % (l_d.common)) # self._logger.info("-->inverter ************* common Serial Number *************: %s" % (l_d.common.SN)) self._logger.info("################# END #################") except Exception as l_e: self._logger.exception("Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) self._logger.error('Error: %s' % (l_e)) raise l_e def events_mail_receivers(self): return [ '*****@*****.**', '*****@*****.**' ] #return ['*****@*****.**'] #return ['*****@*****.**', '*****@*****.**'] def invariants_modbus_device(self): super().invariants() def invariants(self): self.invariants_modbus_device()
class ClusterControllerInverter(ClusterController): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 3 MIN_W_FOR_RAISE_EVENT_GENERATION = 50 PARSER_DESCRIPTION = 'Actions with sma cluster controller inverter. ' + SitConstants.DEFAULT_HELP_LICENSE_NOTICE # CLASS ATTRIBUTES _byte_order = Endian.Big _word_order = Endian.Big _substract_one_to_register_index = False _slave_addresses_list = None _current_read_device_class = None _last_read_slave_address = None _last_read_serial_number = None # INITIALIZE """ Initialize """ def __init__(self, a_slave_address=DEFAULT_SLAVE_ADDRESS, a_port=ClusterController.DEFAULT_MODBUS_PORT, an_ip_address=None): """ slave_address priority to commandline arguments """ assert self.valid_slave_address(a_slave_address), 'init invalid slave address' try: self.init_arg_parse() l_slave_address = a_slave_address if (hasattr(self._args, 'slave_address') and self._args.slave_address): self._slave_addresses_list = SitUtils.args_to_list(self._args.slave_address) assert self.valid_slave_address_list(self._slave_addresses_list), 'Given script arguments are not valid, or could not be parsed' assert self.valid_ip(self._args.host_ip), 'valid ip address:{}'.format(self._args.host_ip) super().__init__(l_slave_address, a_port=a_port, an_ip_address=self._args.host_ip) self._logger = SitLogger().new_logger(self.__class__.__name__, self._args.host_mac) self.invariants() #self._logger.debug('init->' + self.out()) except OSError as l_e: self._logger.warning("init-> OSError, probably rollingfileAppender" % (l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: print('Error in init: %s' % (l_e)) raise l_e #exit(1) def _init_sit_modbus_registers(self, a_slave_address): """ Initializes self._sit_modbus_registers """ self.add_common_sit_modbus_registers(a_slave_address) #self.add_cc_only_sit_modbus_registers(a_slave_address) self.invariants() def add_common_sit_modbus_registers(self, a_slave_address): """ COMMON REGISTERS to ClusterController and Inverters """ super().add_common_sit_modbus_registers(a_slave_address) l_reg_list = OrderedDict() l_slave_address = a_slave_address #PARAMETERS UNIT_ID = 2 (p.26 of doc) SitUtils.od_extend(l_reg_list, RegisterTypeInt32u('EvtNr', '30213: For error description refere to SMA register address 30247; 30247: Description of the event message, see the documentation of the product', 30247, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'enum', an_is_metadata=False)) #SitUtils.od_extend(l_reg_list, RegisterTypeInt32u('EvtNr', '30213: For error description refere to SMA register address 30247; 30247: Description of the event message, see the documentation of the product', 30247, SitModbusRegister.ACCESS_MODE_R, 'enum', an_is_metadata=False, a_slave_address=a_slave_address)) self.append_modbus_registers(l_reg_list) # MODBUS READING def read_all_sit_modbus_registers(self): """ Reads all registers and print result as debug """ self._logger.debug('read_all_sit_modbus_registers-> registers to read count({}) start --------------------------------------------------'.format(len(self._sit_modbus_registers))) for l_short_desc, l_sit_reg in self._sit_modbus_registers.items(): self.read_sit_modbus_register(l_sit_reg) # Setting slave address if changed if self._last_read_slave_address != l_sit_reg.slave_address: self._current_read_device_class = None self._last_read_serial_number = None self._last_read_slave_address = l_sit_reg.slave_address #Setting device class if l_sit_reg.short_description == 'DeviceClass': self._current_read_device_class = l_sit_reg.value elif l_sit_reg.short_description == 'SN': self._last_read_serial_number = l_sit_reg.value if l_sit_reg.has_post_set_value_call(): l_sit_reg.call_post_set_value() #self._logger.debug('read_all_registers-> sit_register.out():%s' % (l_sit_reg.out())) self._logger.debug('read_all_registers-> sit_register.out_short():%s' % (l_sit_reg.out_short())) # EVENTS def _W_event(self, a_sit_modbus_register): """ REDEFINE Called by modbus_device.call_sit_modbus_registers_events() """ if 'PV inverter' in self._current_read_device_class: super()._W_event(a_sit_modbus_register) def _setted_parts(self, a_subject, a_body): """ return subject and body """ l_sub = a_subject + ' SN:{}'.format(self._last_read_serial_number) l_body = a_body return l_sub, l_body # IMPLEMENTATION # EXECUTE ARGS """ Parsing arguments and calling corresponding functions """ def execute_corresponding_args(self): try: self.connect() if self._args.verbose: self._logger.setLevel(logging.DEBUG) else: self._logger.setLevel(logging.INFO) if self._args.store_values or self._args.display_all or self._args.test or self._args.raise_event: assert self.valid_slave_address_list(self._slave_addresses_list), 'Slave addresses list is invalid:{}'.format(self._slave_addresses_list) self._logger.debug('execute_corresponding_args-> _slave_addresses_list:{}'.format(self._slave_addresses_list)) # FOR EACH SLAVE for l_slave in self._slave_addresses_list: self._sit_modbus_registers = OrderedDict() try: self._init_sit_modbus_registers(l_slave) self.read_all_sit_modbus_registers() if self._args.store_values: self.store_values_into_csv(self._sit_modbus_registers, l_slave) if self._args.display_all: print(self.out_human_readable(a_with_description=self._args.long)) if self._args.raise_event: assert len(self._sit_modbus_registers) > 0, 'modbus_registers_not_empty' self.call_sit_modbus_registers_events() if self._args.test: self.test() except ModbusException as l_e: self._logger.error('Modbus error on slave {}, msg:{}'.format(l_slave, l_e)) except Exception as l_e: self._logger.error('Exception on slave {}, msg:{}'.format(l_slave, l_e)) raise l_e except Exception as l_e: self._logger.exception("execute_corresponding_args-> Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) raise l_e finally: if self.is_connected(): self.disconnect() self.invariants() def add_arg_parse(self): """ Override method """ self.add_arg_parse_modbus_device() self._parser.add_argument('-e', '--raise_event', help='Raises the corresponding event if setted', action="store_true") self._parser.add_argument('-c', '--slave_address', help='Slave address of modbus device', nargs='?', required=True) def add_required_named(self, a_required_named): pass def test(self): """ Test function """ try: self._logger.info ("################# BEGIN #################") # l_sit_dt = SitDateTime() # l_is_between_sunrise_sunset = l_sit_dt.now_is_into_sunrise_sunset_from_conf(self._sit_json_conf) #self.call_sit_modbus_registers_events(l_slave) # self._logger.info("--> ************* device models *************: %s" % (l_d.models)) #Lists properties to be loaded with l_d.<property>.read() and then access them # self._logger.info("-->inverter ************* l_d.inverter.points *************: %s" % (l_d.inverter.points)) #Gives the inverter available properties # self._logger.info("-->inverter ************* common *************: %s" % (l_d.common)) # self._logger.info("-->inverter ************* common Serial Number *************: %s" % (l_d.common.SN)) self._logger.info ("################# END #################") except Exception as l_e: self._logger.exception("Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) self._logger.error('Error: %s' % (l_e)) raise l_e def invariants(self): self.invariants_modbus_device() assert isinstance(self._slave_addresses_list, list) or self._slave_addresses_list is None, 'self._slave_address is list or None {}'.format(self._slave_addresses_list) for l_slave in self._slave_addresses_list: assert l_slave >=3, 'l_slave >=3 not the case:{}'.format(l_slave)
class DirectMarketerInterface(InverterManager): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 200 # CLASS ATTRIBUTES # FUNCTIONS DEFINITION """ Initialize """ def __init__(self, a_slave_address=DEFAULT_SLAVE_ADDRESS): try: self.init_arg_parse() l_slave_address = self.DEFAULT_SLAVE_ADDRESS if self._args.slave_address: if self.valid_slave_address(self._args.slave_address): self._slave_address = int(self._args.slave_address) #self, a_slave_address=DEFAULT_SLAVE_ADDRESS, a_port=DEFAULT_MODBUS_PORT, an_ip_address=None super().__init__(l_slave_address, a_port=self.DEFAULT_MODBUS_PORT, an_ip_address=self._args.host_ip) self._logger = SitLogger().new_logger(__name__, self._args.host_mac) self._init_sit_modbus_registers() #self._logger.debug('init->' + self.out()) except OSError as l_e: self._logger.warning("init-> OSError, probably rollingfileAppender" % (l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: print('Error in init: %s' % (l_e)) raise l_e #exit(1) def _init_sit_modbus_registers(self): """ Initializes self._sit_modbus_registers """ # P.44 of doc self.add_modbus_register('OutLimitPerc', 'Specified output limitation through direct marketer n% (0-10000)', 1, SitModbusRegister.REGISTER_TYPE_INT_16, SitModbusRegister.ACCESS_MODE_RW, 'uint16') self.add_modbus_register('OutLimitPercMan', 'Manual output limitation that has been set via Sunspec Modbus', 2, SitModbusRegister.REGISTER_TYPE_INT_16, SitModbusRegister.ACCESS_MODE_R, 'uint16') self.add_modbus_register('OutLimitPercIoBox', 'Output limitation through the electric utility company that has been set via the IO box.', 3, SitModbusRegister.REGISTER_TYPE_INT_16, SitModbusRegister.ACCESS_MODE_R, 'uint16') self.add_modbus_register('OutLimitMin', 'Minimum of all output limitations. The nominal PV system power is derated to this value.', 4, SitModbusRegister.REGISTER_TYPE_INT_16, SitModbusRegister.ACCESS_MODE_R, 'uint16') # self.add_modbus_register('Md', 'Model (Md): SMA Inverter Manager', 40021, SitModbusRegister.REGISTER_TYPE_STRING_16, SitModbusRegister.ACCESS_MODE_R, 'String16') # self.add_modbus_register('Opt', 'Options (Opt): Inverter Manager name', 40037, SitModbusRegister.REGISTER_TYPE_STRING_8, SitModbusRegister.ACCESS_MODE_R, 'String8') # self.add_modbus_register('Vr', 'Version (Vr): Version number of the installed firmware', 40045, SitModbusRegister.REGISTER_TYPE_STRING_8, SitModbusRegister.ACCESS_MODE_R, 'String8') # self.add_modbus_register('SN', 'Serial number (SN) of the device that uses the Modbus unit ID', 40053, SitModbusRegister.REGISTER_TYPE_STRING_16, SitModbusRegister.ACCESS_MODE_R, 'String16') # self.add_modbus_register('PPVphA', 'Voltage, line conductor L1 to N (PPVphA), in V-V_SF (40199): average value of all inverters', 40196, SitModbusRegister.REGISTER_TYPE_INT_16, SitModbusRegister.ACCESS_MODE_R, 'V', 40199) # self.add_modbus_register('AC_A', 'AC Current sum of all inverters', 40188, SitModbusRegister.REGISTER_TYPE_INT_16, SitModbusRegister.ACCESS_MODE_R, 'A', 40192) # self.add_modbus_register('W', 'Active power (W), in W-W_SF (40201): sum of all inverters', 40200, SitModbusRegister.REGISTER_TYPE_INT_16, SitModbusRegister.ACCESS_MODE_R, 'W', 40192) # self.add_modbus_register('WH', 'Total yield (WH), in Wh WH_SF (40212): sum of all inverters', 40210, SitModbusRegister.REGISTER_TYPE_INT_32, SitModbusRegister.ACCESS_MODE_R, 'WH', 40212) # self.add_modbus_register('TmpCab', 'Internal temperature, in °C Tmp_SF (40223): average value of all inverters', 40219, SitModbusRegister.REGISTER_TYPE_INT_16, SitModbusRegister.ACCESS_MODE_R, '°C', 40223) # self.add_modbus_register('ID', 'Model ID (ID): 120 = Sunspec nameplate model', 40238, SitModbusRegister.REGISTER_TYPE_INT_16, SitModbusRegister.ACCESS_MODE_R, 'uint16') # self.add_modbus_register('VArPct_Mod', 'Mode of the percentile reactive power limitation: 1 = in % of WMax', 40365, SitModbusRegister.REGISTER_TYPE_ENUM_16, SitModbusRegister.ACCESS_MODE_R, 'enum16') # self.add_modbus_register('VArPct_Ena', 'Control of the percentile reactive power limitation,(SMA: Qext): 1 = activated', 40365, SitModbusRegister.REGISTER_TYPE_ENUM_16, SitModbusRegister.ACCESS_MODE_RW, 'enum16') def init_arg_parse(self): """ Parsing arguments """ self._parser = argparse.ArgumentParser(description='Actions with Inverter Manager through TCP') self._parser.add_argument('-v', '--verbose', help='increase output verbosity', action="store_true") self._parser.add_argument('-t', '--test', help='Runs test method', action="store_true") self._parser.add_argument('-u', '--slave_address', help='Slave address of modbus device', nargs='?') #self._parser.add_argument('-u', '--base_url', help='NOT_IMPLEMENTED:Gives the base URL for requests actions', nargs='?', default=self.DEFAULT_BASE_URL) l_required_named = self._parser.add_argument_group('required named arguments') l_required_named.add_argument('-i', '--host_ip', help='Host IP', nargs='?', required=True) l_required_named.add_argument('-m', '--host_mac', help='Host MAC', nargs='?', required=True) # l_required_named.add_argument('-l', '--longitude', help='Longitude coordinate (beware timezone is set to Chile)', nargs='?', required=True) # l_required_named.add_argument('-a', '--lattitude', help='Lattitude coordinate (beware timezone is set to Chile)', nargs='?', required=True) # l_required_named.add_argument('-d', '--device_type', help='Device Type:' + ('|'.join(str(l) for l in self.DEVICE_TYPES_ARRAY)), nargs='?', required=True) l_args = self._parser.parse_args() self._args = l_args # ACCESS # IMPLEMENTATION # EXECUTE ARGS """ Parsing arguments and calling corresponding functions """ def execute_corresponding_args(self): if self._args.verbose: self._logger.setLevel(logging.DEBUG) else: self._logger.setLevel(logging.DEBUG) if self._args.test: self.test() #if self._args.store_values: """ Test function """ def test(self): try: self.connect() self.read_all_sit_modbus_registers() print ("################# BEGIN #################") # self._logger.info("--> ************* device models *************: %s" % (l_d.models)) #Lists properties to be loaded with l_d.<property>.read() and then access them # self._logger.info("-->inverter ************* l_d.inverter.points *************: %s" % (l_d.inverter.points)) #Gives the inverter available properties # self._logger.info("-->inverter ************* common *************: %s" % (l_d.common)) # self._logger.info("-->inverter ************* common Serial Number *************: %s" % (l_d.common.SN)) print ("################# END #################") except Exception as l_e: self._logger.exception("Exception occured: %s" % (l_e)) print('Error: %s' % (l_e)) self._logger.error('Error: %s' % (l_e)) raise l_e finally: self.disconnect()