{"_id":"587d49a4cd6f210f00949bd5","__v":0,"user":"573b7f42fdc9d320001b777d","version":{"_id":"582789aabe5c080f00a5a7fe","__v":10,"project":"568bdc1483d2061900d86cdc","createdAt":"2016-11-12T21:29:14.915Z","releaseDate":"2016-11-12T21:29:14.915Z","categories":["582789abbe5c080f00a5a7ff","582789abbe5c080f00a5a800","582789abbe5c080f00a5a801","582789abbe5c080f00a5a802","582789abbe5c080f00a5a803","582789abbe5c080f00a5a804","582789abbe5c080f00a5a805","582789abbe5c080f00a5a806","582789abbe5c080f00a5a807","582789abbe5c080f00a5a808","5827ea984ca29e0f00137a9e","583df597887db62f00644283","583df5d9c622791900e78da5","5845cd8763c11b250037967d","5845d13063c11b2500379681","5859e859e3306d1900126725","587aeb9a01cf3a0f008359eb","587c422af45e2d0f005e200d","587d84dc82f6f30f004ceee5"],"is_deprecated":false,"is_hidden":false,"is_beta":true,"is_stable":true,"codename":"beta2","version_clean":"0.0.0","version":"0"},"project":"568bdc1483d2061900d86cdc","category":{"_id":"5859e859e3306d1900126725","project":"568bdc1483d2061900d86cdc","__v":0,"version":"582789aabe5c080f00a5a7fe","sync":{"url":"","isSync":false},"reference":false,"createdAt":"2016-12-21T02:26:33.525Z","from_sync":false,"order":5,"slug":"edge-server","title":"Edge Server"},"parentDoc":null,"updates":[],"next":{"pages":[],"description":""},"createdAt":"2017-01-16T22:31:00.728Z","link_external":false,"link_url":"","githubsync":"","sync_unique":"","hidden":false,"api":{"settings":"","results":{"codes":[]},"auth":"required","params":[],"url":""},"isReference":false,"order":0,"body":"# Introduction\nPlugins are the mechanism through which the Edge server communicates with local devices.\n\nPlugins are responsible for: discovering local devices, coordinating communication between the devices and the edge server, and tracking the state changes for each device. Relaying this to droplit.io service is the responsibility of the Edge server.\n\n## What makes for a good plugin?\n\nA good plugin is one that is focused on a common application-level protocol. Take, for example, WeMo devices. As the family of WeMo devices works over HTTP with a common SOAP structure behind them, a WeMo Plugin is good plugin design. A general HTTP device plugin; however, would make a poor plugin, as the structure of data used to talk to devices over HTTP varies widely. A general ZWave plugin, though, would make for a better plugin as ZWave communication follows a common structure.\n\n## What languages can I use?\n\nPlugins may be written in both TypeScript and native JavaScript. The example plugins `droplit-plugin-ts-example` and `droplit-plugin-js-example` (for each language accordingly) are included with the Edge Server code—making a great starting point for writing your own plugin.\n[block:callout]\n{\n  \"type\": \"info\",\n  \"title\": \"Native code through node-gyp\",\n  \"body\": \"Node.js is able to use native libraries through a C++ addon module using node-gyp.\"\n}\n[/block]\n# Fundamental Concepts\n\n## Service classes\nA plugin author should be familiar with the following service class concepts that are referenced throughout this article: members, indexes, values, properties, methods, and events.\n\nSee [Service Classes](doc:services) for full explanation.\n\n## localId\nA `localId` is an identifier used in order to uniquely identify a device within a plugin. The identifier only needs to be unique within the plugin for each Edge server rather than across all plugins. The Edge server itself is responsible for mapping between the device id from the droplit.io service and the `localId`.\n\nWhile a `localId` only needs to be unique within a plugin, globally unique identifiers that a device may have make for a good localId (e.g., MAC address or a UUID from SSDP).\n\n## address\nAn `address` is the identifier used to address the device on the network. The form the address takes depends on the network protocol the device uses. E.g., a WiFi device would use an IP address or URI for its address.\n\n## Discovery\n\nIt is the responsibility of each plugin to discover the devices that the plugin will work with.\n\nThe Edge server starts discovery by calling the `discover` method on the plugin. This is first called after all plugins have been initialized and then on a routine interval, in order to discover devices that were added after the first call. Discovery calls to each plugin are staggered to avoid interference between plugins. Beyond the `discover` call, the discovery process is implementation agnostic.\n\nEvery time a new device is discovered, the plugin should call the `onDeviceInfo` method. This will register the device and its details with the droplit.io service based on the provided `localId`. Subsequent calls will update the device details with only the properties specified; so only make additional calls when something has actually changed.\n\nIt is recommended to cache the devices discovered in order to keep track of which discoveries are new and to track property changes.\n\nThe following example illustrates discovering a new device, caching it, and registering the device to the droplit.io service:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"// setup device discoverer\\nconst discoverer = new Discoverer();\\n// Cache of known devices\\nconst devices = new Map();\\n\\n// Handle device being discovered\\ndiscoverer.on('device discovered', device => {\\n  // If we already know about this device, do nothing\\n  if (devices.has(device.identifier))\\n    return;\\n\\n  // Cache the device so we know it's been discovered\\n  devices.set(device.identifier, device);\\n  \\n  // Send device info\\n  onDeviceInfo({\\n    localId: device.identifier,\\n    address: device.ipAddress,\\n    deviceMeta: {\\n      customName: device.name,\\n      manufacturer: 'Sirius Cybernetics Corp.',\\n      modelName: 'Nutrimatics Drinks Despenser'\\n    },\\n    services: [ 'BinarySwitch' ]\\n  });\\n});\\n\\n// The edge process will call this when the time is right\\nfunction discover() {\\n  discoverer.discover(); // do discovery\\n}\",\n      \"language\": \"javascript\"\n    },\n    {\n      \"code\": \"// setup device discoverer\\nconst discoverer = new Discoverer();\\n// Cache of known devices\\nconst devices: Map<string, Device> = new Map<string, Device>();\\n\\n// Handle device being discovered\\ndiscoverer.on('device discovered', (device: Device) => {\\n  // If we already know about this device, do nothing\\n  if (devices.has(device.identifier))\\n    return;\\n\\n  // Cache the device so we know it's been discovered\\n  devices.set(device.identifier, device);\\n  \\n  // Send device info\\n  onDeviceInfo({\\n    localId: device.identifier,\\n    address: device.ipAddress,\\n    deviceMeta: {\\n      customName: device.name,\\n      manufacturer: 'Sirius Cybernetics Corp.',\\n      modelName: 'Nutrimatics Drinks Despenser'\\n    },\\n    services: [ 'BinarySwitch' ]\\n  });\\n});\\n\\n// The edge process will call this when the time is right\\nfunction discover() {\\n  discoverer.discover(); // do discovery\\n}\",\n      \"language\": \"javascript\",\n      \"name\": \"TypeScript\"\n    }\n  ]\n}\n[/block]\nIt is the responsibility of each plugin to handle the case of an address change (e.g., the IP address for a WiFi address). Changes in the device addresses are often found in subsequent discovery calls by comparing the known unique identifier with the address. When this happens, the droplit.io service should be notified with an `onDeviceInfo` call containing only the `localId` and `address`.\n\nThe following example illustrates updating the address on change:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"discoverer.on('ipChange', data => {\\n  const device = this.devices.get(data.identifier);\\n  // In case we don’t already know about this device for some reason\\n  if (!device)\\n    return;\\n  // Update the address in the cache\\n  device.address = data.address;\\n      \\n  // Only send the localId and changed data\\n  onDeviceInfo({\\n    localId: device.identifier,\\n    address: device.address\\n  });\\n});\",\n      \"language\": \"javascript\"\n    },\n    {\n      \"code\": \"discoverer.on('ipChange', (data: Device) => {\\n  const device = this.devices.get(data.identifier);\\n  // In case we don’t already know about this device for some reason\\n  if (!device)\\n    return;\\n  // Update the address in the cache\\n  device.address = data.address;\\n      \\n  // Only send the localId and changed data\\n  onDeviceInfo({\\n    localId: device.identifier,\\n    address: device.address\\n  });\\n});\",\n      \"language\": \"javascript\",\n      \"name\": \"TypeScript\"\n    }\n  ]\n}\n[/block]\nThe Edge server will call the plugin’s `dropDevice` method when a device is deleted (e.g., through the droplit.io service’s REST API). On `dropDevice`, the plugin _should_ clear the specified device from its local cache. Dropped devices are to be considered no longer discovered and may be rediscovered on subsequent discovery calls.\n\nThe following example illustrates a `dropDevice` implementation:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"function dropDevice(localId) {\\n    // The device in question is not previously known\\n    const device = devices.get(localId);\\n    if (!device)\\n      return false;\\n\\n    // Remove the device from the cache\\n    this.devices.delete(device.identifier);\\n    return true;\\n}\",\n      \"language\": \"javascript\"\n    },\n    {\n      \"code\": \"function dropDevice(localId: string): boolean {\\n    // The device in question is not previously known\\n    const device = devices.get(localId);\\n    if (!device)\\n      return false;\\n\\n    // Remove the device from the cache\\n    this.devices.delete(device.identifier);\\n    return true;\\n}\",\n      \"language\": \"javascript\",\n      \"name\": \"TypeScript\"\n    }\n  ]\n}\n[/block]\n# Edge as a Device\n\nThe Droplit Edge software connects remote devices to the Droplit.io cloud for monitoring and control. Devices connected through the edge software are referred to as _downstream devices_.\n\nDownstream devices are independently addressable and controlled without needing a specialized knowledge of how they are connected up to the cloud. The node running the Droplit Edge software is also represented as a device in its respective environment.\n\nEdge devices can expose services that can be monitored or controlled as well. Services local to the edge device are referred to as _local services_.\n\n## Local Services\n\nLocal services are exposed by configuring a plugin to handle requests for a particular service class. All requests made to the edge device accessing that service class will be routed to that plugin. This does not include requests addressed to downstream devices.\n\nEx., if you wish to handle the hypothetical `DoStuff` service with the hypothetical `plugin-stuff` plugin, it would be configured as:\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"{\\n  \\\"plugins\\\": {\\n    \\\"plugin-stuff\\\": {\\n      \\\"enabled\\\": true,\\n      \\\"localServices\\\": [ \\\"DoStuff\\\" ]\\n    }\\n  }\\n}\",\n      \"language\": \"json\"\n    }\n  ]\n}\n[/block]\nFrom within the plugin the `localId` used for services is `.`.\n\nThis localId will come in on any property accessor or method call and should be set for any upstream message such as an event or property changed message.\n\nEx., sending a `notifyStuff` event on `DoStuff`.\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"this.onEvents([{ localId: '.', service: 'DoStuff', member: 'notifyStuff' }])\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\n# Data Structures\nThere are common data structures used by various plugin interfaces.\n\n## DeviceInfo\nThe `DeviceInfo` structure is an `object` representing all of the data about a device, except for service property states. This structure is used by `onDeviceInfo`.\n\n### localId\nThe `localId` of the device represented by a `string`.\n\n### address\nThe `address` of the device represented by a `string`.\n\n### services\nThe `services` supported by the device represented by an `array` of `strings`.\n\n### deviceMeta\nThe `deviceMeta` property is an `object` representing device metadata. This device metadata is directly mapped to general [device metadata](doc:metadata). The explicit properties on `deviceMeta` get mapped to system metadata keys while custom properties get mapped to regular metadata keys.\n\nSystem keys:\n* `customName`: A custom name stored on device. Many devices support the ability for a user to set a name through their first-party device — for such devices, this name should be used for custom name.\n* `manufacturer`: The device manufacturer.\n* `modelDescription`: A brief description of the device.\n* `modelName`: The name of the device’s model.\n* `modelNumber`: The device model number.\n\n## DeviceServiceMember\nThe `DeviceServiceMember` structure is an `object` used by property setters/getters, method calls, and events.\n\n### localId\nThe `localId` of the device represented by a `string`.\n\n### address\nThe `address` of the device represented by a `string`.\n\n### service\nThe name of the service class represented by a `string`\n\n### index\nThe index on the service class. Omit for service class implementations that are not indexed. Represented by a `string`.\n\n### member\nThe name of the member on the service class represented by a `string`.\n\n### value\nA value to pass with the Edge event. For properties, this is the value of the property. For method calls, this is that method arguments. For events, this is an event value. this value may be `any` valid JS type.\n\n# Plugin interface\nA plugin is expected to expose an object implementing a particular set of interfaces in order to work with the Edge server. For the convenience, Droplit provides a [`droplit-plugin` module](https://www.npmjs.com/package/droplit-plugin) (supporting both TypeScript and native JavaScript) to make implementing a plugin as easy as extending a ES6 class; however, using the module is not strictly necessary for implementing a plugin (if for example you do not wish to use ES6+).\n\nThe following are plugin methods (signatures defined in TypeScript notation):\n## discover\n`discover(): void` — discover all devices\n`discover` is called by the Edge server to tell the plugin to attempt to find new devices (e.g., invoking SSDP).\n\n## drop device\n`dropDevice(localId: string): boolean` — clear device information from memory\n`dropDevice` is called by the Edge server to tell the plugin to remove knowledge of a specified device from any sort of cache. The device is specified with the `localId` parameter. The return value is a boolean signifying whether the device was successfully removed or not (e.g., the specified id is not one that the plugin is aware of).\n\n## onDeviceInfo\n`onDeviceInfo(deviceInfo: DeviceInfo, callback?: (deviceInfo: DeviceInfo) => {}): void`\n`onDeviceInfo` is called by the Edge server to update the non-service data of a device. It is expected that `onDeviceInfo` is called on device discovery in order to first initialize device data; subsequent calls may be made to modify this data.\n\nThe callback will be called with the complete deviceInfo data as it is known by the server in response to the update. This is useful if there is existing known data cached in the `droplit.io` service that is not easily discoverable from the plugin; on discovery, the plugin may send its incomplete information and fill-in the remainder with what the server already knows.\n\n## onEvents\n`onEvents(events: DeviceServiceMember[]): void`\n`onEvents` is called within the plugin to communicate to the Edge server when an event occurs. The `events` parameter is in the form of an array to allow for reporting multiple events being raised simultaneously.\n\nex. report `MotionSensor.motion` event occurring for device `1`\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"this.onEvents({\\n  localId: '1',\\n  service: 'MotionSensor',\\n  member: 'motion'\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\n## onPropertiesChanged\n`onPropertiesChanged(properties: DeviceServiceMember[]): void`\n`onPropertiesChanged` is called within the plugin to communicate to the Edge server when a property’s value has changed. This method should not be called unless the underlying state value is actually different than the previously known value. Initial property values after device discovery should be reported with this method. The `properties` parameter is in the form of an array to allow for reporting multiple property changes simultaneously.\n\nex. report `BinarySwitch.switch` being set to `on` for device `1`\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"this.onPropertyChanged({\\n  localId: '1',\\n  service: 'BinarySwitch',\\n  member: 'switch',\\n  value: 'on'\\n});\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\n## services\n`services: any` — maps service class members to functions\n`services` is a property of the plugin object that maps [service class](doc:services) members to functions with an object of key-value pairs with members as keys and functions as values.\n\nex. maps the members of the BinarySwitch service class\n[block:code]\n{\n  \"codes\": [\n    {\n      \"code\": \"this.services = {\\n    BinarySwitch: {\\n        get_switch: this.getSwitch,\\n        set_switch: this.setSwitch,\\n        switchOff: this.switchOff,\\n        switchOn: this.switchOn\\n    }\\n};\",\n      \"language\": \"javascript\"\n    }\n  ]\n}\n[/block]\nFor members that are methods, the mapping key is the member name. For members that are properties, a mapping is expected for the property getter and setter (read-only properties may omit a setter). The format of these mapping keys is to prefix the member name with `get_` and `set_` for getters and setters accordingly.\n\nThe mapped functions have difference signatures depending on the type of member.\nproperty getter: `function(localId: string, callback: (value: any) => void, index: string): boolean`\nproperty setter: `function(localId: string, value: any, index: string): boolean`\nmethod: `function(localId: string, value: any, callback: (value: any) => void, index: string): boolean`\n\n`localId` — the id of the device to invoke the getter/setter/method call on\n`index` — for indexed devices, this specifies the index to use on the device specified by the localId\n`value` — the value to set a property to or call a method with\n`callback` — invoke to return the get value for a getter or a value to return in response to a method call when it is a `request` rather than a `call`","excerpt":"How to create an Edge Server Plugin","slug":"creating-plugins","type":"basic","title":"Creating Plugins"}

Creating Plugins

How to create an Edge Server Plugin

# Introduction Plugins are the mechanism through which the Edge server communicates with local devices. Plugins are responsible for: discovering local devices, coordinating communication between the devices and the edge server, and tracking the state changes for each device. Relaying this to droplit.io service is the responsibility of the Edge server. ## What makes for a good plugin? A good plugin is one that is focused on a common application-level protocol. Take, for example, WeMo devices. As the family of WeMo devices works over HTTP with a common SOAP structure behind them, a WeMo Plugin is good plugin design. A general HTTP device plugin; however, would make a poor plugin, as the structure of data used to talk to devices over HTTP varies widely. A general ZWave plugin, though, would make for a better plugin as ZWave communication follows a common structure. ## What languages can I use? Plugins may be written in both TypeScript and native JavaScript. The example plugins `droplit-plugin-ts-example` and `droplit-plugin-js-example` (for each language accordingly) are included with the Edge Server code—making a great starting point for writing your own plugin. [block:callout] { "type": "info", "title": "Native code through node-gyp", "body": "Node.js is able to use native libraries through a C++ addon module using node-gyp." } [/block] # Fundamental Concepts ## Service classes A plugin author should be familiar with the following service class concepts that are referenced throughout this article: members, indexes, values, properties, methods, and events. See [Service Classes](doc:services) for full explanation. ## localId A `localId` is an identifier used in order to uniquely identify a device within a plugin. The identifier only needs to be unique within the plugin for each Edge server rather than across all plugins. The Edge server itself is responsible for mapping between the device id from the droplit.io service and the `localId`. While a `localId` only needs to be unique within a plugin, globally unique identifiers that a device may have make for a good localId (e.g., MAC address or a UUID from SSDP). ## address An `address` is the identifier used to address the device on the network. The form the address takes depends on the network protocol the device uses. E.g., a WiFi device would use an IP address or URI for its address. ## Discovery It is the responsibility of each plugin to discover the devices that the plugin will work with. The Edge server starts discovery by calling the `discover` method on the plugin. This is first called after all plugins have been initialized and then on a routine interval, in order to discover devices that were added after the first call. Discovery calls to each plugin are staggered to avoid interference between plugins. Beyond the `discover` call, the discovery process is implementation agnostic. Every time a new device is discovered, the plugin should call the `onDeviceInfo` method. This will register the device and its details with the droplit.io service based on the provided `localId`. Subsequent calls will update the device details with only the properties specified; so only make additional calls when something has actually changed. It is recommended to cache the devices discovered in order to keep track of which discoveries are new and to track property changes. The following example illustrates discovering a new device, caching it, and registering the device to the droplit.io service: [block:code] { "codes": [ { "code": "// setup device discoverer\nconst discoverer = new Discoverer();\n// Cache of known devices\nconst devices = new Map();\n\n// Handle device being discovered\ndiscoverer.on('device discovered', device => {\n // If we already know about this device, do nothing\n if (devices.has(device.identifier))\n return;\n\n // Cache the device so we know it's been discovered\n devices.set(device.identifier, device);\n \n // Send device info\n onDeviceInfo({\n localId: device.identifier,\n address: device.ipAddress,\n deviceMeta: {\n customName: device.name,\n manufacturer: 'Sirius Cybernetics Corp.',\n modelName: 'Nutrimatics Drinks Despenser'\n },\n services: [ 'BinarySwitch' ]\n });\n});\n\n// The edge process will call this when the time is right\nfunction discover() {\n discoverer.discover(); // do discovery\n}", "language": "javascript" }, { "code": "// setup device discoverer\nconst discoverer = new Discoverer();\n// Cache of known devices\nconst devices: Map<string, Device> = new Map<string, Device>();\n\n// Handle device being discovered\ndiscoverer.on('device discovered', (device: Device) => {\n // If we already know about this device, do nothing\n if (devices.has(device.identifier))\n return;\n\n // Cache the device so we know it's been discovered\n devices.set(device.identifier, device);\n \n // Send device info\n onDeviceInfo({\n localId: device.identifier,\n address: device.ipAddress,\n deviceMeta: {\n customName: device.name,\n manufacturer: 'Sirius Cybernetics Corp.',\n modelName: 'Nutrimatics Drinks Despenser'\n },\n services: [ 'BinarySwitch' ]\n });\n});\n\n// The edge process will call this when the time is right\nfunction discover() {\n discoverer.discover(); // do discovery\n}", "language": "javascript", "name": "TypeScript" } ] } [/block] It is the responsibility of each plugin to handle the case of an address change (e.g., the IP address for a WiFi address). Changes in the device addresses are often found in subsequent discovery calls by comparing the known unique identifier with the address. When this happens, the droplit.io service should be notified with an `onDeviceInfo` call containing only the `localId` and `address`. The following example illustrates updating the address on change: [block:code] { "codes": [ { "code": "discoverer.on('ipChange', data => {\n const device = this.devices.get(data.identifier);\n // In case we don’t already know about this device for some reason\n if (!device)\n return;\n // Update the address in the cache\n device.address = data.address;\n \n // Only send the localId and changed data\n onDeviceInfo({\n localId: device.identifier,\n address: device.address\n });\n});", "language": "javascript" }, { "code": "discoverer.on('ipChange', (data: Device) => {\n const device = this.devices.get(data.identifier);\n // In case we don’t already know about this device for some reason\n if (!device)\n return;\n // Update the address in the cache\n device.address = data.address;\n \n // Only send the localId and changed data\n onDeviceInfo({\n localId: device.identifier,\n address: device.address\n });\n});", "language": "javascript", "name": "TypeScript" } ] } [/block] The Edge server will call the plugin’s `dropDevice` method when a device is deleted (e.g., through the droplit.io service’s REST API). On `dropDevice`, the plugin _should_ clear the specified device from its local cache. Dropped devices are to be considered no longer discovered and may be rediscovered on subsequent discovery calls. The following example illustrates a `dropDevice` implementation: [block:code] { "codes": [ { "code": "function dropDevice(localId) {\n // The device in question is not previously known\n const device = devices.get(localId);\n if (!device)\n return false;\n\n // Remove the device from the cache\n this.devices.delete(device.identifier);\n return true;\n}", "language": "javascript" }, { "code": "function dropDevice(localId: string): boolean {\n // The device in question is not previously known\n const device = devices.get(localId);\n if (!device)\n return false;\n\n // Remove the device from the cache\n this.devices.delete(device.identifier);\n return true;\n}", "language": "javascript", "name": "TypeScript" } ] } [/block] # Edge as a Device The Droplit Edge software connects remote devices to the Droplit.io cloud for monitoring and control. Devices connected through the edge software are referred to as _downstream devices_. Downstream devices are independently addressable and controlled without needing a specialized knowledge of how they are connected up to the cloud. The node running the Droplit Edge software is also represented as a device in its respective environment. Edge devices can expose services that can be monitored or controlled as well. Services local to the edge device are referred to as _local services_. ## Local Services Local services are exposed by configuring a plugin to handle requests for a particular service class. All requests made to the edge device accessing that service class will be routed to that plugin. This does not include requests addressed to downstream devices. Ex., if you wish to handle the hypothetical `DoStuff` service with the hypothetical `plugin-stuff` plugin, it would be configured as: [block:code] { "codes": [ { "code": "{\n \"plugins\": {\n \"plugin-stuff\": {\n \"enabled\": true,\n \"localServices\": [ \"DoStuff\" ]\n }\n }\n}", "language": "json" } ] } [/block] From within the plugin the `localId` used for services is `.`. This localId will come in on any property accessor or method call and should be set for any upstream message such as an event or property changed message. Ex., sending a `notifyStuff` event on `DoStuff`. [block:code] { "codes": [ { "code": "this.onEvents([{ localId: '.', service: 'DoStuff', member: 'notifyStuff' }])", "language": "javascript" } ] } [/block] # Data Structures There are common data structures used by various plugin interfaces. ## DeviceInfo The `DeviceInfo` structure is an `object` representing all of the data about a device, except for service property states. This structure is used by `onDeviceInfo`. ### localId The `localId` of the device represented by a `string`. ### address The `address` of the device represented by a `string`. ### services The `services` supported by the device represented by an `array` of `strings`. ### deviceMeta The `deviceMeta` property is an `object` representing device metadata. This device metadata is directly mapped to general [device metadata](doc:metadata). The explicit properties on `deviceMeta` get mapped to system metadata keys while custom properties get mapped to regular metadata keys. System keys: * `customName`: A custom name stored on device. Many devices support the ability for a user to set a name through their first-party device — for such devices, this name should be used for custom name. * `manufacturer`: The device manufacturer. * `modelDescription`: A brief description of the device. * `modelName`: The name of the device’s model. * `modelNumber`: The device model number. ## DeviceServiceMember The `DeviceServiceMember` structure is an `object` used by property setters/getters, method calls, and events. ### localId The `localId` of the device represented by a `string`. ### address The `address` of the device represented by a `string`. ### service The name of the service class represented by a `string` ### index The index on the service class. Omit for service class implementations that are not indexed. Represented by a `string`. ### member The name of the member on the service class represented by a `string`. ### value A value to pass with the Edge event. For properties, this is the value of the property. For method calls, this is that method arguments. For events, this is an event value. this value may be `any` valid JS type. # Plugin interface A plugin is expected to expose an object implementing a particular set of interfaces in order to work with the Edge server. For the convenience, Droplit provides a [`droplit-plugin` module](https://www.npmjs.com/package/droplit-plugin) (supporting both TypeScript and native JavaScript) to make implementing a plugin as easy as extending a ES6 class; however, using the module is not strictly necessary for implementing a plugin (if for example you do not wish to use ES6+). The following are plugin methods (signatures defined in TypeScript notation): ## discover `discover(): void` — discover all devices `discover` is called by the Edge server to tell the plugin to attempt to find new devices (e.g., invoking SSDP). ## drop device `dropDevice(localId: string): boolean` — clear device information from memory `dropDevice` is called by the Edge server to tell the plugin to remove knowledge of a specified device from any sort of cache. The device is specified with the `localId` parameter. The return value is a boolean signifying whether the device was successfully removed or not (e.g., the specified id is not one that the plugin is aware of). ## onDeviceInfo `onDeviceInfo(deviceInfo: DeviceInfo, callback?: (deviceInfo: DeviceInfo) => {}): void` `onDeviceInfo` is called by the Edge server to update the non-service data of a device. It is expected that `onDeviceInfo` is called on device discovery in order to first initialize device data; subsequent calls may be made to modify this data. The callback will be called with the complete deviceInfo data as it is known by the server in response to the update. This is useful if there is existing known data cached in the `droplit.io` service that is not easily discoverable from the plugin; on discovery, the plugin may send its incomplete information and fill-in the remainder with what the server already knows. ## onEvents `onEvents(events: DeviceServiceMember[]): void` `onEvents` is called within the plugin to communicate to the Edge server when an event occurs. The `events` parameter is in the form of an array to allow for reporting multiple events being raised simultaneously. ex. report `MotionSensor.motion` event occurring for device `1` [block:code] { "codes": [ { "code": "this.onEvents({\n localId: '1',\n service: 'MotionSensor',\n member: 'motion'\n});", "language": "javascript" } ] } [/block] ## onPropertiesChanged `onPropertiesChanged(properties: DeviceServiceMember[]): void` `onPropertiesChanged` is called within the plugin to communicate to the Edge server when a property’s value has changed. This method should not be called unless the underlying state value is actually different than the previously known value. Initial property values after device discovery should be reported with this method. The `properties` parameter is in the form of an array to allow for reporting multiple property changes simultaneously. ex. report `BinarySwitch.switch` being set to `on` for device `1` [block:code] { "codes": [ { "code": "this.onPropertyChanged({\n localId: '1',\n service: 'BinarySwitch',\n member: 'switch',\n value: 'on'\n});", "language": "javascript" } ] } [/block] ## services `services: any` — maps service class members to functions `services` is a property of the plugin object that maps [service class](doc:services) members to functions with an object of key-value pairs with members as keys and functions as values. ex. maps the members of the BinarySwitch service class [block:code] { "codes": [ { "code": "this.services = {\n BinarySwitch: {\n get_switch: this.getSwitch,\n set_switch: this.setSwitch,\n switchOff: this.switchOff,\n switchOn: this.switchOn\n }\n};", "language": "javascript" } ] } [/block] For members that are methods, the mapping key is the member name. For members that are properties, a mapping is expected for the property getter and setter (read-only properties may omit a setter). The format of these mapping keys is to prefix the member name with `get_` and `set_` for getters and setters accordingly. The mapped functions have difference signatures depending on the type of member. property getter: `function(localId: string, callback: (value: any) => void, index: string): boolean` property setter: `function(localId: string, value: any, index: string): boolean` method: `function(localId: string, value: any, callback: (value: any) => void, index: string): boolean` `localId` — the id of the device to invoke the getter/setter/method call on `index` — for indexed devices, this specifies the index to use on the device specified by the localId `value` — the value to set a property to or call a method with `callback` — invoke to return the get value for a getter or a value to return in response to a method call when it is a `request` rather than a `call`