diff --git a/packages/binding-coap/src/coap-server.ts b/packages/binding-coap/src/coap-server.ts index b3f8b4d03..6355c6dc0 100644 --- a/packages/binding-coap/src/coap-server.ts +++ b/packages/binding-coap/src/coap-server.ts @@ -136,81 +136,143 @@ export default class CoapServer implements ProtocolServer { } } - public expose(thing: ExposedThing, tdTemplate?: WoT.ExposedThingInit): Promise { - let urlPath = slugify(thing.title, { lower: true }); + public async expose(thing: ExposedThing, tdTemplate?: WoT.ExposedThingInit): Promise { + const port = this.getPort(); + const urlPath = this.createThingUrlPath(thing); + + if (port === -1) { + warn("CoapServer is assigned an invalid port, aborting expose process."); + return; + } + + this.fillInBindingData(thing, port, urlPath); + + debug(`CoapServer on port ${port} exposes '${thing.title}' as unique '/${urlPath}'`); + + this.setUpIntroductionMethods(thing, urlPath, port); + } + + private createThingUrlPath(thing: ExposedThing) { + const urlPath = slugify(thing.title, { lower: true }); if (this.things.has(urlPath)) { - urlPath = Helpers.generateUniqueName(urlPath); + return Helpers.generateUniqueName(urlPath); } - this.coreResources.set(urlPath, { urlPath, parameters: thingDescriptionParameters }); - debug(`CoapServer on port ${this.getPort()} exposes '${thing.title}' as unique '/${urlPath}'`); + return urlPath; + } - const port = this.getPort(); - if (port !== -1) { - this.things.set(urlPath, thing); - - // fill in binding data - for (const address of Helpers.getAddresses()) { - for (const type of ContentSerdes.get().getOfferedMediaTypes()) { - const base: string = - this.scheme + "://" + address + ":" + this.getPort() + "/" + encodeURIComponent(urlPath); - - for (const propertyName in thing.properties) { - const href = base + "/" + this.PROPERTY_DIR + "/" + encodeURIComponent(propertyName); - const form = new TD.Form(href, type); - ProtocolHelpers.updatePropertyFormWithTemplate(form, thing.properties[propertyName]); - if (thing.properties[propertyName].readOnly) { - form.op = ["readproperty"]; - } else if (thing.properties[propertyName].writeOnly) { - form.op = ["writeproperty"]; - } else { - form.op = ["readproperty", "writeproperty"]; - } - if (thing.properties[propertyName].observable) { - if (!form.op) { - form.op = []; - } - form.op.push("observeproperty"); - form.op.push("unobserveproperty"); - } + private fillInBindingData(thing: ExposedThing, port: number, urlPath: string) { + const addresses = Helpers.getAddresses(); + const offeredMediaTypes = ContentSerdes.get().getOfferedMediaTypes(); - thing.properties[propertyName].forms.push(form); - debug(`CoapServer on port ${this.getPort()} assigns '${href}' to Property '${propertyName}'`); - } + for (const address of addresses) { + for (const offeredMediaType of offeredMediaTypes) { + const base = this.createThingBase(address, port, urlPath); - for (const actionName in thing.actions) { - const href = base + "/" + this.ACTION_DIR + "/" + encodeURIComponent(actionName); - const form = new TD.Form(href, type); - ProtocolHelpers.updateActionFormWithTemplate(form, thing.actions[actionName]); - form.op = "invokeaction"; - thing.actions[actionName].forms.push(form); - debug(`CoapServer on port ${this.getPort()} assigns '${href}' to Action '${actionName}'`); - } + this.fillInPropertyBindingData(thing, base, port, offeredMediaType); + this.fillInActionBindingData(thing, base, port, offeredMediaType); + this.fillInEventBindingData(thing, base, port, offeredMediaType); + } + } + } - for (const eventName in thing.events) { - const href = base + "/" + this.EVENT_DIR + "/" + encodeURIComponent(eventName); - const form = new TD.Form(href, type); - ProtocolHelpers.updateEventFormWithTemplate(form, thing.events[eventName]); - form.op = ["subscribeevent", "unsubscribeevent"]; - thing.events[eventName].forms.push(form); - debug(`CoapServer on port ${this.getPort()} assigns '${href}' to Event '${eventName}'`); - } - } // media types - } // addresses + private createThingBase(address: string, port: number, urlPath: string): string { + return `${this.scheme}://${address}:${port}/${encodeURIComponent(urlPath)}`; + } - const parameters = { - urlPath, - port, - serviceName: "_wot._udp.local", - }; + private fillInPropertyBindingData(thing: ExposedThing, base: string, port: number, offeredMediaType: string) { + for (const [propertyName, property] of Object.entries(thing.properties)) { + const opValues = ProtocolHelpers.getPropertyOpValues(property); + const [href, form] = this.createHrefAndForm( + base, + this.PROPERTY_DIR, + propertyName, + offeredMediaType, + opValues + ); - this.mdnsIntroducer?.registerExposedThing(thing, parameters); - } // running + ProtocolHelpers.updatePropertyFormWithTemplate(form, property); - return new Promise((resolve, reject) => { - resolve(); - }); + property.forms.push(form); + this.logHrefAssignment(port, href, "Property", propertyName); + } + } + + private fillInActionBindingData(thing: ExposedThing, base: string, port: number, offeredMediaType: string) { + for (const [actionName, action] of Object.entries(thing.actions)) { + const [href, form] = this.createHrefAndForm( + base, + this.ACTION_DIR, + actionName, + offeredMediaType, + "invokeaction" + ); + + ProtocolHelpers.updateActionFormWithTemplate(form, action); + action.forms.push(form); + + this.logHrefAssignment(port, href, "Action", actionName); + } + } + + private fillInEventBindingData(thing: ExposedThing, base: string, port: number, offeredMediaType: string) { + for (const [eventName, event] of Object.entries(thing.events)) { + const [href, form] = this.createHrefAndForm(base, this.EVENT_DIR, eventName, offeredMediaType, [ + "subscribeevent", + "unsubscribeevent", + ]); + + ProtocolHelpers.updateEventFormWithTemplate(form, event); + event.forms.push(form); + + this.logHrefAssignment(port, href, "Event", eventName); + } + } + + private createHrefAndForm( + base: string, + affordancePathSegment: string, + affordanceName: string, + offeredMediaType: string, + opValues: string | string[] + ): [string, TD.Form] { + const href = this.createFormHref(base, affordancePathSegment, affordanceName); + const form = this.createAffordanceForm(href, offeredMediaType, opValues); + + return [href, form]; + } + + private createFormHref(base: string, affordancePathSegment: string, affordanceName: string) { + return `${base}/${affordancePathSegment}/${encodeURIComponent(affordanceName)}`; + } + + private createAffordanceForm(href: string, offeredMediaType: string, op: string[] | string) { + const form = new TD.Form(href, offeredMediaType); + form.op = op; + + return form; + } + + private logHrefAssignment(port: number, href: string, affordanceType: string, affordanceName: string) { + debug(`CoapServer on port ${port} assigns '${href}' to ${affordanceType} '${affordanceName}'`); + } + + private setUpIntroductionMethods(thing: ExposedThing, urlPath: string, port: number) { + this.createCoreResource(urlPath); + this.things.set(urlPath, thing); + + const parameters = { + urlPath, + port, + serviceName: "_wot._udp.local", + }; + + this.mdnsIntroducer?.registerExposedThing(thing, parameters); + } + + private createCoreResource(urlPath: string): void { + this.coreResources.set(urlPath, { urlPath, parameters: thingDescriptionParameters }); } public async destroy(thingId: string): Promise { diff --git a/packages/core/src/protocol-helpers.ts b/packages/core/src/protocol-helpers.ts index b66d4604a..85435ec2f 100644 --- a/packages/core/src/protocol-helpers.ts +++ b/packages/core/src/protocol-helpers.ts @@ -19,7 +19,7 @@ import { ReadableStream as PolyfillStream } from "web-streams-polyfill/ponyfill/ import { ActionElement, EventElement, PropertyElement } from "wot-thing-description-types"; import { createLoggers } from "./logger"; -const { debug } = createLoggers("core", "protocol-helpers"); +const { debug, warn } = createLoggers("core", "protocol-helpers"); export interface IManagedStream { nodeStream: Readable; @@ -380,4 +380,27 @@ export default class ProtocolHelpers { // No suitable form found for this operation return finalFormIndex; } + + public static getPropertyOpValues(property: PropertyElement): string[] { + const op: string[] = []; + + if (!property.readOnly) { + op.push("writeproperty"); + } + + if (!property.writeOnly) { + op.push("readproperty"); + } + + if (op.length === 0) { + warn("Property was declared both as readOnly and writeOnly."); + } + + if (property.observable) { + op.push("observeproperty"); + op.push("unobserveproperty"); + } + + return op; + } }