An adapter in ioBroker is an independent process, that reads and writes objects and states in a central data storage.
Data storage can be represented as database (redis/couchDB) or just text file, but the connection way is always the same - via API. That means, the developer should not care about what the database it is and how the data will be stored and delivered there.
Objects are static descriptions of some data point. States are the dynamic values of data points. So normally for every state there is a object with description. (But not vice versa).
The state ID must always start with channel name and channel name with device name. E.g. in the state name **hm-rpc.0.IEQ1234567.0.INFO** above, the part **hm-rpc.0.IEQ1234567.0** is the channel name and **hm-rpc.0.IEQ1234567** is the device name.
**Adapter** is just the package of files and placed in _node_modules_ directory.
For every adapter the description of this adapter can be found in object "_system.adapter.adapterName_". It is just the fields "common" and "native" from the io-package.json file. This entry is created automatically when _iobroker install adapterName_ or _iobroker add adapterName_ called. If the adapter was installed with _npm install iobroker.adapterName_ no entry will be created till first instance creation. But it is not so important. The required for "updates" information will be read from _io-package.json_ directly.
Full list of common settings for adapter can be found [here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#adapter)
**Instance** is an instance of adapter. Depending on type of adapter more than one instance can be created , but for some adapters there is no use to create more than one instance. E.g. in case of vis or rickshaw only one instance can be created. This behavior is controlled by flags in _io-package.json_.
For each instance the configuration object can be found in the data storage under "_system.adapter.adapterName.X_" ID, where X is the adapter instance number. It contains the settings for this instance of the adapter. Normally it consist of "common" and "native" settings. Common settings are:
For every adapter (not the instance) the following objects will be created automatically:
- system.adapter.adapterName: Description of adapter (like name, version number, ...)
- adapterName: Object that consists of HTML/JS/CSS files from "www" directory of adapter. This object will be created only if "www" directory is found in adapter package.
- adapterName.admin: Object that consists of HTML/JS/CSS files from "admin" directory of adapter package.
For every adapter instance 'X', the following objects will be created automatically:
- system.adapter.adapterName.X: configuration of adapter instance
- system.adapter.adapterName.X.alive: indication if instance alive (send messages every 30 seconds)
- system.adapter.adapterName.X.connected: indication if instance is connected to data storage, because it can be connected, but because of deadlock can not send alive messages.
- system.adapter.adapterName.X.uptime: How many seconds adapter runs.
Explanation of memory states can be found [here](http://stackoverflow.com/questions/12023359/what-do-the-return-values-of-node-js-process-memoryusage-stand-for).
If adapter has mode 'none' or 'once', then alive, uptime, ... objects will not be created.
- xxx.png - optional, better if it has name adapterName.png (any image formats are supported: jpeg, jpg, svg, bmp, ...)
- www - (optional directory)
- lib - (mandatory directory, because of utils.js)
- utils.js
- package.json - mandatory
- io-package.json - mandatory
- main.js - mandatory (can be adapterName.js)
**Note**: lib/utils.js is a common file for all adapters, used to detect the position of js-controller and accordingly path to iobroker.js-controller/lib/adapter.js. Most actual utils.js can be downloaded [here](https://github.com/ioBroker/ioBroker.build/blob/master/adapters/utils.js). _Do not change this file._
1. On github (or somewhere else) it must have name _io**B**roker.adapterName_.
2. If the adapter will be available on npm it must have name _io**b**roker.adapterName_, because npm doe snot allow capital letters in package names. It can be defined in in _package.json_
3. GUI html file for configuration of adapter must have name admin/index.html. It can be more files in the "admin" directory, but index.html must exist.
3. The start file of adapter must have name _main.js_ or _adapterName.js_.
4. Name of adapter must be unique, lowercase, with no special characters and without spaces. "-", "_" are allowed in the name of adapter.
- %ip%: will be replaced with IP address defined in first "web" instance.
- %field%, where field is attribute from "native" part of configuration of adapter instance.
E.g. "http://%ip%:%port%" will be shown as "http://192.168.0.1:8080", where "192.168.0.1" is IP address from "web" adapter and 8080 is value from "system.adapter.adapterName.X => native.port".
objects - static objects for all instances of adapter (xxx.object)
By installation of adapter (not the instance creation) some predefined objects (normally that describe something) can be created automatically. These objects must not depend on some specific instance and are common for all instances of this adapter. For example [hm-rpc](https://github.com/ioBroker/ioBroker.hm-rpc/blob/master/io-package.json) adapter has the structure description of all HomeMatic devices.
Here are two views defined for hm-rpc adapter: "listDevices" and "paramsetDescription". They returns the set of filtered by view condition objects from data store. It can effective (if CouchDB used) request the specified list of objects.
Please check [https://github.com/ioBroker/ioBroker.template](https://github.com/ioBroker/ioBroker.template) for a template of your own adapter.
If you want to create a widget or an adapter with a widget please check [ioBroker.vis-template]https://github.com/ioBroker/ioBroker.vis-template) for a template of your own adapter.
var utils = require(__dirname + '/lib/utils'); // Get common adapter utils - mandatory
```
This line loads module lib/utils.js. It has common for all adapters function to find the root of iobroker.js-controller. Because adapter can be installed in three different paths:
var adapter = utils.adapter('adapterName'); // - mandatory
```
This line creates the object "Adapter" with name "adapterName". It loads all configuration for adapterName.X instance where X is the instance number of adapter.
js-controller starts adapter as fork of own process with two parameters: instance and log level; like:
There is an attribute of adapter object to read the configuration of the instance: "adapter.config".
This object consist of "**native**" part of object "system.adapter.adapterName.X".
E.g. if **io-package.json** looks like:
```
{
"common": {
"name": "adapterName"
},
"native": {
"location": "Stuttgart",
"language": ""
}
}
```
So the adapter.config is equal to:
```
{
"location": "Stuttgart",
"language": ""
}
```
and has the data entered by user in configuration dialog.
You can access the **common **part of instance configuration with attribute "common" of object "adapter".
E.g. for the shown io-package.json "adapter.common" will be:
```
{
"name": "adapterName"
}
```
To access the **ioBroker configuration** (stored in file iobroker-data/iobroker.json) set the adapter option systemConfig to true.
```
var adapter = utils.adapter({
name: 'adapterName', // adapter name
systemConfig: true // load ioBroker configuration into systemConfig
});
```
To get the global date format the option "**useFormatDate**" must be set to true:
```
var adapter = utils.adapter({
name: 'adapterName', // adapter name
useFormatDate: true // load from system.config the global date format
});
```
Date format will be available under ```adapter.dateFormat```.
All other configurations can be read manually with ```getForeignObject``` function.
#### How to read state
There are two modes to read states in ioBroker adapter:
- event subscription (suggested)
- polling
To **subscribe** on own events the following command must be called:
```
adapter.subscribeStates('*'); // subscribe on all variables of this adapter instance with pattern "adapterName.X.*"
adapter.subscribeStates('memory*'); // subscribe on all variables of this adapter instance with pattern "adapterName.X.memory*"
```
To subscribe on other events:
```
adapter.subscribeForeignStates('yr.*.forecast.html'); // subscribe on variable "forecast.html" of all adapter instances "yr".
```
Wildcard "*" can be used in both functions.
After that you will get the event ["stateChange"](#most-important-events) and can do something with this value.
After subscription you will not get the actual state, because events will come only on change. To get the initial state you should perform "poll" one time at start (normally in "ready" event).
**Polling**
To read own states at start or to read the values with interval use function ```adapter.getState```, like here:
```
adapter.getState('myState', function (err, state) {
adapter.log.info(
'State ' + adapter.namespace + '.myState -' +
' Value: ' + state.val +
', ack: ' + state.ack +
', time stamp: ' + state.ts +
', last changed: ' + state.lc
);
});
```
Pay attention, that result will be returned asynchronous.
To read states of other adapters you should use ```adapter.getForeignState``` function. No wildcards are supported.
#### Commands and Statuses
We should distinguish between commands and statuses, when we are talking about states.
"Command" has **ack** flag as _false_ and will be sent by user (over vis, javascript-adapter, admin) to control the devices or specific adapter. Normally adapters (e.g. homematic) are subscribed on all own changes and if some state changes with _ack=false_, they will try to execute this command (e.g. light on).
"Status" has "ack" flag as _true_ and indicate that it is from device or service. E.g. if the weather adapter got new weather forecast, it will be published with _ack=true_ or if **homematic** thermometer measures new temperature, it will be published with _ack=true_ too. Even if the user physically will switch the light on, the new state will be published with ack=true.
_Ack=false_ will be normally overwritten by execution after the response from device.
E.g. if the user in "vis" has pressed the button and sent command _"hm-rpc.0.kitchen.light"=ON_. The **Socketio** adapter will send to the _hm-rpc.0_ instance the new state with _"kitchen.light" = {val: 1, ack: false}_.
Homematic adapter is subscribed for all states of _hm-rpc.0_ and if the new state will be received with _ack=false_, it sends the new value to the physical switch.
Physical switch executes the command and sends to _hm-rpc_ adapter the new own state **ON**. The _hm-rpc.0_ adapter publishes the new status of state _"hm-rpc.0.kitchen.light"={val: 1, ack: true}_ (with time stamps).
This change will not be executed by _hm-rpc_ adapter, because **ack** is _true_. And this is an acknowledgment from physical device.
#### How to write state
States can be written as commands or as statuses. For that ```adapter.setState``` and ```adapter.setForeignState``` must be used:
```
adapter.setForeignState('otherAdapter.X.someState', 1); // Control other adapter (there is no need to control own state, we can do it directly)
adapter.setState('myState', 1, true); // indicate new status of own state
adapter.setState('myState', {val: 1, ack: true}); // the same as above
adapter.setState('myState', 1, true, function (err) {
// analyse if the state could be set (because of permissions)
if (err) adapter.log.error(err);
});
```
**Note**: Following commands are identical
```
adapter.setState('myState', 1, false);
adapter.setState('myState', 1);
```
#### Structure of state
State is a javascript object with following attributes:
- **val**: Value of state (desired value or actual value)
- **ack**: direction flag. **false** for desired value and **true** for actual value. Default: false (command)
- **ts**: time stamp as the number of milliseconds between midnight of January 1, 1970 and the specified date. Result of method getTime() of Javascript object Date. Default: actual time.
- **lc**: last change time stamp. Same format as **ts**, but the time stamp of value change. It can be so that the value will be updated, but the value will stay the same. In this case **lc** will not be changed.
- **from**: name of the adapter instance, that set the value, e.g. "system.adapter.web.0" (In case of vis)
- **expire**: (optional) there is a possibility to set the expire timeout in seconds. After this period of time the variable will be set to "null". It will be used e.g. by "active" states of the adapter instances. If adapter instance will not trigger "active" state in 30 seconds it will be marked as **down**. To set state with expiration use following code ```setState('variable', {val: true, expire: 30})```
- **q**: (optional) Quality. See [here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#states) the description
#### Running modes of adapter
Adapter can run in different [modes](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#adapterinstance-commonmode). The mode for adapter can me defined over ```common.mode``` attribute.
- **none** - this adapter will not be started.
- **daemon** - always running process (will be restarted if process exits)
- **subscribe** - is started when state system.adapter.<adapter-name>.<instance-number>.alive changes to true. Is killed when .alive changes to false and sets .alive to false if process exits (will not be restarted when process exits)
- **schedule** - is started by schedule found in system.adapter.<adapter-name>.<instance-number>.common.schedule - reacts on changes of .schedule by rescheduling with new state
- **once** - this adapter will be started every time the system.adapter.<adapter-name>.<instance-number> object changed. **It will not be restarted after termination.**
Normally adapters should use mode **daemon**.
If adapter just checks something every X minutes it should use mode "schedule" and define cron schedule in common.schedule (e.g. "1 * ** *" - every hour)
#### How to read object
Objects can be read with getObject or getForeignObject command:
```
adapter.getForeignObject('otherAdapter.X.someState', function (err, obj) {
if (err) {
adapter.log.error(err);
} else {
adapter.log.info(JSON.stringify(obj));
}
});
adapter.getObject('myObject', function (err, obj) {
});
```
Functions are always asynchronous.
Objects of adapter must be organized in devices, channels and states.
To write the objects generally two functions can be used: setObject, setForeignObject.
But there are many help functions to modify objects:
- extendObject, extendForeignObject,
- delObject, delForeignObject,
- setObjectNotExists, setForeignObjectNotExists
- createDevice, deleteDevice
- createChannel, deleteChannel,
- createState, deleteState
- addStateToEnum, deleteStateFromEnum
extendObject is just reads object, merges with given one and write object back.
Difference between xxxObject and xxxForeignObject is that xxxObject automatically extends the object id with "adapter.instance." text.
Functions are always asynchronous.
```
adapter.getForeignObject('otherAdapter.X.someState', function (err, obj) {
if (err) {
adapter.log.error(err);
} else {
adapter.log.info(JSON.stringify(obj));
obj.native = {}; // modify object
adapter.setForeignObject(obj._id, obj, function (err) {
if (err) adapter.log.error(err);
});
}
});
```
#### info.connection
If the adapter establishes and monitors some connection (e.g. to controlled device), it should create and maintenance **info.connection** variable.
If it happens, the status of connection will be shown in the instance's list in "admin" and if desired, the quality of states will be set up depends on the connection status.
## Functions
- setObject = function setObject(id, obj, callback)
- extendObject = function extendObject(id, obj, callback)
- setForeignObject = function setForeignObject(id, obj, callback)
- extendForeignObject = function extendForeignObject(id, obj, callback)
- getEnum = function getEnum(_enum, callback)
- getEnums = function getEnums(_enumList, callback)
- getForeignObjects = function getForeignObjects(pattern, type, enums, callback)
- findForeignObject = function findForeignState(id, type, callback)
- getForeignObject = function getForeignObject(id, callback)
- delObject = function delObject(id, callback)
- delForeignObject = function delForeignObject(id, callback)
- subscribeObjects = function subscribeObjects(pattern)
- subscribeForeignObjects = function subscribeObjects(pattern)
- setObjectNotExists = function setObjectNotExists(id, object, callback)
- setForeignObjectNotExists = function setForeignObjectNotExists(id, obj, callback)
- createDevice = function createDevice(deviceName, common, _native, callback)
- createChannel = function createChannel(parentDevice, channelName, roleOrCommon, _native, callback)
- createState = function createState(parentDevice, parentChannel, stateName, roleOrCommon, _native, callback)
- deleteDevice = function deleteDevice(deviceName, callback)
- addChannelToEnum = function addChannelToEnum(enumName, addTo, parentDevice, channelName, callback)
- deleteChannelFromEnum = function deleteChannelFromEnum(enumName, parentDevice, channelName, callback)
- deleteChannel = function deleteChannel(parentDevice, channelName, callback)
- deleteState = function deleteState(parentDevice, parentChannel, stateName, callback)
- getChannelsOf = function getChannelsOf(parentDevice, callback)
- getStatesOf = function getStatesOf(parentDevice, parentChannel, callback)
- addStateToEnum = function addStateToEnum(enumName, addTo, parentDevice, parentChannel, stateName, callback)
- deleteStateFromEnum = function deleteStateFromEnum(enumName, parentDevice, parentChannel, stateName, callback)
- rmDir = function rmDir(path, callback)
- mkDir = function mkDir(path, mode, callback)
- readDir = function readDir(adapter, path, callback)
- unlink = function unlink(adapter, name, callback)
- rename = function rename(adapter, oldName, newName, callback)
- mkdir = function mkdir(adapter, dirname, callback)
- readFile = function readFile(adapter, filename, options, callback)
- writeFile = function writeFile(adapter, filename, data, mimeType, callback)
- formatDate = function formatDate(dateObj, isSeconds, _format)
- sendTo = function sendTo(objName, command, message, callback)
- sendToHost = function sendToHost(objName, command, message, callback)
- setState = function setState(id, state, callback)
- setForeignState = function setForeignState(id, state, callback)
- getState = function getState(id, callback)
- getStateHistory = function getStateHistory(id, start, end, callback)
- getForeignStateHistory = function getStateHistory(id, start, end, callback)
- idToDCS = function idToDCS(id)
- getForeignState = function getForeignState(id, callback)
- delForeignState = function delForeignState(id, callback)
- delState = function delState(id, callback)
- getStates = function getStates(pattern, callback)
- getForeignStates = function getForeignStates(pattern, callback)
- subscribeForeignStates = function subscribeForeignStates(pattern)
- unsubscribeForeignStates = function unsubscribeForeignStates(pattern)
- subscribeStates = function subscribeStates(pattern)
- pushFifo = function pushFifo(id, state, callback)
- trimFifo = function trimFifo(id, start, end, callback)
- getFifoRange = function getFifoRange(id, start, end, callback)
- getFifo = function getFifo(id, callback)
- lenFifo = function lenFifo(id, callback)
- subscribeFifo = function subscribeFifo(pattern)
- getSession = function getSession(id, callback)
- setSession = function setSession(id, ttl, data, callback)
- destroySession = function destroySession(id, callback)
- getMessage = function getMessage(callback)
- lenMessage = function lenMessage(callback)
- setBinaryState = function setBinaryState(id, binary, callback)
- getBinaryState = function getBinaryState(id, callback)
- getPort = function adapterGetPort(port, callback)
- checkPassword = function checkPassword(user, pw, callback)
- setPassword = function setPassword(user, pw, callback)
- checkGroup = function checkGroup(user, group, callback)
- stop (common.mode: subscribe, schedule, once)
- log.debug(msg)
- log.info(msg)
- log.warn(msg)
- log.error(msg)
EVENTS:
- ready
- objectChange(id, obj) (Warning obj can be null if deleted)
- message(obj)
- stateChange(id, state) (Warning state can be null if deleted)
- unload
## How to create instance
Before published to npm: copy into ioBroker/node_modules, go to "admin" and add instance
After published at npm: go to ioBroker/ and write "npm install iobroker.xxx --production", go to "admin" and add instance
## How to debug
- Start ioBroker
- Add instance of adapter
- Disable instance of adapter
- Start WebStorm
- Create Configuration for Debug with node.js
- Flags for application: --force, instance, log level (you can start the adapter as "node xxx.js 1 debug --force", 1 is instance index (by default 0, debug is log level and --force means ignore "enabled: false" settings)
## admin.html
- function showMessage(message, title, icon)
- function getObject(id, callback)
- function getState(id, callback)
- function getEnums(_enum, callback)
- function getIPs(host, callback)
- function fillSelectIPs(id, actualAddr, noIPv4, noIPv6)
- function sendTo(_adapter_instance, command, message, callback)
- function sendToHost(host, command, message, callback)
- function fillSelectCertificates(id, type, actualValued)
- function getAdapterInstances(_adapter, callback)
- function getIsAdapterAlive(_adapter, callback)
- function addToTable(tabId, value, $grid, _isInitial)
- function enumName2Id(enums, name)
- function editTable(tabId, cols, values, top, onChange)