def setUpClass(self): point_temperature_1 = PointAnalog( description="Temperature reading 1", u_of_m="ºC", hmi_writeable=False, update_period=1.0, ) point_temperature_1.value = 5.0 PointManager().add_to_database(name="temp_1", obj=point_temperature_1) point_temperature_2 = PointAnalog( description="Temperature reading 2", u_of_m="ºC", hmi_writeable=False, update_period=1.0, ) PointManager().add_to_database(name="temp_2", obj=point_temperature_1) point_temperature_2.value = 5.1 self.point = PointAnalogDual( description="Temperature reading", point_1=point_temperature_1, point_2=point_temperature_2, ) PointManager().add_to_database(name="temp", obj=self.point)
def test_yaml_pickle(self): s = PointManager().dump_database_to_yaml() # print (f"YAML:\n {s}") PointManager().clear_database PointManager().load_points_from_yaml_string(s) PointManager().find_point(test_process_point_name)
def test_unscaling(self): point_analog = \ PointManager().find_point("setpoint").readonly_object point_scaled = \ PointManager().find_point("setpoint_scaled").readwrite_object point_scaled.value = 44.0 self.assertEqual(point_analog.value, 87.0)
def test_scaling(self): point_analog = \ PointManager().find_point("temperature_source").readwrite_object point_scaled = \ PointManager().find_point("test_scaled_point").readonly_object point_analog.value = 44.0 print(point_scaled.value) self.assertEqual(point_scaled.value, 22.5)
def test_yaml_pickle(self): point = PointManager().find_point("test_scaled_point") s = PointManager().dump_database_to_yaml() print(f"YAML:\n {s}") PointManager().clear_database PointManager().load_points_from_yaml_string(s) unpickled_point = PointManager().find_point("test_scaled_point") self.assertEqual(point.scaling, unpickled_point.scaling) self.assertEqual(point.offset, unpickled_point.offset)
def test_cmd_05_fail(modbus_server): ''' Test force COIL command. Force COIL 4 and verify that the server returns that the command is invalid. ''' pd1 = PointManager().get_point_test('point_discrete_1')
def modbus_server(): yml = ruamel.yaml.YAML(typ='safe', pure=True) yml.default_flow_style = False yml.indent(sequence=4, offset=2) cfg = None filename = os.path.join( os.path.dirname(__file__), './server_test_logic.yaml', ) with open(filename, 'r') as ymlfile: cfg = yml.load(ymlfile) logger = 'testbench' server = ModbusServer( name="Testbench Modbus Server", logger=logger, ) filename = os.path.join( os.path.dirname(__file__), "./test_points.yaml", ) PointManager().load_points_from_yaml_file(filename) parameters = { 'endpoint_address': 'localhost', 'port': 10587, 'socket_timeout': 100, } PointManager().assign_parameters( data = parameters, target = server, ) PointManager().assign_points( data = cfg, point_handler = server, target_name = 'Modbus Server', interruptable = server, ) return server
def config(self, config: dict): self.logger.debug("Entering function") for device in config: self.logger.info("I2C: attempting to import " + device) imported_module = import_module(config[device]["module"], config[device]["package"]) for i in dir(imported_module): attribute = getattr(imported_module, i) if inspect.isclass(attribute) \ and issubclass(attribute, i2cPrototype) \ and attribute != i2cPrototype: device_instance = attribute( name=device, logger=config[device]["logger"], bus=int(self.bus)) # Assign points to the module. PointManager().assign_points( data=config[device], point_handler=device_instance, target_name=device, interruptable=self, ) # Populate module parameters. PointManager().assign_parameters( target=device_instance, data=config[device], ) device_instance.config() self.devices.append(device_instance) for d in self.devices: for p in d.interrupt_points: p.add_observer(self.name, self) self.current_read_device = self.devices[0]
def test_a_yaml_pickle(self): s = PointManager().dump_database_to_yaml() PointManager().clear_database() print(f"YAML:\n {s}") PointManager().load_points_from_yaml_string(s) unpickled_point = PointManager().find_point("pump_run") self.assertEqual( self.point.description, unpickled_point.description, ) self.assertEqual( self.point.requestable, unpickled_point.requestable, ) self.assertEqual( self.point.retentive, unpickled_point.retentive, ) self.assertEqual( self.point.hmi_writeable, unpickled_point.hmi_writeable, ) self.assertEqual( self.point.update_period, unpickled_point.update_period, ) self.assertEqual(self.point.on_state_description, unpickled_point.on_state_description) self.assertEqual(self.point.off_state_description, unpickled_point.off_state_description) self.assertEqual( self.point.value, unpickled_point.value, )
def test_yaml_pickle(self): s = PointManager().dump_database_to_yaml() PointManager().clear_database PointManager().load_points_from_yaml_string(s) unpickled_point = PointManager().find_point("temp") self.assertEqual( self.point, unpickled_point, ) self.assertEqual( self.point._point_1, unpickled_point._point_1, ) self.assertEqual( self.point._point_2, unpickled_point._point_2, )
def setUpClass(self): # Build the PointAnalog self.point_analog = PointAnalog( description="Temperature reading", u_of_m="ºC", hmi_writeable=False, retentive=True, update_period=1.0, ) PointManager().add_to_database( name="temp_1", obj=self.point_analog, ) # Build the PointDiscrete self.point_discrete = PointDiscrete( description="run cooling", hmi_writeable=False, ) PointManager().add_to_database( name="run_cooling", obj=self.point_discrete, ) # Build the PointEnumeration self.point_enumeration = PointEnumeration( description="System mode", states=["Hand", "Off", "Auto"], hmi_writeable=False, ) PointManager().add_to_database( name="system_mode", obj=self.point_enumeration, )
def setUpClass(self): self.point = PointDiscrete( description="Pump run", on_state_description="Running", off_state_description="Stopped", hmi_writeable=True, requestable=True, retentive=True, update_period=20.0, ) self.point.value = True PointManager().add_to_database( name="pump_run", obj=self.point, )
def setUpClass(self): point = PointAnalog( description="Temperature reading 1 (pressure sensor)", u_of_m="ºC", update_period=1.2, ) PointManager.add_to_database( name="temperature_source", obj=point, ) point = PointAnalogScaled( scaling=2.0, offset=-1.0, point=point, readonly=True, ) PointManager.add_to_database( name="test_scaled_point", obj=point, ) point = PointAnalog( description="Temperature Setpoint", u_of_m="ºC", update_period=None, ) PointManager.add_to_database( name="setpoint", obj=point, ) point = PointAnalogScaled( scaling=2.0, offset=-1.0, point=point, readonly=False, ) PointManager.add_to_database( name="setpoint_scaled", obj=point, )
def test_cmd_01(modbus_server): ''' Test read COIL status command. Read 3 coils, starting at address 3. Note that only coils 3 and 5 are populated. The routine should return a 0 bit for the unpopulated coil. ''' pd1 = PointManager().get_point_test('point_discrete_1') pd2 = PointManager().get_point_test('point_discrete_2') pd1.value = True pd2.value = True data_array = [] # MBAP header # transaction ID = 0x12 0x34 # protocol identifier = 0x00 0x00 # length = 0x00 0x06 # Unit identifier = 01 data_array = [0x12, 0x34, 0x00, 0x00, 0x00, 0x06, 0x01] # MBAP header # read 3 coils starting at address 03 # command = 0x01 # starting address = 0x00 0x03 # coils to read = 0x00 0x03 data_array += [0x01, 0x00, 0x03, 0x00, 0x03] data = bytearray(data_array) del data_array return_data = modbus_server.process_request(data) del data # return command 1, one byte, both discretes on expected_responce = [0x12, 0x34, 0x00, 0x00, 0x00, 0x04, 0x01] # MBAP expected_responce += [0x01, 0x01, 0x05] expected_responce = bytearray(expected_responce) assert return_data == expected_responce
def test_json_pickle(self): point = PointManager().find_point("setpoint_scaled").readwrite_object pickle_text = jsonpickle.encode(point) unpickled_point = jsonpickle.decode(pickle_text) self.assertEqual(point.scaling, unpickled_point.scaling) self.assertEqual(point.offset, unpickled_point.offset)
def test_cmd_05(modbus_server): ''' Test force COIL command. Force COIL 3 and verify that 3 changes and 5 doesn't. ''' pd1 = PointManager().get_point_test('point_discrete_1')
def __init__(self, logic_yaml_files: 'List[str]', point_database_yaml_files: 'List[str]') -> 'None': yml = ruamel.yaml.YAML(typ='safe', pure=True) yml.default_flow_style = False yml.indent(sequence=4, offset=2) # open the supplied logic yaml file. for file in logic_yaml_files: with open(file, 'r') as ymlfile: cfg = yml.load(ymlfile) # Import all the loggers section = "loggers" for logger in cfg[section]: # self.logger.info("attempting to create logger: " + logger) # setup the device logger logger_obj = logging.getLogger(logger) logger_obj.setLevel(cfg[section][logger]['level']) fh = RotatingFileHandler( filename=cfg[section][logger]['file'], maxBytes=cfg[section][logger]['maxBytes'], backupCount=cfg[section][logger]['backupCount'], encoding=None, delay=False, ) fh.setFormatter(formatter) logger_obj.addHandler(fh) self.logger = logging.getLogger('supervisory') assert self.logger is not None, \ "The logger didn't assign properly" PointManager.logger = self.logger # load the point database(s). for file in point_database_yaml_files: self.logger.info(f"loading file: {file}") PointManager().load_points_from_yaml_file(file) # Setup the alarm notifiers. section = "AlarmNotifiers" for notifier in cfg[section]: self.logger.info(f"attempting import AlarmNotifier {notifier}") imported_module = import_module(cfg[section][notifier]["module"], cfg[section][notifier]["package"]) assert 'logger' in cfg[section][notifier], \ f"No logger entry defined for {notifier}" logger = cfg[section][notifier]["logger"] for i in dir(imported_module): attribute = getattr(imported_module, i) # Search the file for an AlarmNotifier object. if inspect.isclass(attribute) \ and issubclass(attribute, AlarmNotifier) \ and attribute != AlarmNotifier: concrete_notifier = attribute(name=notifier, logger=logger) self.logger.info( f"adding {imported_module.__name__} {attribute} to " "alarm notifiers list") Alarm.alarm_notifiers.append(concrete_notifier) # Populate module assign_parameters PointManager().assign_parameters( data=cfg[section][notifier], target=concrete_notifier, ) self.logger.info("Starting Supervisor") # Create the signleton alarm handler thread. # The alarm handler is not an option. self.alarm_handler = AlarmHandler( name="alarm processer", logger="supervisory", ) Alarm.alarm_handler = self.alarm_handler self.threads.append(self.alarm_handler) # Create all of the custom threads. section = 'SupervisedThreads' for thread_name in cfg[section]: self.logger.info( f"attempting to import {cfg[section][thread_name]['module']}") imported_module = import_module( cfg[section][thread_name]["module"], cfg[section][thread_name]["package"]) for i in dir(imported_module): attribute = getattr(imported_module, i) # Search the file for the SupervisedThread object. if inspect.isclass(attribute) \ and issubclass(attribute, SupervisedThread) \ and attribute != SupervisedThread: # Everything looks valid, # import the instansiate the module. supervised_thread = attribute( name=thread_name, logger=cfg[section][thread_name]["logger"]) self.logger.info(f"added {thread_name} {attribute}") # Populate module points PointManager().assign_points( data=cfg[section][thread_name], point_handler=supervised_thread, target_name=thread_name, interruptable=supervised_thread, ) # Populate module assign_parameters PointManager().assign_parameters(cfg[section][thread_name], supervised_thread) # Now that the points and parameters are assigned. Run any # remaining configuration # config is free form specific to the device where required. if "config" in cfg[section][thread_name]: config = cfg[section][thread_name]['config'] else: config = None supervised_thread.config(config=config) # Append the fully built module to the threads dict. self.threads.append(supervised_thread) # Fire up all the threads. for thread in self.threads: self.logger.info(f"starting: {thread.name}") thread.start() # Start the point database server rpc_object = RpcServer() rpc_object.active_alarm_list = self.alarm_handler.active_alarm_list rpc_object.global_alarm_list = PointManager().global_alarms() rpc_object.point_dict = PointManager().global_points() rpc_object.thread_list = self.threads rpc_object.get_hmi_point = PointManager().get_hmi_point self.rpc_server = ThreadedServer( rpc_object, port=18861, logger=self.logger, protocol_config={"allow_public_attrs": True}) self.rpc_server_thread = threading.Thread(target=self.rpc_server.start) self.rpc_server_thread.start() self.logger.info("Completed Supervisor setup")
def setUpClass(self): point_pump_water_pressure = PointAnalog( description="Pump water pressure", u_of_m="psi", hmi_writeable=False, update_period=0.333333, value=123.45, ) # PointManager.add_to_database( # name = "pump_water_pressure", # obj = point_pump_water_pressure, # ) # point_pump_water_pressure.value = 123.45 point_pump_on_water_pressure = PointAnalog( description="Pump water pressure on setpoint", u_of_m="psi", hmi_writeable=True, value=115.0, ) # PointManager.add_to_database( # name = "pump_on_water_pressure", # obj = point_pump_on_water_pressure, # ) point_pump_off_water_pressure = PointAnalog( description="Pump water pressure off setpoint", u_of_m="psi", hmi_writeable=True, value=125.0, ) # PointManager.add_to_database( # name = "pump_off_water_pressure", # obj = point_pump_off_water_pressure, # ) alarm_h1_pump_water_pressure = AlarmAnalog( description="Pump pressure H1", alarm_value=128.0, on_delay=1.0, off_delay=1.0, hysteresis=1.0, high_low_limit="HIGH", consequences="The pump has been temporarily shut off", more_info= "The pressure setpoint of the system has probably been set beyond " "the H1 level. The H1 level provides a safety mechanism and " "prevents the pump from overpressuring the system.", ) # PointManager.add_to_database( # name = "h1_pump_water_pressure", # obj = alarm_h1_pump_water_pressure, # ) alarm_h2_pump_water_pressure = AlarmAnalog( description="Pump pressure H2", alarm_value=132.0, on_delay=0.0, off_delay=1.0, hysteresis=1.0, high_low_limit="HIGH", consequences= "The system will attempt to bleed all pressure. The charge and " "relief valves will be opened until this alarm is acknowledged.", more_info= "The pump relay is likely stuck closed. The system pressure will " "be bled to prevent overpressure and prevent thermal failure of the" "pump by allowing water to circulate.", ) # PointManager.add_to_database( # name = "h2_pump_water_pressure", # obj = alarm_h2_pump_water_pressure, # ) alarm_l1_pump_water_pressure = AlarmAnalog( description="Pump pressure L1", alarm_value=113.0, on_delay=2.0, off_delay=1.0, hysteresis=1.0, high_low_limit="LOW", consequences="Feed system is will not satisfy demand", more_info="The pump may have failed or a leak may have developed on " "the high pressure side", ) # PointManager.add_to_database( # name = "l1_pump_water_pressure", # obj = alarm_l1_pump_water_pressure, # ) alarm_l2_pump_water_pressure = AlarmAnalog( description="Pump pressure L2", alarm_value=40.0, on_delay=5.0, off_delay=5.0, hysteresis=1.0, high_low_limit="LOW", consequences="System will be shut down.", more_info="The pump may have failed or a leak may have developed on " "the high pressure side", ) # PointManager.add_to_database( # name = "l2_pump_water_pressure", # obj = alarm_l2_pump_water_pressure, # ) # Logic driven pump related alarms. alarm_pump_runtime_fault = Alarm( description="Pump runtime exceeded", on_delay=60.0, off_delay=1.0, consequences="The pump has been locked out until this alarm is " "acknowledged", more_info="The following may be happened: pump may be run dry, " "failing, or a leak may have developed.", ) PointManager.add_to_database( name="pump_runtime_fault", obj=alarm_pump_runtime_fault, ) # Active pressure decay constant. Measures how fast the pressure in the # accumlator tank drops when the valves are open. Too much indicates a # leak, not enough and you've got a clog. point_active_pressure_decay = PointAnalog( description="Active water pressure decay constant", u_of_m="%", hmi_writeable=False, ) # PointManager.add_to_database( # name = "active_pressure_decay", # obj = point_active_pressure_decay, # ) alarm_h1_active_pressure_decay = AlarmAnalog( description="Pressure decay high - misting clog detected", alarm_value=0.98, hysteresis=0.01, on_delay=180, off_delay=180, high_low_limit="HIGH", consequences="None", more_info= "The system has detected that the pressure accumulator is " + "not draing at the proper rate.", ) # PointManager.add_to_database( # name = "active_pressure_decay", # obj = alarm_h1_active_pressure_decay, # ) alarm_l1_active_pressure_decay = AlarmAnalog( description="Pressure decay low - misting leak", alarm_value=0.95, hysteresis=0.01, on_delay=180, off_delay=180, high_low_limit="LOW", consequences="None", more_info="The system has detected that the pressure accumulator is " "not draining at the proper rate.") # PointManager.add_to_database( # name = "l1_active_pressure_decay", # obj = alarm_l1_active_pressure_decay, # ) process_active_pressure_decay = \ ProcessValue(point_active_pressure_decay) process_active_pressure_decay.high_display_limit = 1.0 process_active_pressure_decay.low_display_limit = 0.0 process_active_pressure_decay.add_alarm( "H1", alarm_h1_active_pressure_decay) process_active_pressure_decay.add_alarm( "L1", alarm_l1_active_pressure_decay) # Static pressure decay constant, Measure how much the system leaks when # the pump is off and valves are closed. Too much decay and there's a # leak. point_static_pressure_decay = PointAnalog( description="Static water pressure decay constant", u_of_m="%", hmi_writeable=False, ) # PointManager.add_to_database( # name = "static_pressure_decay", # obj = point_static_pressure_decay, # ) alarm_l1_static_pressure_decay = AlarmAnalog( description="Pressure decay low - accumulator leak", alarm_value=0.99, hysteresis=0.01, on_delay=180, off_delay=180, high_low_limit="LOW", ) # PointManager.add_to_database( # name = "l1_static_pressure_decay", # obj = alarm_l1_static_pressure_decay, # ) process_static_pressure_decay = ProcessValue( point_static_pressure_decay) process_static_pressure_decay.high_display_limit = 1.0 process_static_pressure_decay.low_display_limit = 0.0 process_static_pressure_decay.add_alarm( "L1", alarm_l1_static_pressure_decay) # PointManager.add_to_database( # name = "static_pressure_decay", # obj = process_static_pressure_decay, # ) point_run_pump = PointDiscrete( description="Pump", on_state_description="On", off_state_description="Off", hmi_writeable=False, ) # PointManager.add_to_database( # name = "run_pump", # obj = point_run_pump, # ) # Pump ProcessValue assembly. self.point = ProcessValue(point_pump_water_pressure) self.point.high_display_limit = 135.0 self.point.low_display_limit = 110.0 self.point.add_control_point("cut_in", point_pump_on_water_pressure) self.point.add_control_point("cut_out", point_pump_off_water_pressure) self.point.add_related_point("run", point_run_pump) self.point.add_related_point( "decay_static", process_static_pressure_decay, ) self.point.add_related_point( "decay_active", process_active_pressure_decay, ) self.point.add_alarm("H1", alarm_h1_pump_water_pressure) self.point.add_alarm("H2", alarm_h2_pump_water_pressure) self.point.add_alarm("L1", alarm_l1_pump_water_pressure) self.point.add_alarm("L2", alarm_l2_pump_water_pressure) PointManager.add_to_database( name=test_process_point_name, obj=self.point, )