I’m trying to build a Matter Controller using .Net. I’m writing these posts as I go, figuring the stuff out and blogging how and what I’m doing.

As I write this post, I’m current on holidays in Cornwall. This means I’m without my typical setup. I quick discovered that without my Raspberry Pi to host my matter.js test device, I was stuck! I tried to get the matter.js Bluetooth code working on my laptop, but it wasn’t happening.

I knew that Matter can work without Bluetooth if the device has a network connection already. When you start a matter.js example, you can see it binding to a port. When you run the “matter-device”, you get this

Matter.js will open a port once it’s started up

I figured that maybe I could just open a UDP connection to that port and send the same MessageFrames I was using over the Bluetooth connection. UDP is an IP protocol. It stands for User Datagram Protocol. It sits at the same level of TCP (Transmission Control Protocol). The main difference between the two is that UDP is unreliable. It’s sort of fire-and-forget.

I had to refactor the code a little, making both the BTP and UDP implementations of a new IConnection class. This meant that my MessageExchange could then use both BTP and UDP for communication. I hardcoded the IP address and port to get started.

public UdpConnection()
{
    _udpClient = new UdpClient(11000);
    IPAddress address = IPAddress.Parse("172.16.47.60");
    _udpClient.Connect(address, 5540);
}

In a proper Matter approach, mDNS would be used to find commissionable devices. I spend an hour or two playing with mDNS, but couldn’t get it working on my laptop. I figured hardcoding the values was essentially the same.

I added Send and Read methods.

public async Task SendAsync(MessageFrame message)
{
    var writer = new MatterMessageWriter();

    message.Serialize(writer);

    var bytes = writer.GetBytes();

    await _udpClient.SendAsync(bytes);
}

public async Task<MessageFrame> ReadAsync()
{
    var receiveResult = await _udpClient.ReceiveAsync();

    MessageFrame messageFrame = new MessageFrame(receiveResult.Buffer);

    return messageFrame;
}

I then created a copy of my Commissioner class, calling it NetworkCommissioner. This would stand up a new UdpConnection and perform the same steps. Horrible yet, but at this point it’s about making it work!

Amazingly, this worked. Almost.

My PbkdfParamRequest is received over UDP

After a few seconds, the matter-device throws an error:

The matter.js code is expecting an ACK of some kind.

Message Reliability

As I mentioned, UDP is fire-and-forget. This makes it unreliable in that you never know if a message you sent has actually arrived! Similar to Bluetooth packets, Matter solves this with a higher level protocol. In the case of Bluetooth, the BTP (Bluetooth Transport Protocol) has a message acknowledgement mechanism. UDP is no different. It uses the Message Reliability Protocol (MRP). See 4.18 of the Matter Specification.

I lifted some of the code from the BTP and added it into my UdpConnection class.

When a message is received, the header is checked. If the Reliability ExchangeFlag is set, we grab the MessageCounter

 public async Task<MessageFrame> ReadAsync()
 {
     var receiveResult = await _udpClient.ReceiveAsync();
     MessageFrame messageFrame = new MessageFrame(receiveResult.Buffer);

     if ((messageFrame.MessagePayload.ExchangeFlags & ExchangeFlags.Reliability) != 0)
     {
         _receivedMessageCounter = messageFrame.MessageCounter;
     }

     return messageFrame;
 }

When sending a MessageFrame, we check for any unacknowledged messages. If we have a mismatch, we set the Acknowledgment ExchangeFlag and pass the value in the AcknowledgedMessageCounter.

public async Task SendAsync(MessageFrame message)
{
    if(_acknowledgedMessageCounter != _receivedMessageCounter)
    {
        _acknowledgedMessageCounter = _receivedMessageCounter;

        message.MessagePayload.ExchangeFlags |= ExchangeFlags.Acknowledgement;
        message.MessagePayload.AcknowledgedMessageCounter = _acknowledgedMessageCounter;
    }

    var writer = new MatterMessageWriter();

    message.Serialize(writer);

    var bytes = writer.GetBytes();

    await _udpClient.SendAsync(bytes);
}

This *almost* worked.

PASE Secure Session is established!

My console app sends all the necessary PASE Commissioning messages and a session is established. However, matter.js isn’t happy.

One message is unacknowledged.

This took me a few minutes to figure out. I am only sending the MessageFrame acknowledgements when *I* am sending a message. This means that when I send my final MessageFrame and matter.js responds, nothing is acknowledging that message!!

I had a similar problem when building out my BTP code and I solved that with Timer that just sends acknowledges as required, checking every few seconds.

Standalone Acknowledgements

MRP has the same principal – It’s covered in section 4.12.7.1. MRP Standalone Acknowledgement. It involves sending a MessageFrame with a particular OpCode and no payload.

private async void SendStandaloneAcknowledgement(object? state)
{
    if (_acknowledgedMessageCounter != _receivedMessageCounter)
    {
        Console.WriteLine("Standalone Acknowledgement for MessageCounter {0}", _receivedMessageCounter);

        _acknowledgedMessageCounter = _receivedMessageCounter;

        MessagePayload payload = new MessagePayload();
        payload.ExchangeFlags |= ExchangeFlags.Acknowledgement;
        payload.AcknowledgedMessageCounter = _acknowledgedMessageCounter;
        payload.ProtocolOpCode = 0x00;
        payload.ProtocolId = 0x10; // MRP Standalone Acknowledgement

        MessageFrame messageFrame = new MessageFrame(payload);
        messageFrame.MessageFlags |= MessageFlags.S;
        messageFrame.SecurityFlags = 0x00;

        await SendAsync(messageFrame);
    }
}

With this Timer in place, the matter.js device was happy.

Summary

Whilst this was an unplanned detour on my Matter journey, having a basic working UDP connection will no doubt be important. I also gained more understanding of MRP and MessageCounters.

My code certainly needs more refactoring, but there is certainly a structure emerging. Sessions, Exchanges and Connections.

Be sure to check out my YouTube channel.

One response

  1. […] At this point, I went on holiday! Whilst on holiday, I realised that I couldn’t use Bluetooth for discovery or commissioning. I ended up building a crude UDP connection handler. […]

Leave a comment

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