You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kaifa/适配器指南.md

951 lines
44 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 适配器开发人员指南 (javascript/NodeJS)
## 内容
- [数据结构 - 对象和状态](#对象和状态)
- [适配器的目录结构](#适配器的目录结构)
- [文件命名](#文件命名)
- [io-package.json 的结构](#io-package.json结构)
- [Common fields](#common-fields)
- [Object fields](#object-fields)
- [Instance object fields](#instance-object-fields)
- [package.json 的结构](#package.json结构)
- [部署](#部署)
- [How to create own adapter](#how-to-create-own-adapter)
- [Structure of main.js](#structure-of-mainjs)
- [Options of adapter](#options-of-adapter)
- [Attributes of adapter object](#attributes-of-adapter-object)
- [Most important events](#most-important-events)
- [日志记录](#logging)
- [实例配置](#instance-configuration)
- [如何读取状态](#how-to-read-state)
- [Commands and Statuses](#commands-and-statuses)
- [如何写状态](#如何写状态)
- [Structure of state](#structure-of-state)
- [Running modes of adapter](#running-modes-of-adapter)
- [读取对象](#读取对象)
- [How to write object](#how-to-write-object)
- [info.connection](#infoconnection)
- [Functions](#functions)
- [如何创建实例](#如何创建实例)
- [How to debug](#how-to-debug)
- [admin.html](#adminhtml)
- [最佳实践](#best-practice)
## 对象和状态
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.
oBroker中的适配器是一个独立的进程它在中央数据存储中读取和写入对象和状态。数据存储可以表示为数据库redis / couchDB或只是文本文件但连接方式始终相同 - 通过API。这意味着开发人员不应该关心数据库是什么以及如何在那里存储和交付数据。
There are two types of data in the storage:
存储中有两种类型的数据:
- Objects
- States
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).
对象是某些数据点的静态描述。状态是数据点的动态值。所以通常对于每个州都有一个描述对象。(但反之亦然)
Objects additionally describe:
- configuration of hosts
- description of adapters
- configuration of adapter instances
- content of configuration HTML files
- content of WEB files
- enumerations
- users
- hierarchies of states (channels and devices)
You can explore the objects and the current state values in admin adapter on the "Objects"-tab.
您可以在“对象”选项卡上的管理适配器中浏览对象和当前状态值。
The name of object consists of different parts. Every part is divided by "." from each other.
There is a system objects (name starts with _ or "system.") and adapter objects (name starts with _adapterName_).
对象的名称由不同的部分组成。每个部分除以“。” 彼此。有一个系统对象名称以_或“system。”开头和适配器对象名称以adapterName开头
**Note:** here and forth **adapterName** is the name of the adapter that a developer wants to create.
注意 adapterName是开发人员想要创建的适配器的名称。
The states can be grouped in channels and the channels in devices. Here is a example of Homematic groups and channels:
状态可以分组在通道和设备中的通道中。以下是Homematic组和频道的示例
- hm-rpc.0.IEQ1234567 - device
- hm-rpc.0.IEQ1234567.0 - channel
- hm-rpc.0.IEQ1234567.0.INFO - state
- hm-rpc.0.IEQ1234567.0.RSSI - state
- hm-rpc.0.IEQ1234567.0 - channel
- hm-rpc.0.IEQ1234567.0.STATE - state
- hm-rpc.0.IEQ1234567.0.BATTERY - state
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.
状态ID必须始终以通道名称和通道名称与设备名称开头
It is used to build the coordination of device, channels and states in hierarchy.
它用于在层次结构中建立设备,通道和状态的协调。
**Note:** If adapter is not so complex, the devices and even channels can be omitted.
注意:如果适配器不是那么复杂,可以省略设备和偶数通道。
**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)
Adapter只是文件包放在node_modules目录中。对于每个适配器可以在对象“ system.adapter.adapterName ”中找到此适配器的描述。它只是来自io-package.json文件的“common”和“native”字段。当iobroker安装adapterName或iobroker add adapterName被调用时将自动创建此条目。如果使用npm install iobroker.adapterName安装适配器则在创建第一个实例之前不会创建任何条目。但它并不那么重要。“更新”信息所需的信息将直接从io-package.json中读取。可以在此处找到适配器的常用设置的完整列表
**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_.
**Instance** 是适配器的实例。根据适配器的类型可以创建多个实例但对于某些适配器创建多个实例是没有用的。例如在vis或rickshaw的情况下只能创建一个实例。此行为由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:
对于每个实例,可以在“ system.adapter.adapterName.X ”ID 下的数据存储中找到配置对象其中X是适配器实例编号。它包含适配器的此实例的设置。通常它由“常见”和“原生”设置组成。常见设置是
- enabled: true/false;
- host: host name where this instance must run;
- mode: none, daemon, subscribe, schedule, once;
Description can be found [here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#instance)
描述可以在这里找到
"Native" settings consist of **specific** configurations for this adapter, e.g.: IP address of device, device settings and so on.
“本机”设置由此适配器的特定配置组成例如设备的IP地址设备设置等。
**Note:** Instances can run on different hosts (in multi-hosts systems) and the adapters can have different version on different hosts.
注意:实例可以在不同的主机上运行(在多主机系统中),并且适配器可以在不同的主机上具有不同的版本。
All adapter instance object IDs starts with _adapterName.X_, where X is number of adapter instance.
所有适配器实例对象ID都以adapterName.X开头其中X是适配器实例的编号。
Objects have different [types](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#object-types) for different purposes.
对象具有不同类型以用于不同目的。
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.memHeapTotal: memory usage
- system.adapter.adapterName.X.memHeapUsed: memory usage
- system.adapter.adapterName.X.memRss: memory usage
- 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.
## 适配器的目录结构
Adapter package must have some mandatory directories and files:
适配器包必须具有一些必需的目录和文件:
- admin (mandatory directory)
- index.html
- 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._
注意lib / utils.js是所有适配器的公共文件用于检测js-controller的位置以及相应的iobroker.js-controller / lib / adapter.js路径。大多数实际的utils.js都可以在这里下载。请勿更改此文件。
## 文件命名
Adapter must must follow some naming convention to be accepted and started by ioBroker controller.
适配器必须遵循某些命名约定以便由ioBroker控制器接受和启动。
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.
## io-package.json结构
io-package.json is used by js-controller to show information about adapter and to know how to treat it.
Complete description of all fields in common part can be found [here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#adapter)
js-controller使用io-package.json来显示有关适配器的信息并知道如何处理它。可以在[here](https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md#adapter)找到共同部分中所有字段的完整描述
io-package.json will be read by "admin" to find out the online version of adapter.
io-package.json将被“admin”读取以找出适配器的在线版本。
### Common fields
### 公共字段
Most important fields in **common** are:
- name: mandatory. Name of adapter without "ioBroker.", like adapterName and not "ioBroker.adapterName"
- version: mandatory. Must be same as in package.json.
- title: mandatory. Short name of adapter, like "Adapter name"
- desc: mandatory. Description of adapter. It can be a string like, "This adapter does this and that" or can be an object like:
```
{
"en": "This adapter does this and that",
"de": "Dieser Aadpter macht dies und jenes",
"ru": "Этот драйвер делает то и это"
}
```
If no entry exists for the current language, the description in english will be shown.
如果当前语言不存在条目,则将显示英语说明。
- platform: 必须. Actually only "Javascript/Node.js" is supported.
- mode: 必须. 适配器启动的模式。
- enabled: optional. When set to true, the instance will be activated after addition.
- license": license name under what the adapter is licensed;
- loglevel": 创建实例后将设置的初始日志级别。 Can be "debug", "info", "warn" or "error"
- readme": link to readme page in internet. Used by admin adapter to show the link if "?" button clicked.
- icon": icon name (not the path) of adapter icon. This icon must be in admin directory of adapter.
- extIcon: icon path in internet to show the icon for adapter if the adapter is not yet installed.
- keywords: key words as array to enable search in admin adapter.
- localLink: link to adapter "www" files (or adapter server). "http://192.168.0.100"
- type: following types are possible: hardware, social, storage, visual, api, scripting, weather, other, connection.
- messagebox: optional. Must be set to **true** if adapter should receive system messages.
**Note:** localLink can have special keys, that will be replaced by real values.
注意: localLink可以有特殊键它将被实际值替换。
- %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.
objects - 适配器的所有实例的静态对象xxx.object通过安装适配器而不是实例创建可以自动创建一些预定义对象通常描述某些内容。这些对象不能依赖于某些特定实例并且对于此适配器的所有实例都是通用的。例如hm-rpc适配器具有所有HomeMatic设备的结构描述。
此外可以定义新视图。在SQL中它们被称为“存储过程”在couchDB中也称为视图。
**注意**不要与“vis”视图混合使用。
For view definitions the javascript language is used. Here is the sample:
```
{
"_id": "_design/hm-rpc",
"language": "javascript",
"views": {
"listDevices": {
"map": "function(doc) {\n if (doc._id.match(/^hm-rpc\\.[0-9]+\\.\\*?[A-Za-z0-9_-]+(\\.[0-9]+)?$/)) {\n emit(doc._id, {ADDRESS:(doc.native?doc.native.ADDRESS:''),VERSION:(doc.native?doc.native.VERSION:'')});\n }\n}"
},
"paramsetDescription": {
"map": "function(doc) {\n if (doc._id.match(/^hm-rpc\\.meta/) && doc.meta.type === 'paramsetDescription') {\n emit(doc._id, doc);\n }\n}"
}
}
}
```
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.
是为hm-rpc适配器定义的两个视图“listDevices”和“paramsetDescription”。它们从数据存储中返回由视图条件对象过滤的集合。它可以有效如果使用CouchDB请求指定的对象列表。
要使用视图:
```
adapter.objects.getObjectView('hm-rpc', 'listDevices',
{startkey: 'hm-rpc.' + adapter.instance + '.', endkey: 'hm-rpc.' + adapter.instance + '.\u9999'},
function (err, doc) {
if (doc && doc.rows) {
for (var i = 0; i < doc.rows.length; i++) {
var id = doc.rows[i].id;
var obj = doc.rows[i].value;
console.log('Found ' + id + ': ' + JSON.stringify(obj));
}
if (!doc.rows.length) console.log('No objects found.');
} else {
console.log('No objects found: ' + err);
}
});
```
Usage of _startkey_ and _endkey_ can be found on the [same page](http://guide.couchdb.org/editions/1/en/views.html) too.
**注意:** 视图的使用是可选的并且需要开发人员关于CouchDB的基本知识水平。
### Instance object fields
### 实例对象字段
可以在io-package.json的 _instanceObjects_ 中定义一些具有类型状态的特定对象或对象。
对于每个创建的实例,将创建来自 _instanceObjects_ 字段的所有条目。
例如适配器_hm-rpc_ 为每个实例创建状态 "updated" 以向其他适配器发出信号一些新设备出现在数据存储中并且必须由hm-rega处理。
```
"instanceObjects": [
{
"_id": "updated",
"type": "state",
"common": {
"name": "Some new devices added",
"type": "bool",
"read": true,
"write": true
}
}
]
```
没有必要提供对象的完整路径因为适配器实例未知所以无法完成。您可以在common.name中使用特殊单词 "%INSTANCE%" 在 _common.name_ 中显示它。例如:
```
"name": "Some new devices added in hm-rpc.%INSTANCE%",
```
Will be expanded to
```
"name": "Some new devices added in hm-rpc.0,
```
by creation of first instance.
## package.json结构
package.json是npm数据包标准描述文件完整描述可以在[https://docs.npmjs.com/files/package.json](https://docs.npmjs.com/files/package.json).下找到。
Short structure of package.json:
```
{
"name": "iobroker.adapterName",
"version": "0.0.1",
"description": "Adapter XXX",
"author": "myName<myemail@mail.com>"
"homepage": "https://github.com/yourgit/ioBroker.adapterName",
"readme": "https://github.com/yourgit/ioBroker.adapterName/blob/master/README.md",
"keywords": ["ioBroker", "adapterName"],
"repository": {
"type": "git",
"url": "https://github.com/yourgit/ioBroker.adapterName"
},
"dependencies": {
"myPacket1": "~0.3.1",
"myPacket2": "~2.1.0"
},
"devDependencies": {
"grunt": "~0.4.4",
"grunt-replace": "~0.7.6",
"grunt-contrib-jshint": "~0.10.0",
"grunt-jscs": "~0.6.1",
"grunt-http": "~1.4.1",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-compress": "~0.8.0",
"grunt-contrib-copy": "~0.5.0",
"grunt-exec": "~0.4.5"
},
"bugs": {
"url": "https://github.com/yourgit/ioBroker.adapterName/issues"
},
"main": "main.js",
"license": "MIT"
}
```
- 所有字段都是必填项。devDependencies也应该在里面以启用grunt任务。
### Deploying
### 部署
建议在github上使用代码。在代码稳定并允许安装适配器之后您可以通过要求他们安装适配器来将适配器共享给其他用户如下所示
```
npm install https://github.com/yourName/iobroker.adapterName/tarball/master/
```
如果一切正常并且您得到了用户的积极反馈您可以在npm上发布适配器。如果在发布之前你将在github上创建realease会很好。
可以使用以下命令完成发布:
```
npm publish
```
Call it in the adapter directory. Be sure, that you deleted all other files except required (e.g. .idea) or add them to ".gitignore" file.
Of course you must first create the account on [npm](https://www.npmjs.com/signup)
**注意:** 您不能使用相同版本发布两次代码。在发布之前增加package.json和io-package.json中的版本。
After the adapter is tested and other users find it useful, it can be taken into common repository, so it can be installed via "admin" adapter.
## How to create own adapter
## 如何创建自己的适配器
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.
## Structure of main.js
## main.js的结构
```
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:
该行加载模块 lib/utils.js。所有适配器功能都有共同点可以找到iobroker.js-controller的根目录。因为适配器可以安装在三个不同的路径中
- .../iobroker/node_modules/iobroker.adapterName - this is standard path and suggested to use
- .../iobroker.js-controller/node_modules/iobroker.adapterName - used by debugging
- .../iobroker.js-controller/adapter/adapterName - old style (deprecated)
=utils.js什么事都不做除了查找iobroker.js-controller / lib / adapter.js文件并加载它。
```
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:
```
child_process.fork('pathToAdapter/main.js', '0 info');
```
它将全部在adapter.js中自动处理适配器的开发人员不必关心它。
适配器支持3个其他启动标志
- --install - Starts adapter even if no configuration exists. Used by adapter to execute some install procedure by installation of adapter.
- --force - Starts adapter even if it is disabled in configuration
- --logs - Show logs in the console, if they shown only in log table.
```
var myPacket1= require('myPacket1'); // add own module
```
Then you can load all other modules that required in adapter, like 'fs', 'require' and so on. Just do not forget to declare them in package.json.
然后您可以加载适配器中所需的所有其他模块如“fs”“require”等。只是不要忘记在package.json中声明它们。
### Options of adapter
### 适配器的选项
You can create adapter object with just by name, like ```utils.adapter('adapterName')``` or with additional parameters, like:
```
var adapter = utils.adapter({
name: 'adapterName', // mandatory - name of adapter
dirname: '', // optional - path to adapter (experts only)
systemConfig: false, // optional - if system global config must be included in object
// (content of iobroker-data/iobroker.json)
config: null, // optional - alternate global configuration for adapter (experts only)
instance: null, // optional - instance of the adapter
useFormatDate: false, // optional - if adapter wants format date according to global settings.
// if true (some libs must be preloaded) adapter can use "formatDate" function.
logTransporter: false,// optional - if adapter collects logs from all adapters (experts only)
objectChange: null, // optional - handler for subscribed objects changes
message: null, // optional - handler for messages for this adapter
stateChange: null, // optional - handler for subscribed states changes
ready: null, // optional - will be called when adapter is initialized
unload: null, // optional - will be called by adapter termination
noNamespace: false // optional - if true, stateChange will be called with id that has no namespace. Instead "adapter.0.state" => "state"
});
```
All handlers can be simulated by events (see below), like:
所有处理程序都可以通过事件进行模拟(见下文),如:
```
adapter.on('ready', function () {
main();
});
```
### Attributes of adapter object
### 适配器对象的属性
As you created "Adapter" object with
```
var adapter = utils.adapter('adapterName');
```
following attributes will be created in this object instance:
- *name* - Name of adapter, e.g "adapterName"
- *host* - Host name, where the adapter instance runs
- *instance* - instance number of this adapter instance
- *namespace* - Namespace of adapter objects, e.g "adapterName.0"
- *config* - native part of adapter settings
- *common* - common part of adapter settings
- *systemConfig* - content of _iobroker-data/iobroker.json_ (only if options.systemConfig = true)
- *adapterDir* - path to the adapter folder
- *ioPack* - content of io-package.json
- *pack* - content of package.json
- *log* - logger object
- *version* - adapter version
- *states* - (experts only)
- *objects* - (experts only)
- *connected* - if adapter connected to host
#### Most important events
#### 最重要的事件
```
adapter.on('objectChange', function (id, obj) {
adapter.log.info('objectChange ' + id + ' ' + JSON.stringify(obj));
});
```
```
adapter.on('stateChange', function (id, state) {
adapter.log.info('stateChange ' + id + ' ' + JSON.stringify(state));
// you can use the ack flag to detect if state is command(false) or status(true)
if (!state.ack) {
adapter.log.info('ack is not set!');
}
});
```
- *Entry point*. 在main中进行所有初始化因为在“ready”之前没有配置。Make all initialisations in main, because before "ready" there is no configuration.
```
adapter.on('ready', function () {
main();
});
```
#### Logging
#### 日志记录
能够记录事件以进行调试和控制是非常重要的。以下函数可用于记录事件:
```
adapter.log.debug("debug message"); // log message with debug level
adapter.log.info("info message"); // log message with info level (enabled by default for all adapters)
adapter.log.warn("warning"); // log message with info warn
adapter.log.error("error"); // log message with info error
```
无需指明消息的来源或时间。这些属性将自动添加,例如:
```admin-0 2015-07-10 17:35:52 info successful connection to socket.io from xx.yy.17.17```
当然也可以使用console.logconsole.debug或console.error但只有在控制台或编程IDE中手动启动适配器时这些消息才会可见。
#### Instance configuration
#### 实例配置
适配器对象的属性用于读取实例的配置: "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": ""
}
}
```
所以adapter.config等于
```
{
"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"
}
```
要访问ioBroker配置存储在文件iobroker-data / iobroker.json中请将适配器选项systemConfig设置为true。
```
var adapter = utils.adapter({
name: 'adapterName', // adapter name
systemConfig: true // load ioBroker configuration into systemConfig
});
```
要获取全局日期格式,必须将选项 "**useFormatDate**" 设置为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```.
可以使用```getForeignObject```功能手动读取所有其他配置。
#### 如何读状态
在ioBroker适配器中有两种读取状态的模式
- event subscription 活动订阅(推荐)
- polling 轮询
要订阅 **subscribe** 事件,必须调用以下命令:
```
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*"
```
订阅其他活动:
```
adapter.subscribeForeignStates('yr.*.forecast.html'); // subscribe on variable "forecast.html" of all adapter instances "yr".
```
通配符 “*” 可用于两种功能。
之后,您将获得事件 ["stateChange"](#most-important-events) 并可以使用此值执行某些操作。
订阅后,您将无法获得实际状态,因为事件只会在变化时出现。要获得初始状态,您应该在开始时执行“轮询”一次(通常在 "ready" event
**轮询**
要在开始时读取自己的状态或使用间隔使用功能读取值 ```adapter.getState``` ,如下所示:
```
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.
注意,结果将异步返回。
要读取其他适配器的状态,您应该使用```adapter.getForeignState```函数。不支持通配符。
#### 命令和状态
我们应该区分命令和状态。
“Command”将 **ack** 标志设置为false并由用户通过visjavascript-adapteradmin发送以控制设备或特定适配器。
通常适配器例如homematic在所有自己的更改上订阅如果某些状态在ack = false时更改则它们将尝试执行此命令例如亮起
“Status”将“ack”标志设置为true表示它来自设备或服务。
例如如果天气适配器获得新的天气预报它将以ack = true发布
又如如果温度计测量新的温度它也将以ack = true发布。即使用户在物理上打开指示灯新状态也将以ack = true发布。
_Ack=false_ will be normally overwritten by execution after the response from device.
Ack = false通常会在设备响应后执行。
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}_.
例如如果“vis”中的用户按下按钮并发送命令“hm-rpc.0.kitchen.light”= ON。该Socketio适配器将发送到HM-rpc.0实例的新状态“kitchen.light”= {VAL1ACK假}。
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.
Homematic适配器订阅了hm-rpc.0的所有状态如果接收新状态ack = false它会将新值发送到物理交换机。
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).
物理交换机执行命令并向hm-rpc适配器发送新的自身状态ON。所述HM-rpc.0适配器发布状态的新状态“HM-rpc.0.kitchen.light”= {VAL1ACK真}(具有时间戳)。
This change will not be executed by _hm-rpc_ adapter, because **ack** is _true_. And this is an acknowledgment from physical device.
hm-rpc适配器不会执行此更改因为ack为true。这是来自物理设备的确认。
#### 如何写状态
状态可以写为命令或状态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);
});
```
**注意**:以下命令是相同的
```
adapter.setState('myState', 1, false);
adapter.setState('myState', 1);
```
#### 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**: 时间戳记为1970年1月1日午夜到指定日期之间的毫秒数。Javascript对象Date的方法getTime的结果。默认值实际时间。
- **lc**: 最后更改时间戳。 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**: 适配器实例的名称, that set the value, e.g. "system.adapter.web.0" (In case of vis)
- **expire**: (optional) 以秒为单位设置过期超时。在这段时间之后变量将被设置为“null”。它将用于例如适配器实例的“活动”状态。如果适配器实例在30秒内不会触发“活动”状态则它将被标记为关闭。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.
适配器可以以不同的模式运行。适配器的模式可以在common.mode属性上定义。
- **none** - 不会启动此适配器.
- **daemon** - 始终运行进程(如果进程退出,将重新启动)
- **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.**
通常,适配器应使用模式守护程序。
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)
#### 读取对象
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) {
});
```
函数始终是异步的。
Objects of adapter must be organized in devices, channels and states.
See: getForeignObjects, findForeignObject, getForeignObject, getDevices, getChannels, getStatesOf
#### 写对象
要编写对象通常可以使用两个函数setObjectsetForeignObject。但是有许多帮助函数来修改对象
- extendObject, extendForeignObject,
- delObject, delForeignObject,
- setObjectNotExists, setForeignObjectNotExists
- createDevice, deleteDevice
- createChannel, deleteChannel,
- createState, deleteState
- addStateToEnum, deleteStateFromEnum
extendObject只是读取对象与给定对象合并并将对象写回。
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.
函数始终是异步的。
```
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
如果适配器建立并监视某些连接例如对受控设备则应创建并维护info.connection变量。
如果发生这种情况连接状态将显示在“admin”的实例列表中如果需要将根据连接状态设置状态质量。
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)
- deleteStateFromEnum('', parentDevice, parentChannel, stateName);
- getDevices = function getDevices(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
## 如何创建实例
在发布到npm之前复制到ioBroker / node_modules转到“admin”并添加实例在npm发布后转到ioBroker /并写入“npm install iobroker.xxx --production”转到“admin”并添加实例
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)
- function getTableResult(tabId, cols)
## 最佳实践
...