Skip to content

Commit

Permalink
Add automatic port assignment and streamline unique Ids
Browse files Browse the repository at this point in the history
  • Loading branch information
Apollon77 committed Oct 18, 2023
1 parent ee0fce9 commit 89d45fe
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 66 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ The main work (all changes without a GitHub username in brackets in the below li
* Adjust some property and structure namings to be more consistent
* Introducing class PairedNode with the High level API for a paired Node
* Restructure CommissioningController to handle multiple nodes and offer new high level API
* Change name of the unique storage id for servers or controllers added to MatterServer to "uniqueStorageKey"
* Feature: Make Port for CommissioningServer optional and add automatic port handling in MatterServer
* matter-node-shell.js
* Feature: Completely refactored and enhances shell to support commissioning, identify and many more new commands. See Readme, try it

Expand Down
42 changes: 15 additions & 27 deletions packages/matter-node.js/test/IntegrationTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ describe("Integration Test", () => {
matterServer = new MatterServer(serverStorageManager, { disableIpv4: true });

commissioningServer = new CommissioningServer({
port: matterPort,
listeningAddressIpv6: SERVER_IPv6,
deviceName,
deviceType,
Expand All @@ -135,6 +134,7 @@ describe("Integration Test", () => {
},
delayedAnnouncement: true, // delay because we need to override Mdns classes
});
assert.equal(commissioningServer.getPort(), undefined);

onOffLightDeviceServer = new OnOffLightDevice();
commissioningServer.addDevice(onOffLightDeviceServer);
Expand Down Expand Up @@ -188,6 +188,7 @@ describe("Integration Test", () => {
);

matterServer.addCommissioningServer(commissioningServer);
assert.equal(commissioningServer.getPort(), matterPort);

// override the mdns scanner to avoid the client to try to resolve the server's address
serverMdnsScanner = await MdnsScanner.create({ enableIpv4: false, netInterface: SERVER_IPv6 });
Expand Down Expand Up @@ -1128,7 +1129,6 @@ describe("Integration Test", () => {
Network.get = () => serverNetwork;

commissioningServer2 = new CommissioningServer({
port: matterPort2,
listeningAddressIpv6: SERVER_IPv6,
deviceName: `${deviceName} 2`,
deviceType,
Expand All @@ -1150,9 +1150,11 @@ describe("Integration Test", () => {
onOffLightDeviceServer = new OnOffLightDevice();
commissioningServer2.addDevice(onOffLightDeviceServer);

matterServer.addCommissioningServer(commissioningServer2, { uniqueStorageKey: "second" });
assert.equal(commissioningServer2.getPort(), matterPort2);

commissioningServer2.setMdnsScanner(serverMdnsScanner);
commissioningServer2.setMdnsBroadcaster(mdnsBroadcaster);
matterServer.addCommissioningServer(commissioningServer2);

await assert.doesNotReject(async () => commissioningServer2.advertise());
});
Expand Down Expand Up @@ -1326,7 +1328,7 @@ describe("Integration Test", () => {
adminFabricIndex: FabricIndex(1001),
adminVendorId: VendorId(0x1234),
});
matterClient.addCommissioningController(commissioningController2);
matterClient.addCommissioningController(commissioningController2, { uniqueStorageKey: "another-second" });
commissioningController2.setMdnsScanner(clientMdnsScanner);

Network.get = () => serverNetwork;
Expand Down Expand Up @@ -1376,7 +1378,7 @@ describe("Integration Test", () => {
assert.equal(storedControllerResumptionRecords.length, 2);

const storedControllerResumptionRecords2 = fakeServerStorage.get(
["1", "SessionManager"],
["second", "SessionManager"],
"resumptionRecords",
);
assert.ok(Array.isArray(storedControllerResumptionRecords2));
Expand All @@ -1403,7 +1405,7 @@ describe("Integration Test", () => {
});

const nodeData2 = fakeControllerStorage.get<[NodeId, any][]>(
["1", "MatterController"],
["another-second", "MatterController"],
"commissionedNodes",
);
assert.ok(nodeData2);
Expand All @@ -1417,7 +1419,7 @@ describe("Integration Test", () => {
});

const storedControllerFabrics = fakeControllerStorage.get<FabricJsonObject>(
["1", "MatterController"],
["another-second", "MatterController"],
"fabric",
);
assert.ok(typeof storedControllerFabrics === "object");
Expand Down Expand Up @@ -1470,7 +1472,7 @@ describe("Integration Test", () => {
assert.equal(result.statusCode, OperationalCredentials.NodeOperationalCertStatus.Ok);
assert.deepEqual(result.fabricIndex, fabricIndex);

// For next test we need to use the read Time implementation
// For next test we need to use the real Time implementation
const mockTimeInstance = Time.get();
Time.get = singleton(() => new TimeNode());

Expand Down Expand Up @@ -1505,29 +1507,15 @@ describe("Integration Test", () => {
});

after(async () => {
const promise = mdnsBroadcaster.expireAllAnnouncements(matterPort);

await MockTime.yield();
await MockTime.yield();
await MockTime.yield();
await MockTime.yield();
await MockTime.advance(150);
await MockTime.advance(150);
await MockTime.yield();

await MockTime.yield();
await MockTime.yield();
await MockTime.yield();
await MockTime.yield();
await MockTime.advance(150);
await MockTime.advance(150);
await MockTime.yield();

await promise;
// For closing all down we need to use the real Time implementation
const mockTimeInstance = Time.get();
Time.get = singleton(() => new TimeNode());

await matterServer.close();
await matterClient.close();
await fakeControllerStorage.close();
await fakeServerStorage.close();

Time.get = () => mockTimeInstance;
});
});
37 changes: 20 additions & 17 deletions packages/matter.js/src/CommissioningController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@ const logger = new Logger("CommissioningController");
* Constructor options for the CommissioningController class
*/
export type CommissioningControllerOptions = CommissioningControllerNodeOptions & {
/** Local port number to use for the UDP interface. By default a random port number will be generated. */
/**
* Local port number to use for the UDP interface. By default, a random port number will be generated
* (strongly recommended!).
*/
readonly localPort?: number;

/** Listening address for IPv4. By default the interface will listen on all IPv4 addresses. */
/** Listening address for IPv4. By default, the interface will listen on all IPv4 addresses. */
readonly listeningAddressIpv4?: string;

/** Listening address for IPv6. By default the interface will listen on all IPv6 addresses. */
/** Listening address for IPv6. By default, the interface will listen on all IPv6 addresses. */
readonly listeningAddressIpv6?: string;

/**
Expand Down Expand Up @@ -122,7 +125,7 @@ export class CommissioningController extends MatterNode {
/** Internal method to initialize a MatterController instance. */
private async initializeController() {
if (this.mdnsScanner === undefined || this.storage === undefined) {
throw new ImplementationError("Add the node to the Matter instance before!");
throw new ImplementationError("Add the node to the Matter instance before.");
}
if (this.controllerInstance !== undefined) {
return this.controllerInstance;
Expand Down Expand Up @@ -153,10 +156,10 @@ export class CommissioningController extends MatterNode {
*/
async commissionNode(nodeOptions: NodeCommissioningOptions) {
if (this.mdnsScanner === undefined || this.storage === undefined) {
throw new ImplementationError("Add the node to the Matter instance before!");
throw new ImplementationError("Add the node to the Matter instance before.");
}
if (this.controllerInstance === undefined) {
throw new ImplementationError("Controller instance not yet started. Please call start() first!");
throw new ImplementationError("Controller instance not yet started. Please call start() first.");
}

const nodeId = await this.controllerInstance?.commission(nodeOptions);
Expand All @@ -173,7 +176,7 @@ export class CommissioningController extends MatterNode {
/** Check if a given node id is commissioned on this controller. */
isNodeCommissioned(nodeId: NodeId) {
if (this.controllerInstance === undefined) {
throw new ImplementationError("Controller instance not yet started. Please call start() first!");
throw new ImplementationError("Controller instance not yet started. Please call start() first.");
}
return this.controllerInstance.getCommissionedNodes().includes(nodeId) ?? false;
}
Expand All @@ -186,13 +189,13 @@ export class CommissioningController extends MatterNode {
*/
async removeNode(nodeId: NodeId, tryDecommissioning = true) {
if (this.controllerInstance === undefined) {
throw new ImplementationError("Controller instance not yet started. Please call start() first!");
throw new ImplementationError("Controller instance not yet started. Please call start() first.");
}
if (tryDecommissioning) {
try {
const node = this.connectedNodes.get(nodeId);
if (node == undefined) {
throw new ImplementationError(`Node ${nodeId} is not connected!`);
throw new ImplementationError(`Node ${nodeId} is not connected.`);
}
await node.decommission();
} catch (error) {
Expand All @@ -209,7 +212,7 @@ export class CommissioningController extends MatterNode {
*/
async connectNode(nodeId: NodeId, connectOptions?: CommissioningControllerNodeOptions) {
if (this.controllerInstance === undefined) {
throw new ImplementationError("Controller instance not yet started. Please call start() first!");
throw new ImplementationError("Controller instance not yet started. Please call start() first.");
}

if (!this.controllerInstance.getCommissionedNodes().includes(nodeId)) {
Expand Down Expand Up @@ -238,12 +241,12 @@ export class CommissioningController extends MatterNode {
*/
async connect() {
if (this.controllerInstance === undefined) {
throw new ImplementationError("Controller instance not yet started. Please call start() first!");
throw new ImplementationError("Controller instance not yet started. Please call start() first.");
}

if (!this.controllerInstance.isCommissioned()) {
throw new ImplementationError(
"Controller instance not yet paired with any device, so nothing to connect to!",
"Controller instance not yet paired with any device, so nothing to connect to.",
);
}

Expand Down Expand Up @@ -283,7 +286,7 @@ export class CommissioningController extends MatterNode {
/** Returns true if t least one node is commissioned/paired with this controller instance. */
isCommissioned() {
if (this.controllerInstance === undefined) {
throw new ImplementationError("Controller instance not yet started. Please call start() first!");
throw new ImplementationError("Controller instance not yet started. Please call start() first.");
}

return this.controllerInstance.isCommissioned();
Expand All @@ -295,7 +298,7 @@ export class CommissioningController extends MatterNode {
*/
async createInteractionClient(nodeId: NodeId): Promise<InteractionClient> {
if (this.controllerInstance === undefined) {
throw new ImplementationError("Controller instance not yet started. Please call start() first!");
throw new ImplementationError("Controller instance not yet started. Please call start() first.");
}
return this.controllerInstance.connect(nodeId);
}
Expand All @@ -308,7 +311,7 @@ export class CommissioningController extends MatterNode {
/** Returns an array with the Node Ids for all commissioned nodes. */
getCommissionedNodes() {
if (this.controllerInstance === undefined) {
throw new ImplementationError("Controller instance not yet started. Please call start() first!");
throw new ImplementationError("Controller instance not yet started. Please call start() first.");
}

return this.controllerInstance.getCommissionedNodes() ?? [];
Expand All @@ -321,8 +324,8 @@ export class CommissioningController extends MatterNode {
this.connectedNodes.clear();
}

getPort() {
return undefined; // TODO Add later if UDC is used
getPort(): number | undefined {
return this.options.localPort;
}

/** Initialize the controller and connect to all commissioned nodes if autoConnect is not set to false. */
Expand Down
21 changes: 17 additions & 4 deletions packages/matter.js/src/CommissioningServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export interface DevicePairingInformation {
*/
export interface CommissioningServerOptions {
/** Port of the server, normally automatically managed. */
port: number;
port?: number;

/** IPv4 listener address, defaults to all interfaces.*/
listeningAddressIpv4?: string;
Expand Down Expand Up @@ -197,7 +197,7 @@ type CommissioningServerCommands = {
* host
*/
export class CommissioningServer extends MatterNode {
private readonly port: number;
private port?: number;
private readonly passcode: number;
private readonly discriminator: number;
private readonly flowType: CommissionningFlowType;
Expand Down Expand Up @@ -524,7 +524,8 @@ export class CommissioningServer extends MatterNode {
this.mdnsInstanceBroadcaster === undefined ||
this.mdnsScanner === undefined ||
this.storage === undefined ||
this.endpointStructureStorage === undefined
this.endpointStructureStorage === undefined ||
this.port === undefined
) {
throw new ImplementationError("Add the node to the Matter instance before!");
}
Expand Down Expand Up @@ -773,6 +774,9 @@ export class CommissioningServer extends MatterNode {
* @param mdnsBroadcaster MdnsBroadcaster instance
*/
setMdnsBroadcaster(mdnsBroadcaster: MdnsBroadcaster) {
if (this.port === undefined) {
throw new ImplementationError("Port must be set before setting the MDNS broadcaster!");
}
this.mdnsInstanceBroadcaster = new MdnsInstanceBroadcaster(this.port, mdnsBroadcaster);
}

Expand All @@ -797,10 +801,19 @@ export class CommissioningServer extends MatterNode {
/**
* Return the port the device is listening on
*/
getPort(): number {
getPort(): number | undefined {
return this.port;
}

/** Set the port the device is listening on. Can only be called before the device is initialized. */
setPort(port: number) {
if (port === this.port) return;
if (this.deviceInstance !== undefined || this.mdnsInstanceBroadcaster !== undefined) {
throw new ImplementationError("Port can not be changed after device is initialized!");
}
this.port = port;
}

/**
* close network connections of the device
*/
Expand Down
Loading

0 comments on commit 89d45fe

Please sign in to comment.