Initial commit

This commit is contained in:
zhongjin 2018-12-25 22:05:19 +08:00
commit 398ec83597
117 changed files with 9969 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
node_modules
.idea
tmp
admin/i18n/flat.txt
admin/i18n/*/flat.txt
iob_npm.done
package-lock.json

10
.npmignore Normal file
View File

@ -0,0 +1,10 @@
gulpfile.js
tasks
tmp
test
.travis.yml
appveyor.yml
admin/i18n
iob_npm.done
package-lock.json
.idea

22
.travis.yml Normal file
View File

@ -0,0 +1,22 @@
os:
- linux
- osx
language: node_js
node_js:
- '6'
- '8'
- '10'
before_script:
- export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1)
- 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi'
- npm -v
- npm install winston@2.3.1
- 'npm install https://github.com/yunkong2/yunkong2.js-controller/tarball/master --production'
env:
- CXX=g++-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014-2018 bluefox<dogafox@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

313
README.md Normal file
View File

@ -0,0 +1,313 @@
![Logo](admin/mqtt.png)
# yunkong2 MQTT
==============
[![NPM version](http://img.shields.io/npm/v/yunkong2.mqtt.svg)](https://www.npmjs.com/package/yunkong2.mqtt)
[![Downloads](https://img.shields.io/npm/dm/yunkong2.mqtt.svg)](https://www.npmjs.com/package/yunkong2.mqtt)
[![Tests](https://travis-ci.org/yunkong2/yunkong2.mqtt.svg?branch=master)](https://travis-ci.org/yunkong2/yunkong2.mqtt)
[![NPM](https://nodei.co/npm/yunkong2.mqtt.png?downloads=true)](https://nodei.co/npm/yunkong2.mqtt/)
Requires node.js **6.0** or higher.
# MQ Telemetry Transport for yunkong2 (MQTT).
MQTT (formerly Message Queue Telemetry Transport) is a publish-subscribe based "light weight" messaging protocol for use on top of the TCP/IP protocol.
It is designed for connections with remote locations where a "small code footprint" is required and/or network bandwidth is limited.
The Publish-Subscribe messaging pattern requires a message broker. The broker is responsible for distributing messages to interested clients based on the topic of a message.
Historically, the 'MQ' in 'MQTT' came from IBM's MQ message queuing product line.
This adapter uses the MQTT.js library from https://github.com/adamvr/MQTT.js/
## Configuration
- **Type** - Select "Client" (If you want to receive and send messages to other broker) or "Server" if you want create own MQTT broker.
### Server settings
- **WebSockets** - if parallel to TCP Server, the WebSocket MQTT Server should run.
- **Port** - Port where the server will run (Default 1883). **WebSockets** will always run on port+1 (Default 1884)
- **SSL** - If TCP and WebSockets should run as secure server.
- **Authentication/User name** - If authentication required, you can specify username. It is suggested to always use SSL with authentication to not send passwords over unsequre connection.
- **Authentication/Password** - Password for user.
- **Mask to publish own states** - Pattern to filter yunkong2 states, which will be sent to clients. You can use wildcards to specify group of messages, e.g "*.memRss, mqtt.0.*" to get all memory states of all adapters and all states of adapter mqtt.0
- **Publish only on change** - New messages will be sent to client only if the state value changes. Every message sent by client will be accepted, even if the value does not changed.
- **Publish own states on connect** - by every client connection the all known states will be sent to client (defined by state mask), to say him which states has the yunkong2.
- **Prefix for all topics** - if set, every sent topic will be prepended with this prefix, e.g if prefix "yunkong2/" all states will have names like "**yunkong2**/mqtt/0/connected"
- **Trace output for every message** - Debug outputs.
- **Send states (ack=true) too** - Normally only the states/commands with ack=false will be sent to partner. If this flag is set every state independent from ack will be sent to partner.
- **Use different topic names for set and get** - if active, so every state will have two topics: ```adapter/instance/stateName``` and ```adapter/instance/stateName/set```. In this case topic with "/set" will be used to send non acknowledged commands (ack: false) and topic without "/set" to receive state updates (with ack: true). The client will receive sent messages back in this mode.
- **Interval before send topics by connection** - Pause between connection and when all topics will be sent to client (if activated).
- **Send interval** - Interval between packets by sending all topics (if activated). Used only by once after the connection establishment.
- **Use chunk patch** - There is a problem with last update of mqtt-packet, that frames will be sent directly to client and not first completely built and then sent to client. Some old clients do not like such a packets and do not work with new library. To fix it you can activate this flag.
### Client settings
- **URL** - name or ip address of the broker/server. Like "localhost".
- **Port** - Port of the MQTT broker. By default 1883
- **Secure** - If secure (SSL) connection must be used.
- **User** - if broker required authentication, define here the user name.
- **Password** - if user name is not empty the password must be set. It can be empty.
- **Password confirmation** - repeat here the password.
- **Subscribe Patterns** - Subscribe pattern. See chapter "Examples of using wildcards" to define the pattern. '#' to subscribe for all topics. 'mqtt/0/#,javascript/#' to subscribe for states of mqtt.0 and javascript
- **Publish only on change** - Store incoming messages only if payload is differ from actual stored.
- **Mask to publish own states** - Mask for states, that must be published to broker. '*' - to publish all states. 'io.yr.*,io.hm-rpc.0.*' to publish states of "yr" and "hm-rpc" adapter.
- **Publish all states at start** - Publish all states (defined by state mask) every time by connection establishment to announce own available states and their values.
- **Prefix for topics** - The prefix can be defined for own states. Like "/var/yunkong2/". Name of topics will be for example published with the name "/var/yunkong2/ping/192-168-1-5".
- **Test connection** - Press the button to check the connection to broker. Adapter must be enabled before.
- **Send states (ack=true) too** - Normally only the states/commands with ack=false will be sent to partner. If this flag is set every state independent from ack will be sent to partner.
- **Use different topic names for set and get** - if active, so every state will have two topics: ```adapter/instance/stateName``` and ```adapter/instance/stateName/set```. In this case topic with "/set" will be used to send non acknowledged commands (ack: false) and topic without "/set" to receive state updates (with ack: true).
## Install
```node yunkong2.js add mqtt```
## Usage
### How to test mqtt client:
- Set type to "Client".
- Leave port on 1883.
- Set URL as "broker.mqttdashboard.com"
- To get absolutely all topics(messages) set pattern to "#".
- To receive all topics for "/4MS" set pattern to "/4MS/#"
- To receive all topics for "/MS and "/floorish" set pattern to "/4MS/#, /floorish/#"
### Examples of using wildcards
The following examples on the use of wildcards, builds on the example provided in topic strings.
- "Sport"
- "Sport/Tennis"
- "Sport/Basketball"
- "Sport/Swimming"
- "Sport/Tennis/Finals"
- "Sport/Basketball/Finals"
- "Sport/Swimming/Finals"
If you want to subscribe to all Tennis topics, you can use the number sign '#', or the plus sign '+'.
- "Sport/Tennis/#" (this will receive "Sport/Tennis" and "Sport/Tennis/Finals")
- "Sport/Tennis/+" (this will receive "Sport/Tennis/Finals" but not "Sport/Tennis")
For JMS topics, if you want to subscribe to all Finals topics, you can use the number sign '#', or the plus sign '+'.
- "Sport/#/Finals"
- "Sport/+/Finals"
For MQTT topics, if you want to subscribe to all Finals topics, you can use the plus sign '+' .
"Sport/+/Finals"
### Tests
The broker was tested with following clients:
- http://mitsuruog.github.io/what-mqtt/
- http://mqttfx.jfx4ee.org/
- http://www.eclipse.org/paho/clients/tool/
## Todo
* Implement resend of "QoS 2" messages after a while.
Whenever a packet gets lost on the way, the sender is responsible for resending the last message after a reasonable amount of time. This is true when the sender is a MQTT client and also when a MQTT broker sends a message.
* queue packets with "QoS 1/2" for the offline clients with persistent session.
[Read here](https://www.hivemq.com/blog/mqtt-essentials-part-7-persistent-session-queuing-messages)
## Changelog
### 2.0.4 (2018-12-01)
* (Apollon77) Subscribe to topics after connect
### 2.0.3 (2018-08-11)
* (bluefox) Prefix in server was corrected
### 2.0.2 (2018-08-09)
* (bluefox) Behaviour of "set" topics was changed
### 2.0.1 (2018-07-06)
* (bluefox) Double prefix by client was fixed
### 2.0.0 (2018-03-05)
* (bluefox) broke node.js 4 support
* (bluefox) remove mqtt-stream-server
* (bluefox) partial mqtt5 support
### 1.5.0 (2018-03-05)
* (bluefox) The patch for wifi-iot removed
* (bluefox) the mqtt library updated
* (bluefox) implement QoS>0
### 1.4.2 (2018-01-30)
* (bluefox) Admin3 settings are corrected
### 1.4.1 (2018-01-13)
* (bluefox) Convert error is caught
* (bluefox) Ready for admin3
### 1.3.3 (2017-10-15)
* (bluefox) Fix sending of QOS=2 if server
### 1.3.2 (2017-02-08)
* (bluefox) Fix convert of UTF8 payloads
* (bluefox) optional fix for chunking problem
### 1.3.1 (2017-02-02)
* (bluefox) Update mqtt packages
* (bluefox) add Interval before send topics by connection ans send interval settings
* (bluefox) reorganise configuration dialog
### 1.3.0 (2017-01-07)
* (bluefox) Update mqtt packages
* (bluefox) configurable client ID
### 1.2.5 (2016-11-24)
* (bluefox) Fix server publishing
### 1.2.4 (2016-11-13)
* (bluefox) additional debug output
### 1.2.1 (2016-11-06)
* (bluefox) fix publish on start
### 1.2.0 (2016-09-27)
* (bluefox) implementation of LWT for server
* (bluefox) update mqtt package version
### 1.1.2 (2016-09-13)
* (bluefox) fix authentication in server
### 1.1.1 (2016-09-12)
* (bluefox) do not parse JSON states, that do not have attribute "val" to support other systems
### 1.1.0 (2016-07-23)
* (bluefox) add new setting: Use different topic names for set and get
### 1.0.4 (2016-07-19)
* (bluefox) convert values like "+58,890" into numbers too
### 1.0.3 (2016-05-14)
* (cdjm) change client protocolID
### 1.0.2 (2016-04-26)
* (bluefox) update mqtt module
### 1.0.1 (2016-04-25)
* (bluefox) Fix translations in admin
### 1.0.0 (2016-04-22)
* (bluefox) Fix error with direct publish in server
### 0.5.0 (2016-03-15)
* (bluefox) fix web sockets
* (bluefox) fix SSL
### 0.4.2 (2016-02-10)
* (bluefox) create object "info.connection"
* (bluefox) add reconnection tests
### 0.4.1 (2016-02-04)
* (bluefox) fix error with states creation
### 0.4.0 (2016-01-27)
* (bluefox) add tests
* (bluefox) client and server run
### 0.3.1 (2016-01-14)
* (bluefox) change creation of states by client
### 0.3.0 (2016-01-13)
* (bluefox) try to fix event emitter
### 0.2.15 (2015-11-23)
* (Pmant) fix publish on subscribe
### 0.2.14 (2015-11-21)
* (bluefox) fix error with wrong variable names
### 0.2.13 (2015-11-20)
* (Pmant) fix error with wrong variable name
### 0.2.12 (2015-11-14)
* (Pmant) send last known value on subscription (server)
### 0.2.11 (2015-10-17)
* (bluefox) set maximal length of topic name
* (bluefox) convert "true" and "false" to boolean values
### 0.2.10 (2015-09-16)
* (bluefox) protect against empty topics
### 0.2.8 (2015-05-17)
* (bluefox) do not ty to parse JSON objects
### 0.2.7 (2015-05-16)
* (bluefox) fix test button
### 0.2.6 (2015-05-16)
* (bluefox) fix names if from mqtt adapter
### 0.2.5 (2015-05-15)
* (bluefox) subscribe to all states if no mask defined
### 0.2.4 (2015-05-14)
* (bluefox) add state "clients" to server with the list of clients
### 0.2.3 (2015-05-14)
* (bluefox) fix some errors
### 0.2.2 (2015-05-13)
* (bluefox) fix some errors with sendOnStart and fix flag sendAckToo
### 0.2.0 (2015-05-13)
* (bluefox) translations and rename config sendNoAck=>sendAckToo
* (bluefox) lets create server not only on localhost
### 0.1.8 (2015-05-13)
* (bluefox) fix topic names in server mode
* (bluefox) implement subscribe
* (bluefox) update mqtt package
### 0.1.7 (2015-03-24)
* (bluefox) create objects if new state received
* (bluefox) update mqtt library
### 0.1.6 (2015-03-04)
* (bluefox) just update index.html
### 0.1.5 (2015-01-02)
* (bluefox) fix error if state deleted
### 0.1.4 (2015-01-02)
* (bluefox) support of npm install
### 0.1.2 (2014-11-28)
* (bluefox) support of npm install
### 0.1.1 (2014-11-22)
* (bluefox) support of new naming concept
### 0.1.0 (2014-10-23)
* (bluefox) Update readme
* (bluefox) Support of authentication for server and client
* (bluefox) Support of prefix for own topics
### 0.0.2 (2014-10-19)
* (bluefox) support of server (actual no authentication)
## License
The MIT License (MIT)
Copyright (c) 2014-2018, bluefox<dogafox@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,46 @@
{
"MQTT adapter settings": "MQTT adapter settings",
"Type:": "Type",
"Port:": "Port",
"User:": "User",
"Password:": "Password",
"Password confirmation:": "Password confirmation",
"URL:": "URL",
"Secure:": "Secure",
"Public certificate:": "Public certificate",
"Private certificate:": "Private certificate",
"Chained certificate:": "Chained certificate",
"Patterns:": "Subscribe patterns",
"Use WebSockets:": "Use WebSockets too",
"Connection": "Connection",
"MQTT Settings": "MQTT Settings",
"Client ID:": "Client ID",
"chars": "chars",
"ms": "ms",
"Interval before send topics by connection:": "Interval before send topics by connection",
"Send interval:": "Send interval",
"Use chunk patch:": "Use chunk patch",
"Divided by comma": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'",
"Mask to publish": "e.g. 'mqtt.0.*,javascript.*'",
"Store only on change:": "Publish only on change",
"Trace output for every message:": "Trace output for every message",
"Test connection": "Test connection with Server",
"Result: ": "Result: ",
"connected": "connected",
"Main settings": "Main settings",
"Connection settings": "Connection settings",
"Authentication settings": "Authentication settings",
"Adapter settings": "MQTT settings",
"Mask to publish own states:": "Mask to publish own states",
"Send states (ack=true) too:": "Send states (ack=true) too",
"Publish all states at start:": "Publish own states on connect",
"Use different topic names for set and get:": "Use different topic names for set and get",
"Publish states on subscribe:": "Publish states on subscribe",
"Set certificates or load it first in the system settings (right top).": "Set certificates or load it first in the system settings (right top).",
"Prefix for topics:": "Prefix for all topics",
"Max topic length:": "Max topic name length",
"Server": "Server/broker",
"Client": "Client/subscriber",
"Enable first the adapter to test client.": "Enable first the adapter to test client.",
"First save the adapter": "First save the adapter"
}

View File

@ -0,0 +1,46 @@
{
"MQTT adapter settings": "MQTT adapter settings",
"Type:": "Type",
"Port:": "Port",
"User:": "User",
"Password:": "Password",
"Password confirmation:": "Password confirmation",
"URL:": "URL",
"Secure:": "Secure",
"Public certificate:": "Public certificate",
"Private certificate:": "Private certificate",
"Chained certificate:": "Chained certificate",
"Patterns:": "Subscribe patterns",
"Use WebSockets:": "Use WebSockets too",
"Connection": "Connection",
"MQTT Settings": "MQTT Settings",
"Client ID:": "Client ID",
"chars": "chars",
"ms": "ms",
"Interval before send topics by connection:": "Interval before send topics by connection",
"Send interval:": "Send interval",
"Use chunk patch:": "Use chunk patch",
"Divided by comma": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'",
"Mask to publish": "e.g. 'mqtt.0.*,javascript.*'",
"Store only on change:": "Publish only on change",
"Trace output for every message:": "Trace output for every message",
"Test connection": "Test connection with Server",
"Result: ": "Result: ",
"connected": "connected",
"Main settings": "Main settings",
"Connection settings": "Connection settings",
"Authentication settings": "Authentication settings",
"Adapter settings": "MQTT settings",
"Mask to publish own states:": "Mask to publish own states",
"Send states (ack=true) too:": "Send states (ack=true) too",
"Publish all states at start:": "Publish own states on connect",
"Use different topic names for set and get:": "Use different topic names for set and get",
"Publish states on subscribe:": "Publish states on subscribe",
"Set certificates or load it first in the system settings (right top).": "Set certificates or load it first in the system settings (right top).",
"Prefix for topics:": "Prefix for all topics",
"Max topic length:": "Max topic name length",
"Server": "Server/broker",
"Client": "Client/subscriber",
"Enable first the adapter to test client.": "Enable first the adapter to test client.",
"First save the adapter": "First save the adapter"
}

378
admin/index.html Normal file
View File

@ -0,0 +1,378 @@
<html>
<head>
<!-- these 4 files always have to be included -->
<link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css"/>
<script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script>
<!-- these three files have to be always included -->
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<!-- you have to define 2 functions in the global scope: -->
<script type="text/javascript">
var onChange;
function encrypt(key, value) {
var result = '';
for(var i = 0; i < value.length; ++i) {
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
}
return result;
}
function decrypt(key, value) {
var result = '';
for(var i = 0; i < value.length; ++i) {
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
}
return result;
}
function showHideSettings() {
if ($('#type').val() === 'client') {
$('#_clientId').show();
$('#_url').show();
$('#test').show();
$('#_patterns').show();
$('#_certPublic').hide();
$('#_certPrivate').hide();
$('#_certChained').hide();
$('#_webSocket').hide();
$('#_publishOnSubscribe').hide();
} else {
$('#_clientId').hide();
$('#_url').hide();
$('#test').hide();
$('#_patterns').hide();
$('#_webSocket').show();
if ($('#ssl').prop('checked')) {
$('#_certPublic').show();
$('#_certPrivate').show();
$('#_certChained').show();
} else {
$('#_certPublic').hide();
$('#_certPrivate').hide();
$('#_certChained').hide();
}
$('#_publishOnSubscribe').show();
}
}
function init() {
showHideSettings();
$('#type').change(showHideSettings);
$('#ssl').change(showHideSettings);
$('#test').button().click(test);
}
function setValue(id, value) {
var $value = $('#' + id + '.value');
if ($value.attr('type') === 'checkbox') {
$value.prop('checked', value).change(function() {
onChange();
});
} else {
$value.val(value).change(function() {
onChange();
}).keyup(function() {
// Check that only numbers entered
if ($(this).hasClass('number')) {
var val = $(this).val();
if (val) {
var newVal = '';
for (var i = 0; i < val.length; i++) {
if (val[i] >= '0' && val[i] <= '9') {
newVal += val[i];
}
}
if (val != newVal) $(this).val(newVal);
}
}
onChange();
});
}
}
function loadHelper(settings, param, subparam) {
if (!settings) return;
if (subparam && param) {
for (var key in settings[param][subparam]) {
if (!settings[param][subparam].hasOwnProperty(key)) continue;
if (typeof settings[param][subparam][key] !== 'object') {
setValue(param + '_' + subparam + '_' + key, settings[param][subparam][key]);
} else {
alert('4th level is not supported');
}
}
} else if (param) {
for (var key in settings[param]) {
if (!settings[param].hasOwnProperty(key)) continue;
if (typeof settings[param][key] !== 'object') {
setValue(param + '_' + key, settings[param][key]);
} else {
loadHelper(settings, param, key);
}
}
} else {
for (var key in settings) {
if (!settings.hasOwnProperty(key)) continue;
if (typeof settings[key] !== 'object') {
if (key === 'pass') {
settings[key] = decrypt('Zgfr56gFe87jJOM', settings[key]);
}
setValue(key, settings[key]);
} else {
loadHelper(settings, key);
}
}
}
}
// the function loadSettings has to exist ...
function load(settings, _onChange) {
$('#tabs').tabs();
onChange = _onChange;
settings.sendAckToo = settings.sendAckToo || false;
settings.webSocket = settings.webSocket || false;
if (settings.sendOnStartInterval === undefined) settings.sendOnStartInterval = 2000;
if (settings.sendInterval === undefined) settings.sendInterval = 10;
settings.clientId = settings.clientId || '';
loadHelper(settings);
$('#pass_confirm').val($('#pass').val());
init();
fillSelectCertificates('#certPublic', 'public', settings.certPublic);
fillSelectCertificates('#certPrivate', 'private', settings.certPrivate);
fillSelectCertificates('#certChained', 'chained', settings.certChained);
onChange(false);
}
function test() {
// var newValue = JSON.stringify(getSettings());
if (!common.enabled) {
showMessage(_('Enable first the adapter to test client.'));
return;
}
if (changed) {
showMessage(_('First save the adapter'));
return;
}
sendTo('mqtt.' + instance, 'test', getSettings(), function (result) {
showMessage(_('Result: ') + _(result));
});
}
function saveHelper(obj, id, value) {
var ids = id.split('_');
if (ids.length === 1) {
if (ids[0] === 'pass') value = encrypt('Zgfr56gFe87jJOM', value);
obj[id] = value;
} else if (ids.length === 2) {
if (!obj[ids[0]]) obj[ids[0]] = {};
obj[ids[0]][ids[1]] = value;
} else if (ids.length === 3) {
if (!obj[ids[0]]) obj[ids[0]] = {};
if (!obj[ids[0]][ids[1]]) obj[ids[0]][ids[1]] = {};
obj[ids[0]][ids[1]][ids[2]] = value;
}
return obj;
}
function getSettings() {
var obj = {};
$('.value').each(function () {
var $this = $(this);
var id = $this.attr('id');
if ($this.attr('type') === 'checkbox') {
obj = saveHelper(obj, id, $this.prop('checked'))
} else {
obj = saveHelper(obj, id, $this.val())
}
});
return obj;
}
function save(callback) {
if ($('#pass').val() !== $('#pass_confirm').val()) {
showMessage(_('Password confirmation is not equal with password'));
return;
}
if ($('#ssl').prop('checked') && $('#type').val() === 'server' && (!$('#certPrivate').val() || !$('#certPublic').val())) {
showMessage(_('Set certificates or load it first in the system settings (right top).'));
return;
}
callback(getSettings());
}
</script>
</head>
<body>
<!-- you have to put your config page in a div with id adapter-container -->
<div id="adapter-container">
<table><tr>
<td><img src="mqtt.png"></td>
<td style="padding-top: 20px;padding-left: 10px"><h3 class="translate">MQTT adapter settings</h3></td>
</tr></table>
<div id="tabs">
<ul>
<li><a href="#tabs-1" class="translate">Connection</a></li>
<li><a href="#tabs-2" class="translate">MQTT Settings</a></li>
</ul>
<div id="tabs-1">
<table>
<tr>
<td colspan='3'><h4 class="translate">Main settings</h4></td>
</tr>
<tr>
<td><label class="translate" for="type">Type:</label></td>
<td><select id="type" class="value">
<option value="client" class="translate">Client</option>
<option value="server" class="translate">Server</option>
</select></td>
<td></td>
</tr>
<tr id="_webSocket">
<td><label class="translate" for="webSocket">Use WebSockets:</label></td>
<td><input type="checkbox" id="webSocket" class="value"/></td>
<td></td>
</tr> <tr>
<td colspan='3'><h4 class="translate">Connection settings</h4></td>
</tr>
<tr id="_url">
<td><label class="translate" for="url">URL:</label></td>
<td><input type="text" id="url" class="value"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="port">Port:</label></td>
<td><input id="port" type="text" size="5" class="value number"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="ssl">Secure:</label></td>
<td><input id="ssl" type="checkbox" class="value"/></td>
<td></td>
</tr>
<tr id="_certPublic">
<td><label class="translate" for="certPublic">Public certificate:</label></td>
<td><select id="certPublic" class="value"></select></td>
<td></td>
</tr>
<tr id="_certPrivate">
<td><label class="translate" for="certPrivate">Private certificate:</label></td>
<td><select id="certPrivate" class="value"></select></td>
<td></td>
</tr>
<tr id="_certChained">
<td><label class="translate" for="certChained">Chained certificate:</label></td>
<td><select id="certChained" class="value"></select></td>
<td></td>
</tr>
<tr>
<td colspan='3'><h4 class="translate">Authentication settings</h4></td>
</tr>
<tr>
<td><label class="translate" for="user">User:</label></td>
<td><input id="user" type="text" size="17" class="value"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="pass">Password:</label></td>
<td><input id="pass" type="password" size="17" class="value"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="pass_confirm">Password confirmation:</label></td>
<td><input id="pass_confirm" type="password" size="17"/></td>
<td></td>
</tr>
</table>
<button id="test" class="translate">Test connection</button>
</div>
<div id="tabs-2">
<table>
<tr>
<td colspan='3'><h4 class="translate">Adapter settings</h4></td>
</tr>
<tr id="_patterns">
<td><label class="translate" for="patterns">Patterns:</label></td>
<td><input id="patterns" type="text" size="17" class="value"/></td>
<td class="translate">Divided by comma</td>
</tr>
<tr>
<td><label class="translate" for="publish">Mask to publish own states:</label></td>
<td><input id="publish" type="text" size="17" class="value"/></td>
<td class="translate">Mask to publish</td>
</tr>
<tr>
<td><label class="translate" for="onchange">Store only on change:</label></td>
<td><input id="onchange" type="checkbox" class="value"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="publishAllOnStart">Publish all states at start:</label></td>
<td><input id="publishAllOnStart" type="checkbox" class="value"/></td>
<td></td>
</tr>
<tr id ="_publishOnSubscribe">
<td><label class="translate" for="publishOnSubscribe">Publish states on subscribe:</label></td>
<td><input id="publishOnSubscribe" type="checkbox" class="value"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="prefix">Prefix for topics:</label></td>
<td><input id="prefix" type="text" size="17" class="value"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="debug">Trace output for every message:</label></td>
<td><input id="debug" type="checkbox" class="value"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="sendAckToo">Send states (ack=true) too:</label></td>
<td><input id="sendAckToo" type="checkbox" class="value"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="extraSet">Use different topic names for set and get:</label></td>
<td><input id="extraSet" type="checkbox" class="value"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="maxTopicLength">Max topic length:</label></td>
<td><input id="maxTopicLength" class="value" size="17"/></td>
<td class="translate">chars</td>
</tr>
<tr id="_clientId">
<td><label class="translate" for="clientId">Client ID:</label></td>
<td><input id="clientId" class="value" size="17"/></td>
<td></td>
</tr>
<tr>
<td><label class="translate" for="sendOnStartInterval">Interval before send topics by connection:</label></td>
<td><input id="sendOnStartInterval" class="value" size="17"/></td>
<td class="translate">ms</td>
</tr>
<tr>
<td><label class="translate" for="sendInterval">Send interval:</label></td>
<td><input id="sendInterval" class="value" size="17"/></td>
<td class="translate">ms</td>
</tr>
</table>
</div>
</div>
</div>
</body>
</html>

406
admin/index_m.html Normal file
View File

@ -0,0 +1,406 @@
<html>
<head>
<!-- Materialze style -->
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
<script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<script type="text/javascript">
var onChange;
function encrypt(key, value) {
var result = '';
for(var i = 0; i < value.length; ++i) {
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
}
return result;
}
function decrypt(key, value) {
var result = '';
for(var i = 0; i < value.length; ++i) {
result += String.fromCharCode(key[i % key.length].charCodeAt(0) ^ value.charCodeAt(i));
}
return result;
}
function showHideSettings() {
if ($('#type').val() === 'client') {
$('#_clientId').show();
$('#_url').show();
$('#test').show();
$('#_patterns').show();
$('#_certPublic').hide();
$('#_certPrivate').hide();
$('#_certChained').hide();
$('#_webSocket').hide();
$('#_publishOnSubscribe').hide();
} else {
$('#_clientId').hide();
$('#_url').hide();
$('#test').hide();
$('#_patterns').hide();
$('#_webSocket').show();
if ($('#ssl').prop('checked')) {
$('#_certPublic').show();
$('#_certPrivate').show();
$('#_certChained').show();
} else {
$('#_certPublic').hide();
$('#_certPrivate').hide();
$('#_certChained').hide();
}
$('#_publishOnSubscribe').show();
}
}
function init() {
showHideSettings();
$('#type').on('change', showHideSettings);
$('#ssl').on('change', showHideSettings);
$('#test').on('click', test);
}
function setValue(id, value) {
var $value = $('#' + id + '.value');
if ($value.attr('type') === 'checkbox') {
$value.prop('checked', value).change(function() {
onChange();
});
} else {
$value.val(value).change(function() {
onChange();
}).keyup(function() {
// Check that only numbers entered
if ($(this).hasClass('number')) {
var val = $(this).val();
if (val) {
var newVal = '';
for (var i = 0; i < val.length; i++) {
if (val[i] >= '0' && val[i] <= '9') {
newVal += val[i];
}
}
if (val != newVal) $(this).val(newVal);
}
}
onChange();
});
}
}
function loadHelper(settings, param, subparam) {
if (!settings) return;
if (subparam && param) {
for (var key in settings[param][subparam]) {
if (!settings[param][subparam].hasOwnProperty(key)) continue;
if (typeof settings[param][subparam][key] !== 'object') {
setValue(param + '_' + subparam + '_' + key, settings[param][subparam][key]);
} else {
alert('4th level is not supported');
}
}
} else if (param) {
for (var key in settings[param]) {
if (!settings[param].hasOwnProperty(key)) continue;
if (typeof settings[param][key] !== 'object') {
setValue(param + '_' + key, settings[param][key]);
} else {
loadHelper(settings, param, key);
}
}
} else {
for (var key in settings) {
if (!settings.hasOwnProperty(key)) continue;
if (typeof settings[key] !== 'object') {
if (key === 'pass') {
settings[key] = decrypt('Zgfr56gFe87jJOM', settings[key]);
}
setValue(key, settings[key]);
} else {
loadHelper(settings, key);
}
}
}
}
// the function loadSettings has to exist ...
function load(settings, _onChange) {
onChange = _onChange;
settings.retransmitInterval = settings.retransmitInterval || 2000;
settings.retransmitCount = settings.retransmitCount || 10;
settings.sendAckToo = settings.sendAckToo || false;
settings.webSocket = settings.webSocket || false;
settings.defaultQoS = (settings.defaultQoS === '' || settings.defaultQoS === -1 || settings.defaultQoS === '-1') ? -1 : settings.defaultQoS || 0;
if (settings.sendOnStartInterval === undefined) settings.sendOnStartInterval = 2000;
if (settings.sendInterval === undefined) settings.sendInterval = 10;
settings.clientId = settings.clientId || '';
loadHelper(settings);
$('#pass_confirm').val($('#pass').val());
init();
fillSelectCertificates('#certPublic', 'public', settings.certPublic);
fillSelectCertificates('#certPrivate', 'private', settings.certPrivate);
fillSelectCertificates('#certChained', 'chained', settings.certChained);
onChange(false);
if (M) M.updateTextFields();
}
function test() {
// var newValue = JSON.stringify(getSettings());
if (!common.enabled) {
showToast(null, _('Enable first the adapter to test client.'));
return;
}
if (changed) {
showToast(null, _('First save the adapter'));
return;
}
$('#test').addClass('disabled');
sendTo('mqtt.' + instance, 'test', getSettings(), function (result) {
$('#test').removeClass('disabled');
showMessage(_(result), _('Result: '));
});
}
function saveHelper(obj, id, value) {
var ids = id.split('_');
if (ids.length === 1) {
if (ids[0] === 'pass') value = encrypt('Zgfr56gFe87jJOM', value);
obj[id] = value;
} else if (ids.length === 2) {
if (!obj[ids[0]]) obj[ids[0]] = {};
obj[ids[0]][ids[1]] = value;
} else if (ids.length === 3) {
if (!obj[ids[0]]) obj[ids[0]] = {};
if (!obj[ids[0]][ids[1]]) obj[ids[0]][ids[1]] = {};
obj[ids[0]][ids[1]][ids[2]] = value;
}
return obj;
}
function getSettings() {
var obj = {};
$('.value').each(function () {
var $this = $(this);
var id = $this.attr('id');
if ($this.attr('type') === 'checkbox') {
obj = saveHelper(obj, id, $this.prop('checked'))
} else {
obj = saveHelper(obj, id, $this.val())
}
});
obj.defaultQoS = parseInt(obj.defaultQoS, 10) || 0;
return obj;
}
function save(callback) {
if ($('#pass').val() !== $('#pass_confirm').val()) {
showMessage(_('Password confirmation is not equal with password'));
return;
}
if ($('#ssl').prop('checked') && $('#type').val() === 'server' && (!$('#certPrivate').val() || !$('#certPublic').val())) {
showMessage(_('Set certificates or load it first in the system settings (right top).'));
return;
}
callback(getSettings());
}
</script>
<style>
.sub-title {
margin-top: 2rem!important;
padding: 0.5rem;
background: #64b5f6;
color: white;
}
.main-page {
height: 100%;
overflow: hidden;
margin-bottom: 0 !important;
}
.page {
height: calc(100% - 34px) !important;
}
</style>
</head>
<body>
<!-- you have to put your config page in a div with id adapter-container -->
<div class="m adapter-container">
<div class="main-page row">
<div class="col s12">
<ul class="tabs">
<li class="tab col s4"><a href="#tab-main" class="translate active">Connection</a></li>
<li class="tab col s4"><a href="#tab-mqtt" class="translate">MQTT Settings</a></li>
</ul>
</div>
<div id="tab-main" class="col s12 page">
<div class="row">
<div class="col s6">
<img src="mqtt.png" class="logo">
</div>
</div>
<div class="row">
<div class="input-field col s12 m6 l4">
<select id="type" class="value">
<option value="client" class="translate">Client</option>
<option value="server" class="translate">Server</option>
</select>
<label class="translate" for="type">Type:</label>
</div>
<div class="input-field col s12 m6 l4" id="_webSocket">
<input type="checkbox" id="webSocket" class="value filled-in" />
<span class="translate" for="webSocket">Use WebSockets:</span>
</div>
</div>
<h6 class="translate sub-title">Connection settings</h6>
<div class="row">
<div class="input-field col s12 m6 l4" id="_url">
<input type="text" id="url" class="value" />
<label class="translate" for="url">URL:</label>
</div>
<div class="input-field col s12 m6 l4">
<input id="port" type="number" min="1" max="65565" class="value" />
<label class="translate" for="port">Port:</label>
</div>
</div>
<div class="row">
<div class="input-field col s12 m12">
<input id="ssl" type="checkbox" class="value filled-in" />
<span class="translate" for="ssl">Secure:</span>
</div>
<div class="input-field col s12 m6 l4" id="_certPublic">
<select id="certPublic" class="value"></select>
<label class="translate" for="certPublic">Public certificate:</label>
</div>
<div class="input-field col s12 m6 l4" id="_certPrivate">
<select id="certPrivate" class="value"></select>
<label class="translate" for="certPrivate">Private certificate:</label>
</div>
<div class="input-field col s12 m6 l4" id="_certChained">
<select id="certChained" class="value"></select>
<label class="translate" for="certChained">Chained certificate:</label>
</div>
</div>
<h6 class="translate sub-title">Authentication settings</h6>
<div class="row">
<div class="input-field col s12 m6 l4">
<input id="user" type="text" size="17" class="value" />
<label class="translate" for="user">User:</label>
</div>
<div class="input-field col s12 m6 l4">
<input id="pass" type="password" size="17" class="value" />
<label class="translate" for="pass">Password:</label>
</div>
<div class="input-field col s12 m6 l4">
<input id="pass_confirm" type="password" size="17" />
<label class="translate" for="pass_confirm">Password confirmation:</label>
</div>
</div>
<div class="row">
<div class="col s12">
<a id="test" class="btn"><span class="translate">Test connection</span></a>
</div>
</div>
</div>
<div id="tab-mqtt" class="col s12 page">
<div class="row">
<div class="col s6">
<img src="mqtt.png" class="logo">
</div>
</div>
<div class="row">
<div id="_patterns" class="input-field col s12 m6 l3">
<input id="patterns" type="text" size="17" class="value" />
<label for="patterns" class="translate">Patterns:</label> (
<span class="translate">Divided by comma</span>)
</div>
<div class="input-field col s12 m6 l3">
<input id="prefix" type="text" size="17" class="value" />
<label class="translate" for="prefix">Prefix for topics:</label>
</div>
<div class="input-field col s12 m6 l3">
<input id="publish" type="text" size="17" class="value" />
<label for="publish" class="translate">Mask to publish own states:</label> (
<span class="translate">Mask to publish</span>)
</div>
</div>
<div class="row">
<div class="input-field col s12 m6 l3">
<input id="onchange" type="checkbox" class="value filled-in" />
<span class="translate" for="onchange">Store only on change:</span>
</div>
<div class="input-field col s12 m6 l3">
<input id="publishAllOnStart" type="checkbox" class="value filled-in" />
<span class="translate" for="publishAllOnStart">Publish all states at start:</span>
</div>
<div id="_publishOnSubscribe" class="input-field col s12 m6 l3">
<input id="publishOnSubscribe" type="checkbox" class="value filled-in" />
<span class="translate" for="publishOnSubscribe">Publish states on subscribe:</span>
</div>
<div class="input-field col s12 m6 l3">
<input id="debug" type="checkbox" class="value filled-in" />
<span class="translate" for="debug">Trace output for every message:</span>
</div>
<div class="input-field col s12 m6 l3">
<input id="sendAckToo" type="checkbox" class="value filled-in" />
<span class="translate" for="sendAckToo">Send states (ack=true) too:</span>
</div>
<div class="input-field col s12 m6 l3">
<input id="extraSet" type="checkbox" class="value filled-in" />
<span class="translate" for="extraSet">Use different topic names for set and get:</span>
</div>
</div>
<div class="row">
<div id="_clientId" class="input-field col s12 m6 l3">
<input id="clientId" type="text" class="value" size="17" />
<label class="translate" for="clientId">Client ID:</label>
</div>
<div class="input-field col s12 m6 l3">
<input id="maxTopicLength" type="text" class="value" size="17" />
<label for="maxTopicLength" class="translate">Max topic length:(<span class="translate">chars</span>)</label>
</div>
<div class="input-field col s12 m6 l3">
<input id="sendOnStartInterval" type="text" class="value" size="17" />
<label for="sendOnStartInterval"><span class="translate">Interval before send topics by connection:</span> (<span class="translate">ms</span>)</label>
</div>
<div class="input-field col s12 m6 l3">
<input id="sendInterval" type="text" class="value" size="17" />
<label for="sendInterval"><span class="translate">Send interval:</span> (<span class="translate">ms</span>)</label>
</div>
</div>
<div class="row">
<div id="_defaultQoS" class="input-field col s12 m6 l3">
<select id="defaultQoS" class="value">
<option value="0" class="translate">0 - At most once</option>
<option value="1" class="translate">1 - At least once</option>
<option value="2" class="translate">2 - Exactly once</option>
</select>
<label class="translate" for="defaultQoS">Default QoS:</label>
</div>
<div class="input-field col s12 m6 l3">
<input id="retain" type="checkbox" class="value filled-in" />
<span class="translate" for="retain">Default retain flag:</span>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

BIN
admin/mqtt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

50
admin/words.js Normal file
View File

@ -0,0 +1,50 @@
// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n
/*global systemDictionary:true */
'use strict';
systemDictionary = {
"MQTT adapter settings": { "en": "MQTT adapter settings", "de": "MQTT Adapter-Einstellungen", "ru": "Настройки драйвера MQTT", "pt": "MQTT adapter settings", "nl": "MQTT adapter settings", "fr": "MQTT adapter settings", "it": "MQTT adapter settings", "es": "MQTT adapter settings", "pl": "MQTT adapter settings"},
"Type:": { "en": "Type", "de": "Typ", "ru": "Тип", "pt": "Type", "nl": "Type", "fr": "Type", "it": "Type", "es": "Type", "pl": "Type"},
"Port:": { "en": "Port", "de": "Port", "ru": "Порт", "pt": "Port", "nl": "Port", "fr": "Port", "it": "Port", "es": "Port", "pl": "Port"},
"User:": { "en": "User", "de": "Username", "ru": "Имя пользователя", "pt": "User", "nl": "User", "fr": "User", "it": "User", "es": "User", "pl": "User"},
"Password:": { "en": "Password", "de": "Kennwort", "ru": "Пароль", "pt": "Password", "nl": "Password", "fr": "Password", "it": "Password", "es": "Password", "pl": "Password"},
"Password confirmation:": { "en": "Password confirmation", "de": "Kennwort-Wiederholung", "ru": "Подтверждение пароля", "pt": "Password confirmation", "nl": "Password confirmation", "fr": "Password confirmation", "it": "Password confirmation", "es": "Password confirmation", "pl": "Password confirmation"},
"URL:": { "en": "URL", "de": "URL", "ru": "URL", "pt": "URL", "nl": "URL", "fr": "URL", "it": "URL", "es": "URL", "pl": "URL"},
"Secure:": { "en": "Secure", "de": "SSL", "ru": "SSL", "pt": "Secure", "nl": "Secure", "fr": "Secure", "it": "Secure", "es": "Secure", "pl": "Secure"},
"Public certificate:": { "en": "Public certificate", "de": "Publikzertifikat", "ru": "'Public' сертификат", "pt": "Public certificate", "nl": "Public certificate", "fr": "Public certificate", "it": "Public certificate", "es": "Public certificate", "pl": "Public certificate"},
"Private certificate:": { "en": "Private certificate", "de": "Privatzertifikat", "ru": "'Private' сертификат", "pt": "Private certificate", "nl": "Private certificate", "fr": "Private certificate", "it": "Private certificate", "es": "Private certificate", "pl": "Private certificate"},
"Chained certificate:": { "en": "Chained certificate", "de": "Kettenzertifikat", "ru": "'Chained' сертификат", "pt": "Chained certificate", "nl": "Chained certificate", "fr": "Chained certificate", "it": "Chained certificate", "es": "Chained certificate", "pl": "Chained certificate"},
"Patterns:": { "en": "Subscribe patterns", "de": "Subscribe patterns", "ru": "Patterns", "pt": "Subscribe patterns", "nl": "Subscribe patterns", "fr": "Subscribe patterns", "it": "Subscribe patterns", "es": "Subscribe patterns", "pl": "Subscribe patterns"},
"Use WebSockets:": { "en": "Use WebSockets too", "de": "Benutze auch WebSockets", "ru": "Сервер WebSockets тоже", "pt": "Use WebSockets too", "nl": "Use WebSockets too", "fr": "Use WebSockets too", "it": "Use WebSockets too", "es": "Use WebSockets too", "pl": "Use WebSockets too"},
"Connection": { "en": "Connection", "de": "Verbindung", "ru": "Соединение", "pt": "Connection", "nl": "Connection", "fr": "Connection", "it": "Connection", "es": "Connection", "pl": "Connection"},
"MQTT Settings": { "en": "MQTT Settings", "de": "MQTT Einstellungen", "ru": "Настройки MQTT", "pt": "MQTT Settings", "nl": "MQTT Settings", "fr": "MQTT Settings", "it": "MQTT Settings", "es": "MQTT Settings", "pl": "MQTT Settings"},
"Client ID:": { "en": "Client ID", "de": "Client ID", "ru": "ID Клиента", "pt": "Client ID", "nl": "Client ID", "fr": "Client ID", "it": "Client ID", "es": "Client ID", "pl": "Client ID"},
"chars": { "en": "chars", "de": "Symbolen", "ru": "символов", "pt": "chars", "nl": "chars", "fr": "chars", "it": "chars", "es": "chars", "pl": "chars"},
"ms": { "en": "ms", "de": "ms", "ru": "мс", "pt": "ms", "nl": "ms", "fr": "ms", "it": "ms", "es": "ms", "pl": "ms"},
"Interval before send topics by connection:": { "en": "Interval before send topics by connection", "de": "Interval vom Senden von allen Topics bei der verbindung", "ru": "Интервал перед отсылкой всей топиков после соединения", "pt": "Interval before send topics by connection", "nl": "Interval before send topics by connection", "fr": "Interval before send topics by connection", "it": "Interval before send topics by connection", "es": "Interval before send topics by connection", "pl": "Interval before send topics by connection"},
"Send interval:": { "en": "Send interval", "de": "Sendeintervall", "ru": "Интервал между пакетами", "pt": "Send interval", "nl": "Send interval", "fr": "Send interval", "it": "Send interval", "es": "Send interval", "pl": "Send interval"},
"Use chunk patch:": { "en": "Use chunk patch", "de": "Benutze Patch für Chunking", "ru": "Использовать заплатку для Chunking", "pt": "Use chunk patch", "nl": "Use chunk patch", "fr": "Use chunk patch", "it": "Use chunk patch", "es": "Use chunk patch", "pl": "Use chunk patch"},
"Divided by comma": { "en": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'", "de": "Mit Komma getrennt, z.B 'mqtt/0/#,javascript/#'", "ru": "Использовать запятую, как разделитеть. Например 'mqtt/0/#,javascript/#'", "pt": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'", "nl": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'", "fr": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'", "it": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'", "es": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'", "pl": "Divided by comma, e.g. 'mqtt/0/#,javascript/#'"},
"Mask to publish": { "en": "e.g. 'mqtt.0.*,javascript.*'", "de": "z.B 'mqtt.0.*,javascript.*'", "ru": "Использовать запятую, как разделитеть. Например 'mqtt.0.*,javascript.*'", "pt": "e.g. 'mqtt.0.*,javascript.*'", "nl": "e.g. 'mqtt.0.*,javascript.*'", "fr": "e.g. 'mqtt.0.*,javascript.*'", "it": "e.g. 'mqtt.0.*,javascript.*'", "es": "e.g. 'mqtt.0.*,javascript.*'", "pl": "e.g. 'mqtt.0.*,javascript.*'"},
"Store only on change:": { "en": "Publish only on change", "de": "Publish nur bei Änderung", "ru": "Отсылать только изменения", "pt": "Publish only on change", "nl": "Publish only on change", "fr": "Publish only on change", "it": "Publish only on change", "es": "Publish only on change", "pl": "Publish only on change"},
"Trace output for every message:": { "en": "Trace output for every message", "de": "Trace Ausgabe für jede Meldung", "ru": "Вывод лога для каждого изменения", "pt": "Trace output for every message", "nl": "Trace output for every message", "fr": "Trace output for every message", "it": "Trace output for every message", "es": "Trace output for every message", "pl": "Trace output for every message"},
"Test connection": { "en": "Test connection with Server", "de": "Teste Verbindung zum Server", "ru": "Проверить настройки", "pt": "Test connection with Server", "nl": "Test connection with Server", "fr": "Test connection with Server", "it": "Test connection with Server", "es": "Test connection with Server", "pl": "Test connection with Server"},
"Result: ": { "en": "Result: ", "de": "Ergebnis: ", "ru": "Результат: ", "pt": "Result: ", "nl": "Result: ", "fr": "Result: ", "it": "Result: ", "es": "Result: ", "pl": "Result: "},
"connected": { "en": "connected", "de": "verbunden", "ru": "успешно", "pt": "connected", "nl": "connected", "fr": "connected", "it": "connected", "es": "connected", "pl": "connected"},
"Main settings": { "en": "Main settings", "de": "Allgemeine Einstellungen", "ru": "Основные настройки", "pt": "Main settings", "nl": "Main settings", "fr": "Main settings", "it": "Main settings", "es": "Main settings", "pl": "Main settings"},
"Connection settings": { "en": "Connection settings", "de": "Verbindungseinstellungen", "ru": "Настройки соединения", "pt": "Connection settings", "nl": "Connection settings", "fr": "Connection settings", "it": "Connection settings", "es": "Connection settings", "pl": "Connection settings"},
"Authentication settings": { "en": "Authentication settings", "de": "Authentication Einstellungen", "ru": "Настройки аутентификации", "pt": "Authentication settings", "nl": "Authentication settings", "fr": "Authentication settings", "it": "Authentication settings", "es": "Authentication settings", "pl": "Authentication settings"},
"Adapter settings": { "en": "MQTT settings", "de": "MQTT Einstellungen", "ru": "Настройки MQTT", "pt": "MQTT settings", "nl": "MQTT settings", "fr": "MQTT settings", "it": "MQTT settings", "es": "MQTT settings", "pl": "MQTT settings"},
"Mask to publish own states:": { "en": "Mask to publish own states", "de": "Maske für Bekanntgeben von eigenen States", "ru": "Маска для собственных значений", "pt": "Mask to publish own states", "nl": "Mask to publish own states", "fr": "Mask to publish own states", "it": "Mask to publish own states", "es": "Mask to publish own states", "pl": "Mask to publish own states"},
"Send states (ack=true) too:": { "en": "Send states (ack=true) too", "de": "Sende auch Zustände (ack=true)", "ru": "Посылать не только команды, но и состояния (ack=true)", "pt": "Send states (ack=true) too", "nl": "Send states (ack=true) too", "fr": "Send states (ack=true) too", "it": "Send states (ack=true) too", "es": "Send states (ack=true) too", "pl": "Send states (ack=true) too"},
"Publish all states at start:": { "en": "Publish own states on connect", "de": "Bekanntgeben eigene States beim Verbinden", "ru": "Выдавать собственные значения при старте", "pt": "Publish own states on connect", "nl": "Publish own states on connect", "fr": "Publish own states on connect", "it": "Publish own states on connect", "es": "Publish own states on connect", "pl": "Publish own states on connect"},
"Use different topic names for set and get:": { "en": "Use different topic names for set and get", "de": "Unterschiedliche Namen für setzten und lesen", "ru": "Использовать разные имена для чтения и записи", "pt": "Use different topic names for set and get", "nl": "Use different topic names for set and get", "fr": "Use different topic names for set and get", "it": "Use different topic names for set and get", "es": "Use different topic names for set and get", "pl": "Use different topic names for set and get"},
"Publish states on subscribe:": { "en": "Publish states on subscribe", "de": "Bekanntgeben von States bei Subscribe", "ru": "Публиковать состояния при подписке", "pt": "Publish states on subscribe", "nl": "Publish states on subscribe", "fr": "Publish states on subscribe", "it": "Publish states on subscribe", "es": "Publish states on subscribe", "pl": "Publish states on subscribe"},
"Set certificates or load it first in the system settings (right top).": {"en": "Set certificates or load it first in the system settings (right top).", "de": "Setze Zertificate oder lade die erst unter System/Einstellungen (oben rechts).", "ru": "Нужно выбрать сертификаты или сначала загрузить их в системных настройках (вверху справа).", "pt": "Set certificates or load it first in the system settings (right top).", "nl": "Set certificates or load it first in the system settings (right top).", "fr": "Set certificates or load it first in the system settings (right top).", "it": "Set certificates or load it first in the system settings (right top).", "es": "Set certificates or load it first in the system settings (right top).", "pl": "Set certificates or load it first in the system settings (right top)."},
"Prefix for topics:": { "en": "Prefix for all topics", "de": "Prefix für alle Topics", "ru": "Префикс для всех значений", "pt": "Prefix for all topics", "nl": "Prefix for all topics", "fr": "Prefix for all topics", "it": "Prefix for all topics", "es": "Prefix for all topics", "pl": "Prefix for all topics"},
"Max topic length:": { "en": "Max topic name length", "de": "Maximale Topicnamelänge", "ru": "Максимальная длина имени топика", "pt": "Max topic name length", "nl": "Max topic name length", "fr": "Max topic name length", "it": "Max topic name length", "es": "Max topic name length", "pl": "Max topic name length"},
"Server": { "en": "Server/broker", "de": "Server/broker", "ru": "Сервер/брокер", "pt": "Server/broker", "nl": "Server/broker", "fr": "Server/broker", "it": "Server/broker", "es": "Server/broker", "pl": "Server/broker"},
"Client": { "en": "Client/subscriber", "de": "Client/subscriber", "ru": "Клиент/подписчик", "pt": "Client/subscriber", "nl": "Client/subscriber", "fr": "Client/subscriber", "it": "Client/subscriber", "es": "Client/subscriber", "pl": "Client/subscriber"},
"Enable first the adapter to test client.": { "en": "Enable first the adapter to test client.", "de": "Aktivieren Sie zuerst den Adapter, um den Client zu testen.", "ru": "Включите сначала адаптер для тестирования клиента.", "pt": "Ative primeiro o adaptador para testar o cliente.", "nl": "Schakel eerst de adapter in om de client te testen.", "fr": "Activez d'abord l'adaptateur pour tester le client.", "it": "Abilitare prima l'adattatore per testare il client.", "es": "Primero habilite el adaptador para probar el cliente.", "pl": "Najpierw włącz adapter, aby przetestować klienta."},
"First save the adapter": { "en": "First save the adapter", "de": "Speichern Sie zuerst den Adapter", "ru": "Сначала сохраните адаптер", "pt": "Primeiro salve o adaptador", "nl": "Sla eerst de adapter op", "fr": "D'abord enregistrer l'adaptateur", "it": "Per prima cosa salva l'adattatore", "es": "Primero guarde el adaptador", "pl": "Najpierw zapisz adapter"},
};

24
appveyor.yml Normal file
View File

@ -0,0 +1,24 @@
version: 'test-{build}'
environment:
matrix:
- nodejs_version: '6'
- nodejs_version: '8'
- nodejs_version: '10'
platform:
- x86
- x64
clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%'
install:
- ps: 'Install-Product node $env:nodejs_version $env:platform'
- ps: '$NpmVersion = (npm -v).Substring(0,1)'
- ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }'
- ps: npm --version
- npm install
- npm install winston@2.3.1
- 'npm install https://github.com/yunkong2/yunkong2.js-controller/tarball/master --production'
test_script:
- echo %cd%
- node --version
- npm --version
- npm test
build: 'off'

695
docs/de/mqtt.md Normal file
View File

@ -0,0 +1,695 @@
# Beschreibung
[MQTT](http://mqtt.org/) (Message Queue Telemetry Transport) ist ein schlankes Protokoll für die Kommunikation zwischen verschiedenen Geräten (M2M - machine-to-machine). Es benutzt das publisher-subscriber Modell um Nachrichten über das TCP / IP Protokoll zu senden. Die zentrale Stelle des Protokolls ist der MQTT-Server oder Broker Der Zugriff auf den publisher und den subscriber besitzt. Dieses Protokoll ist sehr simpel: ein kurzer Header ohne Integrität (deshalb setzt die Übermittlung auf TCP auf), legt der Struktur keinerlei Beschränkungen beim Code oder einem Datenbankschema auf. Die einzige Bedingung ist dass jedes Datenpaket eine Information zur Identifikation beinhalten muss. Diese Identifikationsinformation heißt Topic Name.
Das MQTT Protokoll benötigt einen Datenbroker. Dieses ist die zentrale Idee dieser Technologie. Alle Geräte senden ihre Daten nur zu diesem Broker und erhalten ihre Informationen auch nur von ihm. Nach dem Empfang des Paketes sendet der Broker es zu allen Geräten in dem Netzwerk, die es abonniert haben. Wenn ein Gerät etwas von dem Broker möchte, muss er das entsprechende Topic abonnieren. Topics entstehen dynamisch bei Abonnement oder beim Empfang eines Paketes mit diesem Topic. Nach dem Abonnement eines Topics braucht man nichts mehr zu tun. Deswegen sind Topics sehr bequem um verschiedenen Beziehungen zu organisieren: one-to-many, many-to-one and many-to-many.
**Wichtig:**
* Die Geräte stellen selber die Kommunikation mit dem Broker her. Sie können hniter einer NAT liegen und  müssen keine feste IP besitzen,
* Man kann den Traffic mit SSL absichern,
* MQTT Broker ermöglichen es diese über ein Websocket Protokoll auf Port 80 zu erreichen,
* Mehrere Broker können miteinander verbunden werden und abonnieren dann die Nachrichte gegenseitig.
# Installation
Die Installation findet im Admin im Reiter _**Adapter**_ statt. In der Gruppe Kommunikation befindet sich eine Zeile **_MQTT Adapter_**, dort wird über das (+)-Icon ganz rechts eine neue Instanz angelegt.
[![](http://www.yunkong2.net/wp-content/uploads//1-1024x342.png)](http://www.yunkong2.net/wp-content/uploads//1.png)
Ein pop-up Fenster erscheint mit den Installationsinformationen und schließt nach der Installation eigenständig.
[![](http://www.yunkong2.net/wp-content/uploads//2-300x153.png)](http://www.yunkong2.net/wp-content/uploads//2.png)
Wenn alles klappt befindet sich anschließend unter dem Reiter _**Instanzen**_ die neu installierte **mqtt.0** Instanz.
[![](http://www.yunkong2.net/wp-content/uploads//3-300x156.png)](http://www.yunkong2.net/wp-content/uploads//3.png)  
# Konfiguration
Wie bereits oben gesagt, besteht ein MQTT-System aus einem Broker und Clients.  Der yunkong2 Server kann als Broker oder als Client arbeiten.  Entsprechend des gewünschten Modus wird der Typ auf _**server/broker**_ oder **_Client/subscriber_** eingestellt. Hier sollte die Einstellung gut überlegt werden.
## yunkong2 als MQTT-Broker
Die Grundeinstellungen um den Adapter als Server/Broker zu verwenden sind in der Abbildung gezeigt:
[![](http://www.yunkong2.net/wp-content/uploads/yunkong2_Adapter_MQTT_Konfig_Server.jpg)](http://www.yunkong2.net/wp-content/uploads//yunkong2_Adapter_MQTT_Konfig_Server.jpg)
### Allgemeine Einstellungen
* **Typ** - Entsprechend der gewünschten Verwendung wird der Typ auf _**server/broker**_ oder **_Client/subscriber_** eingestellt
* **Use WebSockets** - Wenn man Websockets für die Verbindung benötigt, muss diese Checkbox aktiviert werden. Dann läuft der TCP-Server parallel zum WebSocket Server,
* **Port** - Der Port um mit TCP zu verbinden (default: 1883),  ein WebSocket Server (siehe oben) läuft einen Port höher (default: 1884),
### Verbindungseinstellungen
* **SSL** - Diese Option wird benötigt um den gesamten Datenverkehr zu verschlüsseln (TCP und WebSocket), deshalb muss man in den jetzt zur Verfügung stehenden Feldern die zu verwendenden Zertifikate angeben. In den Pulldowns kann man diese aus den in den Systemeinstellungen angelegten Zertifikaten auswählen,
![](yunkong2_Adapter_MQTT_Konfig_Server_SSH.jpg)
### Authentication Einstellungen
* **Username** und **Passwort** - Wenn gewünscht kann hier ein Username und ein Passwort vergeben werden. Dies muss auf jeden Fall mit SSH Verschlüsselung benutzt werden, damit Passworte nicht unverschlüsselt übertragen werden.
### MQTT Einstellungen
* **Maske für Bekanntgeben von eigenen States** - Diese Maske (oder mehrere durch Komma getrennt)dienen dazu die Variablen zu filtern, die an den Client geschickt werden sollen. Man kann Sonderzeichen angeben um eine Gruppe von Nachrichten zu definieren (z.B.  `memRSS, mqtt.0` - sendet alle Variablen zum memory status aller Adapter und alle variablen der **mqtt.0 Adapter** Instanz),
* **Publish nur bei Änderungen** - sending data to the client will be made only in case of change of a variable (if the state simply update - the value is not changed, the customer message will not be sent) from the client will be accepted any message, even if the value has not changed,
* **To give private values at startup** - for each successful client connection will be transferred to all known states (defined by the mask state) in order to tell the client about the current state of the yunkong2,
* **Post status subscribes** - immediately after the subscription will be sent to the customer value of the variable on which it is signed (at the first start or restart the client will receive the values of variables on which it is signed, can be used to initialize variables),
* **The prefix for all values** - if the specified value, it will be added as a prefix to every sent topic, for example, if you specify yunkong2/, then all topics sent along the following lines: `yunkong2/mqtt/0/connected`,
* **Output log for each change** - in the log file will display debugging information for each change,
* **To send not only commands, but also the state (ack=true)** - if this option is not active, the client will only send variables/commands with ack=false, if the flag is set, then variables will be transferred regardless of the state of ack (false / true),
* **The maximum length of the name of a topic** - the maximum number of characters for the description of the topic, including service.
As an example, consider the exchange of data between the client based on the [arduino board](https://www.arduino.cc/) and the broker is an instance of mqtt.0 driver system yunkong2.
* - the client the fee for developing [arduino UNO](https://www.arduino.cc/en/Main/ArduinoBoardUno) + [ethernet shield](https://store.arduino.cc/product/A000072) based on W5100 chip,
* - to work with the ethernet board uses the standard [library](https://www.arduino.cc/en/Reference/Ethernet) for working with MQTT library [Pubsubclient](https://github.com/knolleary/pubsubclient),
* - the AM2302 sensor (temperature and humidity) connected to pin_8 for the survey used library with DHTlib with [DHTlib](https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib) resource github.com,
* - led **led_green** is connected to pin_9, control in discrete mode on/off
* - broker yunkong2 system driver mqtt.
Format topics of data exchange:
* `example1/send_interval` - client signed to change the transmission interval of the temperature readings and humidity (int value in seconds),
* `example1/temp` - client publishes a specified temperature interval with DHT22 sensor (float type),
* `example1/hum` - client publishes a specified humidity value intervals with DHT22 sensor (float type),
* `example1/led` - the client is subscribed to the state change of the led (the text on/off or 0/1 or true/false).
Driver settings will be as follows:
[![](http://www.yunkong2.net/wp-content/uploads//5-283x300.png)](http://www.yunkong2.net/wp-content/uploads//5.png)
Connecting via TCP (WebSocket is not necessary), default port 1883\. The client within the local network, so to encrypt traffic and authenticate the user is not necessary. We will send only the changes since the client signed on the send interval indications and led state to obtain information about the update (without changing the value) to a variable makes no sense. To publish the subscription - note this option, as when you first connect (or connected after disconnection) of the client, he must know the state of the variables on which it is signed (a current interval of sending and whether the LED is to be turned on). Setting to send variables ack = true or false is also worth noting, as a variable (which signed the client) can change any driver / script / VIS and any changes should be sent to the client. The complete code for the arduino board will look like this:
<pre>// Connecting libraries
#include
#include
#include //https://github.com/knolleary/pubsubclient
#include //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
//Settings of a network
byte mac[] = {
0xAB,
0xBC,
0xCD,
0xDE,
0xEF,
0x31
};
byte ip[] = {
192,
168,
69,
31
}; //arduino board IP address
byte mqttserver[] = {
192,
168,
69,
51
}; // yunkong2 server IP address
EthernetClient ethClient;
void callback(char * topic, byte * payload, unsigned int length);
PubSubClient client(mqttserver, 1883, callback, ethClient);
//Global variables
#define LED_pin 9
unsigned int send_interval = 10; // the sending interval of indications to the server, by default 10 seconds
unsigned long last_time = 0; // the current time for the timer
dht DHT;
#define DHT22_PIN 8
char buff[20];
// The processing function for incoming connections - reception of data on a subscription
void callback(char * topic, byte * payload, unsigned int length) {
Serial.println("");
Serial.println("-------");
Serial.println("New callback of MQTT-broker");
// let's transform a subject (topic) and value (payload) to a line
payload[length] = '\0';
String strTopic = String(topic);
String strPayload = String((char * ) payload);
// research that "arrived" from the server on a subscription::
// Change of an interval of inquiry
if (strTopic == "example1/send_interval") {
int tmp = strPayload.toInt();
if (tmp == 0) {
send_interval = 10;
} else {
send_interval = strPayload.toInt();
}
}
// Control of a LED
if (strTopic == "example1/led") {
if (strPayload == "off" || strPayload == "0" || strPayload == "false") digitalWrite(LED_pin, LOW);
if (strPayload == "on" || strPayload == "1" || strPayload == "true") digitalWrite(LED_pin, HIGH);
}
Serial.print(strTopic);
Serial.print(" ");
Serial.println(strPayload);
Serial.println("-------");
Serial.println("");
}
void setup() {
Serial.begin(9600);
Serial.println("Start...");
// start network connection
Ethernet.begin(mac, ip);
Serial.print("IP: ");
Serial.println(Ethernet.localIP());
// initialize input/output ports, register starting values
pinMode(LED_pin, OUTPUT);
digitalWrite(LED_pin, LOW); // when the LED is off
}
void loop() {
// If the MQTT connection inactively, then we try to set it and to publish/subscribe
if (!client.connected()) {
Serial.print("Connect to MQTT-boker... ");
// Connect and publish / subscribe
if (client.connect("example1")) {
Serial.println("success");
// Value from sensors
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example1/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example1/temp", buff);
}
// subscribe for an inquiry interval
client.subscribe("example1/send_interval");
// subscribe to the LED control variable
client.subscribe("example1/led");
} else {
// If weren't connected, we wait for 10 seconds and try again
Serial.print("Failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 10 seconds");
delay(10000);
}
// If connection is active, then sends the data to the server with the specified time interval
} else {
if (millis() & gt;
(last_time + send_interval * 1000)) {
last_time = millis();
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example1/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example1/temp", buff);
}
}
}
// Check of incoming connections on a subscription
client.loop();
}
</pre>
The result of the part of the broker (temperature and humidity data is updated with the preset time period): [![](http://www.yunkong2.net/wp-content/uploads//6-1024x201.png)](http://www.yunkong2.net/wp-content/uploads//6.png) The result of the client-side (incoming data subscription output to the console for debugging): [![](http://www.yunkong2.net/wp-content/uploads//MQTT-server4.jpg)](http://www.yunkong2.net/wp-content/uploads//MQTT-server4.jpg)  
## yunkong2 working as MQTT-client
For an instance MQTT driver earned as a client / subscriber - you need to choose the appropriate type of configuration. In this set of options will change slightly: [![](http://www.yunkong2.net/wp-content/uploads//yunkong2_Adapter_MQTT_Konfig_Client.jpg)](http://www.yunkong2.net/wp-content/uploads//yunkong2_Adapter_MQTT_Konfig_Client.jpg)  
### Allgemeine Einstellungen
* Typ
### Verbindungseinstellungen
- specifies the URL and port of the broker (if you want to encrypt traffic, indicated SSL) - settings to connect to the broker,  
### **Authentication settings**
* - user name and password, if the broker requires authentication (it is appropriate to use SSL to avoid transmitting the password in clear text),
### MQTT Einstellungen
* **Patterns** - a mask for variables for which the customer subscribes (variables broker), the values are listed separated by commas, the # (pound) is used to indicate the set,
* **Mask private values** - filter variables that should be published (client variables) whose values are listed separated by commas, for indicating a set use the symbol * (asterisk),
* **To send only changes** - the client will publish only the variables that changed value (according to mask),
* **To give private values at startup** - if this option is checked, the will be <span id="result_box" lang="en"><span title="Publish all states at start - Publish all states (defined by state mask) every time by connection establishment to announce own available states and their values. ">published all the States (according to mask) every time a connection is established, to declare the available variables and their values,</span></span>
* **The prefix for all values** - if the specified value, it will be added as a prefix to each published topic, for example, if you specify client1 /, then all topics will be published the following lines: `client1/javascript/0/cubietruck`,
* **Output log for each change** - in the log file will display debugging information for each change,
* **To send not only the team, but also the state (ack = true)** - if this option is not checked, the broker only sent variables / commands with ack = false, if the option to note that will be sent to all data, regardless of ack = true or ack = false,
* **The maximum length of a topic** - the maximum number of characters for the description of the topic, including service.
Examples for setting the subscription mask variables (patterns). Consider topics:
* "Sport"
* "Sport/Tennis"
* "Sport/Basketball"
* "Sport/Swimming"
* "Sport/Tennis/Finals"
* "Sport/Basketball/Finals"
* "Sport/Swimming/Finals"
If you want to subscribe to a certain set of topics, you can use the characters # (pound sign) or + (plus sign).
* "Sport/Tennis/#" (subscription only "Sport/Tennis" and "Sport/Tennis/Finals")
* "Sport/Tennis/+" (subscription only "Sport/Tennis/Finals", but not "Sport/Tennis")
For JMS topics, if you want to subscribe to all topics "Finals", you can use the characters # (pound sign) or + (plus sign)
* "Sport/#/Finals"
* "Sport/+/Finals"
For MQTT topics if you want to subscribe to all topics "Finals", you can use the + (plus sign)
* "Sport/+/Finals"
As an example, consider the exchange of data between the two systems yunkong2. There is a working system yunkong2 for BananaPi-Board (IP address 192.168.69.51), it launched MQTT- driver in the server/broker mode from the example above. To the server connects a client that publishes data from the sensor DHT22 temperature and humidity, as well as signed variables of interval measurement transmission and the status led (enable/disable) in the example above. The second operating system yunkong2 on the Board Cubietruck, it will run the MQTT driver in a client/subscriber mode. He signs up for the variables temperature and humidity of the broker (which, in turn, receives from another client) and will publish all the script variables - [the state of the battery](http://www.yunkong2.net/?page_id=4268&lang=ru#_Li-polLi-ion) board (only the changes). Client configuration will be similar to the following: [![](http://www.yunkong2.net/wp-content/uploads//7-284x300.png)](http://www.yunkong2.net/wp-content/uploads//7.png) Connection type the customer/subscriber indicates the IP address of the broker and the port (default 1883). Traffic encryption and authentication is not needed. Mask for the subscriptions (Patterns) - `mqtt/0/example1/hum,mqtt/0/example1/temp` - client is subscribed only on temperature and humidity (values separated by comma without spaces). Mask the data for publication - `javascript.0.cubietruck.battery.*` - publish all the script variables `cubietruck` in the group `battery` driver `javascript.0`. To send only the changes - send state variables batteries (makes no sense to send if the value has not changed). To give private values at startup when starting the driver, the client immediately will release all variables according to the mask even if they are null or empty to create variables in the broker. To send data with ack=false, variables work battery updated driver javascript, so they are always ack=false. The result of the work on the client side (temperature and humidity data of another customer - see the example above): [![](http://www.yunkong2.net/wp-content/uploads//9-1024x267.png)](http://www.yunkong2.net/wp-content/uploads//9.png) The result of the broker (status data of the battery client): [![](http://www.yunkong2.net/wp-content/uploads//11-1024x297.png)](http://www.yunkong2.net/wp-content/uploads//11.png)
## Application - MQTT gateway protocols - ModBus RTU
Driver MQTT can be used as a gateway for various protocols to connect new devices to the system yunkong2 or any other. A universal basis for the development of such solutions are arduino boards. In a network many examples, libraries and best practices. A huge community is working with these controllers, and the system integrated a variety of devices/equipment/devices. For example, consider the common industrial protocol ModBus. In yunkong2 system has a driver to work with it - version ModBus TCP (over ethernet). A set of sensors, controllers and actuators work physically on the RS-485 Network / 232 and ModBus RTU protocol. In order to integrate them can be applied MQTT Gateway - ModBus RTU based on arduino platform. Consider an example. <span style="text-decoration: underline;">**There is a temperature and humidity sensor** </span>(for the test on the basis of arduino pro mini board DHT22 Sensor), that outputs data via ModBUS RTU:
* Port UART (you can use MAX485 chip to convert RS-485 interface) running at 9600 with options 8E1 (1 start bit, 8 data bits, 1 Even parity bit, 1 stop bit),
* the address of the ModBus 10,
* temperature address 0 the value multiplied by 10 (the reader function 3),
* humidity address 1 value multiplied by 10 (read function 3),
* PWM LED address 2 value 0...1023 to check the recording function (write function 6).
Connection scheme:
![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus1.jpg)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus1.jpg)
by Fritzing
Code for arduino pro mini controller produces the following:
<pre>#include //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
#include //https://code.google.com/archive/p/simple-modbus/
#include //https://github.com/PaulStoffregen/MsTimer2
// modbus registers
enum {
TEMP,
HUM,
PWM,
TEST,
HOLDING_REGS_SIZE
};
#define ID_MODBUS 10 // modbus address of the slave device
unsigned int holdingRegs[HOLDING_REGS_SIZE]; // modbus register array
// temperature and humidity sensor DHT22
dht DHT;
#define DHT22_PIN 2
#define LED 9 // LED is connected to the PWM pin-9
void setup()
{
// configure the modbus
modbus_configure(& Serial, 9600, SERIAL_8E1, ID_MODBUS, 0, HOLDING_REGS_SIZE, holdingRegs);
holdingRegs[TEST] = -157; // for the test of the negative values
// initialize a timer for 2 seconds update data in temperature and humidity registers
MsTimer2::set(2000, read_sensors);
MsTimer2::start(); // run timer
pinMode(LED, OUTPUT); // LED port initialization
}
// the function launched by timer each 2 seconds
void read_sensors()
{
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
if
data from the sensor DHT22 managed to be read
// we write integer value in the register of humidity
holdingRegs[HUM]
= 10 * DHT.humidity;
// we write integer value in the register of temperature
holdingRegs[TEMP] = 10 * DHT.temperature;
}
else {
// if it wasn't succeeded to read data from the sensor DHT22, we write zero in registers
holdingRegs[HUM] = 0;
holdingRegs[TEMP] = 0;
}
}
void loop()
{
modbus_update(); // modbus data update
// data from the LED control register transmit to the PWM (bit shift by 2 bits)
analogWrite(LED, holdingRegs[PWM] & gt; > 2);
}</pre>
To test the operation code and schema, you can connect to port serial board (for example, using a USB-UART Converter) and a special program to interview just made the temperature sensor and humidity with ModBus RTU interface. For the survey can be used, for example, [qmodbus](http://qmodbus.sourceforge.net/) or any other program. Settings:
* port (choose from the list which port is connected to the serial Arduino boards);
* speed and other parameters 9600 8E1;
* slave id: 10, read: function No. 3 read holding registers, starting address: 0, number of registers: 3,
* slave id: 10, record: function No. 6 write single register start address: 2,
The answer in the program when reading should be approximately the following:
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus2.jpg)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus2.jpg)
The answer in the program when recording:
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus3.jpg)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus3.jpg)
<span style="text-decoration: underline;">**Now configure the gateway itself and connect it to the yunkong2**</span> The gateway will be based on the platform arduino MEGA 2560 with ethernet shield - client MQTT, broker - an instance mqtt.0 yunkong2 system driver. Choosing the MEGA 2560 due to the fact that on this Board more than one UART port, respectively, is zero Serial0 (pin_0 (RX) and pin_1 (TX)) or simply Serial use to output debug messages, and Serial1 (pin_19 (RX) and pin_18 (TX)) for slave via ModBus.
* the client the fee for developing arduino MEGA 2560 + ethernet shield based on W5100 chip;
* to work with the ethernet board uses the [standard library](https://www.arduino.cc/en/Reference/Ethernet) for working with MQTT library [Pubsubclient](https://github.com/knolleary/pubsubclient);
* for the survey on the modbus use library [SimpleModbus](https://code.google.com/archive/p/simple-modbus/) version master;
* survey on UART port (just connect the RX port master, TX port slave and respectively TX port master, RX port slave), transmission control port is not used (it is for RS-485);
* port settings: speed 9600, 8Е1;
* - the address of the slave device 10, a function of reading number 3 (read holding registers), recording function no. 6 (write single register);
* - broker yunkong2 system driver mqtt.
Format topics of data exchange:
* `modbusgateway/send_interval` - client signed to change the transmission interval of the temperature readings and humidity (int value in seconds),
* `modbusgateway/temp` - client publishes with a a given interval the value of the temperature sensor DHT22 (type float),
* `modbusgateway/hum` - the client publishes with a given interval the value of the humidity sensor DHT22 (type float),
* `modbusgateway/led` - the client is subscribed to the state change of the led (PWM control value 0...1024).
СThe connection diagram will look something like this:
[caption id="" align="alignnone" width="699"][![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus6.jpg)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus6.jpg)
By Fritzing
For the test slave device energized from the master device. The Master in turn will work from the USB port, which is being debug (Serial0). Driver settings will be as follows:
[![](http://www.yunkong2.net/wp-content/uploads//14-283x300.png)](http://www.yunkong2.net/wp-content/uploads//14.png)
Connecting via TCP (WebSocket is not necessary), default port 1883\. The client within the local network, so to encrypt traffic and authenticate the user is not necessary. We will send only the changes since the client signed on the send interval indications and led state to obtain information about the update (without changing the value) to a variable makes no sense. To publish the subscription - note this option, as when you first connect (or connected after disconnection) of the client, he must know the state of the variables on which it is signed (a current interval of sending and whether the LED is to be turned on). Setting to send variables ack = true or false is also worth noting, as a variable (which signed the client) can change any driver / script / VIS and any changes should be sent to the client. The complete code for the arduino board will look like this:
<pre>// Connecting libraries
#include
#include
#include //https://github.com/knolleary/pubsubclient
#include //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
// Settings of a network
byte mac[] = { 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0x31 };
byte ip[] = { 192, 168, 69, 31 }; // arduino board IP address
byte mqttserver[] = { 192, 168, 69, 51 }; // yunkong2 server IP address
EthernetClient ethClient;
void callback(char* topic, byte* payload, unsigned int length);
PubSubClient client(mqttserver, 1884, callback, ethClient);
// Global variables
unsigned int send_interval = 10; // the sending interval of indications to the server, by default 10 seconds
unsigned long last_time = 0; // the current time for the timer
dht DHT;
#define DHT22_PIN 8
char buff[20];
//The processing function for incoming connections - reception of data on a subscription
void callback(char* topic, byte* payload, unsigned int length)
{
Serial.println("");
Serial.println("-------");
Serial.println("New callback of MQTT-broker");
// let's transform a subject (topic) and value (payload) to a line
payload[length] = '\0';
String strTopic = String(topic);
String strPayload = String((char*)payload);
// Research that "arrived" from the server on a subscription:
// Change of an interval of inquiry
if (strTopic == "example2/send_interval") {
int tmp = strPayload.toInt();
if (tmp == 0) {
send_interval = 10;
}
else {
send_interval = strPayload.toInt();
}
}
Serial.print(strTopic);
Serial.print(" ");
Serial.println(strPayload);
Serial.println("-------");
Serial.println("");
}
void setup()
{
Serial.begin(9600);
Serial.println("Start...");
// start network connection
Ethernet.begin(mac, ip);
Serial.print("IP: ");
Serial.println(Ethernet.localIP());
// initialize input/output ports, register starting values
}
void loop()
{
// If the MQTT connection inactively, then we try to set it and to publish/subscribe
if (!client.connected()) {
Serial.print("Connect to MQTT-boker... ");
// Connect and publish / subscribe
if (client.connect("example2")) {
Serial.println("success");
// Value from sensors
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
// Subscribe for an inquiry interval
client.subscribe("example2/send_interval");
}
else {
// If weren't connected, we wait for 10 seconds and try again
Serial.print("Failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 10 seconds");
delay(10000);
}
// If connection is active, then sends the data to the server with the specified time interval
}
else {
if (millis() & gt; (last_time + send_interval * 1000)) {
last_time = millis();
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
}
}
// Check of incoming connections on a subscription
client.loop();
}
</pre>
This solution can be used as a prototype (example) ModBus network in your automation system. The data from the slave is transmitted with the desired spacing in the yunkong2.
[![](http://www.yunkong2.net/wp-content/uploads//10-1024x202.png)](http://www.yunkong2.net/wp-content/uploads//10.png)
MQTT client signed variables and redirects needed in slave-device on the ModBus network.
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus5.jpg)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-modbus5.jpg)
## Application - connecting mobile clients
Recently MQTT protocol became very common due to the simplicity, economy of the traffic and the elaboration of good libraries for different platforms. There are many programs to work with MQTT on mobile devices, for example [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=en). With this program you can connect to the MQTT broker in a local network or the Internet. Consider an example, in the role of the broker will be the yunkong2 system, to which using MQTT to connect the client application IoT MQTT Dashboard. In this example, we control the light controller [MegaD-328](http://www.ab-log.ru/smart-house/ethernet/megad-328), which is connected to the yunkong2 with the driver [MegaD](http://www.yunkong2.net/?page_id=4052&lang=en). Controls relay (MegaD port **P7**) light in the lobby, a special script, which is signed by the state of the port - button **P0** and MQTT-variable state **mqtt.0.remotectrl.light.hall**, which will publish the mobile client. This script toggles the state of the port that is bound to the switch (port P7), ie inverts it. It turns out that each time you press the button, electrically connected to port **P0** (caught the **true** state) and every time you publish variable **mqtt.0.remotectrl.light.hall** value as **true**, the port **P7** to turn on or off the light. The text of the script will be like this:
<pre class="">// Control of lighting in the hall by means of the button p0 port of the MegaD controller the driver instance megad.0
on({ id : 'megad.0.p0_P0', change : 'any' }, function(obj) {
if (obj.newState.val != = '' || typeof obj.newState.val != = "undefined") {
if (obj.newState.val == = true) {
if (getState('megad.0.p7_P7').val == = true) {
setState('megad.0.p7_P7', false);
}
else {
setState('megad.0.p7_P7', true);
}
}
}
});
// Control of lighting in the hall is remote on MQTT a topic "mqtt.0.remotectrl.light.hall"
on({ id : 'mqtt.0.remotectrl.light.hall', change : 'any' }, function(obj) {
if (obj.newState.val != = '' || typeof obj.newState.val != = "undefined") {
if (obj.newState.val == = true) {
if (getState('megad.0.p7_P7').val == = true) {
setState('megad.0.p7_P7', false);
}
else {
setState('megad.0.p7_P7', true);
}
}
}
});
</pre>
Connect button and light bulbs to MegaD controller:
[![](http://www.yunkong2.net/wp-content/uploads//mqtt-mobile1.jpg)](http://www.yunkong2.net/wp-content/uploads//mqtt-mobile1.jpg)
MQTT driver settings: [![](http://www.yunkong2.net/wp-content/uploads//14-283x300.png)](http://www.yunkong2.net/wp-content/uploads//14.png)
The mobile client can publish data to variable mqtt.0.remotectrl.light.hall and signs up for a real port status MegaD megad.0.p7_P7\. The configure publishing and subscriptions:
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-mobile3.png)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-mobile3.png)
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-mobile4.png)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-mobile4.png)
In total for one channel light control turn the control window (publish) and subscription window is a real condition light relay (for feedback):
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-mobile5.png) ![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-mobile6.png)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-mobile6.png)
## Application - working with cloud servers
The example described above has several disadvantages. First, it is not always the mobile client may be on the same local network as the server yunkong2, and secondly, even if you implement port forwarding in the Internet and to protect the connection, not always the server itself yunkong2 can accept incoming connection (located behind a NAT which has no access to settings). In the global network many different services that support MQTT - paid and free, for example sending weather data, geolocation, etc. Some services may act as MQTT protocol broker and can be used as a gateway (bridge) to output data from yunkong2 the global network, or to obtain data in yunkong2. As an example, consider the work of the bundles:
* server / broker - service [cloudmqtt.com](https://www.cloudmqtt.com/) (there is a free tariff),
* customer/subscriber the yunkong2 system with access to the Internet, publishes data of temperature and humidity (see [example above](http://www.yunkong2.net/?page_id=6435&lang=en#yunkong2_working_as_MQTT-broker)), publishes the real status of ports **P7-P13** (relay driver MegaD **megad.0** light control), subscribing to properties of the remote light control (an instance of the driver mqtt **mqtt.0**),
* килент/подписчик - приложение [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=en) для удаленной работы - подписка на данные сенсора температуры и влажности, подписка на реальное состояние портов **P7-P13** (реле MegaD драйвера **megad.0**), публикация переменных удаленного управления светом (экземпляр драйвера **mqtt.0**). - customer/subscriber the application of [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=en) to work remotely subscribe to sensor data of temperature and humidity, subscription to the real status of ports **P7-P13** (relay driver MegaD **megad.0**), publication of variables of a remote control light (driver instance **mqtt.0**).
he result is the following structure:
[![](http://www.yunkong2.net/wp-content/uploads//mqtt-cloud1-1024x511.jpg)](http://www.yunkong2.net/wp-content/uploads//mqtt-cloud1.jpg)
Bundle driver **mqtt.1** (broker) Arduino UNO + Ethernet + DHT22 (client) as in [the example above](http://www.yunkong2.net/?page_id=6435&lang=en#yunkong2_working_as_MQTT-broker) with a few modifications. Configuring an instance of the mqtt **driver.1**:
[![](http://www.yunkong2.net/wp-content/uploads//14-283x300.png)](http://www.yunkong2.net/wp-content/uploads//14.png)
Code for the arduino platform:
<pre>// Connecting libraries
#include
#include
#include //https://github.com/knolleary/pubsubclient
#include //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
// Settings of a network
byte mac[] = { 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0x31 };
byte ip[] = { 192, 168, 69, 31 }; // arduino board IP address
byte mqttserver[] = { 192, 168, 69, 51 }; // yunkong2 server IP address
EthernetClient ethClient;
void callback(char* topic, byte* payload, unsigned int length);
PubSubClient client(mqttserver, 1884, callback, ethClient);
// Global variables
unsigned int send_interval = 10; // the sending interval of indications to the server, by default 10 seconds
unsigned long last_time = 0; // the current time for the timer
dht DHT;
#define DHT22_PIN 8
char buff[20];
//The processing function for incoming connections - reception of data on a subscription
void callback(char* topic, byte* payload, unsigned int length)
{
Serial.println("");
Serial.println("-------");
Serial.println("New callback of MQTT-broker");
// let's transform a subject (topic) and value (payload) to a line
payload[length] = '\0';
String strTopic = String(topic);
String strPayload = String((char*)payload);
// Research that "arrived" from the server on a subscription:
// Change of an interval of inquiry
if (strTopic == "example2/send_interval") {
int tmp = strPayload.toInt();
if (tmp == 0) {
send_interval = 10;
}
else {
send_interval = strPayload.toInt();
}
}
Serial.print(strTopic);
Serial.print(" ");
Serial.println(strPayload);
Serial.println("-------");
Serial.println("");
}
void setup()
{
Serial.begin(9600);
Serial.println("Start...");
// start network connection
Ethernet.begin(mac, ip);
Serial.print("IP: ");
Serial.println(Ethernet.localIP());
// initialize input/output ports, register starting values
}
void loop()
{
// If the MQTT connection inactively, then we try to set it and to publish/subscribe
if (!client.connected()) {
Serial.print("Connect to MQTT-boker... ");
// Connect and publish / subscribe
if (client.connect("example2")) {
Serial.println("success");
// Value from sensors
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
// Subscribe for an inquiry interval
client.subscribe("example2/send_interval");
}
else {
// If weren't connected, we wait for 10 seconds and try again
Serial.print("Failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 10 seconds");
delay(10000);
}
// If connection is active, then sends the data to the server with the specified time interval
}
else {
if (millis() & gt; (last_time + send_interval * 1000)) {
last_time = millis();
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
}
}
// Check of incoming connections on a subscription
client.loop();
}</pre>
The result of the work - **mqtt.1** driver objects:
[![](http://www.yunkong2.net/wp-content/uploads//12-1024x187.png)](http://www.yunkong2.net/wp-content/uploads//12.png)
Now let's set up publish/subscribe data to the cloud. For a start, register on the site [cloudmqtt.com](https://www.cloudmqtt.com/), select the desired rate, create instance, get settings:
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud4.jpg)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud4.jpg)
For greater security it is better to create a separate user, assume that it will be user **yunkong2**with the password **1234**. Give user permission to read and write in any topic:
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud5.jpg)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud5.jpg)
Next set the instance of the mqtt **driver.0** to connect as a client/subscriber cloud broker and a list of publications/subscriptions:
[![](http://www.yunkong2.net/wp-content/uploads//8-283x300.png)](http://www.yunkong2.net/wp-content/uploads//8.png)
* connection type the customer/subscriber,
* connection settings specify the URL issued in the control panel [cloudmqtt.com](https://www.cloudmqtt.com/) the port will use **22809**that works with **SSL**,
* in the authentication options specify the user name and password,
* patterns our client yunkong2 will be signed on all the topics that are in the cloud, so you specify here the number sign (**#**), you can use a mask to selectively subscribe,
* mask of the eigenvalues client will publish to the server **temperature/humidity** and the status of all ports megaD (ports with relay P7-P13),this field separated by a comma specify the required variables: **mqtt.1.example2.hum,mqtt.1.example2.temp,megad.0.p7_P7,megad.0.p8_P8,megad.0.p9_P9,megad.0.p10_P10,megad.0.p11_P11,megad.0.p12_P12,megad.0.p13_P13**,
* to send only changes put a tick, will publish only the changes,
* to give your own values at the start just specify to create topics,
* to send not only commands, but also the state (ack=true) it should be noted that setting both the temperature/humidity updated driver mqtt (ack=true).
Settings saved, make sure that the connection is established (on the control panel [cloudmqtt.com](https://www.cloudmqtt.com/) watch the log server). After some time, data will appear (in the panel link **WebsocketUI**):
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud7.jpg)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud7.jpg)
In the end, it remains only to configure a mobile client, for example [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=en). Create a new connection:
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud8.png)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud8.png)
Create topics for publication (for example, lighting of the hall - port **P7** MegaD):
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud9.png)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud9.png)
Создаем топики подписок (температура, влажность, освещение зала порт **P7** MegaD):
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud10.png) ](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud10.png)[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud11.png)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud11.png)
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud12.png)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud12.png)
In the end, your dashboard might look something like this:
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud13.png)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud13.png)
[![](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud14.png)](http://www.yunkong2.net/wp-content/uploads//MQTT-example-cloud14.png)
After the creation of the publications on a mobile device, in the driver instance **mqtt.0** system yunkong2 should appear variable light control that should be used in the script for lighting control (see [example above](http://www.yunkong2.net/?page_id=6435&lang=en#Application_8211_connecting_mobile_clients)):
[![](http://www.yunkong2.net/wp-content/uploads//mqtt_13-1024x230.png)](http://www.yunkong2.net/wp-content/uploads//mqtt_13.png)
Congratulations! Now you can control the system yunkong2 and receive data via a cloud service!

BIN
docs/en/img/mqtt_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

BIN
docs/en/img/mqtt_10.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

BIN
docs/en/img/mqtt_11.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 KiB

BIN
docs/en/img/mqtt_12.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

BIN
docs/en/img/mqtt_13.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

BIN
docs/en/img/mqtt_14.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

BIN
docs/en/img/mqtt_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

BIN
docs/en/img/mqtt_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
docs/en/img/mqtt_4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

BIN
docs/en/img/mqtt_5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
docs/en/img/mqtt_6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
docs/en/img/mqtt_7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
docs/en/img/mqtt_8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
docs/en/img/mqtt_9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

BIN
docs/en/img/mqtt_cloud1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

799
docs/en/mqtt.md Normal file
View File

@ -0,0 +1,799 @@
![](MQTT)
# MQTT Server and Client
## Description
[MQTT](http://mqtt.org/) (Message Queue Telemetry Transport) is a lightweight protocol
used for communication between devices (M2M - machine-to-machine).
It uses a model publisher-subscriber to send messages over TCP / IP protocol.
The central part of the protocol is MQTT-server or broker who has access to the
publisher and the subscriber. This protocol is very primitive: a short title, without
integrity (that is why the transmission is implemented on top of TCP), does not impose any restrictions
on the structure, coding or database schema. The only requirement for the data in each packet - they
must be accompanied by the identifier information channel. This identifier specification called Topic Name.
The MQTT Protocol requires a data broker. This is the Central idea of the technology. All devices send
data only to the broker and receive data also from him only. After receiving the packet, the broker
sends it to all devices on the network according to their subscription . For the device to get
something from the broker it must subscribe to a topic. Topics arise dynamically upon subscription
or upon arrival of the package with this topic. By subscribing to a topic, you can give up. Thus
topics are a convenient mechanism for organizing different kinds of relationships: one-to-many,
many-to-one and many-to-many.
**Important points:**
* the devices themselves establish communication with the broker, they may is behind a NAT and does not have static IP addresses,
* you can use SSL to encrypt the traffic,
* MQTT brokers allow you to connect to them through the WebSocket protocol on port 80,
* different brokers may be interconnected by subscribing to messages from each other.
## Installation
Installation is on the **Driver** tab page of the [administration system](http://www.yunkong2.net/?page_id=4179&lang=en).
In the driver group **Network** find a line called **MQTT Adapter**, and press the
button with the plus icon in the right side of the line.  
![](img/mqtt_1.png)
You will see a pop-up window driver installation, after installation, it will automatically close.
![](img/mqtt_2.png)
If all goes well, on the **Settings driver** tab appears **mqtt.0** installed instance of the driver.
![](img/mqtt_3.png)
## Setting
As stated above, MQTT protocol implies a broker and clients. yunkong2 server can act as a broker and a client.
Setting to specify the operating mode - the type (server / broker or the customer / subscriber)
Consider every option.
### yunkong2 working as MQTT-broker
Basic settings if you intend to use the server/broker is shown in the picture:
![](img/mqtt_4.png)
* **Use WebSockets** - if there is a need to use WEB sockets for connection, you must install this option, with TCP-server will run in parallel with the WebSocket server,
* **Port** - the port to connect on TCP (default is 1883), a WebSocket server (see option above) runs on port +1 (default: 1884),
* **SSL** - this option is used if you want to encrypt all traffic (TCP or the WebSocket), thus it is necessary to specify certificates - simply select from a list of pre-set (specified in the system settings, see the [systems management driver description](http://www.yunkong2.net/?page_id=4179&lang=en)),
* **authentication settings** (username and password) - indicate, if necessary a specific user authentication, this setting is always used in conjunction with SSL-encryption option (not to transmit passwords in clear text over an insecure connection),
* **Mask private values** - the template (or several comma-separated) to filter variables yunkong2, which will be sent to the client, you can use special characters to specify a group of messages (for example, `memRSS, mqtt.0` - can be transmitted all the variables memory status of all drivers and all **mqtt.0 driver** instance variables),
* **To send only changes** - sending data to the client will be made only in case of change of a variable (if the state simply update - the value is not changed, the customer message will not be sent) from the client will be accepted any message, even if the value has not changed,
* **To give private values at startup** - for each successful client connection will be transferred to all known states (defined by the mask state) in order to tell the client about the current state of the yunkong2,
* **Post status subscribes** - immediately after the subscription will be sent to the customer value of the variable on which it is signed (at the first start or restart the client will receive the values of variables on which it is signed, can be used to initialize variables),
* **The prefix for all values** - if the specified value, it will be added as a prefix to every sent topic, for example, if you specify yunkong2/, then all topics sent along the following lines: `yunkong2/mqtt/0/connected`,
* **Output log for each change** - in the log file will display debugging information for each change,
* **To send not only commands, but also the state (ack=true)** - if this option is not active, the client will only send variables/commands with ack=false, if the flag is set, then variables will be transferred regardless of the state of ack (false / true),
* **The maximum length of the name of a topic** - the maximum number of characters for the description of the topic, including service.
As an example, consider the exchange of data between the client based on the [arduino board](https://www.arduino.cc/) and the broker is an instance of mqtt.0 driver system yunkong2.
* - the client the fee for developing [arduino UNO](https://www.arduino.cc/en/Main/ArduinoBoardUno) + [ethernet shield](https://store.arduino.cc/product/A000072) based on W5100 chip,
* - to work with the ethernet board uses the standard [library](https://www.arduino.cc/en/Reference/Ethernet) for working with MQTT library [Pubsubclient](https://github.com/knolleary/pubsubclient),
* - the AM2302 sensor (temperature and humidity) connected to pin_8 for the survey used library with DHTlib with [DHTlib](https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib) resource github.com,
* - led **led_green** is connected to pin_9, control in discrete mode on/off
* - broker yunkong2 system driver mqtt.
Format topics of data exchange:
* `example1/send_interval` - client signed to change the transmission interval of the temperature readings and humidity (int value in seconds),
* `example1/temp` - client publishes a specified temperature interval with DHT22 sensor (float type),
* `example1/hum` - client publishes a specified humidity value intervals with DHT22 sensor (float type),
* `example1/led` - the client is subscribed to the state change of the led (the text on/off or 0/1 or true/false).
Driver settings will be as follows:
![](img/mqtt_5.png)
Connecting via TCP (WebSocket is not necessary), default port 1883\. The client within the local network, so to encrypt traffic and authenticate the user is not necessary. We will send only the changes since the client signed on the send interval indications and led state to obtain information about the update (without changing the value) to a variable makes no sense. To publish the subscription - note this option, as when you first connect (or connected after disconnection) of the client, he must know the state of the variables on which it is signed (a current interval of sending and whether the LED is to be turned on). Setting to send variables ack = true or false is also worth noting, as a variable (which signed the client) can change any driver / script / VIS and any changes should be sent to the client. The complete code for the arduino board will look like this:
<pre>// Connecting libraries
#include
#include
#include //https://github.com/knolleary/pubsubclient
#include //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
//Settings of a network
byte mac[] = {
0xAB,
0xBC,
0xCD,
0xDE,
0xEF,
0x31
};
byte ip[] = {
192,
168,
69,
31
}; //arduino board IP address
byte mqttserver[] = {
192,
168,
69,
51
}; // yunkong2 server IP address
EthernetClient ethClient;
void callback(char * topic, byte * payload, unsigned int length);
PubSubClient client(mqttserver, 1883, callback, ethClient);
//Global variables
#define LED_pin 9
unsigned int send_interval = 10; // the sending interval of indications to the server, by default 10 seconds
unsigned long last_time = 0; // the current time for the timer
dht DHT;
#define DHT22_PIN 8
char buff[20];
// The processing function for incoming connections - reception of data on a subscription
void callback(char * topic, byte * payload, unsigned int length) {
Serial.println("");
Serial.println("-------");
Serial.println("New callback of MQTT-broker");
// let's transform a subject (topic) and value (payload) to a line
payload[length] = '\0';
String strTopic = String(topic);
String strPayload = String((char * ) payload);
// research that "arrived" from the server on a subscription::
// Change of an interval of inquiry
if (strTopic == "example1/send_interval") {
int tmp = strPayload.toInt();
if (tmp == 0) {
send_interval = 10;
} else {
send_interval = strPayload.toInt();
}
}
// Control of a LED
if (strTopic == "example1/led") {
if (strPayload == "off" || strPayload == "0" || strPayload == "false") digitalWrite(LED_pin, LOW);
if (strPayload == "on" || strPayload == "1" || strPayload == "true") digitalWrite(LED_pin, HIGH);
}
Serial.print(strTopic);
Serial.print(" ");
Serial.println(strPayload);
Serial.println("-------");
Serial.println("");
}
void setup() {
Serial.begin(9600);
Serial.println("Start...");
// start network connection
Ethernet.begin(mac, ip);
Serial.print("IP: ");
Serial.println(Ethernet.localIP());
// initialize input/output ports, register starting values
pinMode(LED_pin, OUTPUT);
digitalWrite(LED_pin, LOW); // when the LED is off
}
void loop() {
// If the MQTT connection inactively, then we try to set it and to publish/subscribe
if (!client.connected()) {
Serial.print("Connect to MQTT-boker... ");
// Connect and publish / subscribe
if (client.connect("example1")) {
Serial.println("success");
// Value from sensors
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example1/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example1/temp", buff);
}
// subscribe for an inquiry interval
client.subscribe("example1/send_interval");
// subscribe to the LED control variable
client.subscribe("example1/led");
} else {
// If weren't connected, we wait for 10 seconds and try again
Serial.print("Failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 10 seconds");
delay(10000);
}
// If connection is active, then sends the data to the server with the specified time interval
} else {
if (millis() & gt;
(last_time + send_interval * 1000)) {
last_time = millis();
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example1/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example1/temp", buff);
}
}
}
// Check of incoming connections on a subscription
client.loop();
}
</pre>
The result of the part of the broker (temperature and humidity data is updated with the preset time period):
![](img/mqtt_6.png)
The result of the client-side (incoming data subscription output to the console for debugging):
![](img/mqtt_server4.jpg)
### yunkong2 working as MQTT-client
For an instance MQTT driver earned as a client / subscriber - you need to choose the appropriate type of configuration.
In this set of options will change slightly:
![](img/mqtt_4.png)
* **Connection settings** - specifies the URL and port of the broker (if you want to encrypt traffic, indicated SSL) - settings to connect to the broker,
* **Authentication settings** - user name and password, if the broker requires authentication (it is appropriate to use SSL to avoid transmitting the password in clear text),
* **Patterns** - a mask for variables for which the customer subscribes (variables broker), the values are listed separated by commas, the # (pound) is used to indicate the set,
* **Mask private values** - filter variables that should be published (client variables) whose values are listed separated by commas, for indicating a set use the symbol * (asterisk),
* **To send only changes** - the client will publish only the variables that changed value (according to mask),
* **To give private values at startup** - if this option is checked, the will be <span id="result_box" lang="en"><span title="Publish all states at start - Publish all states (defined by state mask) every time by connection establishment to announce own available states and their values. ">published all the States (according to mask) every time a connection is established, to declare the available variables and their values,</span></span>
* **The prefix for all values** - if the specified value, it will be added as a prefix to each published topic, for example, if you specify client1 /, then all topics will be published the following lines: `client1/javascript/0/cubietruck`,
* **Output log for each change** - in the log file will display debugging information for each change,
* **To send not only the team, but also the state (ack = true)** - if this option is not checked, the broker only sent variables / commands with ack = false, if the option to note that will be sent to all data, regardless of ack = true or ack = false,
* **The maximum length of a topic** - the maximum number of characters for the description of the topic, including service.
Examples for setting the subscription mask variables (patterns). Consider topics:
* "Sport"
* "Sport/Tennis"
* "Sport/Basketball"
* "Sport/Swimming"
* "Sport/Tennis/Finals"
* "Sport/Basketball/Finals"
* "Sport/Swimming/Finals"
If you want to subscribe to a certain set of topics, you can use the characters # (pound sign) or + (plus sign).
* "Sport/Tennis/#" (subscription only "Sport/Tennis" and "Sport/Tennis/Finals")
* "Sport/Tennis/+" (subscription only "Sport/Tennis/Finals", but not "Sport/Tennis")
For JMS topics, if you want to subscribe to all topics "Finals", you can use the characters # (pound sign) or + (plus sign)
* "Sport/#/Finals"
* "Sport/+/Finals"
For MQTT topics if you want to subscribe to all topics "Finals", you can use the + (plus sign)
* "Sport/+/Finals"
As an example, consider the exchange of data between the two systems yunkong2. There is a working system yunkong2 for
BananaPi-Board (IP address 192.168.69.51), it launched MQTT- driver in the server/broker mode from the example above.
To the server connects a client that publishes data from the sensor DHT22 temperature and humidity,
as well as signed variables of interval measurement transmission and the status led (enable/disable) in the example above.
The second operating system yunkong2 on the Board Cubietruck, it will run the MQTT driver in a client/subscriber mode.
He signs up for the variables temperature and humidity of the broker (which, in turn, receives from another client)
and will publish all the script variables - [the state of the battery](http://www.yunkong2.net/?page_id=4268&lang=ru#_Li-polLi-ion)
board (only the changes). Client configuration will be similar to the following:
![](img/mqtt_7.png)
Connection type the customer/subscriber indicates the IP address of the broker and the port (default 1883).
Traffic encryption and authentication is not needed.
Mask for the subscriptions (Patterns) - `mqtt/0/example1/hum,mqtt/0/example1/temp` - client is subscribed only
on temperature and humidity (values separated by comma without spaces).
Mask the data for publication - `javascript.0.cubietruck.battery.*` - publish all the script
variables `cubietruck` in the group `battery` driver `javascript.0`.
To send only the changes - send
state variables batteries (makes no sense to send if the value has not changed). To give private values
at startup when starting the driver, the client immediately will release all variables according to the
mask even if they are null or empty to create variables in the broker.
To send data with ack=false,
variables work battery updated driver javascript, so they are always ack=false. The result of the work
on the client side (temperature and humidity data of another customer - see the example above):
![](img/mqtt_9.png)
The result of the broker (status data of the battery client):
![](img/mqtt_11.png)
## Application - MQTT gateway protocols - ModBus RTU
Driver MQTT can be used as a gateway for various protocols to connect new devices to the
system yunkong2 or any other. A universal basis for the development of such solutions are
arduino boards. In a network many examples, libraries and best practices. A huge community
is working with these controllers, and the system integrated a variety of devices/equipment/devices.
For example, consider the common industrial protocol ModBus. In yunkong2 system has a driver to work with it - version
ModBus TCP (over ethernet). A set of sensors, controllers and actuators work physically on the RS-485 Network / 232 and ModBus RTU protocol.
In order to integrate them can be applied MQTT Gateway - ModBus RTU based on arduino platform. Consider an example.
<span style="text-decoration: underline;">**There is a temperature and humidity sensor**</span> (for the test on the basis of arduino pro mini board DHT22 Sensor),
that outputs data via ModBUS RTU:
* Port UART (you can use MAX485 chip to convert RS-485 interface) running at 9600 with options 8E1 (1 start bit, 8 data bits, 1 Even parity bit, 1 stop bit),
* the address of the ModBus 10,
* temperature address 0 the value multiplied by 10 (the reader function 3),
* humidity address 1 value multiplied by 10 (read function 3),
* PWM LED address 2 value 0...1023 to check the recording function (write function 6).
Connection scheme:
![](img/mqtt_example-modbus1.jpg)
by Fritzing
Code for arduino pro mini controller produces the following:
<pre>
#include //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
#include //https://code.google.com/archive/p/simple-modbus/
#include //https://github.com/PaulStoffregen/MsTimer2
// modbus registers
enum {
TEMP,
HUM,
PWM,
TEST,
HOLDING_REGS_SIZE
};
#define ID_MODBUS 10 // modbus address of the slave device
unsigned int holdingRegs[HOLDING_REGS_SIZE]; // modbus register array
// temperature and humidity sensor DHT22
dht DHT;
#define DHT22_PIN 2
#define LED 9 // LED is connected to the PWM pin-9
void setup()
{
// configure the modbus
modbus_configure(& Serial, 9600, SERIAL_8E1, ID_MODBUS, 0, HOLDING_REGS_SIZE, holdingRegs);
holdingRegs[TEST] = -157; // for the test of the negative values
// initialize a timer for 2 seconds update data in temperature and humidity registers
MsTimer2::set(2000, read_sensors);
MsTimer2::start(); // run timer
pinMode(LED, OUTPUT); // LED port initialization
}
// the function launched by timer each 2 seconds
void read_sensors()
{
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
if
data from the sensor DHT22 managed to be read
// we write integer value in the register of humidity
holdingRegs[HUM]
= 10 * DHT.humidity;
// we write integer value in the register of temperature
holdingRegs[TEMP] = 10 * DHT.temperature;
}
else {
// if it wasn't succeeded to read data from the sensor DHT22, we write zero in registers
holdingRegs[HUM] = 0;
holdingRegs[TEMP] = 0;
}
}
void loop()
{
modbus_update(); // modbus data update
// data from the LED control register transmit to the PWM (bit shift by 2 bits)
analogWrite(LED, holdingRegs[PWM] >> 2);
}
</pre>
To test the operation code and schema, you can connect to port serial board (for example, using a USB-UART Converter)
and a special program to interview just made the temperature sensor and humidity with ModBus RTU interface.
For the survey can be used, for example, [qmodbus](http://qmodbus.sourceforge.net/) or any other program.
Settings:
- port (choose from the list which port is connected to the serial Arduino boards);
- speed and other parameters 9600 8E1;
- slave id: 10, read: function No. 3 read holding registers, starting address: 0, number of registers: 3,
- slave id: 10, record: function No. 6 write single register start address: 2,
The answer in the program when reading should be approximately the following:
![](img/mqtt_example-modbus2.jpg)
The answer in the program when recording:
![](img/mqtt_example-modbus3.jpg)
<span style="text-decoration: underline;">**Now configure the gateway itself and connect it to the yunkong2**</span>
The gateway will be based on the
platform arduino MEGA 2560 with ethernet shield - client MQTT, broker - an instance mqtt.0 yunkong2 system driver.
Choosing the MEGA 2560 due to the fact that on this Board more than one UART port, respectively, is
zero Serial0 (pin_0 (RX) and pin_1 (TX)) or simply Serial use to output debug messages, and Serial1 (pin_19 (RX) and pin_18 (TX)) for slave via ModBus.
* the client the fee for developing arduino MEGA 2560 + ethernet shield based on W5100 chip;
* to work with the ethernet board uses the [standard library](https://www.arduino.cc/en/Reference/Ethernet)
for working with MQTT library [Pubsubclient](https://github.com/knolleary/pubsubclient);
* for the survey on the modbus use library [SimpleModbus](https://code.google.com/archive/p/simple-modbus/) version master;
* survey on UART port (just connect the RX port master, TX port slave and respectively TX port master, RX port slave), transmission control port is not used (it is for RS-485);
* port settings: speed 9600, 8Е1;
* the address of the slave device 10, a function of reading number 3 (read holding registers), recording function no. 6 (write single register);
* broker yunkong2 system driver mqtt.
Format topics of data exchange:
* `modbusgateway/send_interval` - client signed to change the transmission interval of the temperature readings and humidity (int value in seconds),
* `modbusgateway/temp` - client publishes with a a given interval the value of the temperature sensor DHT22 (type float),
* `modbusgateway/hum` - the client publishes with a given interval the value of the humidity sensor DHT22 (type float),
* `modbusgateway/led` - the client is subscribed to the state change of the led (PWM control value 0...1024).
СThe connection diagram will look something like this:
![](img/mqtt_example-modbus6.jpg)
For the test slave device energized from the master device. The Master in turn will work from the USB port, which is being debug (Serial0).
Driver settings will be as follows:
![](img/mqtt_14.png)
Connecting via TCP (WebSocket is not necessary), default port 1883\. The client within the local network, so to encrypt traffic and authenticate the user is not necessary. We will send only the changes since the client signed on the send interval indications and led state to obtain information about the update (without changing the value) to a variable makes no sense. To publish the subscription - note this option, as when you first connect (or connected after disconnection) of the client, he must know the state of the variables on which it is signed (a current interval of sending and whether the LED is to be turned on). Setting to send variables ack = true or false is also worth noting, as a variable (which signed the client) can change any driver / script / VIS and any changes should be sent to the client. The complete code for the arduino board will look like this:
<pre class="">// Connecting libraries
#include
#include
#include //https://github.com/knolleary/pubsubclient
#include //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
// Settings of a network
byte mac[] = { 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0x31 };
byte ip[] = { 192, 168, 69, 31 }; // arduino board IP address
byte mqttserver[] = { 192, 168, 69, 51 }; // yunkong2 server IP address
EthernetClient ethClient;
void callback(char* topic, byte* payload, unsigned int length);
PubSubClient client(mqttserver, 1884, callback, ethClient);
// Global variables
unsigned int send_interval = 10; // the sending interval of indications to the server, by default 10 seconds
unsigned long last_time = 0; // the current time for the timer
dht DHT;
#define DHT22_PIN 8
char buff[20];
//The processing function for incoming connections - reception of data on a subscription
void callback(char* topic, byte* payload, unsigned int length)
{
Serial.println("");
Serial.println("-------");
Serial.println("New callback of MQTT-broker");
// let's transform a subject (topic) and value (payload) to a line
payload[length] = '\0';
String strTopic = String(topic);
String strPayload = String((char*)payload);
// Research that "arrived" from the server on a subscription:
// Change of an interval of inquiry
if (strTopic == "example2/send_interval") {
int tmp = strPayload.toInt();
if (tmp == 0) {
send_interval = 10;
}
else {
send_interval = strPayload.toInt();
}
}
Serial.print(strTopic);
Serial.print(" ");
Serial.println(strPayload);
Serial.println("-------");
Serial.println("");
}
void setup()
{
Serial.begin(9600);
Serial.println("Start...");
// start network connection
Ethernet.begin(mac, ip);
Serial.print("IP: ");
Serial.println(Ethernet.localIP());
// initialize input/output ports, register starting values
}
void loop()
{
// If the MQTT connection inactively, then we try to set it and to publish/subscribe
if (!client.connected()) {
Serial.print("Connect to MQTT-boker... ");
// Connect and publish / subscribe
if (client.connect("example2")) {
Serial.println("success");
// Value from sensors
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
// Subscribe for an inquiry interval
client.subscribe("example2/send_interval");
}
else {
// If weren't connected, we wait for 10 seconds and try again
Serial.print("Failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 10 seconds");
delay(10000);
}
// If connection is active, then sends the data to the server with the specified time interval
}
else {
if (millis() & gt; (last_time + send_interval * 1000)) {
last_time = millis();
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
}
}
// Check of incoming connections on a subscription
client.loop();
}
</pre>
This solution can be used as a prototype (example) ModBus network in your automation system. The data from the slave is
transmitted with the desired spacing in the yunkong2.
![](img/mqtt_10.png)
MQTT client signed variables and redirects needed in slave-device on the ModBus network.
![](img/mqtt_example-modbus5.jpg)
## Application - connecting mobile clients
Recently MQTT protocol became very common due to the simplicity, economy of the traffic and the elaboration of good libraries for different platforms.
There are many programs to work with MQTT on mobile devices, for example [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=en).
With this program you can connect to the MQTT broker in a local network or the Internet.
Consider an example, in the role of the broker will be the yunkong2 system, to which using MQTT to connect the client application
IoT MQTT Dashboard.
In this example, we control the light controller [MegaD-328](http://www.ab-log.ru/smart-house/ethernet/megad-328),
which is connected to the yunkong2 with the driver [MegaD](http://www.yunkong2.net/?page_id=4052&lang=en).
Controls relay (MegaD port **P7**) light in the lobby, a special script, which is signed by the state
of the port - button **P0** and MQTT-variable state **mqtt.0.remotectrl.light.hall**, which will publish the mobile client.
This script toggles the state of the port that is bound to the switch (port P7), ie inverts it.
It turns out that each time you press the button, electrically connected to port **P0** (caught the **true** state) and every
time you publish variable **mqtt.0.remotectrl.light.hall** value as **true**, the port **P7** to turn on or off the light.
The text of the script will be like this:
<pre>
// Control of lighting in the hall by means of the button p0 port of the MegaD controller the driver instance megad.0
on({ id : 'megad.0.p0_P0', change : 'any' }, function(obj) {
if (obj.newState.val != = '' || typeof obj.newState.val != = "undefined") {
if (obj.newState.val == = true) {
if (getState('megad.0.p7_P7').val == = true) {
setState('megad.0.p7_P7', false);
}
else {
setState('megad.0.p7_P7', true);
}
}
}
});
// Control of lighting in the hall is remote on MQTT a topic "mqtt.0.remotectrl.light.hall"
on({ id : 'mqtt.0.remotectrl.light.hall', change : 'any' }, function(obj) {
if (obj.newState.val != = '' || typeof obj.newState.val != = "undefined") {
if (obj.newState.val == = true) {
if (getState('megad.0.p7_P7').val == = true) {
setState('megad.0.p7_P7', false);
}
else {
setState('megad.0.p7_P7', true);
}
}
}
});
</pre>
Connect button and light bulbs to MegaD controller:
![](img/mqtt_mobile1.jpg)
MQTT driver settings:
![](img/mqtt_14.png)
The mobile client can publish data to variable mqtt.0.remotectrl.light.hall and signs up for a real port status MegaD megad.0.p7_P7.
The configure publishing and subscriptions:
![](img/mqtt_example-mobile3.png)
![](img/mqtt_example-mobile4.png)
In total for one channel light control turn the control window (publish) and subscription window is a real condition light
relay (for feedback):
![](img/mqtt_example-mobile5.png)
![](img/mqtt_example-mobile6.png)
## Application - working with cloud servers
The example described above has several disadvantages. First, it is not always the mobile client may be on the same local network as the server yunkong2, and secondly, even if you implement port forwarding in the Internet and to protect the connection, not always the server itself yunkong2 can accept incoming connection (located behind a NAT which has no access to settings). In the global network many different services that support MQTT - paid and free, for example sending weather data, geolocation, etc. Some services may act as MQTT protocol broker and can be used as a gateway (bridge) to output data from yunkong2 the global network, or to obtain data in yunkong2. As an example, consider the work of the bundles:
* server / broker - service [cloudmqtt.com](https://www.cloudmqtt.com/) (there is a free tariff),
* customer/subscriber the yunkong2 system with access to the Internet, publishes data of temperature and humidity (see [example above](http://www.yunkong2.net/?page_id=6435&lang=en#yunkong2_working_as_MQTT-broker)), publishes the real status of ports **P7-P13** (relay driver MegaD **megad.0** light control), subscribing to properties of the remote light control (an instance of the driver mqtt **mqtt.0**),
* customer/subscriber the application of [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=en) to work remotely subscribe to sensor data of temperature and humidity, subscription to the real status of ports **P7-P13** (relay driver MegaD **megad.0**), publication of variables of a remote control light (driver instance **mqtt.0**).
The result is the following structure:
![](img/mqtt_cloud1.jpg)
Bundle driver **mqtt.1** (broker) Arduino UNO + Ethernet + DHT22 (client)
as in [the example above](http://www.yunkong2.net/?page_id=6435&lang=en#yunkong2_working_as_MQTT-broker) with a few modifications.
Configuring an instance of the mqtt **driver.1**:
![](img/mqtt_14.png)
Code for the arduino platform:
<pre class="">// Connecting libraries
#include
#include
#include //https://github.com/knolleary/pubsubclient
#include //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
// Settings of a network
byte mac[] = { 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0x31 };
byte ip[] = { 192, 168, 69, 31 }; // arduino board IP address
byte mqttserver[] = { 192, 168, 69, 51 }; // yunkong2 server IP address
EthernetClient ethClient;
void callback(char* topic, byte* payload, unsigned int length);
PubSubClient client(mqttserver, 1884, callback, ethClient);
// Global variables
unsigned int send_interval = 10; // the sending interval of indications to the server, by default 10 seconds
unsigned long last_time = 0; // the current time for the timer
dht DHT;
#define DHT22_PIN 8
char buff[20];
//The processing function for incoming connections - reception of data on a subscription
void callback(char* topic, byte* payload, unsigned int length)
{
Serial.println("");
Serial.println("-------");
Serial.println("New callback of MQTT-broker");
// let's transform a subject (topic) and value (payload) to a line
payload[length] = '\0';
String strTopic = String(topic);
String strPayload = String((char*)payload);
// Research that "arrived" from the server on a subscription:
// Change of an interval of inquiry
if (strTopic == "example2/send_interval") {
int tmp = strPayload.toInt();
if (tmp == 0) {
send_interval = 10;
}
else {
send_interval = strPayload.toInt();
}
}
Serial.print(strTopic);
Serial.print(" ");
Serial.println(strPayload);
Serial.println("-------");
Serial.println("");
}
void setup()
{
Serial.begin(9600);
Serial.println("Start...");
// start network connection
Ethernet.begin(mac, ip);
Serial.print("IP: ");
Serial.println(Ethernet.localIP());
// initialize input/output ports, register starting values
}
void loop()
{
// If the MQTT connection inactively, then we try to set it and to publish/subscribe
if (!client.connected()) {
Serial.print("Connect to MQTT-boker... ");
// Connect and publish / subscribe
if (client.connect("example2")) {
Serial.println("success");
// Value from sensors
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
// Subscribe for an inquiry interval
client.subscribe("example2/send_interval");
}
else {
// If weren't connected, we wait for 10 seconds and try again
Serial.print("Failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 10 seconds");
delay(10000);
}
// If connection is active, then sends the data to the server with the specified time interval
}
else {
if (millis() & gt; (last_time + send_interval * 1000)) {
last_time = millis();
if (DHT.read22(DHT22_PIN) == DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
}
}
// Check of incoming connections on a subscription
client.loop();
}</pre>
The result of the work - **mqtt.1** driver objects:
![](img/mqtt_12.png)
Now let's set up publish/subscribe data to the cloud. For a start, register on the site [cloudmqtt.com](https://www.cloudmqtt.com/), select
the desired rate, create instance, get settings:
![](img/mqtt_example-cloud4.jpg)
For greater security it is better to create a separate user, assume that it will be user **yunkong2**with the password **1234**.
Give user permission to read and write in any topic:
![](img/mqtt_example-cloud5.jpg)
Next set the instance of the mqtt **driver.0** to connect as a client/subscriber cloud broker and a list of publications/subscriptions:
![](img/mqtt_8.png)
* connection type the customer/subscriber,
* connection settings specify the URL issued in the control panel [cloudmqtt.com](https://www.cloudmqtt.com/) the port will use **22809**that works with **SSL**,
* in the authentication options specify the user name and password,
* patterns our client yunkong2 will be signed on all the topics that are in the cloud, so you specify here the number sign (**#**), you can use a mask to selectively subscribe,
* mask of the eigenvalues client will publish to the server **temperature/humidity** and the status of all ports megaD (ports with relay P7-P13),this field separated by a comma specify the required variables: **mqtt.1.example2.hum,mqtt.1.example2.temp,megad.0.p7_P7,megad.0.p8_P8,megad.0.p9_P9,megad.0.p10_P10,megad.0.p11_P11,megad.0.p12_P12,megad.0.p13_P13**,
* to send only changes put a tick, will publish only the changes,
* to give your own values at the start just specify to create topics,
* to send not only commands, but also the state (ack=true) it should be noted that setting both the temperature/humidity updated driver mqtt (ack=true).
Settings saved, make sure that the connection is established (on the control panel [cloudmqtt.com](https://www.cloudmqtt.com/) watch the log server).
After some time, data will appear (in the panel link **WebsocketUI**):
![](img/mqtt_example-cloud7.jpg)
In the end, it remains only to configure a mobile client, for example [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=en).
Create a new connection:
![](img/mqtt_example-cloud8.png)
Create topics for publication (for example, lighting of the hall - port **P7** MegaD):
![](img/mqtt_example-cloud9.png)
then create subscription for the topics (temperature, humidity, hall light on port **P7** MegaD):
![](img/mqtt_example-cloud10.png)
![](img/mqtt_example-cloud11.png)
![](img/mqtt_example-cloud12.png)
In the end, your dashboard might look something like this:
![](img/mqtt_example-cloud13.png)
![](img/mqtt_example-cloud14.png)
After the creation of the publications on a mobile device, in the driver instance **mqtt.0** system yunkong2 should appear variable
light control that should be used in the script for lighting
control (see [example above](http://www.yunkong2.net/?page_id=6435&lang=en#Application_8211_connecting_mobile_clients)):
![](img/mqtt_13.png)
Congratulations! Now you can control the system yunkong2 and receive data via a cloud service!

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

741
docs/ru/mqtt.md Normal file
View File

@ -0,0 +1,741 @@
![](MQTT)
## MQTT Broker и клиент
[MQTT](http://mqtt.org/) (Message Queue Telemetry Transport) это легковесный протокол,
применяемый для общения между устройствами (M2M — machine-to-machine).
Он использует модель издатель-подписчик (publish/subscribe) для передачи сообщений поверх
протокола TCP/IP. Центральная часть протокола это MQTT-сервер или брокер, который имеет
доступ к издателю и подписчику. Этот протокол предельно примитивен: с коротким заголовком,
без контроля целостности (поэтому передача реализована поверх TCP), не накладывает никаких
ограничения на структуру, кодирование или схему данных. Единственное требование к данным в
каждом пакете они должны сопровождаться идентификатором информационного канала.
Этот идентификатор в спецификации называется Topic Name.
Протокол MQTT требует обязательного наличия брокера данных. Это центральная идея технологии.
Все устройства посылают данные только брокеру и принимают данные тоже только от него.
Получив пакет, брокер рассылает его всем устройствам в сети согласно их подписке. Чтобы
устройство что-то получило от брокера оно должно подписаться на топик. Топики возникают
динамически по факту подписки или по факту прихода пакета с данным топиком. От подписки
на топик можно отказаться. Таким образом топики представляют собой удобный механизм
организации связей разных видов: один ко многим, многие к одному и многие ко многим.
**Важные моменты:**
* устройства сами устанавливают связь с брокером, они могут находится за NAT и не иметь статических IP-адресов,
* можно применить протокол SSL для шифрования трафика,
* MQTT брокеры позволяют подключаться к ним через протокол WebSocket на 80-й порт,
* разные брокеры могут соединяться между собой подписываясь на сообщения друг у друга.
## Установка
Установка осуществляется на вкладке **Драйвера** странички [администрирования](http://www.yunkong2.net/?page_id=3800&lang=ru) системы.
В группе драйверов **Сетевые** находим строчку с названием **MQTT Adapter** и нажимаем кнопку со
значком плюса в этой строке справа.
![](img/mqtt_install1.jpg)
На экране появится всплывающее окно установки драйвера, в конце установки оно
автоматически закроется.
![](img/mqtt_install2.jpg)
Если все прошло удачно, на вкладке **Настройка драйверов** появится строка **mqtt.0** с установленным экземпляром драйвера.
![](img/mqtt_install3.jpg)
## Настройка
Как писалось выше, протокол MQTT подразумевает наличие брокера и клиентов.
Сервер yunkong2 может выступать как в роли брокера, так и в роли клиента.
Настройка для указания режима работы - тип (сервер/брокер или клиент/подписчик) Рассмотрим каждый вариант.
### Работа yunkong2 в качестве MQTT-брокера
Основные настройки, если предполагается использование режима сервер/брокер, приведены на картинке:
![](img/mqtt_server1.jpg)
* **Use WebSockets** - если есть надобность использовать WEB-сокеты для соединения, необходимо установить эту опцию, при этом TCP-server будет работать параллельно с WebSocket-сервером,
* **Порт** -порт для установки соединения по TCP (значение по-умолчанию 1883), сервер WebSocket (см. опция выше) запускается по порту +1 (по-умолчанию 1884),
* **SSL** - данная опция используется, если необходимо шифровать трафик (TCP или WebSocket), при этом необходимо указать сертификаты - просто выбрать из списка заранее установленные (указываются в системных настройках, см. описание драйвера [администрирования](http://www.yunkong2.net/?page_id=3800&lang=ru) системы),
* **настройки аутентификации** (имя пользователя и пароль) - указываются, если необходима аутентификация конкретного пользователя, данная настройка всегда используется вместе с опцией SSL-шифрования (чтобы не передавать пароли в открытом виде через незащищенное соединение).
* **Маска для собственных значений** - шаблон (или несколько через запятую) для фильтрации переменных yunkong2, которые будут отправляться клиентам, можно использовать специальные символы чтобы указать группу сообщения (к примеру, `memRSS, mqtt.0` - могут передаваться все переменные состояния памяти всех драйверов и все переменные экземпляра драйвера **mqtt.0**)
* **Отсылать только изменения** - отправка данных клиенту будет произведена только в случае изменения переменной (если состояние просто обновилось - значение не поменялось, клиенту сообщение не будет отправлено), от клиента будет принято любое сообщение, даже если значение не изменилось,
* **Выдавать собственные значения при старте** - для каждого успешного соединения с клиентом ему будут переданы все известные состояния (определяются маской состояний) - для того, чтобы сообщить клиенту текущее состояние yunkong2,
* **Публиковать состояния при подписке** - сразу после подписки клиенту будет отправлено значение переменной, на которую он подписан (при первом старте или рестарте клиент получит значения переменных, на которые он подписан, можно использовать для инициализации переменных),
* **Префикс для всех значений** - если указано значение, то оно будет добавляться как префикс к каждому отправленному топику, к примеру, если указать `yunkong2/`, то все отправленные топики примерно следующего содержания: `yunkong2/mqtt/0/connected`,
* **Вывод лога для каждого изменения** - в лог-файле будет отображаться отладочная информация по каждому изменению,
* **Посылать не только команды, но и состояния (ack=true)** - если опция не активна, то клиенту будут отправляться только переменные/команды с ack=false, если флаг установлен, то будут переданы переменные не зависимо от состояния ack (false или true),
* **Максимальная длина имени топика** - максимальное кол-во символов для описания топика, включая служебные.
В качестве примера рассмотрим обмен данными между клиентом на базе платы [arduino](https://www.arduino.cc/) и брокером - экземпляр драйвера mqtt.0 системы yunkong2.
* клиент - плата для разработки [arduino UNO](https://www.arduino.cc/en/Main/ArduinoBoardUno) + [ethernet shield](https://store.arduino.cc/product/A000072) на базе чипа W5100,
* для работы с платой ethernet используется стандартная [библиотека](https://www.arduino.cc/en/Reference/Ethernet), для работы с MQTT библиотека [Pubsubclient](https://github.com/knolleary/pubsubclient),
* датчик AM2302 (температура и влажность) подключен на pin_8 для опроса используется библиотека с [DHTlib](https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib) с ресурса github.com,
* светодиод led_green подключен на pin_9, управление в дискретном режиме вкл/откл,
* брокер - система yunkong2 драйвер mqtt.
Формат топиков обмена данными:
* `example1/send_interval` - клиент подписан на изменение интервала отправки показаний температуры и влажности (значение int в секундах)
* `example1/temp` - клиент публикует с заданным интервалом значение температуры с датчика DHT22 (тип float),
* `example1/hum` -клиент публикует с заданным интервалом значение влажности с датчика DHT22 (тип float),
* `example1/led` -клиент подписан на изменение состояния светодиода (тип text on/off или 0/1 или true/false).
Настройки драйвера будут следующие:
![](img/mqtt_server2.jpg)
Подключение по TCP (WebSocket не нужен), порт по-умолчанию 1883\. Клиент внутри локальной сети, поэтому шифровать трафик и проводить
аутентификацию пользователя нет необходимости. Отсылать будем только изменения, так как клиент подписан на интервал
отправки показаний и состояние светодиода, получать информацию только об обновлении (без изменения значения)
переменной нет смысла. Публиковать состояния при подписке - отметим эту опцию, так как при первом подключении
(или подключении после обрыва соединения) клиента он должен знать состояния переменных, на которые он
подписан (какой текущий интервал отправки и должен ли быть включен светодиод). Настройку отсылать
переменные с ack=true или false тоже стоит отметить, так как переменную (на которую подписан клиент)
может изменить любой драйвер/скрипт/VIS и все изменения надо отправлять клиенту. Полный код для платы
arduino будет выглядеть так:
<pre>
//Подключаем библиотеки
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h> //https://github.com/knolleary/pubsubclient
#include <dht.h> //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
//Настройки сети
byte mac[] = { 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0x31 };
byte ip[] = { 192, 168, 69, 31 }; //IP-адрес платы arduino
byte mqttserver[] = { 192, 168, 69, 51 }; //IP-адрес сервера yunkong2
EthernetClient ethClient;
void callback(char* topic, byte* payload, unsigned int length);
PubSubClient client(mqttserver, 1883, callback, ethClient);
//Глобальные переменные
#define LED_pin 9
unsigned int send_interval = 10; //интервал отправки показаний на сервер по-умолчанию 10 секунд
unsigned long last_time = 0; //текущее время для таймера
dht DHT;
#define DHT22_PIN 8
char buff[20];
//Функция обработки входящих соединений - прием данных по подписке
void callback(char* topic, byte* payload, unsigned int length) {
Serial.println ("");
Serial.println ("-------");
Serial.println ("New callback of MQTT-broker");
//преобразуем тему(topic) и значение (payload) в строку
payload[length] = '\0';
String strTopic = String(topic);
String strPayload = String((char*)payload);
//Исследуем что "прилетело" от сервера по подписке:
//Изменение интервала опроса
if (strTopic == "example1/send_interval") {
int tmp = strPayload.toInt();
if (tmp == 0) {
send_interval = 10;
} else {
send_interval = strPayload.toInt();
}
}
//Управление светодиодом
if (strTopic == "example1/led") {
if (strPayload == "off" || strPayload == "0" || strPayload == "false") digitalWrite(LED_pin, LOW);
if (strPayload == "on" || strPayload == "1" || strPayload == "true") digitalWrite(LED_pin, HIGH);
}
Serial.print (strTopic);
Serial.print (" ");
Serial.println (strPayload);
Serial.println ("-------");
Serial.println ("");
}
void setup() {
Serial.begin(9600);
Serial.println("Start...");
//стартуем сетевое подключение
Ethernet.begin(mac, ip);
Serial.print("IP: ");
Serial.println(Ethernet.localIP());
//Инициализируем порты ввода-вывода, прописываем начальные значения
pinMode(LED_pin, OUTPUT);
digitalWrite(LED_pin, LOW); //при светодиод отключен
}
void loop() {
//Если соединение MQTT неактивно, то пытаемся установить его и опубликовать/подписаться
if (!client.connected()) {
Serial.print("Connect to MQTT-boker... ");
//Подключаемся и публикуемся/подписываемся
if (client.connect("example1")) {
Serial.println("success");
//Значение с датчиков
if (DHT.read22(DHT22_PIN)==DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example1/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example1/temp", buff);
}
//Подписываемся на интервал опроса
client.subscribe("example1/send_interval");
//Подписываемся на переменную управления светодиодом
client.subscribe("example1/led");
} else {
//Если не подключились, ждем 10 секунд и пытаемся снова
Serial.print("Failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 10 seconds");
delay (10000);
}
//Если соединение активно, то отправляем данные на сервер с заданным интервалом времени
} else {
if(millis() > (last_time + send_interval*1000)) {
last_time = millis();
if (DHT.read22(DHT22_PIN)==DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example1/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example1/temp", buff);
}
}
}
//Проверка входящих соединений по подписке
client.loop();
}
</pre>
### Работа yunkong2 в качестве MQTT-клиента
Чтобы экземпляр драйвера MQTT заработал как клиент/подписчик - нужно в настройках выбрать соответствующий тип.
При этом набор настроек немного поменяется:
![](img/mqtt_client1.jpg)
* **Настройки соединения** - указывается URL и порт брокера (если необходимо шифровать трафик, то указывается SSL) - настройки для подключения к брокеру,
* **Настройки аутентификации** - имя пользователя и пароль, если брокер требует аутентификацию (уместно использовать SSL-шифрование, чтобы не передавать пароль в открытом виде),
* **Patterns** - маска для переменных, на которые клиент подписывается (переменные брокера), значения перечисляются через запятую, для указания множества используется символ # (решетка),
* **Маска для собственных значений** - фильтр переменных, которые необходимо публиковать (переменные клиента), значения перечисляются через запятую, для указания множества используется символ * (звездочка),
* **Отсылать только изменения** - клиент будет публиковать только переменные, которые изменили значение (согласно маски),
* **Выдавать собственные значения при старте** - если эту опцию отметить, то будут <span id="result_box" class="" lang="ru"><span title="Publish all states at start - Publish all states (defined by state mask) every time by connection establishment to announce own available states and their values. ">публиковаться все состояния (согласно маски) каждый раз, когда устанавливается соединение, чтобы объявить доступные собственные переменные и их значения,</span></span>
* **Префикс для всех значений** - если указано значение, то оно будет добавляться как префикс к каждому публикуемому топику, к примеру, если указать `client1/`, то все публикуемые топики будут примерно следующего содержания: `client1/javascript/0/cubietruck`,
* **Вывод лога для каждого изменения** - в лог-файле будет отображаться отладочная информация по каждому изменению,
* **Посылать не только команды, но и состояния (ack=true)** - если данная опция не отмечена, то брокеру отправляются только переменные/команды с ack=false, если опцию отметить, то будут отправляться все данные, независимо от ack=true или ack=false,
* **Максимальная длина топика** - максимальное кол-во символов для описания топика, включая служебные.
Примеры для задания маски подписки на переменные (patterns). Рассмотрим топики:
* "Sport"
* "Sport/Tennis"
* "Sport/Basketball"
* "Sport/Swimming"
* "Sport/Tennis/Finals"
* "Sport/Basketball/Finals"
* "Sport/Swimming/Finals"
Если необходимо подписаться на определенное множество топиков, можно использовать символы # (решетка) или + (знак плюс).
* "Sport/Tennis/#" (подписка только на "Sport/Tennis" и "Sport/Tennis/Finals")
* "Sport/Tennis/+" (подписка только на  "Sport/Tennis/Finals", но не "Sport/Tennis")
Для JMS топиков, если нужно подписаться на все топики "Finals", можно использовать символы # (решетка) или + (знак плюс)
* "Sport/#/Finals"
* "Sport/+/Finals"
Для MQTT топиков, если нужно подписаться на все топики "Finals", можно использовать символ + (знак плюс)
* "Sport/+/Finals"
В качестве примера рассмотрим обмен данными между двумя системами yunkong2. Есть
работающая система yunkong2 на плате BananaPi (IP-адрес 192.168.69.51), на ней
запущен MQTT-драйвер в режиме сервер/брокер из примера выше. К серверу
подключается клиент, который публикует данные с датчика DHT22 - температуру и
влажность, а так же подписывается на переменные интервал передачи показаний и
состояние светодиода (включить/отключить) - так же из примера выше. Вторая работающая
система yunkong2 на плате Cubietruck, на ней запустим MQTT-драйвер в режиме клиент/подписчик.
Он подпишется на переменные температура и влажность брокера (который в свою очередь получает от
другого клиента) и будет публиковать все переменные скрипта - [состояние АКБ](http://www.yunkong2.net/?page_id=4268&lang=ru#_Li-polLi-ion) платы
(только изменения). Настройки клиента будут примерно следующие:
![](img/mqtt_client2.jpg)
Тип соединения - клиент/подписчик, указывается IP-адрес брокера и порт (по-умолчанию 1883).
Шифрование трафика и аутентификация не нужны.
Маска для подписки (Patterns) - `mqtt/0/example1/hum,mqtt/0/example1/temp` - клиент подписывается только на температуру и
влажность (значения через запятую без пробелов).
Маска данных для публикации - `javascript.0.cubietruck.battery.*` - публикуются все переменные
скрипта `cubietruck` в группе `battery` драйвера `javascript.0`.
Отсылать только изменения - отправляем переменные состояния АКБ (нет смысла отправлять, если значение не изменилось).
Выдавать собственные значения при старте - при старте драйвера, клиент сразу опубликует все переменные
согласно маске - даже если они нулевые или пустые, чтобы создать переменные в брокере. Посылать данные
с ack=false - переменные работы АКБ обновляются драйвером javascript, поэтому они всегда ack=false.
Результат работы на стороне клиента (данные температуры и влажности другого клиента - см. пример выше):
![](img/mqtt_client3.jpg)
Результат работы со стороны брокера (данные состояния АКБ клиента):
![](img/mqtt_client4.jpg)
## Применение - шлюз протоколов MQTT - ModBus RTU
Драйвер MQTT можно использовать как шлюз различных протоколов, чтобы подключить новые
устройства в систему yunkong2 или любую другую. Универсальной базой для разработки подобных
решений являются платы arduino. В сети много примеров, библиотек и наработок. Огромное сообщество
работает с этими контроллерами и в систему интегрированы множество устройств и оборудования. Для примера,
рассмотрим распространенный промышленный протокол ModBus. В системе yunkong2 имеется драйвер для работы с ним - версии
ModBus TCP (по сети ethernet). Множество датчиков, контроллеров и исполнительных устройств работают физически по
сети RS-485/232 и протоколу ModBus RTU. Чтобы интегрировать их можно применить шлюз MQTT - ModBus RTU на базе платформы
arduino.
Рассмотрим пример.
<span style="text-decoration: underline;">**Имеется датчик температуры и влажности**</span> (для теста на базе платы arduino pro mini и сенсора DHT22), 
который выдает данные по ModBUS RTU:
* Порт UART (можно с помощью микросхемы MAX485 преобразовать в интерфейс RS-485) работает на скорости 9600 с параметрами 8E1 (1 start bit, 8 data bits, 1 Even parity bit, 1 stop bit)
* Адрес ModBus - 10
* температура - адрес 0 значение, умноженное на 10 (read function 3)
* влажность - адрес 1 значение, умноженное на 10 (read function 3)
* PWM LED - адрес 2 значение 0...1023 для проверки функции записи (write function 6)
Схема соединения:
![](img/mqtt_example-modbus1.jpg)
by Fritzing
Код для контроллера arduino pro mini получается следующий:
<pre>
#include <dht.h> //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
#include <SimpleModbusSlave.h> //https://code.google.com/archive/p/simple-modbus/
#include <MsTimer2.h> //https://github.com/PaulStoffregen/MsTimer2
//регистры modbus
enum {
TEMP,
HUM,
PWM,
TEST,
HOLDING_REGS_SIZE
};
#define ID_MODBUS 10 //адрес modbus slave устройства
unsigned int holdingRegs[HOLDING_REGS_SIZE]; //массив регистров modbus
//датчик температуры и влажности DHT22
dht DHT;
#define DHT22_PIN 2
#define LED 9 //светодиод подключен на PWM-пин 9
void setup() {
//конфигурируем modbus
modbus_configure(&Serial, 9600, SERIAL_8E1, ID_MODBUS, 0, HOLDING_REGS_SIZE, holdingRegs);
holdingRegs[TEST] = -157; //для теста отрицательных значений
//инициализируем таймер 2 секунды обновления данных в регистрах температуры и влажности
MsTimer2::set(2000, read_sensors);
MsTimer2::start(); //запускаем таймер
pinMode(LED, OUTPUT); //инициализация порта светодиода
}
//функция, запускаемая по таймеру каждые 2 секунды
void read_sensors() {
if (DHT.read22(DHT22_PIN)==DHTLIB_OK) { //если данные с датчика DHT22 удалось прочитать
//записываем в регистр влажности целочисленное значение
holdingRegs[HUM] = 10 * DHT.humidity;
//записываем в регистр температуры целочисленное значение
holdingRegs[TEMP]= 10 * DHT.temperature;
} else {
//если не удалось прочитать данные с датчика DHT22, записываем в регистры нули
holdingRegs[HUM] = 0;
holdingRegs[TEMP] = 0;
}
}
void loop() {
modbus_update(); //обновляем данные modbus
//данные из регистра управления светодиодом передаем в ШИМ (битовый сдвиг на 2 разряда)
analogWrite(LED, holdingRegs[PWM]>>2);
}
</pre>
* порт (выбрать из списка к какому порту подключен serial платы ардуино);
* скорость и прочие параметры - 9600 8E1;
* slave id: 10, чтение: функция №3 read holding registers, начальный адрес: 0, число регистров: 3,
* slave id: 10, запись: функция №6 write single register, начальный адрес: 2,
Ответ в программе при чтении должен быть примерно следующий:
![](img/mqtt_example-modbus2.jpg)
Ответ в программе при записи:
![](img/mqtt_example-modbus3.jpg)
<span style="text-decoration: underline;">**Теперь настроем сам шлюз и подключим его в yunkong2**</span> Шлюз будет на базе платформы
arduino MEGA 2560 с ethernet shield - клиент MQTT, брокер - экземпляр драйвера mqtt.0 системы yunkong2. Выбор именно MEGA 2560
обусловлен тем, что на этой плате более одного UART-порта, соответственно нулевой Serial0 (pin_0 (RX) и зшт_1 (TX)) или
просто Serial - используем для вывода отладочных сообщений, а Serial1 (pin_19 (RX) и pin_18 (TX)) - для
работы с slave-устройством по ModBus.
* клиент - плата для разработки [arduino MEGA 2560](https://www.arduino.cc/en/Main/ArduinoBoardMega2560) + [ethernet shield](https://store.arduino.cc/product/A000072) на базе чипа W5100;
* для работы с платой ethernet используется стандартная [библиотека](https://www.arduino.cc/en/Reference/Ethernet), для работы с MQTT библиотека [Pubsubclient](https://github.com/knolleary/pubsubclient);
* для опроса по modbus используется библиотека [SimpleModbus](https://code.google.com/archive/p/simple-modbus/) версии master;
* опрос по порту UART (просто соединить RX порт master, TX порт slave и соответственно TX порт master, RX порт slave), порт управления передачей не используется (он для RS-485);
* параметры порта: скорость 9600, 8Е1;
* адрес slave-устройства 10, функция чтения №3 (read holding registers), функция записи №6 (write single register);
* брокер - система yunkong2 драйвер mqtt.
Формат топиков обмена данными:
* `modbusgateway/send_interval` - клиент подписан на изменение интервала отправки показаний температуры и влажности (значение int в секундах)
* `modbusgateway/temp` - клиент публикует с заданным интервалом значение температуры с датчика DHT22 (тип float),
* `modbusgateway/hum` -клиент публикует с заданным интервалом значение влажности с датчика DHT22 (тип float),
* `modbusgateway/led` -клиент подписан на изменение состояния светодиода (ШИМ управление, значения 0...1024).
Схема соединений получится примерно такая:
![](img/mqtt_example-modbus6.jpg)
By Fritzing[/caption]
Для теста slave-устройство запитаем от master-устройства. Master в свою очередь, будет работать от USB-порта,
по которому ведется отладка (Serial0). Настройки драйвера будут следующие:
![](img/mqtt_server2.jpg)
Подключение по TCP (WebSocket не нужен), порт по-умолчанию 1883\. Клиент внутри локальной сети, поэтому шифровать трафик и
проводить аутентификацию пользователя нет необходимости. Отсылать будем только изменения, так как клиент подписан на интервал
отправки показаний и состояние светодиода, получать информацию только об обновлении (без изменения значения) переменной нет смысла.
Публиковать состояния при подписке - отметим эту опцию, так как при первом подключении (или подключении после обрыва соединения)
клиента он должен знать состояния переменных, на которые он подписан (какой текущий интервал отправки и должен ли быть включен светодиод).
Настройку отсылать переменные с ack=true или false тоже стоит отметить, так как переменную (на которую подписан клиент) может изменить
любой драйвер/скрипт/VIS и все изменения надо отправлять клиенту.
Полный код для платы arduino MEGA 2560 будет выглядеть так:
<pre>
//Подключаем библиотеки
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h> //https://github.com/knolleary/pubsubclient
#include <SimpleModbusMaster.h> //https://code.google.com/archive/p/simple-modbus/
//Настройки сети
byte mac[] = { 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0x31 };
byte ip[] = { 192, 168, 69, 31 }; //IP-адрес платы arduino
byte mqttserver[] = { 192, 168, 69, 51 }; //IP-адрес сервера yunkong2
//Объекты/переменные/функции ethernet и MQTT
EthernetClient ethClient;
void callback(char* topic, byte* payload, unsigned int length);
PubSubClient client(mqttserver, 1883, callback, ethClient);
//Переменные временного интервала и буфер для отправки данных по MQTT
unsigned int send_interval = 10; //интервал отправки показаний на сервер по-умолчанию 10 секунд
unsigned long last_time = 0; //текущее время для таймера
char buff[20];
//Параметры порта Serial1 (19 (RX) and 18 (TX))
#define baud 9600 //скорость порта
#define timeout 1000 //интервал времени ожидания ответа /(мс)
#define polling 200 //интервал опроса (мс)
#define retry_count 10 //кол-во повторов при неудачном опросе
#define TxEnablePin 0 //пин управления передачей для RS485 (в UART не используется = 0)
// Общая сумма доступной памяти на master устройстве для хранения данных
// Из слейва запрашиваем 4 регистра, в массиве regs должно быть не меньше 4х ячеек
#define TOTAL_NO_OF_REGISTERS 4
//Массив регистров для работы с modbus (хранение, чтение, запись)
unsigned int regs[TOTAL_NO_OF_REGISTERS];
//Определение пакетов mmodbus
enum {
TEMP,
HUM,
PWM,
TEST,
TOTAL_NO_OF_PACKETS //всегда последней записью
};
//Создание массива пакетов modbus
Packet packets[TOTAL_NO_OF_PACKETS];
//Функция обработки входящих соединений - прием данных по подписке
void callback(char* topic, byte* payload, unsigned int length) {
Serial.println ("");
Serial.println ("-------");
Serial.println ("New callback of MQTT-broker");
//преобразуем тему(topic) и значение (payload) в строку
payload[length] = '\0';
String strTopic = String(topic);
String strPayload = String((char*)payload);
//Исследуем что "прилетело" от сервера по подписке:
//Изменение интервала опроса
if (strTopic == "modbusgateway/send_interval") {
int tmp = strPayload.toInt();
if (tmp == 0) {
send_interval = 10;
} else {
send_interval = strPayload.toInt();
}
}
//Управление светодиодом значение int от 0 до 1023
if (strTopic == "modbusgateway/led") {
int tmp = strPayload.toInt();
if (tmp >= 0 && tmp <=1023) {
regs[2] = tmp;
}
}
Serial.print (strTopic);
Serial.print (" ");
Serial.println (strPayload);
Serial.println ("-------");
Serial.println ("");
}
void setup() {
Serial.begin(9600);
Serial.println("Start...");
//стартуем сетевое подключение
Ethernet.begin(mac, ip);
Serial.print("IP: ");
Serial.println(Ethernet.localIP());
//Инициализируем все пакеты modbus
modbus_construct(&packets[TEMP], 10, READ_HOLDING_REGISTERS, 0, 1, 0); //температура
modbus_construct(&packets[HUM], 10, READ_HOLDING_REGISTERS, 1, 1, 1); //влажность
modbus_construct(&packets[PWM], 10, PRESET_SINGLE_REGISTER, 2, 1, 2); //данные ШИМ для светодиода
modbus_construct(&packets[TEST], 10, READ_HOLDING_REGISTERS, 3, 1, 3); //тест
//Конфигурируем соединение modbus (порт serial1, скорость и пр.)
modbus_configure(&Serial1, baud, SERIAL_8E1, timeout, polling, retry_count, TxEnablePin, packets, TOTAL_NO_OF_PACKETS, regs);
}
void loop() {
//Обоновляем данные в регистрах modbus
modbus_update();
//Если соединение MQTT неактивно, то пытаемся установить его и опубликовать/подписаться
if (!client.connected()) {
Serial.print("Connect to MQTT-boker... ");
//Подключаемся и публикуемся/подписываемся
if (client.connect("modbusgateway")) {
Serial.println("success");
//Значение с датчиков температуры и влажности
dtostrf((float)regs[0]/10, 5, 1, buff);
client.publish("modbusgateway/temp", buff);
dtostrf((float)regs[1]/10, 5, 1, buff);
client.publish("modbusgateway/hum", buff);
//Подписываемся на интервал опроса
client.subscribe("modbusgateway/send_interval");
//Подписываемся на переменную управления светодиодом
client.subscribe("modbusgateway/led");
} else {
//Если не подключились, пытаемся снова
Serial.print("Failed, rc=");
Serial.print(client.state());
Serial.println(" try again");
delay(10000);
}
//Если соединение активно, то отправляем данные на сервер с заданным интервалом времени
} else {
if(millis() > (last_time + send_interval*1000)) {
last_time = millis();
//Значение с датчиков температуры и влажности
dtostrf((float)regs[0]/10, 5, 1, buff);
client.publish("modbusgateway/temp", buff);
dtostrf((float)regs[1]/10, 5, 1, buff);
client.publish("modbusgateway/hum", buff);
}
}
//Проверка входящих соединений по подписке
client.loop();
}
</pre>
## Применение - подключение мобильных клиентов
В последнее время протокол MQTT получил большое распространение ввиду простоты, экономии трафика и хорошей
проработке библиотек под разные платформы. Существует множество программ для работы с MQTT на мобильных
устройствах, к примеру [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=ru).
С помощью этой программы можно подключиться к MQTT-брокеру в локальной сети или в сети интернет. Рассмотрим пример -
в роли брокера будет выступать система yunkong2, к которой по MQTT будет подключаться клиент - приложение IoT MQTT Dashboard.
В данном примере будем управлять светом с помощью контроллера [MegaD-328](http://www.ab-log.ru/smart-house/ethernet/megad-328),
который подключен к yunkong2 с помощью драйвера [MegaD](http://www.yunkong2.net/?page_id=4052&lang=ru).
Управляет непосредственно реле (MegaD порт **P7**) света в холле специальный скрипт, который подписывается на состояние
порта-кнопки **P0** и состояние MQTT-переменной **mqtt.0.remotectrl.light.hall**, которую будет публиковать мобильный клиент.
При этом скрипт переключает состояние порта, привязанного к реле (порт P7), т.е. инвертирует его.
Получается, что при каждом
нажатии на кнопку, электрически подключенную к порту **P0** (вылавливается состояние **true**) и при каждой публикации
переменной **mqtt.0.remotectrl.light.hall** значением так же **true**, порт **P7** будет включать или выключать свет.
Текст скрипта будет примерно такой:
<pre>
//Управление освещением в зале с помощью кнопки порт p0 контроллера MegaD драйвер экземпляр megad.0
on({id: 'megad.0.p0_P0', change: 'any'}, function (obj) {
if (obj.newState.val !== '' || typeof obj.newState.val !== "undefined"){
if (obj.newState.val === true) {
if (getState('megad.0.p7_P7').val === true) {
setState('megad.0.p7_P7', false);
} else {
setState('megad.0.p7_P7', true);
}
}
}
});
//Управление освещением в зале удаленно по MQTT топик "mqtt.0.remotectrl.light.hall"
on({id: 'mqtt.0.remotectrl.light.hall', change: 'any'}, function (obj) {
if (obj.newState.val !== '' || typeof obj.newState.val !== "undefined"){
if (obj.newState.val === true) {
if (getState('megad.0.p7_P7').val === true) {
setState('megad.0.p7_P7', false);
} else {
setState('megad.0.p7_P7', true);
}
}
}
});
</pre>
## Применение - работа с облачными серверами
Описанный выше пример имеет ряд недостатков. Во-первых, не всегда мобильный клиент может находиться в одной локальной сети с сервером yunkong2, во-вторых, даже если осуществить проброс портов в интернет и защитить соединение, не всегда сам сервер yunkong2 может принять входящие подключения (находится за NAT, к которому нет доступа для настройки). В глобальной сети много различных сервисов, которые поддерживают MQTT - платных и бесплатных, к примеру отправка погодных данных, геолокации и пр. Некоторые сервисы могут выступать в качестве брокера протокола MQTT и их можно использовать как шлюз (мост) для вывода данных из yunkong2 в глобальную сеть или для получения данных в yunkong2. В качестве примера рассмотрим работу связки:
* сервер/брокер - сервис [cloudmqtt.com](https://www.cloudmqtt.com/) (есть бесплатный тариф),
* клиент/подписчик - система yunkong2 с выходом в сеть интернет, публикует данные температуры и влажности (см. [пример выше](http://www.yunkong2.net/?page_id=4643&lang=ru#_yunkong2__MQTT)), публикует реальное состояние портов **P7-P13** (реле MegaD драйвера **megad.0** - управление освещением), подписка на переменные удаленного управления светом (экземпляр драйвера **mqtt.0**),
* клиент/подписчик - приложение [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=ru) для удаленной работы - подписка на данные сенсора температуры и влажности, подписка на реальное состояние портов **P7-P13** (реле MegaD драйвера **megad.0**), публикация переменных удаленного управления светом (экземпляр драйвера **mqtt.0**).
В итоге получается следующая структура:
![](img/mqtt_example-cloud1.jpg)
Связка драйвер **mqtt.1** (брокер) - Arduino UNO + Ethernet + DHT22 (клиент) как в [примере выше](http://www.yunkong2.net/?page_id=4643&lang=ru#_yunkong2__MQTT) с
несколькими изменениями. Настройки экземпляра драйвера **mqtt.1**:
[![](img/mqtt_example-cloud2.jpg)](img/mqtt_example-cloud2.jpg)
Код для платформы arduino:
<pre>
//Подключаем библиотеки
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h> //https://github.com/knolleary/pubsubclient
#include <dht.h> //https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
//Настройки сети
byte mac[] = { 0xAB, 0xBC, 0xCD, 0xDE, 0xEF, 0x31 };
byte ip[] = { 192, 168, 69, 31 }; //IP-адрес платы arduino
byte mqttserver[] = { 192, 168, 69, 51 }; //IP-адрес сервера yunkong2
EthernetClient ethClient;
void callback(char* topic, byte* payload, unsigned int length);
PubSubClient client(mqttserver, 1884, callback, ethClient);
//Глобальные переменные
unsigned int send_interval = 10; //интервал отправки показаний на сервер по-умолчанию 10 секунд
unsigned long last_time = 0; //текущее время для таймера
dht DHT;
#define DHT22_PIN 8
char buff[20];
//Функция обработки входящих соединений - прием данных по подписке
void callback(char* topic, byte* payload, unsigned int length) {
Serial.println ("");
Serial.println ("-------");
Serial.println ("New callback of MQTT-broker");
//преобразуем тему(topic) и значение (payload) в строку
payload[length] = '\0';
String strTopic = String(topic);
String strPayload = String((char*)payload);
//Исследуем что "прилетело" от сервера по подписке:
//Изменение интервала опроса
if (strTopic == "example2/send_interval") {
int tmp = strPayload.toInt();
if (tmp == 0) {
send_interval = 10;
} else {
send_interval = strPayload.toInt();
}
}
Serial.print (strTopic);
Serial.print (" ");
Serial.println (strPayload);
Serial.println ("-------");
Serial.println ("");
}
void setup() {
Serial.begin(9600);
Serial.println("Start...");
//стартуем сетевое подключение
Ethernet.begin(mac, ip);
Serial.print("IP: ");
Serial.println(Ethernet.localIP());
//Инициализируем порты ввода-вывода, прописываем начальные значения
}
void loop() {
//Если соединение MQTT неактивно, то пытаемся установить его и опубликовать/подписаться
if (!client.connected()) {
Serial.print("Connect to MQTT-boker... ");
//Подключаемся и публикуемся/подписываемся
if (client.connect("example2")) {
Serial.println("success");
//Значение с датчиков
if (DHT.read22(DHT22_PIN)==DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
//Подписываемся на интервал опроса
client.subscribe("example2/send_interval");
} else {
//Если не подключились, ждем 10 секунд и пытаемся снова
Serial.print("Failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 10 seconds");
delay (10000);
}
//Если соединение активно, то отправляем данные на сервер с заданным интервалом времени
} else {
if(millis() > (last_time + send_interval*1000)) {
last_time = millis();
if (DHT.read22(DHT22_PIN)==DHTLIB_OK) {
dtostrf(DHT.humidity, 5, 2, buff);
client.publish("example2/hum", buff);
dtostrf(DHT.temperature, 5, 2, buff);
client.publish("example2/temp", buff);
}
}
}
//Проверка входящих соединений по подписке
client.loop();
}
</pre>
* тип соединения - клиент/подписчик,
* настройки соединения - указываем URL выданный в панели управления [cloudmqtt.com](https://www.cloudmqtt.com/) порт будем использовать **22809**, который работает с **SSL**,
* в настройках аутентификации указываем имя пользователя и пароль,
* patterns - наш клиент yunkong2 будет подписан на все топики, что есть в облаке, поэтому указываем здесь символ решетка (**#**), можно использовать маску и подписаться выборочно,
* маска для собственных значений - клиент будет публиковать на сервер значения температуры/влажности и состояния всех портов megaD (порты с реле **P7-P13**),в этом поле через запятую указываем необходимые переменные: **mqtt.1.example2.hum,mqtt.1.example2.temp,megad.0.p7_P7,megad.0.p8_P8,megad.0.p9_P9,megad.0.p10_P10,megad.0.p11_P11,megad.0.p12_P12,megad.0.p13_P13**,
* отсылать только изменения - ставим галочку, публиковать будем только изменения,
* выдавать собственные значения при старте - так же указываем для создания топиков,
* посылать не только команды, но и состояния (ack=true) - необходимо отметить эту настройку, так как данные температуры/влажности обновляются драйвером mqtt (ack=true).
Настройки сохраняем, убеждаемся, что соединение установилось
(можно на панели управления [cloudmqtt.com](https://www.cloudmqtt.com/) посмотреть лог сервера). Через
некоторое время появятся данные (в панели ссылка **WebsocketUI**):
![](img/mqtt_example-cloud7.jpg)
В итоге остается только настроить мобильный клиент, к примеру [IoT MQTT Dashboard](https://play.google.com/store/apps/details?id=com.thn.iotmqttdashboard&hl=ru).
Создаем новое подключение:
![](img/mqtt_example-cloud8.png)
Создаем топики для публикации (на примере освещения зала - порт **P7** MegaD):
![](img/mqtt_example-cloud9.png)
Создаем топики подписок (температура, влажность, освещение зала порт **P7** MegaD):
![](img/mqtt_example-cloud10.png)
![](img/mqtt_example-cloud11.png)
![](img/mqtt_example-cloud12.png)
В итоге dashboard может выглядеть примерно так:
![](img/mqtt_example-cloud13.png)
![](img/mqtt_example-cloud14.png)
После создания публикаций на мобильном устройстве, в экземпляре драйвера **mqtt.0** системы yunkong2 должны появиться переменные управления светом,
которые следует использовать в скрипте управления освещением (см. [пример выше](http://www.yunkong2.net/?page_id=4643&lang=ru#_8211)):
![](img/mqtt_example-cloud15.jpg)
Поздравляем! Теперь вы сможете управления системой yunkong2 и
получать от нее данные через облачный сервис!

400
gulpfile.js Normal file
View File

@ -0,0 +1,400 @@
'use strict';
var gulp = require('gulp');
var fs = require('fs');
var pkg = require('./package.json');
var iopackage = require('./io-package.json');
var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version;
/*var appName = getAppName();
function getAppName() {
var parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 1].split('.')[0].toLowerCase();
}
*/
const fileName = 'words.js';
var languages = {
en: {},
de: {},
ru: {},
pt: {},
nl: {},
fr: {},
it: {},
es: {},
pl: {}
};
function lang2data(lang, isFlat) {
var str = isFlat ? '' : '{\n';
var count = 0;
for (var w in lang) {
if (lang.hasOwnProperty(w)) {
count++;
if (isFlat) {
str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n';
} else {
var key = ' "' + w.replace(/"/g, '\\"') + '": ';
str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n';
}
}
}
if (!count) return isFlat ? '' : '{\n}';
if (isFlat) {
return str;
} else {
return str.substring(0, str.length - 2) + '\n}';
}
}
function readWordJs(src) {
try {
var words;
if (fs.existsSync(src + 'js/' + fileName)) {
words = fs.readFileSync(src + 'js/' + fileName).toString();
} else {
words = fs.readFileSync(src + fileName).toString();
}
var lines = words.split(/\r\n|\r|\n/g);
var i = 0;
while (!lines[i].match(/^systemDictionary = {/)) {
i++;
}
lines.splice(0, i);
// remove last empty lines
i = lines.length - 1;
while (!lines[i]) {
i--;
}
if (i < lines.length - 1) {
lines.splice(i + 1);
}
lines[0] = lines[0].replace('systemDictionary = ', '');
lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}');
words = lines.join('\n');
var resultFunc = new Function('return ' + words + ';');
return resultFunc();
} catch (e) {
return null;
}
}
function padRight(text, totalLength) {
return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : '');
}
function writeWordJs(data, src) {
var text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n\n';
text += '/*global systemDictionary:true */\n';
text += '\'use strict\';\n\n';
text += 'systemDictionary = {\n';
for (var word in data) {
if (data.hasOwnProperty(word)) {
text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50);
var line = '';
for (var lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' ';
}
}
if (line) {
line = line.trim();
line = line.substring(0, line.length - 1);
}
text += line + '},\n';
}
}
text += '};';
if (fs.existsSync(src + 'js/' + fileName)) {
fs.writeFileSync(src + 'js/' + fileName, text);
} else {
fs.writeFileSync(src + '' + fileName, text);
}
}
const EMPTY = '';
function words2languages(src) {
var langs = Object.assign({}, languages);
var data = readWordJs(src);
if (data) {
for (var word in data) {
if (data.hasOwnProperty(word)) {
for (var lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
langs[lang][word] = data[word][lang];
// pre-fill all other languages
for (var j in langs) {
if (langs.hasOwnProperty(j)) {
langs[j][word] = langs[j][word] || EMPTY;
}
}
}
}
}
}
if (!fs.existsSync(src + 'i18n/')) {
fs.mkdirSync(src + 'i18n/');
}
for (var l in langs) {
if (!langs.hasOwnProperty(l)) continue;
var keys = Object.keys(langs[l]);
//keys.sort();
var obj = {};
for (var k = 0; k < keys.length; k++) {
obj[keys[k]] = langs[l][keys[k]];
}
if (!fs.existsSync(src + 'i18n/' + l)) {
fs.mkdirSync(src + 'i18n/' + l);
}
fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj));
}
} else {
console.error('Cannot read or parse ' + fileName);
}
}
function words2languagesFlat(src) {
var langs = Object.assign({}, languages);
var data = readWordJs(src);
if (data) {
for (var word in data) {
if (data.hasOwnProperty(word)) {
for (var lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
langs[lang][word] = data[word][lang];
// pre-fill all other languages
for (var j in langs) {
if (langs.hasOwnProperty(j)) {
langs[j][word] = langs[j][word] || EMPTY;
}
}
}
}
}
}
var keys = Object.keys(langs.en);
//keys.sort();
for (var l in langs) {
if (!langs.hasOwnProperty(l)) continue;
var obj = {};
for (var k = 0; k < keys.length; k++) {
obj[keys[k]] = langs[l][keys[k]];
}
langs[l] = obj;
}
if (!fs.existsSync(src + 'i18n/')) {
fs.mkdirSync(src + 'i18n/');
}
for (var ll in langs) {
if (!langs.hasOwnProperty(ll)) continue;
if (!fs.existsSync(src + 'i18n/' + ll)) {
fs.mkdirSync(src + 'i18n/' + ll);
}
fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en));
}
fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n'));
} else {
console.error('Cannot read or parse ' + fileName);
}
}
function languagesFlat2words(src) {
var dirs = fs.readdirSync(src + 'i18n/');
var langs = {};
var bigOne = {};
var order = Object.keys(languages);
dirs.sort(function (a, b) {
var posA = order.indexOf(a);
var posB = order.indexOf(b);
if (posA === -1 && posB === -1) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
} else if (posA === -1) {
return -1;
} else if (posB === -1) {
return 1;
} else {
if (posA > posB) return 1;
if (posA < posB) return -1;
return 0;
}
});
var keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n');
for (var l = 0; l < dirs.length; l++) {
if (dirs[l] === 'flat.txt') continue;
var lang = dirs[l];
var values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n');
langs[lang] = {};
keys.forEach(function (word, i) {
langs[lang][word] = values[i].replace(/<\/ i>/g, '</i>').replace(/<\/ b>/g, '</b>').replace(/<\/ span>/g, '</span>').replace(/% s/g, ' %s');
});
var words = langs[lang];
for (var word in words) {
if (words.hasOwnProperty(word)) {
bigOne[word] = bigOne[word] || {};
if (words[word] !== EMPTY) {
bigOne[word][lang] = words[word];
}
}
}
}
// read actual words.js
var aWords = readWordJs();
var temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt'];
if (aWords) {
// Merge words together
for (var w in aWords) {
if (aWords.hasOwnProperty(w)) {
if (!bigOne[w]) {
console.warn('Take from actual words.js: ' + w);
bigOne[w] = aWords[w]
}
dirs.forEach(function (lang) {
if (temporaryIgnore.indexOf(lang) !== -1) return;
if (!bigOne[w][lang]) {
console.warn('Missing "' + lang + '": ' + w);
}
});
}
}
}
writeWordJs(bigOne, src);
}
function languages2words(src) {
var dirs = fs.readdirSync(src + 'i18n/');
var langs = {};
var bigOne = {};
var order = Object.keys(languages);
dirs.sort(function (a, b) {
var posA = order.indexOf(a);
var posB = order.indexOf(b);
if (posA === -1 && posB === -1) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
} else if (posA === -1) {
return -1;
} else if (posB === -1) {
return 1;
} else {
if (posA > posB) return 1;
if (posA < posB) return -1;
return 0;
}
});
for (var l = 0; l < dirs.length; l++) {
if (dirs[l] === 'flat.txt') continue;
var lang = dirs[l];
langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString();
langs[lang] = JSON.parse(langs[lang]);
var words = langs[lang];
for (var word in words) {
if (words.hasOwnProperty(word)) {
bigOne[word] = bigOne[word] || {};
if (words[word] !== EMPTY) {
bigOne[word][lang] = words[word];
}
}
}
}
// read actual words.js
var aWords = readWordJs();
var temporaryIgnore = ['pt', 'fr', 'nl', 'it'];
if (aWords) {
// Merge words together
for (var w in aWords) {
if (aWords.hasOwnProperty(w)) {
if (!bigOne[w]) {
console.warn('Take from actual words.js: ' + w);
bigOne[w] = aWords[w]
}
dirs.forEach(function (lang) {
if (temporaryIgnore.indexOf(lang) !== -1) return;
if (!bigOne[w][lang]) {
console.warn('Missing "' + lang + '": ' + w);
}
});
}
}
}
writeWordJs(bigOne, src);
}
gulp.task('adminWords2languages', function (done) {
words2languages('./admin/');
done();
});
gulp.task('adminWords2languagesFlat', function (done) {
words2languagesFlat('./admin/');
done();
});
gulp.task('adminLanguagesFlat2words', function (done) {
languagesFlat2words('./admin/');
done();
});
gulp.task('adminLanguages2words', function (done) {
languages2words('./admin/');
done();
});
gulp.task('updatePackages', function (done) {
iopackage.common.version = pkg.version;
iopackage.common.news = iopackage.common.news || {};
if (!iopackage.common.news[pkg.version]) {
var news = iopackage.common.news;
var newNews = {};
newNews[pkg.version] = {
en: 'news',
de: 'neues',
ru: 'новое'
};
iopackage.common.news = Object.assign(newNews, news);
}
fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4));
done();
});
gulp.task('updateReadme', function (done) {
var readme = fs.readFileSync('README.md').toString();
var pos = readme.indexOf('## Changelog\n');
if (pos !== -1) {
var readmeStart = readme.substring(0, pos + '## Changelog\n'.length);
var readmeEnd = readme.substring(pos + '## Changelog\n'.length);
if (readme.indexOf(version) === -1) {
var timestamp = new Date();
var date = timestamp.getFullYear() + '-' +
('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' +
('0' + (timestamp.getDate()).toString(10)).slice(-2);
var news = '';
if (iopackage.common.news && iopackage.common.news[pkg.version]) {
news += '* ' + iopackage.common.news[pkg.version].en;
}
fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd);
}
}
done();
});
gulp.task('default', ['updatePackages', 'updateReadme']);

Some files were not shown because too many files have changed in this diff Show More