Swarm Space – Azure IoT FromDevice with webhooks

The initial versions of the Swarm Space Azure Cloud Identity Gateway were based on my The Things Industries(TTI) Azure IoT Connector which used six HTTP Triggered Azure Functions. My Swarm Space Azure IoT connector only has one webhook endpoint so a .NET Core WebAPI with controllers based solution appeared to be more practical. The first step was to get some sample JavaScript Object Notation(JSON) uplink message payloads with the SwarmSpace-From Device with Webhooks project.

{
  "packetId": 0,
  "deviceType": 1,
  "deviceId": 0,
  "userApplicationId": 0,
  "organizationId": 65760,
  "data": "VGhpcyBpcyBhIHRlc3QgbWVzc2FnZS4gVGhlIHBhY2tldElkIGFuZCBkZXZpY2VJZCBhcmUgbm90IHBvcHVsYXRlZCwgYnV0IHdpbGwgYmUgZm9yIGEgcmVhbCBtZXNzYWdlLg==",
  "len": 100,
  "status": 0,
  "hiveRxTime": "2022-11-29T04:52:06"
}

I used JSON2CSharp to generate an initial version of a Plain Old CLR(ComonLanguage Runtime) Object(POCO) to deserialise the Delivery Webhook payload.

 https://json2csharp.com/
    
    // Root myDeserializedClass = JsonConvert.DeserializeObject<Root>(myJsonResponse);
    public class Root
    {
        public int packetId { get; set; }
        public int deviceType { get; set; }
        public int deviceId { get; set; }
        public int userApplicationId { get; set; }
        public int organizationId { get; set; }
        public string data { get; set; }
        public int len { get; set; }
        public int status { get; set; }
        public DateTime hiveRxTime { get; set; }
    }
*/

I then “tweaked” the JSON2CSharp class

 public class UplinkPayload
    {
        [JsonProperty("packetId")]
        public int PacketId { get; set; }

        [JsonProperty("deviceType")]
        public int DeviceType { get; set; }

        [JsonProperty("deviceId")]
        public int DeviceId { get; set; }

        [JsonProperty("userApplicationId")]
        public int UserApplicationId { get; set; }

        [JsonProperty("organizationId")]
        public int OrganizationId { get; set; }

        [JsonProperty("data")]
        [JsonRequired]
        public string Data { get; set; }

        [JsonProperty("len")]
        public int Len { get; set; }

        [JsonProperty("status")]
        public int Status { get; set; }

        [JsonProperty("hiveRxTime")]
        public DateTime HiveRxTime { get; set; }
    }

This class is used to “automagically” deserialise Delivery Webhook payloads. There is also some additional payload validation which discards test messages (not certain this is a good idea) etc.

//---------------------------------------------------------------------------------
// Copyright (c) December 2022, 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.SwarmSpace.AzureIoT.Connector.Controllers
{
    using System.Globalization;
    using System.Text;
    using System.Threading.Tasks;

    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Azure.Devices.Client;
    using Microsoft.Extensions.Logging;

    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;

    [ApiController]
    [Route("api/[controller]")]
    public class UplinkController : ControllerBase
    {
        private readonly ILogger<UplinkController> _logger;
        private readonly IAzureIoTDeviceClientCache _azureIoTDeviceClientCache;

        public UplinkController(ILogger<UplinkController> logger, IAzureIoTDeviceClientCache azureIoTDeviceClientCache)
        {
            _logger = logger;
            _azureIoTDeviceClientCache = azureIoTDeviceClientCache;
        }

        [HttpPost]
        public async Task<IActionResult> Uplink([FromBody] Models.UplinkPayload payload)
        {
            DeviceClient deviceClient;

            _logger.LogDebug("Payload {0}", JsonConvert.SerializeObject(payload, Formatting.Indented));

            if (payload.PacketId == 0)
            {
                _logger.LogWarning("Uplink-payload simulated DeviceId:{DeviceId}", payload.DeviceId);

                return this.Ok();
            }

            if ((payload.UserApplicationId < Constants.UserApplicationIdMinimum) || (payload.UserApplicationId > Constants.UserApplicationIdMaximum))
            {
                _logger.LogWarning("Uplink-payload invalid User Application Id:{UserApplicationId}", payload.UserApplicationId);

                return this.BadRequest($"Invalid User Application Id {payload.UserApplicationId}");
            }

            if ((payload.Len < Constants.PayloadLengthMinimum) || string.IsNullOrEmpty(payload.Data))
            {
                _logger.LogWarning("Uplink-payload.Data is empty PacketId:{PacketId}", payload.PacketId);

                return this.Ok("payload.Data is empty");
            }

            Models.AzureIoTDeviceClientContext context = new Models.AzureIoTDeviceClientContext()
            {
                OrganisationId = payload.OrganizationId,
                UserApplicationId = payload.UserApplicationId,
                DeviceType = payload.DeviceType,
                DeviceId = payload.DeviceId,
            };

            deviceClient = await _azureIoTDeviceClientCache.GetOrAddAsync(payload.DeviceId.ToString(), context);

            JObject telemetryEvent = new JObject
            {
                { "packetId", payload.PacketId},
                { "deviceType" , payload.DeviceType},
                { "DeviceID", payload.DeviceId },
                { "organizationId", payload.OrganizationId },
                { "ApplicationId", payload.UserApplicationId},
                { "ReceivedAtUtc", payload.HiveRxTime.ToString("s", CultureInfo.InvariantCulture) },
                { "DataLength", payload.Len },
                { "Data", payload.Data },
                { "Status", payload.Status },
            };

            // Send the message to Azure IoT Hub
            using (Message ioTHubmessage = new Message(Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(telemetryEvent))))
            {
                // Ensure the displayed time is the acquired time rather than the uploaded time. 
                ioTHubmessage.Properties.Add("iothub-creation-time-utc", payload.HiveRxTime.ToString("s", CultureInfo.InvariantCulture));
                ioTHubmessage.Properties.Add("OrganizationId", payload.OrganizationId.ToString());
                ioTHubmessage.Properties.Add("ApplicationId", payload.UserApplicationId.ToString());
                ioTHubmessage.Properties.Add("DeviceId", payload.DeviceId.ToString());
                ioTHubmessage.Properties.Add("deviceType", payload.DeviceType.ToString());

                await deviceClient.SendEventAsync(ioTHubmessage);

                _logger.LogInformation("Uplink-DeviceID:{deviceId} SendEventAsync success", payload.DeviceId);
            }

            return this.Ok();
        }
    }
}

I initially debugged and tested the Uplink controller with Telerik Fiddler using sample payloads captured with the SwarmSpace-From Device with Webhooks project.

Using Telerik Fiddler to make test delivery webhook calls

Which I could then inspect with Azure IoT Explorer as they arrived

Azure IoT Explorer displaying a test message

The next step was to create a new Delivery Method

Swarm delivery webhook creation

Configured to call my Uplink controller endpoint.

Swarm delivery webhook configuration

The webhook was configured to “acknowledge messages on successful delivery”. I then checked my Delivery Method configuration with a couple of “Test” messages.

My Swarm Space Eval Kit arrived md-week and after some issues with jumper settings it started reporting position and status information.

Swarm Eval Kit in my backyard

The first position was just of the coast of West Africa(null island)

Swarm Map centered on Null Island

After the Global Positioning System(GPS) receiver got a good fix the location of the Eval Kit was in the middle of my backyard.

Azure IoT Explorer displaying payload with good latitude and longitude
Swarm Map displaying the location of my device (zoomed out)

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.