class ActuatorAdapterManagerTest(unittest.TestCase): """ This test case class contains very basic unit tests for ActuatorSimAdapterManager. It should not be considered complete, but serve as a starting point for the student implementing additional functionality within their Programming the IoT environment. """ @classmethod def setUpClass(self): logging.basicConfig( format='%(asctime)s:%(module)s:%(levelname)s:%(message)s', level=logging.DEBUG) logging.info("Testing ActuatorAdapterManager class...") self.configUtil = ConfigUtil() self.enableEmulator = self.configUtil.getBoolean( ConfigConst.CONSTRAINED_DEVICE, ConfigConst.ENABLE_EMULATOR_KEY) self.defaultMsgListener = DefaultDataMessageListener() self.actuatorAdapterMgr = ActuatorAdapterManager( useEmulator=self.enableEmulator) self.actuatorAdapterMgr.setDataMessageListener(self.defaultMsgListener) def setUp(self): pass def tearDown(self): pass def testHumidifierSimulation(self): ad = ActuatorData(actuatorType=ActuatorData.HUMIDIFIER_ACTUATOR_TYPE) ad.setValue(50.0) ad.setCommand(ActuatorData.COMMAND_ON) self.actuatorAdapterMgr.sendActuatorCommand(ad) ad.setCommand(ActuatorData.COMMAND_OFF) self.actuatorAdapterMgr.sendActuatorCommand(ad) def testHvacSimulation(self): ad = ActuatorData(actuatorType=ActuatorData.HVAC_ACTUATOR_TYPE) ad.setValue(22.5) ad.setCommand(ActuatorData.COMMAND_ON) self.actuatorAdapterMgr.sendActuatorCommand(ad) ad.setCommand(ActuatorData.COMMAND_OFF) self.actuatorAdapterMgr.sendActuatorCommand(ad) def testLEDSimulation(self): ad = ActuatorData(actuatorType=ActuatorData.LED_DISPLAY_ACTUATOR_TYPE) ad.setStateData(stateData="Test stateData") ad.setCommand(ActuatorData.COMMAND_ON) self.actuatorAdapterMgr.sendActuatorCommand(ad) ad.setCommand(ActuatorData.COMMAND_OFF) self.actuatorAdapterMgr.sendActuatorCommand(ad)
class ActuatorEmulatorManagerTest(unittest.TestCase): """ This test case class contains very basic unit tests for ActuatorSimAdapterManager. It should not be considered complete, but serve as a starting point for the student implementing additional functionality within their Programming the IoT environment. NOTE: This test requires the sense_emu_gui to be running and must have access to the underlying libraries that support the pisense module. On Windows, one way to do this is by installing pisense and sense-emu within the Bash on Ubuntu on Windows environment and then execute this test case from the command line, as it will likely fail if run within an IDE in native Windows. """ @classmethod def setUpClass(self): logging.basicConfig( format='%(asctime)s:%(module)s:%(levelname)s:%(message)s', level=logging.DEBUG) logging.info( "Testing ActuatorAdapterManager class [using SenseHAT emulator]..." ) self.defaultMsgListener = DefaultDataMessageListener() self.actuatorAdapterMgr = ActuatorAdapterManager(useEmulator=True) self.actuatorAdapterMgr.setDataMessageListener(self.defaultMsgListener) def setUp(self): pass def tearDown(self): pass def testHumidifierEmulation(self): ad = ActuatorData(actuatorType=ConfigConst.HUMIDIFIER_ACTUATOR_TYPE) ad.setValue(50.0) ad.setCommand(ConfigConst.COMMAND_ON) self.actuatorAdapterMgr.sendActuatorCommand(ad) ad.setCommand(ConfigConst.COMMAND_OFF) self.actuatorAdapterMgr.sendActuatorCommand(ad) def testHvacEmulation(self): ad = ActuatorData(actuatorType=ConfigConst.HVAC_ACTUATOR_TYPE) ad.setValue(22.5) ad.setCommand(ConfigConst.COMMAND_ON) self.actuatorAdapterMgr.sendActuatorCommand(ad) ad.setCommand(ConfigConst.COMMAND_OFF) self.actuatorAdapterMgr.sendActuatorCommand(ad) def testLedDisplayEmulation(self): ad = ActuatorData(actuatorType=ConfigConst.LED_DISPLAY_ACTUATOR_TYPE) ad.setCommand(ConfigConst.COMMAND_ON) ad.setStateData("What's up?") self.actuatorAdapterMgr.sendActuatorCommand(ad) ad.setCommand(ConfigConst.COMMAND_OFF) self.actuatorAdapterMgr.sendActuatorCommand(ad)
class DeviceDataManager(IDataMessageListener): """ Shell representation of class for student implementation. """ def __init__(self, enableMqtt: bool = True, enableCoap: bool = False): """ Initialization of class. Create an instance of DeviceDataManager """ self.configUtil = ConfigUtil() self.enableEmulator = self.configUtil.getBoolean( ConfigConst.CONSTRAINED_DEVICE, ConfigConst.ENABLE_EMULATOR_KEY) self.enableRedis = False self.sysPerfManager = SystemPerformanceManager() self.sysPerfManager.setDataMessageListener(self) self.sensorAdapterManager = SensorAdapterManager( useEmulator=self.enableEmulator) self.sensorAdapterManager.setDataMessageListener(self) self.actuatorAdapterManager = ActuatorAdapterManager( useEmulator=self.enableEmulator) self.actuatorAdapterManager.setDataMessageListener(self) ##add by miaoyao @10/30/2020 if self.enableRedis: self.redisClient = RedisPersistenceAdapter() self.enableHandleTempChangeOnDevice = self.configUtil.getBoolean( ConfigConst.CONSTRAINED_DEVICE, ConfigConst.ENABLE_HANDLE_TEMP_CHANGE_ON_DEVICE_KEY) self.triggerHvacTempFloor = self.configUtil.getFloat( ConfigConst.CONSTRAINED_DEVICE, ConfigConst.TRIGGER_HVAC_TEMP_FLOOR_KEY) self.triggerHvacTempCeiling = self.configUtil.getFloat( ConfigConst.CONSTRAINED_DEVICE, ConfigConst.TRIGGER_HVAC_TEMP_CEILING_KEY) ##add by miaoyao for final project self.enableHandleSoilHumidityChangeOnDevice = self.configUtil.getBoolean( ConfigConst.CONSTRAINED_DEVICE, ConfigConst.ENABLE_HANDLE_SOIL_HUMIDITY_CHANGE_ON_DEVICE_KEY) self.triggerWaterDeviceHumiFloor = self.configUtil.getFloat( ConfigConst.CONSTRAINED_DEVICE, ConfigConst.TRIGGER_WATER_SOIL_HUMI_FLOOR_KEY) self.triggerWaterDeviceHumiCeiling = self.configUtil.getFloat( ConfigConst.CONSTRAINED_DEVICE, ConfigConst.TRIGGER_WATER_SOIL_HUMI_CEILING_KEY) ##add by miaoyao @11/02/2020 ##self.enableMqtt = self.configUtil.getBoolean(ConfigConst.CONSTRAINED_DEVICE, ConfigConst.ENABLE_MQTT_KEY) self.enableMqtt = enableMqtt self.enableCoap = enableCoap if self.enableMqtt: self.mqttClient = MqttClientConnector() self.mqttClient.setDataMessageListener(self) if self.enableCoap: self.coapClient = CoapClientConnector() self.coapClient.setDataMessageListener(self) def handleActuatorCommandResponse(self, data: ActuatorData) -> bool: """ handle the ActuatorCommandResponse @return bool """ # Use the DataUtil class to convert the ActuatorData to JSON. logging.info( "[CDA_CALLBACK]----->>>The handleActuatorCommandResponse method is being called" ) adJson = DataUtil.actuatorDataToJson(self, data) self._handleUpstreamTransmission( ResourceNameEnum.CDA_ACTUATOR_RESPONSE_RESOURCE, adJson) def handleActuatorCommandMessage(self, data: ActuatorData) -> bool: """ handle the handleActuatorCommandMessage @return bool """ logging.info( "[CDA_CALLBACK]----->>>The handleActuatorCommandMessage method is being called" ) if data: logging.info("Processing actuator command message.") # TODO: add further validation before sending the command self.actuatorAdapterManager.sendActuatorCommand(data) return True else: logging.warning( "Received invalid ActuatorData command message. Ignoring.") return False def handleIncomingMessage(self, resourceEnum: ResourceNameEnum, msg: str) -> bool: """ handle the IncomingMessage @return bool """ logging.info( "[CDA_CALLBACK]----->>>The handleIncomingMessage method is being called" ) # Use the DataUtil class to convert the msg content (which should be JSON) to an ActuatorData instance ad = DataUtil.jsonToActuatorData(self, msg) self._handleIncomingDataAnalysis(msg) def handleSensorMessage(self, data: SensorData) -> bool: """ handle the SensorMessage @return bool """ logging.info( "[CDA_CALLBACK]----->>>The handleSensorMessage method is being called" ) # Use the DataUtil class to convert the SensorData to JSON sdJosn = DataUtil.sensorDataToJson(self, data) self._handleUpstreamTransmission( ResourceNameEnum.CDA_SENSOR_MSG_RESOURCE, sdJosn) self._handleSensorDataAnalysis(data) if self.enableRedis: self.redisClient.storeData( ResourceNameEnum.CDA_SENSOR_MSG_RESOURCE, data) def handleSystemPerformanceMessage(self, data: SystemPerformanceData) -> bool: """ handle the SystemPerformanceMessage @return bool """ logging.info( "[CDA_CALLBACK]----->>>The handleSystemPerformanceMessage method is being called" ) spmJson = DataUtil.systemPerformanceDataToJson(self, data) self._handleUpstreamTransmission( ResourceNameEnum.CDA_SYSTEM_PERF_MSG_RESOURCE, spmJson) def startManager(self): """ Start the DeviceDataManager. Calls startManager() on the sysPerfManager instance. Calls startManager() on the sensorAdapterManager instance. """ logging.info("----->>>The DeviceDataManager will be started") self.sysPerfManager.startManager() self.sensorAdapterManager.startManager() if self.enableRedis: self.redisClient.connectClient() if self.enableMqtt: self.mqttClient.connectClient() def stopManager(self): """ Stop the DeviceDataManager. Calls stopManager() on the sysPerfManager instance. Calls stopManager() on the sensorAdapterManager instance. """ self.sysPerfManager.stopManager() self.sensorAdapterManager.stopManager() if self.enableRedis: self.redisClient.disconnectClient() if self.enableMqtt: self.mqttClient.disconnectClient() logging.info("----->>>The DeviceDataManager stopped") def _handleIncomingDataAnalysis(self, msg: str): """ Call this from handleIncomeMessage() to determine if there's any action to take on the message. Steps to take: 1) Validate msg: Most will be ActuatorData, but you may pass other info as well. 2) Convert msg: Use DataUtil to convert if appropriate. 3) Act on msg: Determine what - if any - action is required, and execute. """ logging.info( "[CDA_CALLBACK]----->>>The _handleIncomingDataAnalysis method is being called" ) ad = DataUtil.jsonToActuatorData(self, msg) self.actuatorAdapterManager.sendActuatorCommand(ad) def _handleSensorDataAnalysis(self, data: SensorData): """ Call this from handleSensorMessage() to determine if there's any action to take on the message. Steps to take: 1) Check config: Is there a rule or flag that requires immediate processing of data? 2) Act on data: If # 1 is true, determine what - if any - action is required, and execute. """ logging.info( "[CDA_CALLBACK]----->>>The _handleSensorDataAnalysis method is being called" ) """ """ if self.enableHandleTempChangeOnDevice and data.getSensorType( ) == SensorData.TEMP_SENSOR_TYPE: ad = ActuatorData(actuatorType=ActuatorData.HVAC_ACTUATOR_TYPE) value = data.getValue() if value >= self.triggerHvacTempFloor and value <= self.triggerHvacTempCeiling: ad.setCommand(ActuatorData.COMMAND_OFF) else: ad.setCommand(ActuatorData.COMMAND_ON) self.actuatorAdapterManager.sendActuatorCommand(ad) """ """ if self.enableHandleSoilHumidityChangeOnDevice and data.getSensorType( ) == SensorData.SOIL_HUMIDITY_SENSOR_TYPE: ad = ActuatorData( actuatorType=ActuatorData.SPRINKLER_ACTUATOR_TYPE) value = data.getValue() if value >= self.triggerWaterDeviceHumiCeiling: ad.setCommand(ActuatorData.COMMAND_OFF) self.actuatorAdapterManager.sendActuatorCommand(ad) elif value <= self.triggerWaterDeviceHumiFloor: ad.setCommand(ActuatorData.COMMAND_ON) self.actuatorAdapterManager.sendActuatorCommand(ad) self.coapClient.sendGetRequest( ResourceNameEnum.CDA_ACTUATOR_CMD_RESOURCE, False, 5) else: self.coapClient.sendGetRequest( ResourceNameEnum.CDA_CLOUD_ACTUATOR_CMD_RESOURCE, False, 5) def _handleUpstreamTransmission(self, resourceName: ResourceNameEnum, msg: str): """ Call this from handleActuatorCommandResponse(), handlesensorMessage(), and handleSystemPerformanceMessage() to determine if the message should be sent upstream. Steps to take: 1) Check connection: Is there a client connection configured (and valid) to a remote MQTT or CoAP server? 2) Act on msg: If # 1 is true, send message upstream using one (or both) client connections. """ logging.info( "[callback]----->>>The _handleUpstreamTransmission method is being called" ) if self.enableMqtt: self.mqttClient.publishMessage(resourceName, msg) """