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
def __init__( self, a_config_file_name=SitConstants.SIT_JSON_DEFAULT_CONFIG_FILE_NAME): """ Initialize """ self._config_dir = SitConstants.DEFAULT_CONF_DIR self._config_file = a_config_file_name if not os.path.isdir(self._config_dir): raise Exception( 'init->Config dir {} does not exist, sudo mkdir -m 777 {}'. format(self._config_dir, self._config_dir)) try: self._logger = SitLogger().new_logger(__name__) if __name__ == '__main__': self.init_arg_parse() self.read_config(self.configuration_file_path()) except OSError as l_e: self._logger.warning( "init-> OSError, probably rollingfileAppender:{}".format(l_e)) if l_e.errno != errno.ENOENT: raise l_e except Exception as l_e: self._logger.error('Error in init: {}'.format(l_e)) raise l_e
def __init__(self, a_short_description, a_description, a_register_index, a_slave_address, an_access_mode=SitModbusRegister.DEFAULT_ACCESS_MODE, a_value_unit=None, a_scale_factor_register_index=None, an_event=None, an_is_metadata=False, a_post_set_value_call=None): """ Initialize """ try: super().__init__(a_short_description, a_description, a_register_index, a_slave_address, an_access_mode, a_value_unit, a_scale_factor_register_index, an_event, an_is_metadata, a_post_set_value_call) #*** Logger self._logger = SitLogger().new_logger(__name__) self.invariants() except OSError as l_e: self._logger.warning( "init-> OSError, probably rollingfileAppender:{}".format(l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: self._logger.error('Error in init: {}'.format(l_e)) raise l_e
def __init__(self, a_slave_address, a_target_mode=DEFAULT_TARGET_MODE, a_port=DEFAULT_PORT, an_ip_address=None): """ Initialize @param a_slave_address @param a_target_mode @param a_port @param an_ip_address """ assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) self._target_port = a_port self._slave_address = a_slave_address self._target_ip = an_ip_address self._logger = SitLogger().new_logger(self.__class__.__name__) self._sit_json_conf = SitJsonConf(__name__) self._target_mode = a_target_mode if self._target_mode == self.TARGET_MODE_TCP: assert self.valid_ip(self._target_ip), 'valid ip address' self.invariants()
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
class RegisterTypeInt64u(SitModbusRegister): # CONSTANTS # VARIABLES _logger = None _words_count = 4 _byte_order = Endian.Big _word_order = Endian.Big # SETTERS AND GETTERS # INITIALIZATION def __init__(self, a_short_description, a_description, a_register_index, a_slave_address, an_access_mode=SitModbusRegister.DEFAULT_ACCESS_MODE, a_value_unit=None, a_scale_factor_register_index=None, an_event=None, an_is_metadata=False, a_post_set_value_call=None): """ Initialize """ try: super().__init__(a_short_description, a_description, a_register_index, a_slave_address, an_access_mode, a_value_unit, a_scale_factor_register_index, an_event, an_is_metadata, a_post_set_value_call) #*** Logger self._logger = SitLogger().new_logger(__name__) self.invariants() except OSError as l_e: self._logger.warning( "init-> OSError, probably rollingfileAppender:{}".format(l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: self._logger.error('Error in init: {}'.format(l_e)) raise l_e #exit(1) # STATUS SETTING def set_value_with_raw(self, a_register_read_res): #self._logger.debug("set_value_with_raw->before decoder:{}".format(a_register_read_res.registers)) decoder = BinaryPayloadDecoder.fromRegisters( a_register_read_res.registers, byteorder=self._byte_order, wordorder=self._word_order ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_v = decoder.decode_64bit_uint() #self._logger.debug("set_value_with_raw->after decoder:{}".format(l_v)) self._value = l_v
class RegisterTypeStringVar(SitModbusRegister): # CONSTANTS # VARIABLES _logger = None _words_count = 15 _byte_order = Endian.Big _word_order = Endian.Big # SETTERS AND GETTERS # INITIALIZATION def __init__(self, a_short_description, a_description, a_register_index, a_word_count, a_slave_address, an_access_mode=SitModbusRegister.DEFAULT_ACCESS_MODE, a_value_unit=None, a_scale_factor_register_index=None, an_event=None, an_is_metadata=False, a_post_set_value_call=None): """ Initialize """ try: super().__init__(a_short_description, a_description, a_register_index, a_slave_address, an_access_mode, a_value_unit, a_scale_factor_register_index, an_event, an_is_metadata, a_post_set_value_call) self._words_count = a_word_count #*** Logger self._logger = SitLogger().new_logger(__name__) self.invariants() except OSError as l_e: self._logger.warning("init-> OSError, probably rollingfileAppender:{}".format(l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: self._logger.error('Error in init: {}'.format(l_e)) raise l_e #exit(1) # STATUS SETTING def set_value_with_raw(self, a_register_read_res): #self._logger.debug("set_value_with_raw->before decoder:{}".format(a_register_read_res.registers)) decoder = BinaryPayloadDecoder.fromRegisters(a_register_read_res.registers, byteorder=self._byte_order, wordorder=self._word_order) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_string(self._words_count) #self._logger.debug("register_values_string16->before decode utf=8:'{}'".format(l_result)) if len (l_result) > 0 and str(l_result[0]) == '0': l_result = '' else: l_result = l_result.decode('utf-8', errors='replace') #self._logger.debug("register_values_string16->after decoder:'%s' last:'{}'".format(l_result, '{0:02x}'.format(ord(l_result[-1])))) l_result = l_result.replace ('\x00', '') #self._logger.debug("register_values_string16->after replace:'%s'" % l_result) assert isinstance(l_result, str), 'result is no str but' + l_result.__class.__name__ #self._logger.debug("set_value_with_raw->after decoder:{}".format(l_v)) self._value = l_result
def __init__(self): """ Initialize """ try: #*** Logger self._logger = SitLogger().new_logger(__name__) except OSError as l_e: self._logger.warning("init-> OSError, probably rollingfileAppender:{}".format(l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: self._logger.error('Error in init: {}'.format(l_e)) raise l_e
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
def __init__(self, a_short_description, a_description, a_register_index, a_slave_address, an_access_mode=DEFAULT_ACCESS_MODE, a_value_unit=None, a_scale_factor_register_index=None, an_event=None, an_is_metadata=False, a_post_set_value_call=None): #require assert self.valid_access_mode( an_access_mode), 'invalid an_access_mode:{}'.format(an_access_mode) assert self.valid_register_index( a_register_index), 'invalid a_register_index:{}'.format( a_register_index) from sit_modbus_device import SitModbusDevice assert SitModbusDevice.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) self._short_description = a_short_description self._description = a_description self._register_index = a_register_index self._access_mode = an_access_mode self._value_unit = a_value_unit self._scale_factor_register_index = a_scale_factor_register_index self._event = an_event self._is_metadata = an_is_metadata self._slave_address = a_slave_address self._post_set_value_call = a_post_set_value_call self._logger = SitLogger().new_logger(self.__class__.__name__) self._logger.debug('init-> created SitModbusRegister:%s' % (self.to_ordered_dict())) #ensure self.invariants()
class SitDateTime(object): # CONSTANTS DAY, NIGHT = 1, 2 # VARIABLES _logger = None # SETTERS AND GETTERS # FUNCTIONS DEFINITION def __init__(self): self._logger = SitLogger().new_logger(__name__) def local_offset_hours(self): """ returns local offset in hours ex. Chile -3 or -4 """ l_res = 0 l_cmd = 'date +%z' try: l_code, l_stdout, l_stderr = SitUtils.system_call(l_cmd) l_res = int(l_stdout) l_res = l_res / 100 except Exception as l_e: self._logger.error('localOffsetHours error'.format(l_e)) raise l_e assert l_res <= -3, 'local_offset_hours->invalid_l_res:{}'.format( l_res) self._logger.debug('localOffsetHours-> cmd_res:{} res:{}'.format( l_stdout, l_res)) return l_res return l_res def time_is_between(self, a_time_to_check, an_on_time, an_off_time): """ https://stackoverflow.com/a/20518510/2118777 l_on_time = datetime.time(23,30) l_off_time = datetime.time(4,15) l_timenow = datetime.datetime.now().time() l_current_time = datetime.datetime.now().time() l_is_day, l_matching = check_time(l_current_time, l_on_time, l_off_time) if l_matching: if l_is_day == NIGHT: print('Night Time detected.') elif l_is_day == DAY: print('Day Time detected.') """ if an_on_time > an_off_time: if a_time_to_check > an_on_time or a_time_to_check < an_off_time: return self.NIGHT, True elif an_on_time < an_off_time: if a_time_to_check > an_on_time and a_time_to_check < an_off_time: return self.DAY, True elif a_time_to_check == an_on_time: return None, True return None, False def time_is_into_sunrise_sunset(self, a_date_time, a_longitude, a_latitude, a_local_offset=-3, a_sunrise_interval_min=0, a_sunset_interval_min=0): """ is given a_date_time into sunrise sunset range? return boolean """ l_ss = SunriseSunset(datetime.now(), latitude=a_latitude, longitude=a_longitude, localOffset=a_local_offset) l_sunrise_time, l_sunset_time = l_ss.calculate() l_res = ( l_sunrise_time + timedelta(seconds=a_sunrise_interval_min * 60) < datetime.now() and l_sunset_time - timedelta(seconds=a_sunset_interval_min * 60) > datetime.now()) self._logger.debug( 'time_is_into_sunrise_sunset-> sunrise_time:{} sunset_time:{} datetime:{} result (sun is up?):{}' .format(l_sunrise_time, l_sunset_time, a_date_time, l_result)) return l_res def now_is_into_sunrise_sunset_from_conf(self, a_sit_json_conf): """ """ assert isinstance(a_sit_json_conf, SitJsonConf) l_longitude = self._sit_json_conf.item('longitude') l_latitude = self._sit_json_conf.item('latitude') l_local_offset = self._sit_json_conf.item('local_offset') l_sunrise_interval_minutes = self._sit_json_conf.item( 'sunrise_interval_minutes') l_sunset_interval_minutes = self._sit_json_conf.item( 'l_sunset_interval_minutes') l_res = time_is_into_sunrise_sunset(datetime.now(), l_longitude, l_latitude, l_local_offset, l_sunrise_interval_minutes, l_sunset_interval_minutes) return l_res
class SitModbusRegister(ABC): # CONSTANTS LOG_FILE_PATH = '/var/log/solarity' DEFAULT_LOGGING_LEVEL = logging.INFO #For console overrided by --verbose DEFAULT_FILE_LOGGING_LEVEL = logging.DEBUG #For file DEFAULT_CSV_FILE_LOCATION = '/var/solarity' #without ending slash DEFAULT_ACCESS_MODE = 'R' ACCESS_MODE_RW = 'RW' ACCESS_MODE_R = 'R' # VARIABLES _logger = None _short_description = None _description = None _register_index = None _value = None _value_unit = None _access_mode = None _scale_factor_register_index = None _is_metadata = False _slave_address = None # Can be none, in that case the default slave_address is taken _words_count = None _event = None _post_set_value_call = None # FUNCTIONS DEFINITION """ Initialize """ def __init__(self, a_short_description, a_description, a_register_index, a_slave_address, an_access_mode=DEFAULT_ACCESS_MODE, a_value_unit=None, a_scale_factor_register_index=None, an_event=None, an_is_metadata=False, a_post_set_value_call=None): #require assert self.valid_access_mode( an_access_mode), 'invalid an_access_mode:{}'.format(an_access_mode) assert self.valid_register_index( a_register_index), 'invalid a_register_index:{}'.format( a_register_index) from sit_modbus_device import SitModbusDevice assert SitModbusDevice.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) self._short_description = a_short_description self._description = a_description self._register_index = a_register_index self._access_mode = an_access_mode self._value_unit = a_value_unit self._scale_factor_register_index = a_scale_factor_register_index self._event = an_event self._is_metadata = an_is_metadata self._slave_address = a_slave_address self._post_set_value_call = a_post_set_value_call self._logger = SitLogger().new_logger(self.__class__.__name__) self._logger.debug('init-> created SitModbusRegister:%s' % (self.to_ordered_dict())) #ensure self.invariants() """ Parsing arguments """ def init_arg_parse(self): """App help""" self._parser = argparse.ArgumentParser( description='Actions with sunspec 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', '--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('-u', '--slave_address', help='Slave address of modbus device', 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) args = self._parser.parse_args() self._args = args """ Parsing arguments and calling corresponding functions """ def execute_corresponding_args(self): self.init_arg_parse() if self._args.verbose: self._logger.setLevel(logging.DEBUG) self._console_handler.setLevel(logging.DEBUG) self._file_handler.setLevel(logging.DEBUG) else: self._logger.setLevel(logging.DEBUG) self._console_handler.setLevel(logging.ERROR) self._file_handler.setLevel(logging.DEBUG) if self._args.test: self.test() #if self._args.store_values: # VALIDATION def valid_access_mode(self, v): """ Is access mode valid """ return v == self.ACCESS_MODE_R or v == self.ACCESS_MODE_RW def valid_register_index(self, v): """ Value is int and > 0 """ try: l_is_int = int(v) l_res = l_is_int >= 0 except Exception as l_e: l_res = False return l_res # GETTERS AND SETTERS @property def short_description(self): return self._short_description @property def description(self): return self._description @property def register_index(self): return self._register_index @property def value(self): return self._value @value.setter def value(self, v): self._value = v def is_access_mode_rw(self): return self._access_mode == self.ACCESS_MODE_RW @property def value_unit(self): return self._value_unit @property def scale_factor_register_index(self): return self._scale_factor_register_index @property def is_metadata(self): return self._is_metadata @property def words_count(self): return self._words_count @property def slave_address(self): return self._slave_address # STATUS REPORT def has_event(self): return self._event is not None def has_post_set_value_call(self): return self._post_set_value_call is not None def has_slave_address(self): from sit_modbus_device import SitModbusDevice return SitModbusDevice.valid_slave_address(self._slave_address) # STATUS SETTING @abstractmethod def set_value_with_raw(self, v): # OR raise NotImplementedError pass # EVENT def call_event(self): """ calls sit_event.call_method_to_call(self) """ assert self.has_event(), 'event not setted, check it before call' assert isinstance( self._event, SitModbusRegisterEvent), 'self._event is not a SitModbusRegister' self._logger.debug( 'call_event-> has_event:{}, register short_out:{}'.format( self.has_event(), self.out_short())) l_optional_args = None self._event.modbus_register = self #self._event.call_method_to_call([self, a_slave_address], l_optional_args) self._event.call_method_to_call([self], l_optional_args) def call_post_set_value(self): """ calls _post_set_value_call function """ assert self._post_set_value_call is not None, '_post_set_value_call is None' self._post_set_value_call(self) # CONVERSION # OUTPUT def to_ordered_dict(self): l_res = OrderedDict() l_res['short_description'] = self._short_description l_res['value'] = self._value l_res['value_unit'] = self._value_unit l_res['description'] = self._description l_res['register_index'] = self._register_index l_res['access_mode'] = self._access_mode l_res[ 'scale_factor_register_index'] = self._scale_factor_register_index l_res['has_event'] = self.has_event() l_res['slave_address'] = self._slave_address return l_res def out(self, a_sep='|', an_item_prefix='', a_with_description=False): """ returns a string with values of to_ordered_dict() """ l_res = '' for l_key, l_val in self.to_ordered_dict().items(): l_res += an_item_prefix + l_key + ' = ' + str(l_val) + a_sep return l_res def out_short(self, a_sep='|'): """ returns a string with values of to_ordered_dict() """ l_res = '' l_res = l_res + 'index:' + str(self._register_index) + a_sep l_res = l_res + 'short:' + self._short_description + a_sep l_res = l_res + 'desc:' + self._description + a_sep l_res = l_res + 'val:' + str(self._value) + a_sep l_res = l_res + 'u:' + str(self._value_unit) + a_sep l_res = l_res + 'has_event:' + str(self.has_event()) + a_sep l_res = l_res + 'slave:' + str(self._slave_address) return l_res def out_human_readable(self, an_item_prefix='', a_with_description=False): l_res = '' assert self.short_description is not None assert self.value_unit is not None l_val = self.value if l_val is None: l_val = 'None' elif isinstance(l_val, int) or isinstance(l_val, float): from sit_utils import SitUtils l_val = SitUtils.format_number_human_readable(l_val, 2) if a_with_description: l_tmp = '({}/{}) '.format(self.register_index, self.slave_address) l_tmp += self.description l_desc = ' {:<55}'.format(l_tmp) else: l_desc = '' l_res += an_item_prefix + '{:<23}'.format( self.short_description) + '{:<5}'.format('=') + '{:<25}'.format( l_val) + '{:<10}'.format(self.value_unit) + l_desc return l_res # TEST FUNCTION def test(self): """ Test function """ 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)) 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)) sys.exit(1) # INVARIANTS def invariants(self): assert self.valid_access_mode( self._access_mode), 'invalid access mode:{}'.format( self._access_mode) assert self.valid_register_index( self._register_index), 'invalid register index:{}'.format( self._register_index)
class DataManagerInverter(ClusterController): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 3 MIN_W_FOR_RAISE_EVENT_GENERATION = 2000 PARSER_DESCRIPTION = 'Actions with sma data manager inverters . ' + 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() # # #PARAMETERS UNIT_ID = 2 (p.26 of doc) # SitUtils.od_extend(l_reg_list, 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=2)) # # 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 InverterManager(SitModbusDevice): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 125 DEFAULT_MODBUS_PORT = 502 DEFAULT_TARGET_MODE = SitModbusDevice.TARGET_MODE_TCP MIN_W_FOR_RAISE_EVENT_GENERATION = 50 PARSER_DESCRIPTION = 'Actions with sma inverter manager. ' + SitConstants.DEFAULT_HELP_LICENSE_NOTICE # CLASS ATTRIBUTES _byte_order = Endian.Big _word_order = Endian.Big _substract_one_to_register_index = True _slave_addresses_list = None # 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), '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) 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.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 """ 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, RegisterTypeInt16u('ID', 'Model ID (ID): 120 = Sunspec nameplate model', 40238, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'uint16', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor('AC_A', 'AC Current sum of all inverters', 40188, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'A', a_scale_factor=40192)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16u( 'VArPct_Mod', 'Mode of the percentile reactive power limitation: 1 = in % of WMax', 40365, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'enum16')) SitUtils.od_extend( l_reg_list, RegisterTypeInt16u( 'VArPct_Ena', 'Control of the percentile reactive power limitation,(SMA: Qext): 1 = activated', 40365, l_slave_address, SitModbusRegister.ACCESS_MODE_RW, 'enum16')) SitUtils.od_extend( l_reg_list, RegisterTypeInt16u('WMaxLim_Ena', 'Limiting (0 Deactivate, 1 activated):', 40353, l_slave_address, SitModbusRegister.ACCESS_MODE_RW, 'enum16')) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'WMaxLimPct', 'Set power to default value, in % of WMax-WMaxLimPct_SF', 40349, l_slave_address, SitModbusRegister.ACCESS_MODE_RW, 'uint16', a_scale_factor=40367)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'VRef', 'Voltage at the PCC (VRef), in V VRef_SF (40289)', 40269, l_slave_address, SitModbusRegister.ACCESS_MODE_RW, 'uint16', 40289)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'VMax', 'Set value for maximum voltage (VMax), in V VMinMax_SF', 40271, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'uint16', a_scale_factor=40291)) self.append_modbus_registers(l_reg_list) self.invariants() def _add_common_registers(self, a_reg_list, a_slave_address): """ adds to a_reg_list """ l_reg_list = a_reg_list l_slave_address = a_slave_address SitUtils.od_extend( l_reg_list, RegisterTypeString16('Mn', 'Manufacturer', 40005, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'String16', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeString16('Md', 'Model (Md): SMA Inverter Manager', 40021, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'String16', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeString8('Opt', 'Options (Opt): Inverter Manager name', 40037, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'String8', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeString8( 'Vr', 'Version (Vr): Version number of the installed firmware', 40045, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'String8', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeString16( 'SN', 'Serial number (SN) of the device that uses the Modbus unit ID', 40053, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'String16', an_is_metadata=True)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'PPVphAB', 'Voltage, line conductor L1 to L2, in V V_SF (40199) : average value of all inverters', 40193, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', a_scale_factor=40199)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'PPVphBC', 'Voltage, line conductor L2 to L3, in V V_SF (40199) : average value of all inverters', 40194, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', a_scale_factor=40199)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'PPVphCA', 'Voltage, line conductor L3 to L1, in V V_SF (40199) : average value of all inverters', 40195, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', a_scale_factor=40199)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'PPVphA', 'Voltage, line conductor L1 to N (PPVphA), in V-V_SF (40199): average value of all inverters', 40196, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', a_scale_factor=40199)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'PPVphB', 'Voltage, line conductor L1 to N (PPVphB), in V-V_SF (40199): average value of all inverters', 40197, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', a_scale_factor=40199)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'PPVphC', 'Voltage, line conductor L1 to N (PPVphC), in V-V_SF (40199): average value of all inverters', 40198, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'V', a_scale_factor=40199)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'W', 'Active power (W), in W-W_SF (40201): sum of all inverters', 40200, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'W', a_scale_factor=40192, an_event=SitModbusRegisterEvent(self._W_event), a_post_set_value_call=self.w_fix)) SitUtils.od_extend( l_reg_list, RegisterTypeInt32uScaleFactor( 'WH', 'Total yield (WH), in Wh WH_SF (40212): sum of all inverters', 40210, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'WH', a_scale_factor=40212)) SitUtils.od_extend( l_reg_list, RegisterTypeInt16uScaleFactor( 'TmpCab', 'Internal temperature, in °C Tmp_SF (40223): average value of all inverters', 40219, l_slave_address, SitModbusRegister.ACCESS_MODE_R, '°C', a_scale_factor=40223)) 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) ' + str( a_sit_modbus_register.slave_address ) + ' $(date +%Y%m%d_%H%M%S) W val:(' + str( l_val) + ' <= ' + str(l_min_val) + ')kW ' 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))) 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 w_fix(self, a_sit_modbus_register): """ """ l_new_val = a_sit_modbus_register.value * 100 self._logger.debug('w_fix->Setting new value-> old:{} new:{}'.format( a_sit_modbus_register.value, l_new_val)) a_sit_modbus_register.value = l_new_val 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 # 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_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) 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 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() 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 SitJsonConf(object): # CONSTANTS # VARIABLES _logger = None _config_dir = None #Setted by init _config_file = None #File name only _config_data = None # Where json.dump are put # FUNCTIONS DEFINITION def __init__( self, a_config_file_name=SitConstants.SIT_JSON_DEFAULT_CONFIG_FILE_NAME): """ Initialize """ self._config_dir = SitConstants.DEFAULT_CONF_DIR self._config_file = a_config_file_name if not os.path.isdir(self._config_dir): raise Exception( 'init->Config dir {} does not exist, sudo mkdir -m 777 {}'. format(self._config_dir, self._config_dir)) try: self._logger = SitLogger().new_logger(__name__) if __name__ == '__main__': self.init_arg_parse() self.read_config(self.configuration_file_path()) except OSError as l_e: self._logger.warning( "init-> OSError, probably rollingfileAppender:{}".format(l_e)) if l_e.errno != errno.ENOENT: raise l_e except Exception as l_e: self._logger.error('Error in init: {}'.format(l_e)) raise l_e def configuration_file_path(self): """ """ l_res = os.path.join(self._config_dir, self._config_file) return l_res def read_config(self, a_config_file_path=None): """ puts into self._config_data json data """ if a_config_file_path is None: l_config_file_path = self.configuration_file_path() else: l_config_file_path = a_config_file_path with open(l_config_file_path, "r") as l_file: self._config_data = json.load(l_file) def item(self, a_key): """ returns value of given key exception if not found """ l_res = None return l_res # SCRIPT ARGUMENTS def init_arg_parse(self): """ Parsing arguments """ self._logger.debug('init_arg_parse-> begin') self._parser = argparse.ArgumentParser( description='Actions with sunspec through TCP') self._add_arguments() l_args = self._parser.parse_args() self._args = l_args def _add_arguments(self): """ Add arguments to parser (called by init_arg_parse()) """ self._parser.add_argument('-v', '--verbose', help='increase output verbosity', action="store_true") self._parser.add_argument('-u', '--update_leds_status', help='Updates led status according to spec', action="store_true") self._parser.add_argument('-t', '--test', help='Runs test method', action="store_true") #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('-u', '--slave_address', help='Slave address of modbus device', 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) def execute_corresponding_args(self): """ Parsing arguments and calling corresponding functions """ if self._args.verbose: self._logger.setLevel(logging.DEBUG) else: self._logger.setLevel(logging.INFO) if self._args.test: self.test() #if self._args.store_values: # TEST def test(self): """ Test function """ try: self._logger.info("################# BEGIN #################") self._logger.info( "--> ************* device models *************: {}".format( l_d.models) ) #Lists properties to be loaded with l_d.<property>.read() and then access them except Exception as l_e: self._logger.exception("Exception occured: {}".format(l_e)) print('Error: {}'.format(l_e)) self._logger.error('Error: {}'.format(l_e)) sys.exit(1)
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 SitModbusRegisterEvent(SitEvent): # CONSTANTS # VARIABLES _logger = None _modbus_register = None # SETTERS AND GETTERS @property def modbus_register(self): return self._modbus_register() @modbus_register.setter def modbus_register(self, v): self._modbus_register = v # INITIALIZE def __init__(self, a_method_to_call): """ Initialize """ super().__init__(a_method_to_call) try: #*** Logger self._logger = SitLogger().new_logger(__name__) except OSError as l_e: self._logger.warning( "init-> OSError, probably rollingfileAppender:{}".format(l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: self._logger.error('Error in init: {}'.format(l_e)) raise l_e #exit(1) # EVENT CALL def call_method_to_call(self, some_positional_args, some_keyword_args): """ Redefine """ assert self._modbus_register is not None, 'setted _modbus_register' super().call_method_to_call(some_positional_args, some_keyword_args) # TEST def test(self): """ Test function """ try: self._logger.info("################# BEGIN #################") self._logger.info( "--> ************* device models *************: {}".format( l_d.models) ) #Lists properties to be loaded with l_d.<property>.read() and then access them except Exception as l_e: self._logger.exception("Exception occured: {}".format(l_e)) print('Error: {}'.format(l_e)) self._logger.error('Error: {}'.format(l_e)) sys.exit(1)
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()
def __init__(self): self._logger = SitLogger().new_logger(__name__)
class RenkeYgcJyz12VW2(SitModbusDevice): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 1 DEFAULT_MODBUS_PORT = '/dev/ttyUSB0' DEFAULT_TARGET_MODE = SitModbusDevice.TARGET_MODE_RTU PARSER_DESCRIPTION = 'Actions with renke_ygc-jyz-12V-W2 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 = 9600 _rtu_parity = 'N' _rtu_timeout = 15 #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)', 0x00, 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', 'ygc-jyz-12V-W2']] 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.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)) #l_res = self.register_value(0x0, 1, 1) l_reg_index = 0 l_slave = 1 l_val = 1 #l_write_res = self.write_register_value(l_reg_index, l_slave, l_val) l_res = self.register_values_int_16_u(0x0, 1) self._logger.info("Register value:{}".format(l_res)) 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 SmartLogger1000aInverter(SitModbusDevice): # CONSTANTS DEFAULT_SLAVE_ADDRESS = 1 DEFAULT_MODBUS_PORT = 502 DEFAULT_TARGET_MODE = SitModbusDevice.TARGET_MODE_TCP MIN_W_FOR_RAISE_EVENT_GENERATION = 50 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 _inverter_indexes_list = None # 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._inverter_indexes_list = SitUtils.args_to_list(self._args.inverter_index) 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, an_inverter_index): """ Initializes self._sit_modbus_registers """ assert self.valid_slave_address(a_slave_address), 'invalid a_slave_address:{}'.format(a_slave_address) assert self.valid_inverter_index (an_inverter_index), 'valid inverter index:{}'.format(an_inverter_index) self.add_inverter_modbus_registers(1, an_inverter_index) self.invariants() def add_inverter_modbus_registers(self, a_slave_address, an_inverter_index): """ INVERTERS see p.20 of documentation """ assert an_inverter_index >= 1, 'inverter index >= 1: {}'.format(an_inverter_index) l_initial_register_address = 51000 l_base_address = l_initial_register_address + (25 * (an_inverter_index - 1)) l_reg_list = OrderedDict() l_slave_address = a_slave_address self._logger.info('add_inverter_modbus_registers-> Base address: {}'.format(l_base_address)) SitUtils.od_extend(l_reg_list, RegisterTypeInt32s(SitConstants.SS_REG_SHORT_ABB_AC_POWER, 'Active power for inverter nr: {}'.format(an_inverter_index), l_base_address, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'W', an_is_metadata=False, an_event=SitModbusRegisterEvent(self._W_event))) SitUtils.od_extend(l_reg_list, RegisterTypeInt16u(SitConstants.SS_REG_SHORT_ABB_STATUS_OPERATING_STATE, 'Status for inverter nr: {}'.format(an_inverter_index), l_base_address + 9, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'deg celcius', an_is_metadata=False)) SitUtils.od_extend(l_reg_list, RegisterTypeInt16s(SitConstants.SS_REG_SHORT_ABB_TEMP_CAB, 'Cabinet temperatore for inverter nr: {}'.format(an_inverter_index), l_base_address + 11, l_slave_address, SitModbusRegister.ACCESS_MODE_R, 'deg celcius', 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 # 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) for l_inverter_index in self._inverter_indexes_list: assert self.valid_inverter_index(l_inverter_index), 'execute_corresponding_args->valid inverter index:{}'.format(l_inverter_index) self._init_sit_modbus_registers(self._slave_address, l_inverter_index) 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): """ """ a_required_named.add_argument('-x', '--inverter_index', help='Inverter index >= 1, can be 1-n or 1,2,5', nargs='?', required=True) 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 valid_inverter_index(self, an_inverter_index): return an_inverter_index >= 1 def invariants_modbus_device(self): super().invariants() def invariants(self): self.invariants_modbus_device()
class SitModbusDevice(object): # CONSTANTS DEFAULT_LOGGING_LEVEL = logging.DEBUG #For console overrided by --verbose DEFAULT_FILE_LOGGING_LEVEL = logging.DEBUG #For file DEFAULT_BASE_URL = "http://localhost:9999/" # with ending slash DEFAULT_PORT = "/dev/ttyUSB0" #https://unix.stackexchange.com/a/144735/47775 to get it TARGET_MODE_TCP = 'tcp' TARGET_MODE_RTU = 'rtu' DEFAULT_TARGET_MODE = TARGET_MODE_TCP # or rtu DEFAULT_TARGET_PORT = 502 MAX_CONNECT_RETRIES_COUNT = 3 MAX_MODBUS_REGISTER_RETRIES_COUNT = 3 LOG_FILE_PATH = '/var/log/solarity' DEFAULT_CSV_FILE_LOCATION = '/var/solarity' #without ending slash PARSER_DESCRIPTION = 'Actions with modbus device. ' + SitConstants.DEFAULT_HELP_LICENSE_NOTICE # VARIABLES _logger = None _args = None _console_handler = None _file_handler = None _sit_json_conf = None _modbus_client = None _is_connected = False _base_url = DEFAULT_BASE_URL _target_ip = None _target_port = DEFAULT_TARGET_PORT _target_mode = DEFAULT_TARGET_MODE _slave_address = None _client_connect_retries = 3 _rtu_timeout = 1 #seconds _rtu_stopbits = 1 _rtu_bytesize = 8 _rtu_parity = 'E' _rtu_baudrate = 19200 #9600 _byte_order = Endian.Big _word_order = Endian.Big _substract_one_to_register_index = False _sit_modbus_registers = OrderedDict() # OrderedDict # FUNCTIONS DEFINITION def __init__(self, a_slave_address, a_target_mode=DEFAULT_TARGET_MODE, a_port=DEFAULT_PORT, an_ip_address=None): """ Initialize @param a_slave_address @param a_target_mode @param a_port @param an_ip_address """ assert self.valid_slave_address( a_slave_address), 'invalid a_slave_address:{}'.format( a_slave_address) self._target_port = a_port self._slave_address = a_slave_address self._target_ip = an_ip_address self._logger = SitLogger().new_logger(self.__class__.__name__) self._sit_json_conf = SitJsonConf(__name__) self._target_mode = a_target_mode if self._target_mode == self.TARGET_MODE_TCP: assert self.valid_ip(self._target_ip), 'valid ip address' self.invariants() def process_script_arguments(self): """ reads script arguments and executes corresponding argument """ try: self.init_arg_parse() self.connect() self.execute_corresponding_args() except Exception as l_e: self._logger.exception('process_script_arguments->Exception:%s' % (l_e)) if self.is_connected(): self.disconnect() def connect(self): """ sets self._modbus_client and connects to it """ if self._target_mode == self.TARGET_MODE_TCP: assert self.valid_ip(self._target_ip), 'Target ip is None' assert not self.is_connected() l_retries_count = 0 self._logger.debug( 'connect-> with args target_mode:{} port:{} rtu_timeout:{} baudrate:{}' .format(self._target_mode, self._target_port, self._rtu_timeout, self._rtu_baudrate)) while l_retries_count < self.MAX_CONNECT_RETRIES_COUNT and not self._is_connected: try: if self._target_mode == self.TARGET_MODE_RTU: assert os.geteuid() == 0, 'user must be root for RTU mode' # DOC: https://github.com/riptideio/pymodbus/blob/8ef32997ee1da1cd465f2e19ff3b54b93d38728c/pymodbus/repl/main.py self._modbus_client = ModbusSerialClient( method=self._target_mode, port=str(self._target_port), timeout=self._rtu_timeout, stopbits=self._rtu_stopbits, bytesize=self._rtu_bytesize, parity=self._rtu_parity, baudrate=self._rtu_baudrate) self._modbus_client.debug_enabled = True self._logger.debug( 'connect->target:{} port:{} timeout:{} stopbit:{} bytesize:{} parity:{} baudrate:{}' .format(self._target_mode, str(self._target_port), self._rtu_timeout, self._rtu_stopbits, self._rtu_bytesize, self._rtu_parity, self._rtu_baudrate)) self._logger.info('connect->RTU Client Mode:{}'.format( self._target_mode)) else: assert self._target_mode == self.TARGET_MODE_TCP self._modbus_client = ModbusTcpClient( self._target_ip, port=str(self._target_port), retries=self._client_connect_retries, retry_on_empty=True) self._logger.info('connect->TCP Client Mode:{}'.format( self._target_mode)) #Connect to the serial modbus server connection = self._modbus_client.connect() if self._modbus_client.connect: #self._logger.debug("Client is connected") self._is_connected = True self._logger.info('connect -> Connection success') else: self._is_connected = False raise ConnectionException( "connect->Could not connect to _modbus_client") except ConnectionException as l_e: l_retries_count += 1 self._logger.exception( "connect->ConnectionException occured during connection, retrying:{}" .format(l_e)) time.sleep(1) except Exception as l_e: self._logger.exception( "connect->Exception occured during connection:{}".format( l_e)) raise l_e # HIGH LEVEL FUNCTIONS def _header_rows(self): #return [['#Mn', 'some_manufacturer'], ['#Md', 'some_model']] return [] def add_modbus_register_from_values( self, a_short_description, a_description, a_register_index, a_register_type, a_slave_address, an_access_mode=SitModbusRegister.ACCESS_MODE_R, a_value_unit=None, a_scale_factor_register_index=None, an_event=None, an_is_metadata=False): """ adds to self._sit_modbus_registers """ assert not a_short_description in self._sit_modbus_registers.keys( ), 'Already has key ' + a_short_description self._sit_modbus_registers[a_short_description] = SitModbusRegister( a_short_description, a_description, a_register_index, a_register_type, an_access_mode, a_value_unit, a_scale_factor_register_index=a_scale_factor_register_index, an_event=an_event, an_is_metadata=an_is_metadata) def add_modbus_register(self, a_modbus_register): assert isinstance( a_modbus_register, SitModbusRegister), 'arg is not a SitModbusRegister but {}'.format( a_modbus_register.__class__.__name__) assert a_modbus_register.has_slave_address( ), 'a_modbus_register has no slave_address' self._sit_modbus_registers[ a_modbus_register.short_description] = a_modbus_register def append_modbus_registers(self, an_ordered_dict): """ add_modbus_register with each item of given an_ordered_dict """ assert isinstance(an_ordered_dict, OrderedDict), 'param is not an OrderedDict' for l_short_desc, l_reg in an_ordered_dict.items(): self.add_modbus_register(l_reg) def read_register_from_short_description(self, a_short_description, a_slave_address): """ returns a read register @param: a_short_description """ l_reg = self._sit_modbus_registers[a_short_description] self.read_sit_modbus_register(l_reg, a_slave_address) def read_sit_modbus_register(self, a_sit_modbus_register): """ setting value of given modbus register @a_sit_modbus_register """ assert isinstance( a_sit_modbus_register, SitModbusRegister), 'given argument must be a SitModbusRegister' assert self.is_connected(), 'Not connected' l_val = self.register_value(a_sit_modbus_register.register_index, a_sit_modbus_register.words_count, a_sit_modbus_register.slave_address) a_sit_modbus_register.set_value_with_raw(l_val) self.set_value_with_scale_factor(a_sit_modbus_register) def set_value_with_scale_factor(self, a_sit_modbus_register): """ set a_sit_modbus_register value with read scale_factor read from scale_factor_register_index """ if a_sit_modbus_register.scale_factor_register_index is not None: l_scale_factor = self.register_values_int_16_s( a_sit_modbus_register.scale_factor_register_index, a_sit_modbus_register.slave_address) l_val = a_sit_modbus_register.value l_val = l_val * 10**l_scale_factor self._logger.debug( 'set_value_with_scale_factor-> old_val:%s scale_factor:%s new_val:%s' % (a_sit_modbus_register.value, l_scale_factor, l_val)) a_sit_modbus_register.value = l_val # else: # self._logger.debug('read_sit_modbus_register-> scale_factor_index should be None:%s' % (a_sit_modbus_register.scale_factor_register_index)) 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) 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())) # LOW LEVEL FUNCTIONS READ def register_value(self, a_register_index, a_register_length, a_slave_address): """ Returns a given register value @a_register_length: 1 register is 16 bits (2 bytes = 1 word) """ assert self.is_connected(), 'register_value->device is not connected' assert isinstance(a_register_index, int), 'register_value->Slave address is not an int' assert self.valid_slave_address( a_slave_address ), 'register_value->Slave address is not valid:' + str(a_slave_address) #self._logger.debug('register_value-> _substract_one_to_register_index:%s' % (self._substract_one_to_register_index)) if self._substract_one_to_register_index: l_register_index = a_register_index - 1 l_register_index_s_debug = str(a_register_index) + '-1' else: l_register_index = a_register_index l_register_index_s_debug = str(l_register_index) l_retries_count = 0 while l_retries_count < self.MAX_MODBUS_REGISTER_RETRIES_COUNT: try: #Starting add, num of reg to read, slave unit. self._logger.debug( 'register_value-> index:{} length:{} unit:{} _substract_one_to_register_index:{}' .format(l_register_index, a_register_length, a_slave_address, self._substract_one_to_register_index)) l_result = self._modbus_client.read_holding_registers( l_register_index, a_register_length, unit=a_slave_address) # Average current if l_result is not None: if (hasattr(l_result, 'function_code') and l_result.function_code < 0xFFFFFFFF): self._logger.debug( "register_value-> read register index:%s (%s) length:%s slave_address:%s" % (l_register_index, l_register_index_s_debug, a_register_length, a_slave_address)) #self._logger.debug(l_result) #self._logger.debug("register_value->register 0 value:%s" % l_result.getRegister(1)) #self._logger.debug("register_value-> 0 type:%s" % type(l_result.getRegister(0))) #self._logger.debug(l_result._str_()) else: self._logger.error( "register_value-> returned code is invalid: {}". format(l_result)) else: l_msg = "register_value-> No register received, l_result is None" self._logger.error(l_msg) raise ModbusException(l_msg) if not hasattr(l_result, 'registers'): l_msg = 'register_value-> read register has no registers attribute, slave:{} reading register:{} length:{}'.format( a_slave_address, l_register_index, a_register_length) self._logger.error(l_msg) raise ModbusException(l_msg) return l_result except KeyboardInterrupt: self._logger.exception( "register_value-> Keyboard interruption") except ModbusException as l_e: l_retries_count += 1 if l_retries_count >= self.MAX_MODBUS_REGISTER_RETRIES_COUNT: self._logger.error( 'register_value-> error with ModbusException not retrying but raising' ) raise l_e else: self._logger.error( 'register_value-> error with ModbusException retrying {}' .format(l_retries_count)) self.disconnect time.sleep(0.2) self.connect except Exception as l_e: self._logger.exception( "register_value-> Exception occured, msg:%s" % l_e) raise l_e def _int_from_register(self, a_register, a_start_index, a_bits_count): """ """ l_tmp_binary = "{0:b}".format(a_register) l_tmp_binary = l_tmp_binary.zfill(16) l_end_index = len(l_tmp_binary) - a_start_index l_start_index = len(l_tmp_binary) - a_start_index - a_bits_count l_tmp = l_tmp_binary[ l_start_index:l_end_index + 1] # second is where ends exclusive, first is inclusive l_result = int(l_tmp, 2) self._logger.debug( "************************************* _int_from_register-> register(%s) binary (%s) %s:%s => %s result(%s)" % (a_register, l_tmp_binary, l_start_index, l_end_index, l_tmp, l_result)) return l_result def register_values_date_time(self, a_register_index, a_slave_address): """ from spec returns a string formated @a_register_index: a register index """ assert False, 'Deprecated' try: l_register_res = self.register_value_invalid_int( a_register_index, 4, a_slave_address) self._logger.debug( "******* BEGIN ****************************** register_values_date_time->a_register_index:%s" % (a_register_index)) l_year = self._int_from_register(l_register_res.registers[0], 0, 6) + 2000 l_day = self._int_from_register(l_register_res.registers[1], 0, 4) l_month = self._int_from_register(l_register_res.registers[1], 8, 4) l_min = self._int_from_register(l_register_res.registers[2], 0, 5) l_hour = self._int_from_register(l_register_res.registers[2], 8, 5) l_sec = int(int(l_register_res.registers[3]) / 1000) l_result = str(l_year) + '-' + str(l_month).zfill(2) + '-' + str( l_day).zfill(2) + 'T' + str(l_hour).zfill(2) + ':' + str( l_min).zfill(2) + ':' + str(l_sec).zfill( 2) + 'Z' #Definitive result as expecte except Exception as l_e: self._logger.exception("register_values_date_time exception: %s" % l_e) raise l_e l_result = (';'.join(str(l) for l in l_register_res.registers) ) # only decimals self._logger.debug( "******** END ***************************** register_values_date_time->registers:%s- result:%s-" % (l_register_res.registers, l_result)) return l_result def register_values_pm5500_date_time(self, a_register_index, a_slave_address): """ from spec returns a string formated with datetime @a_register_index: a register index start index, the followings will be read """ l_i = 0 l_register_res = self.register_value_invalid_int( a_register_index + l_i, 1, a_slave_address) l_year = l_register_res.registers[0] l_i = l_i + 1 l_register_res = self.register_value_invalid_int( a_register_index + l_i, 1, a_slave_address) l_month = l_register_res.registers[0] l_i = l_i + 1 l_register_res = self.register_value_invalid_int( a_register_index + l_i, 1, a_slave_address) l_day = l_register_res.registers[0] l_i = l_i + 1 l_register_res = self.register_value_invalid_int( a_register_index + l_i, 1, a_slave_address) l_hour = l_register_res.registers[0] l_i = l_i + 1 l_register_res = self.register_value_invalid_int( a_register_index + l_i, 1, a_slave_address) l_min = l_register_res.registers[0] l_i = l_i + 1 l_register_res = self.register_value_invalid_int( a_register_index + l_i, 1, a_slave_address) l_sec = l_register_res.registers[0] l_i = l_i + 1 l_result = str(l_year) + str(l_month).zfill(2) + str(l_day).zfill( 2) + 'T' + str(l_hour).zfill(2) + str(l_min).zfill(2) + str( l_sec).zfill(2) + 'Z' self._logger.debug("register_values_pm5500_date_time->result:%s" % l_result) return l_result def register_values_float_32(self, a_register_index, _slave_address): """ from spec returns a float from given register index @a_register_index: a register index """ l_register_res = self.register_value_invalid_int( a_register_index, 2, a_slave_address) #self._logger.debug("register_values_u_long->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=self._byte_order, wordorder=self._word_order ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_32bit_float() if not instance(l_result, float): self._logger.error( "register_values_float_32-> result of decode_32bit_float is not a float but:'%s'" % l_result) l_result = 0 self._logger.debug("register_values_float_32->after decoder:%s" % l_result) return l_result def register_values_float_32_pf(self, a_register_index, _slave_address): """ from spec returns a float from given register index for power factor @a_register_index: a register index ********* FROM DOC *********************** Power factor values are specially encoded floating point values. Pseudo code to decode PF Value if (rigVal > 1) { PF_Val = 2 - regVal; PF is leading } else if (regVal < -1) { PF_Val = -2-regVal PF is leading } else if ( abs(regVal) equals 1 ) { PF_Val = regVal PF is at unity } else { PF_Val = regVal PF is lagging } """ l_register_res = self.register_value(a_register_index, 2, a_slave_address) #self._logger.debug("register_values_u_long->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=Endian.Big, wordorder=Endian.Big ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_32bit_float() if l_result > 1: l_result = 2 - l_result elif l_result < -1: l_result = -2 - l_result else: pass self._logger.debug("register_values_float_32_pf->after decoder:%s" % l_result) return l_result def register_values_string_8(self, a_register_index, _slave_address): """ from spec returns a string @a_register_index: a register index """ l_register_res = self.register_value(a_register_index, 8, a_slave_address) #self._logger.debug("register_values_u_long->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=Endian.Big, wordorder=Endian.Big ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_string(8) if len(l_result) > 0 and str(l_result[0]) == '0': l_result = '' else: l_result = l_result.decode('utf-8', errors='replace') #self._logger.debug("register_values_string16->after decoder:%s" % l_result) assert isinstance( l_result, str), 'result is no str but' + l_result.__class.__name__ return l_result def register_values_string_16(self, a_register_index, a_slave_address): """ from spec returns a word from given register index @a_register_index: a register index """ l_register_res = self.register_value(a_register_index, 16, a_slave_address) #self._logger.debug("register_values_u_long->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=Endian.Big, wordorder=Endian.Big ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_string(16) if len(l_result) > 0 and str(l_result[0]) == '0': l_result = '' else: l_result = l_result.decode('utf-8', errors='replace') #self._logger.debug("register_values_string16->after decoder:%s" % l_result) assert isinstance( l_result, str), 'result is no str but' + l_result.__class.__name__ return l_result def register_values_int_16_s(self, a_register_index, a_slave_address): """ from spec returns a word from given register index @a_register_index: a register index """ l_register_res = self.register_value(a_register_index, 1, a_slave_address) #self._logger.debug("register_values_16_s->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=Endian.Big, wordorder=Endian.Big ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_16bit_int() #self._logger.debug("register_values_u_word->after decoder:%s" % l_result) assert isinstance( l_result, int), 'result is no int but' + l_result.__class.__name__ return l_result def register_values_int_16_u(self, a_register_index, a_slave_address): """ from spec returns an int @a_register_index: a register index """ l_register_res = self.register_value(a_register_index, 1, a_slave_address) #self._logger.debug("register_values_int_16_u->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=Endian.Big, wordorder=Endian.Big ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_16bit_uint() #self._logger.debug("register_values_int16_u->after decoder:%s" % l_result) return l_result def register_values_int_32_u(self, a_register_index, a_slave_address): """ from spec returns a long from given register index @a_register_index: a register index """ l_register_res = self.register_value(a_register_index, 2, a_slave_address) #self._logger.debug("register_values_u_long->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=Endian.Big, wordorder=Endian.Big ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_32bit_uint() #self._logger.debug("register_values_int_32_u->after decoder:%s" % l_result) return l_result def register_values_int_32_s(self, a_register_index, a_slave_address): """ from spec returns a long from given register index @a_register_index: a register index """ l_register_res = self.register_value(a_register_index, 2, a_slave_address) #self._logger.debug("register_values_u_long->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=Endian.Big, wordorder=Endian.Big ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_32bit_int() #self._logger.debug("register_values_int_32_u->after decoder:%s" % l_result) return l_result def register_values_int_64_u(self, a_register_index, a_slave_address): """ from spec returns a long from given register index @a_register_index: a register index """ l_register_res = self.register_value(a_register_index, 4, a_slave_address) #self._logger.debug("register_values_u_long->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=Endian.Big, wordorder=Endian.Big ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_64bit_uint() #self._logger.debug("register_values_int_32_u->after decoder:%s" % l_result) return l_result def register_values_int_64_s(self, a_register_index, a_slave_address): """ from spec returns a long from given register index @a_register_index: a register index """ l_register_res = self.register_value(a_register_index, 4, a_slave_address) #self._logger.debug("register_values_u_long->before decoder:%s" % l_register_res.registers) decoder = BinaryPayloadDecoder.fromRegisters( l_register_res.registers, byteorder=Endian.Big, wordorder=Endian.Big ) #https://pymodbus.readthedocs.io/en/latest/source/example/modbus_payload.html #https://pymodbus.readthedocs.io/en/v1.3.2/library/payload.html?highlight=binarypayloaddecoder#pymodbus.payload.BinaryPayloadDecoder l_result = decoder.decode_64bit_int() #self._logger.debug("register_values_int_32_u->after decoder:%s" % l_result) return l_result # FILE OUTPUT def store_values_into_csv(self, an_ordered_dict, a_slave_address): """ Stores values into CSV into self.get_csv_file_path() @param a_row_dict: an OrderedDict """ assert isinstance(an_ordered_dict, OrderedDict), 'param is not an OrderedDict' assert isinstance(a_slave_address, int), 'param is not an int' try: l_f_name = self.csv_file_path(a_slave_address) l_file_exists = os.path.isfile(l_f_name) self._logger.info( "store_values_into_csv->Writting into file %s exists:%s" % (l_f_name, l_file_exists)) with open(l_f_name, mode='a+') as l_csv_file: l_csv_writter = csv.writer(l_csv_file, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL) if not l_file_exists: for l_header_row in self._header_rows(): self._logger.info( "store_values_into_csv->writting header:{}".format( l_header_row)) l_csv_writter.writerow(l_header_row) # Metadata and registers l_header_list = [] l_values_dict = [] l_values_dict.append( datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%SZ')) for l_reg in an_ordered_dict.values(): if l_reg.is_metadata: l_header_line = [] l_header_line.append('#' + l_reg.short_description) l_header_line.append(l_reg.value) if not l_file_exists: self._logger.info( "store_values_into_csv->Writting METADATA row: %s" % (';'.join(str(l) for l in l_header_line))) l_csv_writter.writerow(l_header_line) else: l_header_list.append(l_reg.short_description) l_val = l_reg.value l_values_dict.append(l_val) # Header l_header_list.insert(0, 'Timestamp') assert len(l_header_list) == len( l_values_dict ), 'header row {} doesnt have the same length as data row{}, hit file {}'.format( len(l_header_list), len(l_values_dict), l_f_name) if not l_file_exists: self._logger.info( "store_values_into_csv->Writting HEADER row: %s" % (';'.join(str(l) for l in l_header_list))) l_csv_writter.writerow(l_header_list) #Registers no metadata self._logger.info( "store_values_into_csv->HEADER (not written) row: %s" % ('|'.join(str(l) for l in l_header_list))) self._logger.info( "store_values_into_csv->Writting row: %s" % ('|'.join(str(l.value) for l in an_ordered_dict.values()))) l_csv_writter.writerow(l_values_dict) except Exception as l_e: self._logger.error('store_values_into_csv->Error: %s' % l_e) raise l_e def csv_file_path(self, a_slave_address): """ Returns the csv file path if test adds tests.csv at the end of file_name """ assert self._target_ip, "host ip is empty" assert isinstance(a_slave_address, int), "slave_address is not an int" assert self._args.host_mac, "host mac is empty" # if __debug__: # try: # socket.inet_aton(self._args.host_ip) # except socket.error as l_e: # assert False, "Host ip address is invalid" # raise l_e l_dir = self.DEFAULT_CSV_FILE_LOCATION + '/' + str( datetime.today().year) + '/' + '{:02d}'.format( datetime.today().month) l_result = (l_dir + '/' + datetime.today().strftime('%Y%m%d') + '_' + self._args.host_mac.replace(':', '-') + '_' + self._target_ip + '_' + str(a_slave_address) + '_' + os.path.basename(__file__) + '_' + self.__class__.__name__ + '.csv') if self._args.test: l_result += 'test.csv' try: os.makedirs(l_dir) except OSError as l_e: if l_e.errno == errno.EEXIST: pass else: self._logger.error('get_csv_file_path Error: %s' % (l_e)) raise l_e return l_result # LOW LEVEL FUNCTIONS READ def write_register_value(self, a_register_index, a_slave_address, a_value): """ Returns a given register value @a_register_index @a_register_length: 1 register is 16 bits (2 bytes = 1 word) @a_slave_address @a_value """ assert self.is_connected(), 'register_value->device is not connected' assert self.valid_slave_address( self._slave_address), 'register_value->Slave address is None' #self._logger.debug('register_value-> _substract_one_to_register_index:%s' % (self._substract_one_to_register_index)) if self._substract_one_to_register_index: l_register_index = a_register_index - 1 l_register_index_s_debug = str(a_register_index) + '-1' else: l_register_index = a_register_index l_register_index_s_debug = str(l_register_index) try: #Starting add, num of reg to read, slave unit. l_result = self._modbus_client.write_register( l_register_index, a_value, unit=a_slave_address) # Average current if l_result is not None: if (hasattr(l_result, 'function_code') and l_result.function_code < 0xFFFFFFFF): self._logger.debug( "register_value-> read register index:%s (%s) value:(%s) length:%s slave_address:%s" % (l_register_index, l_register_index_s_debug, '|'.join( str(l_elt) for l_elt in l_result.registers), a_register_length, self._slave_address)) #self._logger.debug(l_result) #self._logger.debug("register_value->register 0 value:%s" % l_result.getRegister(1)) #self._logger.debug("register_value-> 0 type:%s" % type(l_result.getRegister(0))) #self._logger.debug(l_result._str_()) else: self._logger.error( "register_value-> returned code is invalid: {}".format( l_result)) else: self._logger.error( "register_value-> No register received, l_result is None") if not hasattr(l_result, 'registers'): raise Exception( 'register_value-> read register has no registers attribute, reading register:' + str(l_register_index)) # TODO CORRECTME HERE return l_result except KeyboardInterrupt: self._logger.exception("register_value-> Keyboard interruption") except Exception as l_e: self._logger.exception( "register_value-> Exception occured, msg:%s" % l_e) raise l_e # CONNECTION def is_connected(self): """ Self explaining """ #self._logger.debug("is_connected-> %s, modbusclient:%s" % (self._is_connected, self._modbus_client)) return self._modbus_client is not None and self._is_connected def disconnect(self): """ Disconnects modbus client """ assert self.is_connected() assert self._modbus_client is not None try: self._modbus_client.close() self._is_connected = False self._logger.info('disconnect -> Disconnection success') except Exception as l_e: self._logger.exception("disconnect->Exception occured msg:" + l_e.message) raise l_e def init_arg_parse(self): """ Parsing arguments override add_arg_parse if necessary """ """App help""" self._parser = argparse.ArgumentParser( description=self.PARSER_DESCRIPTION) self.add_arg_parse() l_args = self._parser.parse_args() self._args = l_args def add_arg_parse(self): """ Adding arguments to parser """ # OPTIONALS self._parser.add_argument('-v', '--verbose', help='increase output verbosity', action="store_true") self._parser.add_argument( '-u', '--base_url', help='NOT_IMPLEMENTED:Gives the base URL for requests actions', nargs='?', default=self.DEFAULT_BASE_URL) self._parser.add_argument( '-s', '--store_values', help='Stores values into csv file located into ' + self.DEFAULT_CSV_FILE_LOCATION, action="store_true") self._parser.add_argument('-t', '--test', help='Runs test method', action="store_true") self._parser.add_argument( '-a', '--display_all', help='Displays all register after reading them', action='store_true') self._parser.add_argument( '-d', '--long', help= 'Displays all register after reading them, with long version and description', action='store_true') # REQUIRED 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) self.add_required_named(l_required_named) def add_required_named(self, a_required_named): a_required_named.add_argument('-c', '--slave_address', help='Slave address of modbus device', nargs='?', required=True) def execute_corresponding_args(self): """ Calls the corresponding function to given script argument """ assert isinstance(self._slave_address, list), 'self._slave_address is list' if self._args.verbose: self._logger.setLevel(logging.DEBUG) # self._console_handler.setLevel(logging.DEBUG) # self._file_handler.setLevel(logging.DEBUG) else: self._logger.setLevel(logging.DEBUG) # self._console_handler.setLevel(logging.ERROR) # self._file_handler.setLevel(logging.DEBUG) if self._args.base_url: self._logger.debug( "execute_corresponding_args->given argument was '%s'" % self._args.base_url) self._base_url = self._args.base_url if self._args.display_all or self._args.store_values or self._args.test: for l_slave in self._slave_address: assert self.valid_slave_address( l_slave ), 'given slave address should be int but' + str(l_slave) self.read_all_sit_modbus_registers(l_slave) if self._args.display_all(): l_long = False if hasattr(self._args, 'long'): l_long = self._args.long print(self.out_human_readable(l_long)) if self._args.store_values: self.store_values_into_csv(self._sit_modbus_registers, l_slave) if self._args.test: self.test() # EVENTS def call_sit_modbus_registers_events(self): l_index = 0 for l_short_desc, l_sit_reg in self._sit_modbus_registers.items(): if l_sit_reg.has_event(): self._logger.debug( 'call_sit_modbus_registers_events-> Calling event for register_short:{}' .format(l_sit_reg.out_short())) self._logger.debug( 'call_sit_modbus_registers_events-> counter:{}/{}'.format( l_index, len(self._sit_modbus_registers.items()))) l_sit_reg.call_event() #self._logger.debug('read_all_registers-> sit_register.out():%s' % (l_sit_reg.out())) l_index += 1 # VALIDATION @staticmethod def valid_slave_address_list(a_list): """ List """ assert isinstance(a_list, list), 'a_list is not a list' l_res = False if isinstance(a_list, list): for l_elt in a_list: l_res = SitModbusDevice.valid_slave_address(l_elt) if not l_res: break return l_res @staticmethod def valid_slave_address(an_int): l_res = False l_res = isinstance(an_int, int) if l_res: l_res = (an_int is not None) and an_int > 0 and an_int <= 253 return l_res def valid_ip(self, v): if v is None: return False else: try: socket.inet_aton(v) # legal return True except socket.error: # Not legal return False # OUTPUT def out_without_registers(self, a_sep='\n', an_item_prefix=''): l_res = '' l_res = l_res + an_item_prefix + 'port:' + str( self._target_port) + a_sep l_res = l_res + an_item_prefix + 'target_ip:' + self._target_ip + a_sep l_res = l_res + an_item_prefix + 'target_mode:' + self._target_mode + a_sep l_res = l_res + an_item_prefix + 'slave_address:' + str( self._slave_address) + a_sep l_res = l_res + an_item_prefix + 'client_connect_retries:' + str( self._client_connect_retries) + a_sep l_res = l_res + an_item_prefix + 'rtu_timeout:' + str( self._rtu_timeout) + a_sep l_res = l_res + an_item_prefix + 'rtu_stopbits:' + str( self._rtu_stopbits) + a_sep l_res = l_res + an_item_prefix + 'rtu_parity:' + self._rtu_parity + a_sep l_res = l_res + an_item_prefix + 'rtu_baudrate:' + str( self._rtu_baudrate) + a_sep l_res = l_res + an_item_prefix + 'byte_order:' + self._byte_order + a_sep l_res = l_res + an_item_prefix + 'word_order:' + self._word_order return l_res def out(self, a_sep='\n', an_item_prefix='\t', a_with_description=False): l_res = self.__class__.__name__ + a_sep l_res += self.out_without_registers(a_sep, an_item_prefix) + a_sep l_res += 'Registers:' + a_sep for l_short_desc, l_sit_reg in self._sit_modbus_registers.items(): l_res += l_sit_reg.out_human_readable( an_item_prefix, a_with_description=a_with_description) + a_sep return l_res def out_human_readable(self, a_with_description=False): l_res = '' l_res += self.out('\n', '\t', a_with_description) return l_res # INVARIANT def invariants(self): """ Raise exception if not complying """ assert self.valid_slave_address( self._slave_address), 'valid slave address' # TEST def test(self): """ Test method """ self._logger.info("Test method->")
class SitUtils(object): # CONSTANTS # VARIABLES _logger = None # SETTERS AND GETTERS @property def logger(self): return self._logger @logger.setter def logger(self, v): self._logger = v # INITIALIZE def __init__(self): """ Initialize """ try: #*** Logger self._logger = SitLogger().new_logger(__name__) except OSError as l_e: self._logger.warning("init-> OSError, probably rollingfileAppender:{}".format(l_e)) if e.errno != errno.ENOENT: raise l_e except Exception as l_e: self._logger.error('Error in init: {}'.format(l_e)) raise l_e #exit(1) # ARGUMENTS @staticmethod def args_to_list(some_args_as_string, a_target_type='int'): """ some_args_as_string as either 1-10 | 1,2,3 | 3 """ assert some_args_as_string is not None, 'some_args_as_string not None' l_res = [] if '-' in some_args_as_string: l_tmp = some_args_as_string.split('-') l_first = int(l_tmp[0]) l_last = int(l_tmp[1]) #print('args_to_list-> l_tmp:{} first:{} last:{}'.format(l_tmp, l_first, l_last)) for l_i in range(l_first, l_last + 1): l_res.append(l_i) #print('args_to_list-> l_i:{} first:{} last:{}'.format(l_i, l_first, l_last)) elif ',' in some_args_as_string: l_tmp = some_args_as_string.split(',') for l_i in l_tmp: l_res.append(int(l_i)) elif len(some_args_as_string) > 0: try: l_res.append(int(some_args_as_string)) except Exception as l_e: raise Exception('args_to_list->Unable to parse args data as int:{}'.format(some_args_as_string)) else: raise Exception('args_to_list->Unable to parse args data:{}'.format(some_args_as_string)) assert isinstance(l_res, list), 'l_res is not a list' #print('args_to_list-> l_res:{}'.format(l_res)) return l_res # COLLECTIONS @staticmethod def od_extend(an_ordered_dict, a_sit_modbus_register): assert isinstance(an_ordered_dict, OrderedDict), 'an_ordered_dict is an OrderedDict' assert isinstance(a_sit_modbus_register, SitModbusRegister), 'a_sit_modbus_register is an SitModbusRegister but {}'.format(a_sit_modbus_register.__class__.__name__) an_ordered_dict[a_sit_modbus_register.short_description] = a_sit_modbus_register # SYSTEM @staticmethod def send_mail(a_dest_list, a_subject, a_body_list, an_attach_list=None): """ Sends with mail command as system command """ assert len(a_dest_list) > 0, 'destinatory not empty' assert not '"' in a_subject, 'No " into subject' if an_attach_list is not None and len(an_attach_list) > 0: l_attachments = '' for l_attach in an_attach_list: l_attachments += ' -A ' + l_attach else: l_attachments = None l_dests = '' for l_dest in a_dest_list: l_dests += l_dest + ', ' if l_dest.endswith(', '): l_dest = l_dest[:-2] assert not l_dest.endswith(', ') #MULTILINE # l_body_echos = '' # for l_body_elt in a_body_list: # l_line = "echo '{}'".format(l_body_elt) + "\n" # l_line = l_line.decode('ascii') # l_body_echos += l_line #ONE LINE ONLY #l_cmd = '{{ {} }}|mail -s "{}" '.format(l_body_echos, a_subject) assert not '"' in a_body_list[0], 'No " into string' l_body_echos = 'echo "' + a_body_list[0] + '"' l_cmd = ' {} |mail -s "{}" '.format(l_body_echos, a_subject) if l_attachments is not None: l_cmd += l_attachments l_cmd += ' ' + l_dests l_return_code, l_stdout, l_stderr = SitUtils.system_call(l_cmd) if l_return_code != 0: # l_return_code = subprocess.call(l_cmd + l_email_to, shell=True) l_msg = 'send_email-> Could not send email, return_code:{},\n\tl_stdout:{} \n\tl_cmd=-{}-'.format(l_return_code, l_stderr, l_cmd) raise Exception(l_msg) #Otherwise file is never created @staticmethod def system_call(a_cmd): """ Returns return_code, stdout, stderr """ l_proc = subprocess.Popen(a_cmd, shell=True, stdout = subprocess.PIPE, stderr = subprocess.PIPE, ) stdout, stderr = l_proc.communicate() return l_proc.returncode, stdout, stderr # FORMATTING @staticmethod def format_number_human_readable(a_val, a_float_digits_count=2): """ """ assert isinstance(a_val, int) or isinstance(a_val, float), 'a_val is int or float, no its an {}'.format(a_val.__class__.__name__) if isinstance(a_val, int) or isinstance(a_val, float): if isinstance(a_val, float): l_fmt = '{:,.' + str(a_float_digits_count) + 'f}' else: l_fmt = '{:,}' l_res = l_fmt.format(a_val) l_res = l_res.replace(',', '\'') else: raise Exception('not implemented') #print('********************a_val:{}- l_res:{}- l_type:{}'.format(a_val, l_res, a_val.__class__.__name__)) return l_res # TEST def test(self): """ Test function """ try: self._logger.info("################# BEGIN #################") self._logger.info("--> ************* device models *************: {}".format(l_d.models)) #Lists properties to be loaded with l_d.<property>.read() and then access them except Exception as l_e: self._logger.exception("Exception occured: {}".format(l_e)) print('Error: {}'.format(l_e)) self._logger.error('Error: {}'.format(l_e)) sys.exit(1)
class ClusterController(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 cluster controller 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_cc_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 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 add_cc_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 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()