class SensorAdapterManagerTest(unittest.TestCase): """ This test case class contains very basic unit tests for SensorAdapterManager. 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 SensorAdapterManager class...") self.configUtil = ConfigUtil() self.enableEmulator = self.configUtil.getBoolean( ConfigConst.CONSTRAINED_DEVICE, ConfigConst.ENABLE_EMULATOR_KEY) self.defaultMsgListener = DefaultDataMessageListener() self.sensorAdapterMgr = SensorAdapterManager( useEmulator=self.enableEmulator) self.sensorAdapterMgr.setDataMessageListener(self.defaultMsgListener) def setUp(self): pass def tearDown(self): pass def testStartAndStopManager(self): self.sensorAdapterMgr.startManager() sleep(20) self.sensorAdapterMgr.stopManager()
class SensorEmulatorManagerTest(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 SensorAdapterManager class [using SenseHAT emulator]...") self.defaultMsgListener = DefaultDataMessageListener() self.sensorAdapterMgr = SensorAdapterManager(useEmulator=True) self.sensorAdapterMgr.setDataMessageListener(self.defaultMsgListener) def setUp(self): pass def tearDown(self): pass def testRunAllSimulators(self): self.sensorAdapterMgr.startManager() sleep(60) self.sensorAdapterMgr.stopManager()
class DeviceDataManager(IDataMessageListener): """ Shell representation of class for student implementation. """ def __init__(self, enableMqtt: bool = True, enableCoap: bool = False): # set enable connection self.enableMqtt = enableMqtt self.enableCoap = enableCoap # load config self.configUtil = ConfigUtil() self.enableEmulator = self.configUtil.getBoolean(ConfigConst.CONSTRAINED_DEVICE, ConfigConst.ENABLE_EMULATOR_KEY) self.stepmotor = StepMotorAdapterTask() # create system Perf manager self.sysPerfManager = SystemPerformanceManager() # set sensor config self.sam = SensorAdapterManager(useEmulator=self.enableEmulator) #set actuator config self.aam = ActuatorAdapterManager(useEmulator=self.enableEmulator) #set data generation config 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); #self.stepmotor.moveTo(-40,-40,True) # set Mqtt client if enableMqtt is True: self.mqttClient = MqttClientConnector() #self.mqttClient.subscribeToTopic(ResourceNameEnum.CDA_ACTUATOR_RESPONSE_RESOURCE.value) #self.mqttClient.subscribeToTopic(ResourceNameEnum.CDA_ACTUATOR_CMD_RESOURCE.value) self.mqttClient.subscribeToTopic("ProgrammingIoT/StepMotor/Instruction") #self.mqttClient.mc.message_callback_add(callback=self.actuatorCallBack, sub=ResourceNameEnum.CDA_ACTUATOR_CMD_RESOURCE.value) self.mqttClient.mc.message_callback_add(callback=self.stepMotorCallBack, sub="ProgrammingIoT/StepMotor/Instruction") def actuatorCallBack(self,client, userdata, message): logging.info("MQTT CallBack:%s,%s" % (message.topic, message.payload)) self._handleIncomingDataAnalysis(message) def stepMotorCallBack(self,client, userdata, message): logging.info("MQTT CallBack:%s,%s" % (message.topic, message.payload)) point = str(message.payload.decode()).split(",") if len(point) == 2: self.stepmotor.moveTo(int(point[0]),int(point[1]),True) def handleActuatorCommandResponse(self, data: ActuatorData) -> bool: logging.info("handleActuatorCommandResponse called") #pass data to handler super().handleActuatorCommandResponse(data) #translate data to json and set updstream du = DataUtil() self._handleUpstreamTransmission(ResourceNameEnum.CDA_ACTUATOR_RESPONSE_RESOURCE.value, du.actuatorDataToJson(data)) def handleIncomingMessage(self, resourceEnum: ResourceNameEnum, msg: str) -> bool: logging.info("handleIncommingMessage called") #translate json to object and pass it to analysis du = DataUtil() du.jsonToActuatorData(msg) self._handleIncomingDataAnalysis(msg) def handleSensorMessage(self, data: SensorData) -> bool: logging.info("handleSensorMessage called") super().handleSensorMessage(data) # translate sensor data to json and handle it du = DataUtil() self._handleUpstreamTransmission(ResourceNameEnum.CDA_SENSOR_MSG_RESOURCE.value, du.sensorDataToJson(data)) self._handleSensorDataAnalysis(data) def handleSystemPerformanceMessage(self, data: SystemPerformanceData) -> bool: logging.info("handleSystemPerformanceMessage called") #translate sys perf data to json and set upstream du = DataUtil() self._handleUpstreamTransmission(ResourceNameEnum.CDA_SYSTEM_PERF_MSG_RESOURC.value, du.systemPerformanceDataToJson(data)) def startManager(self): #start manager self.sysPerfManager.startManager() self.sam.startManager() self.sam.setDataMessageListener(self) self.mqttClient.connectClient() def stopManager(self): #stop manager self.sysPerfManager.stopManager() self.sam.stopManager() self.mqttClient.disconnectClient() 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("_handleIncomingDataAnalysis called,msg:"+str(msg)) #du = DataUtil() #self.aam.sendActuatorCommand(du.jsonToActuatorData(msg)) 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("_handleSensorDataAnalysis called,msg:"+str(data)) if self.enableHandleTempChangeOnDevice is True: hvac = ActuatorData(ActuatorData.HVAC_ACTUATOR_TYPE) if data.getValue() < self.triggerHvacTempCeiling and data.getValue() > self.triggerHvacTempFloor: # start hvac when in trigger range hvac.setCommand(ActuatorData.COMMAND_ON) else: # stop hvac when not in range hvac.setCommand(ActuatorData.COMMAND_OFF) # send command self.aam.sendActuatorCommand(hvac) def handleActuatorCommandMessage(self, data: ActuatorData) -> bool: if data: logging.info("Processing actuator command message.") # TODO: add further validation before sending the command self.aam.sendActuatorCommand(data) return True else: logging.warning("Received invalid ActuatorData command message. Ignoring.") return False 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("_handleUpstreamTransmission called") # send message to mqtt broker if self.enableMqtt is True: self.mqttClient.publishMessage(resourceName, msg)
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) """