class MqttClientConnectorTest(unittest.TestCase): """ This test case class contains very basic unit tests for MqttClientConnector. 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.info("Testing MqttClientConnector class...") self.cfg = ConfigUtil() self.mcc = MqttClientConnector(clientID = 'CDAMqttClientConnectorTest001') def setUp(self): pass def tearDown(self): pass # @unittest.skip("Ignore for now.") def testConnectAndDisconnect(self): delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) self.mcc.connect() sleep(delay + 5) self.mcc.disconnect() # @unittest.skip("Ignore for now.") def testConnectAndPublish(self): qos = 1 delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) listener = DefaultDataMessageListener() self.mcc.setDataMessageListener(listener) self.mcc.connect() self.mcc.subscribeToTopic(ResourceNameEnum.CDA_MGMT_STATUS_MSG_RESOURCE, qos) sleep(5) self.mcc.publishMessage(ResourceNameEnum.CDA_MGMT_STATUS_MSG_RESOURCE, "TEST: This is the CDA message payload.", qos) sleep(5) self.mcc.unsubscribeFromTopic(ResourceNameEnum.CDA_MGMT_STATUS_MSG_RESOURCE) sleep(5) sleep(delay) self.mcc.disconnect() def testActuatorCmdPubSub(self): qos = 1 # NOTE: delay can be anything you'd like - the sleep() calls are simply to slow things down a bit for observation delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) actuatorData = ActuatorData() payload = DataUtil().actuatorDataToJson(actuatorData) self.mcc.connect() sleep(5) self.mcc.publishMessage(resource=ResourceNameEnum.CDA_ACTUATOR_CMD_RESOURCE, msg=payload, qos=qos) sleep(delay) self.mcc.disconnect() @unittest.skip("Ignore for now.") def testIntegrateWithGdaSubscribeCdaCmdTopic(self): qos = 1 delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) listener = DefaultDataMessageListener() self.mcc.connect() self.mcc.subscribeToTopic(ResourceNameEnum.CDA_MGMT_STATUS_CMD_RESOURCE, qos) sleep(delay) self.mcc.disconnect() @unittest.skip("Ignore for now.") def testIntegrateWithGdaPublishCdaMgmtTopic(self): qos = 1 delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) listener = DefaultDataMessageListener() self.mcc.connect() self.mcc.publishMessage(ResourceNameEnum.CDA_MGMT_STATUS_MSG_RESOURCE, "TEST: This is the CDA message payload.", qos) sleep(5) self.mcc.disconnect()
class MqttClientConnectorTest(unittest.TestCase): """ This test case class contains very basic unit tests for MqttClientConnector. 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 MqttClientConnector class...") self.cfg = ConfigUtil() self.mcc = MqttClientConnector() def setUp(self): pass def tearDown(self): pass @unittest.skip("Ignore for now.") def testConnectAndDisconnect(self): delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) self.mcc.connectClient() sleep(delay + 5) self.mcc.disconnectClient() @unittest.skip("Ignore for now.") def testConnectAndCDAManagementStatusPubSub(self): qos = 1 delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) self.mcc.connectClient() self.mcc.subscribeToTopic( resource=ResourceNameEnum.CDA_MGMT_STATUS_MSG_RESOURCE, qos=qos) sleep(5) self.mcc.publishMessage( resource=ResourceNameEnum.CDA_MGMT_STATUS_MSG_RESOURCE, msg="TEST: This is the CDA message payload.", qos=qos) sleep(5) self.mcc.unsubscribeFromTopic( resource=ResourceNameEnum.CDA_MGMT_STATUS_MSG_RESOURCE) sleep(5) sleep(delay) self.mcc.disconnectClient() @unittest.skip("Ignore for now.") def testActuatorCmdPubSub(self): qos = 0 delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) actuatorData = ActuatorData() actuatorData.setCommand(7) self.mcc.setDataMessageListener(DefaultDataMessageListener()) payload = DataUtil().actuatorDataToJson(actuatorData) self.mcc.connectClient() sleep(5) self.mcc.publishMessage( resource=ResourceNameEnum.CDA_ACTUATOR_CMD_RESOURCE, msg=payload, qos=qos) sleep(delay + 5) self.mcc.disconnectClient() @unittest.skip("Ignore for now.") def testCDAManagementStatusSubscribe(self): qos = 1 delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) self.mcc.connectClient() self.mcc.subscribeToTopic( resource=ResourceNameEnum.CDA_MGMT_STATUS_CMD_RESOURCE, qos=qos) sleep(delay) self.mcc.disconnectClient() @unittest.skip("Ignore for now.") def testCDAManagementStatusPublish(self): """ Uncomment this test when integration between the GDA and CDA using MQTT. """ qos = 1 delay = self.cfg.getInteger(ConfigConst.MQTT_GATEWAY_SERVICE, ConfigConst.KEEP_ALIVE_KEY, ConfigConst.DEFAULT_KEEP_ALIVE) self.mcc.connectClient() self.mcc.publishMessage( resource=ResourceNameEnum.CDA_MGMT_STATUS_MSG_RESOURCE, msg="TEST: This is the CDA message payload.", qos=qos) sleep(delay) self.mcc.disconnectClient()
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) """