Skip to content

PIOT-CDA-08-004-A: Update AsyncCoapServerAdapter with the remaining method implementations #211

@labbenchstudios

Description

@labbenchstudios

Description

Update the AsyncCoapServerAdapter with the remaining functionality necessary to support the following helper methods and any other necessary functionality:

  • def _initServer(self)
  • async def _runServer(self)
  • async def _runServerTask(self)
  • async def _keepServerRunning(self)
  • async def _shutdownServer(self)
  • NOTE: These instructions make use of the following CoAP library:
    • The aiocoap open source CoAP library, located at: aiocoap. Reference: Amsüss, Christian and Wasilak, Maciej. aiocoap: Python CoAP Library. Energy Harvesting Solutions, 2013–. http://github.com/chrysn/aiocoap/.

Review the README

  • Please see README.md for further information on, and use of, this content.
  • License for embedded documentation and source codes: PIOT-DOC-LIC

Estimated effort may vary greatly

  • The estimated level of effort for this exercise shown in the 'Estimate' section below is a very rough approximation. The actual level of effort may vary greatly depending on your development and test environment, experience with the requisite technologies, and many other factors.

Actions

Step 1: Implement _initServer() in AsyncCoapServerAdapter

  • This method will initialize the server component, set the necessary configuration properties, and initialize the request handlers created earlier.
    • Here's a sample implementation:
	def _initServer(self):
		try:
			# Resource tree creation - lib-specific (this next line of code assumes use of aiocoap)
			self.rootResource = resource.Site()
			
			# Necessary to ensure discovery will function
			self.rootResource.add_resource( \
				['.well-known', 'core'], \
				resource.WKCResource(self.rootResource.get_resources_as_linkheader))
			
			self.addResource( \
				resourcePath = ResourceNameEnum.CDA_ACTUATOR_CMD_RESOURCE, \
				endName = ConfigConst.HUMIDIFIER_ACTUATOR_NAME, \
				resource = AsyncUpdateActuatorResourceHandler(dataMsgListener = self.dataMsgListener))
				
			# TODO: add other actuator resource handlers (for HVAC, etc.)
			
			sysPerfDataListener = AsyncGetSystemPerformanceResourceHandler()
			
			self.addResource( \
				resourcePath = ResourceNameEnum.CDA_SYSTEM_PERF_MSG_RESOURCE, \
				resource = sysPerfDataListener)
			
			sensorDataListener = AsyncGetTelemetryResourceHandler()
			
			self.addResource( \
				resourcePath = ResourceNameEnum.CDA_SENSOR_MSG_RESOURCE, \
				resource = sensorDataListener)
			
			# TODO: register the callbacks with the data message listener instance
			
			self.dataMsgListener.setTelemetryDataListener(listener = sensorDataListener)
			self.dataMsgListener.setSystemPerformanceDataListener(listener = sysPerfDataListener)
			
			logging.info("Created CoAP server with default resources.")
			
		except Exception as e:
			traceback.print_exception(type(e), e, e.__traceback__)
			logging.warning(f"Failed to create Async CoAP Server at {self.serverUri}")

Step 2: Implement _runServer() in AsyncCoapServerAdapter

  • This method will attempt to gracefully start the async server. It will be called from within the _runServerTask() method.
    • Here's a sample implementation:
	async def _runServer(self):
		if self.rootResource:
			logging.info("Creating Async CoAP Server context...")

			try:
				bindTuple = (self.host, self.port)
				
				self.coapServer = \
					await aiocoap.Context.create_server_context( \
						site = self.rootResource, \
						bind = bindTuple)

				logging.info("Initializing Async CoAP Server Task...")

				self._serverTask = asyncio.current_task()

				await self._keepServerRunning()

			except asyncio.CancelledError:
				logging.info("Server coroutine cancelled.")

			except Exception as e:
				logging.exception(f"Failed to spin up Async CoAP Server task: {e}")

		else:
			logging.warning("Root resource not yet created. Can't start server.")

Step 3: Implement _runServerTask() in AsyncCoapServerAdapter

  • This method will initialize the async server's start sequence, using the newly created event loop "thread" instantiated in the constructor.
    • Here's a sample implementation:
	def _runServerTask(self):
		logging.info("Starting Async CoAP Server task...")

		asyncio.set_event_loop(self._eventLoopThread)

		try:
			self._eventLoopThread.run_until_complete(self._runServer())

		except asyncio.CancelledError:
			logging.info("Server task cancelled.")

		except Exception as e:
			logging.exception(f"Error starting Async CoAP Server task: {e}")
			traceback.print_exception(type(e), e, e.__traceback__)

		finally:
			pendingTasks = asyncio.all_tasks(self._eventLoopThread)

			try:
				for task in pendingTasks:
					task.cancel()

				if pendingTasks:
					self._eventLoopThread.run_until_complete(
						asyncio.gather(*pendingTasks, return_exceptions = True))

				self._eventLoopThread.close()

			except Exception as e:
				logging.exception(f"Async CoAP Server event loop thread exception: {e}")
				traceback.print_exception(type(e), e, e.__traceback__)

			logging.info("Successfully closed Async CoAP Server event loop.")

Step 4: Implement _keepServerRunning() in AsyncCoapServerAdapter

  • This method will initialize an asyncio Future component for use when cleanly shutting down the server.
    • Here's a sample implementation:
	async def _keepServerRunning(self):
		try:
			logging.info("Initializing Async CoAP Server shutdown future...")

			self._shutdownFuture = asyncio.get_running_loop().create_future()

			await self._shutdownFuture

		except asyncio.CancelledError:
			logging.info("Server keep-alive cancelled.")

		except Exception as e:
			logging.info("Async CoAP Server future cancelled [OK]")

Step 5: Implement _shutdownServer() in AsyncCoapServerAdapter

  • This method will attempt to cleanly shutdown the async server.
    • Here's a sample implementation:
	async def _shutdownServer(self):
		try:
			if self.coapServer:
				logging.info(f"Shutting down Async CoAP Server at {self.serverUri}")

				await self.coapServer.shutdown()

				self.coapServer = None

				if hasattr(self, "_shutdownFuture") and not self._shutdownFuture.done():
					self._shutdownFuture.set_result(None)

				if self._serverTask and not self._serverTask.done():
					self._serverTask.cancel()

					with suppress(asyncio.exceptions.CancelledError):
						await self._serverTask

				logging.info("Async CoAP Server shutdown completed.")

		except Exception as e:
			logging.warning("Failed to shutdown Async CoAP server.")
			traceback.print_exception(type(e), e, e.__traceback__)

Estimate (Small = < 2 hrs; Medium = 4 hrs; Large = 8 hrs)

  • Medium

Tests

  • You can use the Californium client described in PIOT-CFG-08-001 to test your CoAP server running within the CDA.

Configure the CDA to run with CoAP server enabled

  • Make sure your CDA's configuration file is updated to enable the CoAP server
    • Update the config file and start the CDA in a separate terminal (or within your IDE)
    • NOTE: The config info below is JUST AN EXAMPLE
#
# CoAP client configuration information
#
[Coap.GatewayService]
credFile       = ./cred/PiotCoapCred.props
certFile       = ./cert/PiotCoapLocalCertFile.pem
host           = localhost
port           = 5683
securePort     = 5684
enableAuth     = False
enableCrypt    = False

#
# CDA specific configuration information
#
[ConstrainedDevice]
deviceLocationID = constraineddevice001
enableSimulator  = True
enableEmulator   = False
enableSenseHAT   = False
enableMqttClient = False
enableCoapServer = True
enableCoapClient = False
enableSystemPerformance = True
enableSensing    = True
enableLogging    = True
pollCycleSecs    = 5
testGdaDataPath  = /tmp/gda-data
testCdaDataPath  = /tmp/cda-data
testEmptyApp     = False
runForever       = True

Configure and run the Californium Client

  • Open a separate terminal window on your system to run the Californium client.
    • Change directory to the Californium Tools path (INSTALL_PATH/californium.tools)
    • Run cf-client with a simple discover or get request

CoAP GET Example

cd cf-client/target
java -jar cf-client-3.10.0-SNAPSHOT.jar -m GET coap://localhost:5683

CoAP DISCOVER Example

cd cf-client/target
java -jar cf-client-3.10.0-SNAPSHOT.jar -m GET coap://localhost:5683/.well-known/core

Californium client help

  • For a list of supported client parameters, use the following:
java -jar cf-client-3.10.0-SNAPSHOT.jar --help

Results (Initial)

  • You should see something similar to the following, depending on how the server is configured:
    • NOTE: The following is an example response from a DISCOVER request

Results (Upon Full Lab Module Completion)

  • Once you've completed all the exercises in this lab module, you should see an output that looks similar to the following, depending on how the server is configured:
    • NOTE: The following is an example response from a DISCOVER request when using the aiocoap library.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    Lab Module 08 - CoAP Servers

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions