Whilst I’m still chipping away at my .Net Matter Controller code (it’s really hard), I wanted to continue exploring the Energy Management capabilities of Matter. If I wait until I’ve fixed all the issues in my implementation, Matter will be obsolete 🙂
To that end, I decided to have a go with matter.js and setup a controller using that. I would then add a ReactJS front end to that.
Matter.js is part of the Matter Github library and can be found here https://github.com/project-chip/matter.js
It’s build on node, so the first thing I needed to do was setup node. As Windows is kinda useless for this stuff, I’m using WSL (Windows Subsystem for Linux). I installed the latest version of node using NVM.
nvm install node
At the time of writing, this was version 24.4.1
I created a new directory and setup a Matter project
npm init @matter
By default, this sets up a Light device.
To help start me down the road of creating a controller, matter.js provide an example: matter.js/packages/examples/src/controller
Getting started
At this point, I needed to choose a UI. I’ve experience with both Vue.js and React.js, but as it had been a while since I did a React project, I chose that. Next, I needed something to host the API and matter.js controller. Nextjs seemed all the rage, so I opted for that.
After a few hours, I have the basic shell of an application up and running. I want to put on the record that JS development is needlessly complicated. So many steps just to produce some HTML. Anyway.

Once I had a basic shell up and running, with some Bootstrap UI, I moved onto commissioning a device.

In the NextJs code, I added an /api/devices endpoint by adding a routes.ts file. At the top of this file, I stand up the Matter.js controller.
import { Diagnostic, Environment, StorageService, Time } from "@matter/main";
import { GeneralCommissioning } from "@matter/main/clusters";
import { ManualPairingCodeCodec } from "@matter/main/types";
import { CommissioningController, NodeCommissioningOptions } from "@project-chip/matter.js";
import { Device } from '../../device.js'
const environment = Environment.default;
const storageService = environment.get(StorageService);
const uniqueId = "EnergyManager";
const adminFabricLabel = "matter.js Controller";
const commissioningController = new CommissioningController({
environment: {
environment,
id: uniqueId,
},
autoConnect: false, // Do not auto connect to the commissioned nodes
adminFabricLabel,
});
const controllerStorage = (await storageService.open("controller")).createContext("data");
await controllerStorage.set("fabriclabel", adminFabricLabel);
await commissioningController.start();
console.log(`Commissioning controller started with id ${uniqueId} and label "${adminFabricLabel}"`);
This basically creates a Matter controller. The uniqueId is how it stores the fabric, so once this name is consistent, the controller can be recreated with the same certificates etc.
I then added a POST function to accept the POST from my simple web page.
export async function POST(request: Request) {
let data = await request.json();
let setupPin, shortDiscriminator;
const pairingCodeCodec = ManualPairingCodeCodec.decode(data.manualSetupCode);
shortDiscriminator = pairingCodeCodec.shortDiscriminator;
setupPin = pairingCodeCodec.passcode;
console.log(`Data extracted from pairing code: ${Diagnostic.json(pairingCodeCodec)}`);
const commissioningOptions: NodeCommissioningOptions["commissioning"] = {
regulatoryLocation: GeneralCommissioning.RegulatoryLocationType.IndoorOutdoor,
regulatoryCountryCode: "XX",
};
const options: NodeCommissioningOptions = {
commissioning: commissioningOptions,
discovery: {
identifierData: { shortDiscriminator }
},
passcode: setupPin,
};
console.log(`Commissioning ... ${Diagnostic.json(options)}`);
const nodeId = await commissioningController.commissionNode(options);
console.log(`Commissioning successfully done with nodeId ${nodeId}`);
return new Response("data", { status: 200 })
}
This uses a ManualPairingCodeCodec to decode the manual pairing code and then firsts these values into the commissionNode function. That function basically does everything and then returns the id of the new node.
To display a list of commissioned nodes, I added a GET function
export async function GET(req: Request, res: any) {
const nodes = commissioningController.getCommissionedNodesDetails();
let devices : Device[] = nodes.map(n => { return <Device> { id: n.nodeId.toString(), name: n.advertisedName } });
return new Response(JSON.stringify(devices), { status: 200 })
}
This asks the controller for all its commissioned nodes and then just pulls out the id and name. I then render this in a table

Unfortunately, the name field is empty, but I’m not worried about that now.
No Bluetooth
I must point out at this stage, I can only scommission on-network devices i.e. devices that have already been commissioned via another device. This is because Windows WSL doesn’t have access to Bluetooth. If I ran this code on my Raspberry Pi, I would expect the Bluetooth to work.
Next Steps
My head is still reeling from the time wasted with Tailwindcss and Bootstrap, but I made some decide progress.
The next step is looking at how to subscribe to the nodes attributes so I can understand how that works.
I also need to start looking at how to add energy management details to my Tiny Dishwasher.
Code available here – https://github.com/tomasmcguinness/matter-js-energy-manager





Leave a comment