Azure IOT Hub and Event Grid Part2

I did notice that the .DeviceConnected and .DeviceDisconnected events did take a while to arrive. When I started the field gateway application on the Windows 10 IoT Core device I would get several DeviceTelemetry events before the DeviceConnected event arrived.

I was using Advanced Message Queueing Protocol (AMQP) so I modified the configuration file so I could try all the available options.

C# TransportType enumeration

namespace Microsoft.Azure.Devices.Client
{
	//
	// Summary:
	//     Transport types supported by DeviceClient - AMQP/TCP, HTTP 1.1, MQTT/TCP, AMQP/WS,
	//     MQTT/WS
	public enum TransportType
	{
		//
		// Summary:
		//     Advanced Message Queuing Protocol transport. Try Amqp over TCP first and fallback
		//     to Amqp over WebSocket if that fails
		Amqp = 0,
		//
		// Summary:
		//     HyperText Transfer Protocol version 1 transport.
		Http1 = 1,
		//
		// Summary:
		//     Advanced Message Queuing Protocol transport over WebSocket only.
		Amqp_WebSocket_Only = 2,
		//
		// Summary:
		//     Advanced Message Queuing Protocol transport over native TCP only
		Amqp_Tcp_Only = 3,
		//
		// Summary:
		//     Message Queuing Telemetry Transport. Try Mqtt over TCP first and fallback to
		//     Mqtt over WebSocket if that fails
		Mqtt = 4,
		//
		// Summary:
		//     Message Queuing Telemetry Transport over Websocket only.
		Mqtt_WebSocket_Only = 5,
		//
		// Summary:
		//     Message Queuing Telemetry Transport over native TCP only
		Mqtt_Tcp_Only = 6
	}
}

Windows 10 IoT Core LoRa Telemetry Field gateway config.json file

{
   "AzureIoTHubDeviceConnectionString": "HostName=FieldGatewayHub.azure-devices.net;DeviceId=LoRa915MHz;SharedAccessKey=y12345678901234567890123456789012345678/arg=",

   "AzureIoTHubTransportType-Amqp": "amqp",
   "AzureIoTHubTransportType-Http1": "Http1",
   "AzureIoTHubTransportType-Amqp_WebSocket_Only": "Amqp_WebSocket_Only",
   "AzureIoTHubTransportType-Amqp_Tcp_Only": "Amqp_Tcp_Only",
   "AzureIoTHubTransportType-Mqtt": "Mqtt",
   "AzureIoTHubTransportType-Mqtt_WebSocket_Only": "Mqtt_WebSocket_Only",
   "AzureIoTHubTransportType-Amqp": "Mqtt_Tcp_Only",

   "AzureIoTHubTransportType": "Mqtt_Tcp_Only",
   "SensorIDIsDeviceIDSensorID": false,
   "Address": "LoRaIoT1",
   "Frequency": 915000000.0,
   "PABoost": true
}

So in alphabetical order here are my not terribly scientific results

AMQP

Device ETW

Device Telemetry messages

{
"id":"07e9a772-d86a-963d-139a-9d2ea2a0866c",
"topic":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"subject":"devices/LoRa915MHz",
"eventType":"Microsoft.Devices.DeviceTelemetry","eventTime":"2020-01-25T00:57:18.477Z",
"data":{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz","iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T00:57:18.477Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiIxMC4wIiwiUGFja2V0UlNTSSI6LTcxLCJSU1NJIjotMTEwLCJEZXZpY2VBZGRyZXNzQkNEIjoiNEQtNjEtNjQtNzUtNjktNkUtNkYtMzIiLCJhdCI6Ijg0LjAiLCJhaCI6IjUwIiwid3NhIjoiMSIsIndzZyI6IjMiLCJ3ZCI6IjE5My44OCIsInIiOiIwLjAwIn0="},
"dataVersion":"",
"metadataVersion":"1"
}
…
{
"id":"ca8e8531-10da-ec99-aad7-49e68f3f9500","topic":"/SUBSCRIPTIONS/712345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"subject":"devices/LoRa915MHz",
"eventType":"Microsoft.Devices.DeviceTelemetry",
"eventTime":"2020-01-25T01:02:23.377Z",
"data":{
"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz",
"iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T01:02:23.377Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiI5LjgiLCJQYWNrZXRSU1NJIjotNzAsIlJTU0kiOi0xMDgsIkRldmljZUFkZHJlc3NCQ0QiOiI0RC02MS02NC03NS02OS02RS02Ri0zMiIsImF0IjoiODUuMSIsImFoIjoiNTEiLCJ3c2EiOiIxIiwid3NnIjoiMyIsIndkIjoiMjM5LjI1IiwiciI6IjAuMDAifQ=="},
"dataVersion":"",
"metadataVersion":"1"
}

Device connectivity messages

{
"id":"d8a393ff-6549-69d2-d728-37eee2437693",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceConnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T01:01:28.4887191Z","data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA90000000700000000000000000000001E"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

{
"id":"b345d18a-bdf5-3397-35c1-fd5c35046d85",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceDisconnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T01:02:24.9605306Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA90000000700000000000000000000001F"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

The first telemetry data arrived 00:57:18, the DeviceConnected arrived 01:01:28 so approximately a 4 minute delay, the DeviceDisconnected arrived within a minute of me shutting the device down.

Amqp-Tcp-Only

Device Telemetry messages

{
"id":"b8cdbc73-5cb8-134c-a328-beed47be5f27",
"topic":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB","subject":"devices/LoRa915MHz",
"eventType":"Microsoft.Devices.DeviceTelemetry",
"eventTime":"2020-01-25T04:16:48.732Z",
"data":{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz",
"iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T04:16:48.732Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiI5LjUiLCJQYWNrZXRSU1NJIjotNzIsIlJTU0kiOi0xMDcsIkRldmljZUFkZHJlc3NCQ0QiOiI0RC02MS02NC03NS02OS02RS02Ri0zMiIsImF0IjoiODYuNSIsImFoIjoiNDUiLCJ3c2EiOiIxIiwid3NnIjoiMiIsIndkIjoiOTkuNzUiLCJyIjoiMC4wMCJ9"},
"dataVersion":"",
"metadataVersion":"1"
}
...
{
"id":"56aaf46d-e1ee-9419-2621-aa97c0564778",
"topic":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"subject":"devices/LoRa915MHz",
"eventType":"Microsoft.Devices.DeviceTelemetry",
"eventTime":"2020-01-25T04:21:53.402Z",
"data":{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz",
"iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T04:21:53.402Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiI5LjUiLCJQYWNrZXRSU1NJIjotNzEsIlJTU0kiOi0xMDksIkRldmljZUFkZHJlc3NCQ0QiOiI0RC02MS02NC03NS02OS02RS02Ri0zMiIsImF0IjoiODQuOSIsImFoIjoiNDYiLCJ3c2EiOiIzIiwid3NnIjoiNyIsIndkIjoiMjE3LjUwIiwiciI6IjAuMDAifQ=="},
"dataVersion":"",
"metadataVersion":"1"
}

Device connectivity messages

{
"id":"a157468c-7d65-00b1-73d6-12e43fd1356b",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceConnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:20:39.7309538Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA900000007000000000000000000000026"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

{
"id":"2e6d7e57-3db3-9e1d-c01b-b0f787b16e05",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceDisconnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:22:33.5276985Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA900000007000000000000000000000027"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

The first telemetry data arrived 04:16:48, the DeviceConnected arrived 04:20:39 so approximately a 4 minute delay, the DeviceDisconnected arrived within a minute of me shutting the device down.

Amqp WebSocket Only

Device Telemetry messages

{
"id":"f82943da-425c-f49c-49b0-13ff2609544b",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceTelemetry",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:05:36.723Z",
"data":{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz",
"iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T04:05:36.723Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiI5LjMiLCJQYWNrZXRSU1NJIjotNzEsIlJTU0kiOi0xMDksIkRldmljZUFkZHJlc3NCQ0QiOiI0RC02MS02NC03NS02OS02RS02Ri0zMiIsImF0IjoiODUuMyIsImFoIjoiNDMiLCJ3c2EiOiIyIiwid3NnIjoiMyIsIndkIjoiMjcyLjYzIiwiciI6IjAuMDAifQ=="}
}
...
{
"id":"8419d4f7-3340-8e29-6370-29f95d40e68c",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceTelemetry",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:11:42.684Z",
"data":{"properties":{},"systemProperties":{"iothub-connection-device-id":"LoRa915MHz",
"iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T04:11:42.684Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiI5LjgiLCJQYWNrZXRSU1NJIjotNzEsIlJTU0kiOi0xMTIsIkRldmljZUFkZHJlc3NCQ0QiOiI0RC02MS02NC03NS02OS02RS02Ri0zMiIsImF0IjoiODcuMyIsImFoIjoiNDMiLCJ3c2EiOiIxIiwid3NnIjoiMyIsIndkIjoiMjcuMDAiLCJyIjoiMC4wMCJ9"}
}

Device connectivity messages

{
"id":"7f10a3e3-0c2c-2b18-e38c-d5ea498304ab",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceConnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:09:52.5786541Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA900000007000000000000000000000024"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

{"id":"474a695b-8c4f-e3fe-0b1b-b6bc6b6d4dbe",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceDisconnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:11:53.076926Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA900000007000000000000000000000025"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

The first telemetry data arrived 04:05:36, DeviceConnected arrived 04:09:52 so approximately a 4 minute delay, the DeviceDisconnected arrived within a minute of me shutting the device down.

HTTP

I waited for 20 minutes and there wasn’t a DeviceConnected message which I sort of expected as HTTP is a connectionless protocol.

MQTT

Device ETW

Device Telemetry messages

{
"id":"bc26c412-c694-3954-5888-baa118cc9f88",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceTelemetry",
"dataschema":"#",
"subject":"devices/LoRa915MHz","time":"2020-01-25T01:11:33.493Z",
"data":{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz",
"iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T01:11:33.493Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiI5LjUiLCJQYWNrZXRSU1NJIjotNzEsIlJTU0kiOi0xMTEsIkRldmljZUFkZHJlc3NCQ0QiOiI0RC02MS02NC03NS02OS02RS02Ri0zMiIsImF0IjoiODUuNiIsImFoIjoiNTAiLCJ3c2EiOiIyIiwid3NnIjoiNCIsIndkIjoiMjg1LjAwIiwiciI6IjAuMDAifQ=="}
}
...
{
"id":"de95b90f-ac96-5c76-7a46-00f5a0eef8cf",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceTelemetry",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T01:12:12.101Z",
"data":{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz","iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T01:12:12.101Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiIxMC4wIiwiUGFja2V0UlNTSSI6LTU4LCJSU1NJIjotMTA5LCJEZXZpY2VBZGRyZXNzQkNEIjoiMDEtMjMtMjktMDctMjMtMEMtODgtNjMtRUUiLCJoIjoiMzQiLCJ0IjoiNDEuOCIsInMiOiI0IiwidiI6IjQuMDcifQ=="}
}

Device connectivity messages

{
"id":"f8f5ee54-394d-05e3-784d-87bc648e8267",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceConnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T01:11:25.2530139Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA900000007000000000000000000000020"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

{
"id":"82d6aa6c-4c71-9623-a4ac-9e562345afad",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceDisconnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T01:12:26.6368519Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA900000007000000000000000000000021"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

The first telemetry data arrived 01:11:33, the DeviceConnected arrived 01:11:25 so they arrived in order and within 10 seconds, the DeviceDisconnected arrived within a 15 seconds of me shutting the device down.

Mqtt-TCP-Only

Device ETW

Device Telemetry messages

{
"id":"bb86bfd9-6d12-4a27-2444-bd6953be7ffd",
"topic":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"subject":"devices/LoRa915MHz",
"eventType":"Microsoft.Devices.DeviceTelemetry",
"eventTime":"2020-01-25T04:42:15.345Z",
"data":{"properties":{},"systemProperties":{"iothub-connection-device-id":"LoRa915MHz","iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}","iothub-connection-auth-generation-id":"637149227434620853","iothub-enqueuedtime":"2020-01-25T04:42:15.345Z","iothub-message-source":"Telemetry"},"body":"eyJQYWNrZXRTTlIiOiIxMC44IiwiUGFja2V0UlNTSSI6LTcxLCJSU1NJIjotMTA4LCJEZXZpY2VBZGRyZXNzQkNEIjoiNEQtNjEtNjQtNzUtNjktNkUtNkYtMzIiLCJhdCI6Ijg2LjkiLCJhaCI6IjQ1Iiwid3NhIjoiMSIsIndzZyI6IjMiLCJ3ZCI6IjI5LjYzIiwiciI6IjAuMDAifQ=="},"dataVersion":"","metadataVersion":"1"
}
...
{
"id":"c5991ed8-42f2-437a-9161-a526248c955f",
"topic":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB","subject":"devices/LoRa915MHz",
"eventType":"Microsoft.Devices.DeviceTelemetry",
"eventTime":"2020-01-25T04:44:16.986Z",
"data":{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz","iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T04:44:16.986Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiIxMC4wIiwiUGFja2V0UlNTSSI6LTcxLCJSU1NJIjotMTEyLCJEZXZpY2VBZGRyZXNzQkNEIjoiNEQtNjEtNjQtNzUtNjktNkUtNkYtMzIiLCJhdCI6Ijg4LjIiLCJhaCI6IjQ0Iiwid3NhIjoiMiIsIndzZyI6IjMiLCJ3ZCI6IjYxLjUwIiwiciI6IjAuMDAifQ=="},
"dataVersion":"",
"metadataVersion":"1"}
}

Device connectivity messages

{
"id":"7861f3c1-5f1d-e9a1-c214-19feea2bf1a3","source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceConnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:42:06.0123436Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA90000000700000000000000000000002A"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

{
"id":"c2090a7b-c827-73cc-d1e8-7d49fe4a03a1",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceDisconnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:44:48.816748Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA90000000700000000000000000000002B"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

The first telemetry data arrived 04:42:15, the DeviceConnected arrived 04:42:06 so they arrived in order and within 10 seconds, the DeviceDisconnected arrived within a 20 seconds of me shutting device down.

Mqtt-Web-Socket-Only

Device ETW

Device Telemetry messages

{
"id":"3afa1a9c-f30d-d051-077e-cd25cc3b2245",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceTelemetry",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:36:08.871Z",
"data":{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz",
"iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T04:36:08.871Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiI5LjUiLCJQYWNrZXRSU1NJIjotNzAsIlJTU0kiOi0xMTIsIkRldmljZUFkZHJlc3NCQ0QiOiI0RC02MS02NC03NS02OS02RS02Ri0zMiIsImF0IjoiODMuOCIsImFoIjoiNDciLCJ3c2EiOiIwIiwid3NnIjoiMSIsIndkIjoiMzQ3LjI1IiwiciI6IjAuMDAifQ=="}
}
...
{
"id":"fa082b67-db32-312d-e716-1b1d37f57d94",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceTelemetry",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:37:09.516Z",
"data":{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz","iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-25T04:37:09.516Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiI5LjgiLCJQYWNrZXRSU1NJIjotNzEsIlJTU0kiOi0xMTEsIkRldmljZUFkZHJlc3NCQ0QiOiI0RC02MS02NC03NS02OS02RS02Ri0zMiIsImF0IjoiODQuNCIsImFoIjoiNDciLCJ3c2EiOiIxIiwid3NnIjoiMiIsIndkIjoiMzU4LjUwIiwiciI6IjAuMDAifQ=="}
}

Device connectivity messages

{
"id":"245e4d68-06e6-4d76-1167-39a9a67b01ac",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceConnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz","time":"2020-01-25T04:36:03.8275263Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA900000007000000000000000000000028"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

{
"id":"9612e8af-84e0-a679-4c7d-28c5e968da3c",
"source":"/SUBSCRIPTIONS/12345678-9012-3456-7890-D12345678901/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceDisconnected",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-25T04:37:47.5794771Z",
"data":{"deviceConnectionStateEventInfo":{"sequenceNumber":"000000000000000001D5CDC11D3AACA900000007000000000000000000000029"},
"hubName":"FieldGatewayHub",
"deviceId":"LoRa915MHz"}
}

The first telemetry data arrived 04:36:08, the DeviceConnected arrived 04:36:03 so they arrived in order and within 10 seconds, the DeviceDisconnected arrived within a 30 seconds of me shutting device down.

Summary

My LoRa sensors nodes are sending data roughly every minute which reduces the precision of the times.

It looks like for AMQP based messaging it can take 4-5 minutes for a Devices.DeviceConnected message to arrive, for based MQTT messaging it’s 5-10 seconds.

Azure IOT Hub and Event Grid Part1

I have one an Azure IoT Hub LoRa Telemetry Field Gateway running in my office and I wanted to process the data collected by the sensors around my property without using a Software as a Service(SaaS) Internet of Things (IoT) package.

Rather than lots of screen grabs of my configuration steps I figured people reading this series of posts would be able to figure the details out themselves.

Raspberry PI with M2M LoRa Hat

I created an Azure Resource Group for this project, and created an Azure IoT Hub.

Azure Resource Group with IoT Hub

I then provisioned an Azure IoT Hub device so I could get the connection string for my Windows 10 Azure IoT Hub LoRa Telemetry Field gateway.

LoRa Field Gateway Provisioned in Azure IoT Hub

I downloaded the JSON configuration file template from my Windows 10 device (which is created on first startup after installation) and configured the Azure IoT Hub connection string.

{
   "AzureIoTHubDeviceConnectionString": "HostName=FieldGatewayHub.azure-devices.net;DeviceId=LoRa915MHz;SharedAccessKey=123456789012345678901234567890123456789/arg=",
   "AzureIoTHubTransportType": "amqp",
   "SensorIDIsDeviceIDSensorID": false,
   "Address": "LoRaIoT1",
   "Frequency": 915000000.0,
   "PABoost": true
}

I then uploaded this to my Windows 10 IoT Core device and restarted the Azure IoT Hub Field gateway so it picked up the new settings.

I could then see on the device messages from sensor nodes being unpacked and uploaded to my Azure IoT Hub.

ETW logging on device

In the Azure IoT Hub metrics I graphed the number of devices connected and the number of telemetry messages sent and could see my device connect then start uploading telemetry.

Azure IoT Hub metrics

One of my customers uses Azure Event Grid for application integration and I wanted to explore using it in an IoT solution. The first step was to create an Event Grid Domain.

I then used the Azure IoT Hub Events tab to wire up these events.

  • Microsoft.Devices.DeviceConnected
  • Microsoft.Devices.DeviceDisconnected
  • Microsoft.Devices.DeviceTelemetry
Azure IoT Hub Event Metrics

To confirm my event subscriptions were successful I previously found the “simplest” approach was to use an Azure storage queue endpoint. I had to create an Azure Storage Account with two Azure Storage Queues one for device connectivity (.DeviceConnected & .DeviceDisconnected) events and the other for device telemetry (.DeviceTelemetry) events.

I created a couple of other subscriptions so I could compare the different Event schemas (Event Grid Schema & Cloud Event Schema v1.0). At this stage I didn’t configure any Filters or Additional Features.

Azure IoT Hub Telemetry Event Metrics

I use Cerebrate Cerculean for monitoring and managing a couple of other customer projects so I used it to inspect the messages in the storage queues.

Cerebrate Ceculean Storage queue Inspector

The message are quite verbose

{
"id":"b48b6376-b7f4-ee7d-82d9-12345678901a",
"source":"/SUBSCRIPTIONS/12345678-901234789-0123-456789012345/RESOURCEGROUPS/AZUREIOTHUBEVENTGRIDAZUREFUNCTION/PROVIDERS/MICROSOFT.DEVICES/IOTHUBS/FIELDGATEWAYHUB",
"specversion":"1.0",
"type":"Microsoft.Devices.DeviceTelemetry",
"dataschema":"#",
"subject":"devices/LoRa915MHz",
"time":"2020-01-24T04:27:30.842Z","data":
{"properties":{},
"systemProperties":{"iothub-connection-device-id":"LoRa915MHz",
"iothub-connection-auth-method":"{\"scope\":\"device\",\"type\":\"sas\",\"issuer\":\"iothub\",\"acceptingIpFilterRule\":null}",
"iothub-connection-auth-generation-id":"637149227434620853",
"iothub-enqueuedtime":"2020-01-24T04:27:30.842Z",
"iothub-message-source":"Telemetry"},
"body":"eyJQYWNrZXRTTlIiOiIxMC4wIiwiUGFja2V0UlNTSSI6LTY5LCJSU1NJIjotMTA5LCJEZXZpY2VBZGRyZXNzQkNEIjoiNEQtNjEtNjQtNzUtNjktNkUtNkYtMzIiLCJhdCI6Ijc2LjYiLCJhaCI6IjU4Iiwid3NhIjoiMiIsIndzZyI6IjUiLCJ3ZCI6IjMyMi44OCIsInIiOiIwLjAwIn0="
}
}

The message payload is base64 encoded, so I used an online tool to decode it.

{
 PacketSNR":"10.0",
"PacketRSSI":-69,
"RSSI":-109,
"DeviceAddressBCD":"4D-61-64-75-69-6E-6F-32",
"at":"76.6",
"ah":"58",
"wsa":"2",
"wsg":"5",
"wd":"322.88",
"r":"0.00"
}

Without writing any code (I will script the configuration) I could upload sensor data to an Azure IoT Hub, subscribe to a selection of events the Azure IoT Hub publishes and then inspect them in an Azure Storage Queue.

I did notice that the .DeviceConnected and .DeviceDisconnected events did take a while to arrive. When I started the field gateway application on the device I would get several DeviceTelemetry events before the DeviceConnected event arrived.

Meadow LoRa Radio 915 MHz Payload Addressing client

This is a demo Wilderness Labs Meadow client that uploads temperature and humidity data to my Azure IoT Hubs/Central, AdaFruit.IO or MQTT on Raspberry PI field gateways.

Bill of materials (Prices Jan 2020).

//---------------------------------------------------------------------------------
// Copyright (c) January 2020, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.FieldGateway.Client
{
   using System;
   using System.Text;
   using System.Threading;

   using devMobile.IoT.Rfm9x;

   using Meadow;
   using Meadow.Devices;
   using Meadow.Foundation.Leds;
   using Meadow.Foundation.Sensors.Atmospheric;
   using Meadow.Hardware;
   using Meadow.Peripherals.Leds;

   public class MeadowClient : App<F7Micro, MeadowClient>
   {
      private const double Frequency = 915000000.0;
      private readonly byte[] fieldGatewayAddress = Encoding.UTF8.GetBytes("LoRaIoT1");
      private readonly byte[] deviceAddress = Encoding.UTF8.GetBytes("Meadow");
      private readonly Rfm9XDevice rfm9XDevice;
      private readonly TimeSpan periodTime = new TimeSpan(0, 0, 60);
      private readonly Sht31D sensor;
      private readonly ILed Led;

      public MeadowClient()
      {
         Led = new Led(Device, Device.Pins.OnboardLedGreen);

         try
         {
            sensor = new Sht31D(Device.CreateI2cBus());

            ISpiBus spiBus = Device.CreateSpiBus(500);

            rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D10, Device.Pins.D12);

            rfm9XDevice.Initialise(Frequency, paBoost: true, rxPayloadCrcOn: true);
#if DEBUG
            rfm9XDevice.RegisterDump();
#endif
            rfm9XDevice.OnReceive += Rfm9XDevice_OnReceive;
            rfm9XDevice.Receive(deviceAddress);
            rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;
         }
         catch (Exception ex)
         {
            Console.WriteLine(ex.Message);
         }

         while (true)
         {
            sensor.Update();

            Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss}-TX T:{sensor.Temperature:0.0}C H:{sensor.Humidity:0}%");

            string payload = $"t {sensor.Temperature:0.0},h {sensor.Humidity:0}";

            Led.IsOn = true;

            rfm9XDevice.Send(fieldGatewayAddress, Encoding.UTF8.GetBytes(payload));

            Thread.Sleep(periodTime);
         }
      }

      private void Rfm9XDevice_OnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
      {
         try
         {
            string addressText = UTF8Encoding.UTF8.GetString(e.Address);
            string addressHex = BitConverter.ToString(e.Address);
            string messageText = UTF8Encoding.UTF8.GetString(e.Data);

            Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss}-RX PacketSnr {e.PacketSnr:0.0} Packet RSSI {e.PacketRssi}dBm RSSI {e.Rssi}dBm = {e.Data.Length} byte message {messageText}");
         }
         catch (Exception ex)
         {
            Console.WriteLine(ex.Message);
         }
      }

      private void Rfm9XDevice_OnTransmit(object sender, Rfm9XDevice.OnDataTransmitedEventArgs e)
      {
         Led.IsOn = false;

         Console.WriteLine("{0:HH:mm:ss}-TX Done", DateTime.Now);
      }
   }
}

The Meadow platform is a work in progress (Jan 2020) so I haven’t put any effort into minimising power consumption but will revisit this in a future post.

Meadow device with Seeedstudio SHT31 temperature & humidity sensor
Meadow sensor data in Field Gateway ETW logging
Meadow Sensor data in Azure IoT Central

RFM9X.Meadow on Github

After a month or so of posts the source code of V1.0 of my Wilderness Labs Meadow RFM9X/SX127X library is on GitHub. I included all of the source for my test harness and proof of concept(PoC) application so other people can follow along with “my meadow learning experience”.

I initially started with a Dragino LoRa Shield for Arduino and jumper cables. I did this so only the pins I was using on the shield were connected to the Meadow.

Dragino LoRa Shield for Arduino based test harness

I then moved to an Adafruit LoRa Radio FeatherWing RFM95W 900MHz RadioFruit and Adafruit LoRa Radio FeatherWing – RFM95W 433 MHz – RadioFruit.

Adafruit FeatherWing based test harness

Using the jumper configuration above, the RFM9X constructor parameters are

  • Chip Select D9 (yellow wire)
  • Reset Pin D10 (grey wire)
  • Interrupt pin D12 (brown wire)
//---------------------------------------------------------------------------------
// Copyright (c) January 2020, devMobile Software
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//---------------------------------------------------------------------------------
namespace devMobile.IoT.Rfm9x.LoRaDeviceClient
{
   using System;
   using System.Diagnostics;
   using System.Text;
   using System.Threading.Tasks;

   using Meadow;
   using Meadow.Devices;
   using Meadow.Hardware;

   public class MeadowApp : App<F7Micro, MeadowApp>
   {
      private const double Frequency = 915000000.0;
      private byte MessageCount = Byte.MaxValue;
      private Rfm9XDevice rfm9XDevice;

      public MeadowApp()
      {
         try
         {
            ISpiBus spiBus = Device.CreateSpiBus(500);
            if (spiBus == null)
            {
               Console.WriteLine("spiBus == null");
            }
            rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D10, Device.Pins.D12);

            rfm9XDevice.Initialise(Frequency, paBoost: true, rxPayloadCrcOn: true);
#if DEBUG
            rfm9XDevice.RegisterDump();
#endif

            rfm9XDevice.OnReceive += Rfm9XDevice_OnReceive;
#if ADDRESSED_MESSAGES_PAYLOAD
            rfm9XDevice.Receive(UTF8Encoding.UTF8.GetBytes("AddressHere"));
#else
            rfm9XDevice.Receive();
#endif
            rfm9XDevice.OnTransmit += Rfm9XDevice_OnTransmit;
         }
         catch (Exception ex)
         {
            Console.WriteLine(ex.Message);
         }

         Task.Delay(10000).Wait();

         while (true)
         {
            string messageText = string.Format("Hello from {0} ! {1}", Environment.MachineName, MessageCount);
            MessageCount -= 2;

            byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
            Console.WriteLine("{0:HH:mm:ss}-TX {1} byte message {2}", DateTime.Now, messageBytes.Length, messageText);
#if ADDRESSED_MESSAGES_PAYLOAD
            this.rfm9XDevice.Send(UTF8Encoding.UTF8.GetBytes("AddressHere"), messageBytes);
#else
            this.rfm9XDevice.Send(messageBytes);
#endif
            Task.Delay(10000).Wait();
         }
      }

      private void Rfm9XDevice_OnReceive(object sender, Rfm9XDevice.OnDataReceivedEventArgs e)
      {
         try
         {
            string messageText = UTF8Encoding.UTF8.GetString(e.Data);

#if ADDRESSED_MESSAGES_PAYLOAD
            string addressText = UTF8Encoding.UTF8.GetString(e.Address);

            Console.WriteLine(@"{0:HH:mm:ss}-RX From {1} PacketSnr {2:0.0} Packet RSSI {3}dBm RSSI {4}dBm = {5} byte message ""{6}""", DateTime.Now, addressText, e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText);
#else
            Console.WriteLine(@"{0:HH:mm:ss}-RX PacketSnr {1:0.0} Packet RSSI {2}dBm RSSI {3}dBm = {4} byte message ""{5}""", DateTime.Now, e.PacketSnr, e.PacketRssi, e.Rssi, e.Data.Length, messageText);
#endif
         }
         catch (Exception ex)
         {
            Console.WriteLine(ex.Message);
         }
      }

      private void Rfm9XDevice_OnTransmit(object sender, Rfm9XDevice.OnDataTransmitedEventArgs e)
      {
         Console.WriteLine("{0:HH:mm:ss}-TX Done", DateTime.Now);
      }
   }
}

My library works but some issues (Dec 2019) with the Serial Peripheral Interface (SPI) ReadRegister method, Debug.WriteLine and Console.WriteLine mean it should be treated as late beta.

The wilderness labs developers are regularly releasing updates which I will test with as soon as they are available.

.Net Meadow RFM95/96/97/98 LoRa library Part8

Transmit and Receive with Interrupts

For the final iteration of the “nasty” test harness I got the interrupts working for the transmitting and receiving of messages. It’s not quite simultaneous, the code sends a message every 10 seconds then goes back to receive continuous mode after each message has been sent.

      public Rfm9XDevice(IIODevice device, ISpiBus spiBus, IPin chipSelectPin, IPin resetPin, IPin interruptPin)
      {
         // Chip select pin configuration
         ChipSelectGpioPin = device.CreateDigitalOutputPort(chipSelectPin, initialState: true);
         if (ChipSelectGpioPin == null)
         {
            Console.WriteLine("ChipSelectGpioPin == null");
         }

         // Factory reset pin configuration
         IDigitalOutputPort resetGpioPin = device.CreateDigitalOutputPort(resetPin);
         if (resetGpioPin == null)
         {
            Console.WriteLine("resetGpioPin == null");
         }
         resetGpioPin.State = false;
         Task.Delay(10);
         resetGpioPin.State = true;
         Task.Delay(10);

         // Interrupt pin for RX message & TX done notification 
         InterruptGpioPin = device.CreateDigitalInputPort(interruptPin, InterruptMode.EdgeRising);
         InterruptGpioPin.Changed += InterruptGpioPin_ValueChanged;

         Rfm9XLoraModem = new SpiPeripheral(spiBus, ChipSelectGpioPin);
         if (Rfm9XLoraModem == null)
         {
            Console.WriteLine("Rfm9XLoraModem == null");
         }
      }

      private void InterruptGpioPin_ValueChanged(object sender, DigitalInputPortEventArgs args)
      {
         byte irqFlags = this.RegisterReadByte(0x12); // RegIrqFlags
         byte numberOfBytes = 0;
         string messageText = "";
         bool transmitDone = false;

         //Console.WriteLine(string.Format("RegIrqFlags:{0}", Convert.ToString(irqFlags, 2).PadLeft(8, '0')));
         if ((irqFlags & 0b01000000) == 0b01000000)
         {
            //Console.WriteLine("Receive-Message");
            byte currentFifoAddress = this.RegisterReadByte(0x10); // RegFifiRxCurrent
            this.RegisterWriteByte(0x0d, currentFifoAddress); // RegFifoAddrPtr

            numberOfBytes = this.RegisterReadByte(0x13); // RegRxNbBytes
            byte[] messageBytes = this.RegisterRead(0x00, numberOfBytes); // RegFifo
            messageText = UTF8Encoding.UTF8.GetString(messageBytes);
         }

         if ((irqFlags & 0b00001000) == 0b00001000)  // TxDone
         {
            this.RegisterWriteByte(0x01, 0b10000101); // RegOpMode set LoRa & RxContinuous
            transmitDone = true;
         }

         this.RegisterWriteByte(0x40, 0b00000000); // RegDioMapping1 0b00000000 DI0 RxReady & TxReady
         this.RegisterWriteByte(0x12, 0xff);// RegIrqFlags
         if (numberOfBytes > 0)
         {
            Console.WriteLine("Received {0} byte message {1}", numberOfBytes, messageText);
         }
         if(transmitDone)
         {
            Console.WriteLine("Transmit-Done");
         }
      }
...
   public class MeadowApp : App<F7Micro, MeadowApp>
   {
      private Rfm9XDevice rfm9XDevice;
      private byte NessageCount = Byte.MaxValue;

      public MeadowApp()
      {
         ISpiBus spiBus = Device.CreateSpiBus(500);
         if (spiBus == null)
         {
            Console.WriteLine("spiBus == null");
         }

         rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D11, Device.Pins.D10);

         // Put device into LoRa + Sleep mode
         rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // RegOpMode 

         // Set the frequency to 915MHz
         byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 }; // RegFrMsb, RegFrMid, RegFrLsb
         rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes);

         rfm9XDevice.RegisterWriteByte(0x0F, 0x0); // RegFifoRxBaseAddress 

         rfm9XDevice.RegisterWriteByte(0x09, 0b10000000); // RegPaConfig

         rfm9XDevice.RegisterWriteByte(0x01, 0b10000101); // RegOpMode set LoRa & RxContinuous

         while (true)
         {
            rfm9XDevice.RegisterWriteByte(0x0E, 0x0); // RegFifoTxBaseAddress 

            // Set the Register Fifo address pointer
            rfm9XDevice.RegisterWriteByte(0x0D, 0x0); // RegFifoAddrPtr 

            string messageText = "W10 IoT Core LoRa! " + NessageCount.ToString();
            NessageCount -= 1;

            // load the message into the fifo
            byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
            rfm9XDevice.RegisterWrite(0x0, messageBytes); // RegFifo 

            // Set the length of the message in the fifo
            rfm9XDevice.RegisterWriteByte(0x22, (byte)messageBytes.Length); // RegPayloadLength
            Console.WriteLine("Sending {0} bytes message {1}", messageBytes.Length, messageText);
            rfm9XDevice.RegisterWriteByte(0x40, 0b01000000); // RegDioMapping1 0b00000000 DI0 RxReady & TxReady
            rfm9XDevice.RegisterWriteByte(0x01, 0b10000011); // RegOpMode 

            Debug.WriteLine("Sending {0} bytes message {1}", messageBytes.Length, messageText);

            Task.Delay(10000).Wait();
         }
      }

The diagnostic output shows inbound and outbound messages (Arduino then Meadow diagnostics)

21:15:56.070 -> 0x1: 0x81
21:15:56.070 -> 0x2: 0x1A
…
21:15:57.527 -> 0x7E: 0x0
21:15:57.527 -> 0x7F: 0x0
21:15:57.527 -> LoRa init succeeded.
21:15:58.101 -> Sending HeLoRa World! 0
21:16:06.958 -> Message: W10 IoT Core LoRa! 254
21:16:06.991 -> RSSI: -60
21:16:06.991 -> Snr: 9.50
21:16:06.991 ->
21:16:09.062 -> Sending HeLoRa World! 2
21:16:17.184 -> Message: W10 IoT Core LoRa! 253
21:16:17.218 -> RSSI: -61
21:16:17.218 -> Snr: 9.75
21:16:17.218 ->
21:16:19.876 -> Sending HeLoRa World! 4
21:16:21.946 -> Message: ⸮LoRaIoT1Maduino2at 65.3,ah 70,wsa 6,wsg 12,wd 167.25,r 0.00,
21:16:22.014 -> RSSI: -76
21:16:22.014 -> Snr: 9.50
21:16:22.014 ->
21:16:27.406 -> Message: W10 IoT Core LoRa! 252
21:16:27.429 -> RSSI: -60
21:16:27.429 -> Snr: 9.50
21:16:27.429 ->
21:16:30.153 -> Sending HeLoRa World! 6
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'. 
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\ReceiveTransmitInterrupt\bin\Debug\net472\App.exe'. Symbols loaded.
'App.exe' (CLR v4.0.30319: App.exe): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\ReceiveTransmitInterrupt\bin\Debug\net472\Meadow.dll'. 
The program '[42104] App.exe: Program Trace' has exited with code 0 (0x0).
The program '[42104] App.exe' has exited with code 0 (0x0).
 .
 .
 DirectRegisterAccess = True
 .
 .
 Sending 22 bytes message W10 IoT Core LoRa! 255
 Transmit-Done
 Received 15 byte message HeLoRa World! 0
 Sending 22 bytes message W10 IoT Core LoRa! 254
 Transmit-Done
 Received 15 byte message HeLoRa World! 2
 Sending 22 bytes message W10 IoT Core LoRa! 253
 Transmit-Done
 Received 15 byte message HeLoRa World! 4
 Received 61 byte message ???LoRaIoT1Maduino2at 65.3,ah 70,wsa 6,wsg 12,wd 167.25,r 0.00,
 Sending 22 bytes message W10 IoT Core LoRa! 252
 Transmit-Done
 Received 15 byte message HeLoRa World! 6
 Sending 22 bytes message W10 IoT Core LoRa! 251
 Transmit-Done
 Received 15 byte message HeLoRa World! 8
 Sending 22 bytes message W10 IoT Core LoRa! 250
 Transmit-Done
 Received 16 byte message HeLoRa World! 10
 Sending 22 bytes message W10 IoT Core LoRa! 249
 Transmit-Done
 Received 16 byte message HeLoRa World! 12
 Received 40 byte message ???LoRaIoT1#)#???c???h 55,t 24.9,s 3,v 4.01
 Sending 22 bytes message W10 IoT Core LoRa! 248
 Transmit-Done
 Received 16 byte message HeLoRa World! 14
 Sending 22 bytes message W10 IoT Core LoRa! 247
 Transmit-Done
 Received 16 byte message ???LoRaIoT1Maduino
 Sending 22 bytes message W10 IoT Core LoRa! 246
 Transmit-Done
 Received 16 byte message HeLoRa World! 18
 Sending 22 bytes message W10 IoT Core LoRa! 245
 Transmit-Done
 Received 16 byte message HeLoRa World! 20
 Sending 22 bytes message W10 IoT Core LoRa! 244
 Transmit-Done
 Received 16 byte message HeLoRa World! 22
 Sending 22 bytes message W10 IoT Core LoRa! 243
 Transmit-Done
 Received 16 byte message HeLoRa World! 24
 Sending 22 bytes message W10 IoT Core LoRa! 242
 Transmit-Done

The RegIrqFlags 01011000 indicates the RxDone, ValidHeader, and TxDone flags were set which was what I was expecting. Note the interference, the 46 byte packet

Next step is some refactoring to extract the register access code and merging with my Windows 10 IoT Core RMF9X library code base.

.Net Meadow RFM95/96/97/98 LoRa library Part7

Transmit Interrupt

Starting with the TransmitBasic sample application I modified the code so that a hardware interrupt (specified in RegDioMapping1) was generated on TxDone (FIFO Payload Transmission completed).

The application inserts a message into the RFM95 transmit FIFO every 10 seconds with confirmation of transmission displayed shortly afterwards

      public Rfm9XDevice(IIODevice device, ISpiBus spiBus, IPin chipSelectPin, IPin resetPin, IPin interruptPin)
      {
         // Chip select pin configuration
         ChipSelectGpioPin = device.CreateDigitalOutputPort(chipSelectPin, initialState: true);
         if (ChipSelectGpioPin == null)
         {
            Console.WriteLine("ChipSelectGpioPin == null");
         }

         // Factory reset pin configuration
         IDigitalOutputPort resetGpioPin = device.CreateDigitalOutputPort(resetPin);
         if (resetGpioPin == null)
         {
            Console.WriteLine("resetGpioPin == null");
         }
         resetGpioPin.State = false;
         Task.Delay(10);
         resetGpioPin.State = true;
         Task.Delay(10);

         // Interrupt pin for RX message & TX done notification 
         InterruptGpioPin = device.CreateDigitalInputPort(interruptPin, InterruptMode.EdgeRising);
         InterruptGpioPin.Changed += InterruptGpioPin_ValueChanged;

         Rfm9XLoraModem = new SpiPeripheral(spiBus, ChipSelectGpioPin);
         if (Rfm9XLoraModem == null)
         {
            Console.WriteLine("Rfm9XLoraModem == null");
         }
      }

      private void InterruptGpioPin_ValueChanged(object sender, DigitalInputPortEventArgs args)
      {
        byte irqFlags = this.RegisterReadByte(0x12); // RegIrqFlags
        this.RegisterWriteByte(0x12, 0xff);// Clear RegIrqFlags

        //Console.WriteLine(string.Format("RegIrqFlags:{0}", Convert.ToString(irqFlags, 2).PadLeft(8, '0')));
         if ((irqFlags & 0b00001000) == 0b00001000)  // TxDone
         {
            Console.WriteLine("Transmit-Done");
         }
      }
…
   public class MeadowApp : App<F7Micro, MeadowApp>
   {
      private Rfm9XDevice rfm9XDevice;

      public MeadowApp()
      {
         ISpiBus spiBus = Device.CreateSpiBus(500);
         if (spiBus == null)
         {
            Console.WriteLine("spiBus == null");
         }

         rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D11, Device.Pins.D10);

         // Put device into LoRa + Sleep mode
         rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // RegOpMode 

         // Set the frequency to 915MHz
         byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 }; // RegFrMsb, RegFrMid, RegFrLsb
         rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes);

         // More power PA Boost
         rfm9XDevice.RegisterWriteByte(0x09, 0b10000000); // RegPaConfig

         // Interrupt on TxDone
         rfm9XDevice.RegisterWriteByte(0x40, 0b01000000); // RegDioMapping1 0b00000000 DI0 TxDone

         while (true)
         {
            // Set the Register Fifo address pointer
            rfm9XDevice.RegisterWriteByte(0x0E, 0x00); // RegFifoTxBaseAddress 

            // Set the Register Fifo address pointer
            rfm9XDevice.RegisterWriteByte(0x0D, 0x0); // RegFifoAddrPtr 

            string messageText = "Hello LoRa!";

            // load the message into the fifo
            byte[] messageBytes = UTF8Encoding.UTF8.GetBytes(messageText);
            rfm9XDevice.RegisterWrite(0x0, messageBytes); // RegFifo 

            // Set the length of the message in the fifo
            rfm9XDevice.RegisterWriteByte(0x22, (byte)messageBytes.Length); // RegPayloadLength
            Console.WriteLine("Sending {0} bytes message {1}", messageBytes.Length, messageText);
            rfm9XDevice.RegisterWriteByte(0x01, 0b10000011); // RegOpMode 

            Task.Delay(10000).Wait();
         }
      }
   }

The output in the debug window

'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'. 
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\TransmitInterrupt\bin\Debug\net472\App.exe'. Symbols loaded.
'App.exe' (CLR v4.0.30319: App.exe): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\TransmitInterrupt\bin\Debug\net472\Meadow.dll'. 
The program '[11164] App.exe: Program Trace' has exited with code 0 (0x0).
The program '[11164] App.exe' has exited with code 0 (0x0).
.
.
DirectRegisterAccess = True
.
.
Sending 11 bytes message Hello LoRa!
Transmit-Done
Sending 11 bytes message Hello LoRa!
Transmit-Done
Sending 11 bytes message Hello LoRa!
Transmit-Done

On the Arduino test client the serial monitor displayed

13:02:09.098 -> Sending HeLoRa World! 4
13:02:19.130 -> Message: ⸮LoRaIoT1Maduino2at 79.7,ah 39,wsa 6,wsg 13,wd 28.13,r 0.00,
13:02:19.177 -> RSSI: -72
13:02:19.177 -> Snr: 9.25
13:02:19.177 -> 
13:02:19.431 -> Sending HeLoRa World! 6
13:02:29.994 -> Sending HeLoRa World! 8
13:02:32.000 -> Message: Hello LoRa!
13:02:32.000 -> RSSI: -46
13:02:32.047 -> Snr: 9.50
13:02:32.047 -> 
13:02:40.750 -> Sending HeLoRa World! 10
13:02:42.260 -> Message: Hello LoRa!
13:02:42.260 -> RSSI: -45
13:02:42.314 -> Snr: 9.50
13:02:42.314 -> 
13:02:51.286 -> Sending HeLoRa World! 12
13:02:52.541 -> Message: Hello LoRa!
13:02:52.541 -> RSSI: -45
13:02:52.541 -> Snr: 9.75
13:02:52.541 -> 
13:03:02.112 -> Sending HeLoRa World! 14
13:03:02.745 -> Message: Hello LoRa!
13:03:02.745 -> RSSI: -45
13:03:02.792 -> Snr: 9.50
13:03:02.792 -> 

Now that I’m confident my hardware is all working the next step will be building a full featured client based on my Windows 10 IoT Core library.

.Net Meadow RFM95/96/97/98 LoRa library Part6

Receive Interrupt

This proof of concept (PoC) was to confirm I could configure the Semtech 127X then handle the received messages using a Meadow event handler. The event handler code is based on the implementation in my Windows IoT 10 Core library.

I had added a few Console.WriteLine statements (Debug.Print currently doesn’t work Dec 2019) so I could see what was going on. But, using Console.WriteLine in the event handler caused me some problems which I had to debug. The irqFlags bit mask indicated there was a message in the FIFO but it wasn’t displayed and the interrupt mask wasn’t getting reset. As a temporary fix I refactored the code so the Console.WriteLine was the last statement in the EventHandler(which may cause other issues).

public Rfm9XDevice(IIODevice device, ISpiBus spiBus, IPin chipSelectPin, IPin resetPin, IPin interruptPin)
      {
         // Chip select pin configuration
         ChipSelectGpioPin = device.CreateDigitalOutputPort(chipSelectPin, initialState: true);
         if (ChipSelectGpioPin == null)
         {
            Console.WriteLine("ChipSelectGpioPin == null");
         }

         // Factory reset pin configuration
         IDigitalOutputPort resetGpioPin = device.CreateDigitalOutputPort(resetPin);
         if (resetGpioPin == null)
         {
            Console.WriteLine("resetGpioPin == null");
         }
         resetGpioPin.State = false;
         Task.Delay(10);
         resetGpioPin.State = true;
         Task.Delay(10);

         // Interrupt pin for RX message & TX done notification 
         InterruptGpioPin = device.CreateDigitalInputPort(interruptPin, InterruptMode.EdgeRising);
         InterruptGpioPin.Changed += InterruptGpioPin_ValueChanged;

         Rfm9XLoraModem = new SpiPeripheral(spiBus, ChipSelectGpioPin);
         if (Rfm9XLoraModem == null)
         {
            Console.WriteLine("Rfm9XLoraModem == null");
         }
      }
...
      private void InterruptGpioPin_ValueChanged(object sender, DigitalInputPortEventArgs args)
      {
         byte irqFlags = this.RegisterReadByte(0x12); // RegIrqFlags
         byte numberOfBytes = 0;
         string messageText = "";

         //Console.WriteLine(string.Format("RegIrqFlags:{0}", Convert.ToString(irqFlags, 2).PadLeft(8, '0')));
         if ((irqFlags & 0b01000000) == 0b01000000)
         {
            //Console.WriteLine("Receive-Message");
            byte currentFifoAddress = this.RegisterReadByte(0x10); // RegFifiRxCurrent
            this.RegisterWriteByte(0x0d, currentFifoAddress); // RegFifoAddrPtr

            numberOfBytes = this.RegisterReadByte(0x13); // RegRxNbBytes
            byte[] messageBytes = this.RegisterRead(0x00, numberOfBytes); // RegFifo
            messageText = UTF8Encoding.UTF8.GetString(messageBytes);
         }

         this.RegisterWriteByte(0x12, 0xff);// RegIrqFlags
         if (numberOfBytes > 0)
         {
            Console.WriteLine("Received {0} byte message {1}", numberOfBytes, messageText);
         }
      }
...
public class MeadowApp : App
{
private Rfm9XDevice rfm9XDevice;
  public MeadowApp()
  {
     ISpiBus spiBus = Device.CreateSpiBus(500);
     if (spiBus == null)
     {
        Console.WriteLine("spiBus == null");
     }

     rfm9XDevice = new Rfm9XDevice(Device, spiBus, Device.Pins.D09, Device.Pins.D11, Device.Pins.D10);

     // Put device into LoRa + Sleep mode
     rfm9XDevice.RegisterWriteByte(0x01, 0b10000000); // RegOpMode 

     // Set the frequency to 915MHz
     byte[] frequencyWriteBytes = { 0xE4, 0xC0, 0x00 }; // RegFrMsb, RegFrMid, RegFrLsb
     rfm9XDevice.RegisterWrite(0x06, frequencyWriteBytes);

     rfm9XDevice.RegisterWriteByte(0x0F, 0x0); // RegFifoRxBaseAddress 

     rfm9XDevice.RegisterWriteByte(0x40, 0b00000000); // RegDioMapping1 0b00000000 DI0 RxReady & TxReady

     rfm9XDevice.RegisterWriteByte(0x01, 0b10000101); // RegOpMode set LoRa & RxContinuous

     rfm9XDevice.RegisterDump();

     Console.WriteLine("Receive-Wait");
     Task.Delay(-1).Wait();
  }
}

The output in the output debug window looked like this

'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\WINDOWS\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll'. 
'App.exe' (CLR v4.0.30319: DefaultDomain): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\ReceiveInterrupt\bin\Debug\net472\App.exe'. Symbols loaded.
'App.exe' (CLR v4.0.30319: App.exe): Loaded 'C:\Users\BrynLewis\source\repos\RFM9X.Meadow\ReceiveInterrupt\bin\Debug\net472\Meadow.dll'. 
The program '[40348] App.exe: Program Trace' has exited with code 0 (0x0).
The program '[40348] App.exe' has exited with code 0 (0x0).
.
.
DirectRegisterAccess = True
.
.
Register dump
Register 0x00 - Value 0X6c - Bits 01101100
…
Register 0x42 - Value 0X12 - Bits 00010010
Receive-Wait
Received 15 byte message HeLoRa World! 0
Received 59 byte message ???LoRaIoT1Maduino2at 57.2,ah 67,wsa 1,wsg 3,wd 56.63,r 0.00,
Received 15 byte message HeLoRa World! 2
Received 15 byte message HeLoRa World! 4
Received 15 byte message HeLoRa World! 6
Received 15 byte message HeLoRa World! 8
Received 16 byte message HeLoRa World! 10

Next steps will be wiring up the transmit done interrupt, then building a full featured client based on my Windows 10 IoT Core library.