iobroker.airzone 1.0.4 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Airzone.png CHANGED
Binary file
@@ -0,0 +1,87 @@
1
+ 'use strict';
2
+
3
+ const AsyncRequest = require('../Utils/asyncRequest');
4
+ const System = require('./System')
5
+
6
+ // Allow to connect to Airzone local API
7
+
8
+ let log;
9
+ let adapter;
10
+ class AirzoneLocalApi {
11
+ constructor(a, local_ip)
12
+ {
13
+ adapter = a;
14
+ log = a.log;
15
+ this.local_ip = local_ip;
16
+ }
17
+
18
+ async init(system_id) {
19
+
20
+ this.system = new System(adapter, this, system_id);
21
+ await this.system.init();
22
+ }
23
+
24
+ async update() {
25
+ if(this.system == undefined)
26
+ return;
27
+
28
+ await this.system.update();
29
+ }
30
+
31
+ logInfo(msg) {
32
+ log.info(msg);
33
+ }
34
+
35
+ logError(msg) {
36
+ log.error(msg);
37
+ }
38
+
39
+ async getZoneState() {
40
+ if(this.system == undefined)
41
+ return undefined;
42
+
43
+ var url = "http://"+this.local_ip+":3000/api/v1/hvac";
44
+ var systemId = this.system.id;
45
+ var data = '{\"systemID\":'+systemId+', \"ZoneID\":0}';
46
+ var response = await AsyncRequest.jsonPostRequest(url, data);
47
+
48
+ var errors = response["errors"];
49
+ if(errors)
50
+ {
51
+ this.logError("Failed to get zone state: (statusCode: "+response["statusCode"]+") - "+response["errors"]);
52
+ return undefined;
53
+ }
54
+ var body = response["body"];
55
+ var zones = JSON.parse(body)["data"];
56
+
57
+ return zones;
58
+ }
59
+
60
+ async sendUpdate(zoneid, key, value)
61
+ {
62
+ if(this.system == undefined)
63
+ return false;
64
+
65
+ try
66
+ {
67
+ var url = "http://"+this.local_ip+":3000/api/v1/hvac";
68
+ var systemId = this.system.id;
69
+ var data = '{\"systemID\":'+systemId+', \"ZoneID\":'+zoneid+', \"'+key+'\":'+value+'}';
70
+ var response = await AsyncRequest.jsonPutRequest(url, data);
71
+ var errors = response["errors"];
72
+ if(errors)
73
+ {
74
+ this.logError("Failed to update '"+key+"' with value '"+value+"': (statusCode: "+response["statusCode"]+") - "+response["errors"]);
75
+ return undefined;
76
+ }
77
+ var body = response["body"];
78
+ var responseData = JSON.parse(body)["data"];
79
+ return responseData.hasOwnProperty(key);
80
+ }
81
+ catch (e) {
82
+ this.logError('error during sendUpdate '+e+'\r\n'+e.stack);
83
+ }
84
+ return false;
85
+ }
86
+ }
87
+ module.exports = AirzoneLocalApi;
@@ -0,0 +1,14 @@
1
+ module.exports = Object.freeze({
2
+ MODES_CONVERTER : {
3
+ "1": {"name": "Stop"},
4
+ "2": {"name": "Cooling"},
5
+ "3": {"name": "Heating"},
6
+ "4": {"name": "Fan"},
7
+ "5": {"name": "Dry"}
8
+ },
9
+
10
+ UNIT_CONVERTER : {
11
+ "0": {"name": "Celsius", "unit": "°C"},
12
+ "1": {"name": "Fahrenheit", "unit": "°F"}
13
+ },
14
+ });
@@ -0,0 +1,150 @@
1
+ const Zone = require('./Zone')
2
+ const AsyncRequest = require('../Utils/asyncRequest');
3
+ const Constants = require('./Constants');
4
+
5
+ class System {
6
+ constructor(adapter, localApi, id)
7
+ {
8
+ this.adapter = adapter;
9
+ this.localApi = localApi;
10
+ this.id = id;
11
+ }
12
+
13
+ /**
14
+ * Initialize the system with the data from the airzone local api
15
+ */
16
+ async init() {
17
+ this.path = "System"+this.id;
18
+ await this.adapter.setObjectNotExistsAsync(this.path, {
19
+ type: 'device',
20
+ common: {
21
+ name: this.path,
22
+ type: 'object',
23
+ read: true,
24
+ write: false,
25
+ },
26
+ native: {},
27
+ });
28
+
29
+ await this.adapter.createProperty(this.path, 'mode_raw', 'number', true, true, 'state');
30
+ await this.adapter.createProperty(this.path, 'mode', 'string', true, false, 'text');
31
+
32
+ this.adapter.subscribeState(this.path+'.mode_raw', this, this.reactToModeRawChange);
33
+
34
+ var masterZone = await this.load_zones(this.path);
35
+ if(masterZone == undefined)
36
+ return false;
37
+
38
+ await this.updateData(masterZone);
39
+
40
+ return true;
41
+ }
42
+
43
+ /**
44
+ * Synchronized the system data from airzone into the iobroker data points
45
+ */
46
+ async updateData(masterZoneData)
47
+ {
48
+ if(masterZoneData == undefined)
49
+ {
50
+ this.localApi.logError("Missing master Zone");
51
+ return;
52
+ }
53
+
54
+ this.mode_raw = masterZoneData["mode"];
55
+ await this.adapter.updatePropertyValue(this.path, 'mode_raw', this.mode_raw);
56
+ this.mode = Constants.MODES_CONVERTER[this.mode_raw]["name"];
57
+ await this.adapter.updatePropertyValue(this.path, 'mode', this.mode);
58
+ }
59
+
60
+ /**
61
+ * Synchronized the system data from airzone into the iobroker data points and call update for all sub zones
62
+ */
63
+ async update() {
64
+ var masterZoneData = await this.update_zones();
65
+
66
+ await this.updateData(masterZoneData);
67
+ }
68
+
69
+ /**
70
+ * Load and initialize the zones of this system from airzone local api
71
+ */
72
+ async load_zones(path) {
73
+
74
+ var zones_relations = await this.localApi.getZoneState();
75
+ if(zones_relations == undefined)
76
+ return undefined;
77
+
78
+ var masterZoneData = undefined;
79
+ this.zones = [];
80
+ for (let index = 0; index < zones_relations.length; index++) {
81
+ var zoneData = zones_relations[index];
82
+ var zone = new Zone(this.adapter, this.localApi);
83
+ await zone.init(path, zoneData)
84
+ this.zones[index] = zone;
85
+
86
+ if(zoneData.hasOwnProperty("mode"))
87
+ {
88
+ masterZoneData = zoneData;
89
+ this.masterZoneId = this.zones[index].id;
90
+ }
91
+ }
92
+
93
+ return masterZoneData;
94
+ }
95
+
96
+ /**
97
+ * Update zones with the current zone data from airzone local api
98
+ */
99
+ async update_zones() {
100
+
101
+ var zones_relations = await this.localApi.getZoneState();
102
+ if(zones_relations == undefined)
103
+ return undefined;
104
+
105
+ if(this.zones == undefined)
106
+ return undefined;
107
+
108
+ var masterZoneData = undefined;
109
+
110
+ for (let index = 0; index < zones_relations.length; index++) {
111
+ var zoneData = zones_relations[index];
112
+ var zId = zoneData["zoneID"];
113
+
114
+ if(zoneData.hasOwnProperty("mode"))
115
+ masterZoneData = zoneData;
116
+
117
+ for(let i = 0;i<this.zones.length;i++) {
118
+ if(this.zones[i].id == zId) {
119
+ await this.zones[i].updateData(zoneData);
120
+ break;
121
+ }
122
+ }
123
+ }
124
+
125
+ return masterZoneData;
126
+ }
127
+
128
+ /**
129
+ * Is called when the state of mode was changed
130
+ */
131
+ async reactToModeRawChange(self, id, state) {
132
+
133
+ if(state.val == 0)
134
+ {
135
+ self.zones.forEach(zone => {
136
+ zone.turn_off();
137
+ });
138
+ }
139
+
140
+ self.sendEvent('mode', state.val);
141
+ }
142
+
143
+ /**
144
+ * Send event to the airzone local api
145
+ */
146
+ async sendEvent(option, value) {
147
+ await this.localApi.sendUpdate(this.masterZoneId, option, value)
148
+ }
149
+ }
150
+ module.exports = System;
@@ -1,31 +1,26 @@
1
1
  const Constants = require('./Constants');
2
2
 
3
3
  class Zone {
4
- constructor(adapter, airzone)
4
+ constructor(adapter, localApi)
5
5
  {
6
6
  this.adapter = adapter;
7
- this.airzone = airzone;
7
+ this.localApi = localApi;
8
8
  }
9
9
 
10
10
  /**
11
- * Initialize the zone with the data from the airzone cloud
11
+ * Initialize the zone with the data from the airzone local api
12
12
  */
13
13
  async init(path, zoneData) {
14
- this.id = zoneData['id'];
15
- this.name = zoneData['name'];
16
-
17
- this.device_id = zoneData['device_id'];
18
- this.system_number = zoneData['system_number'];
19
- this.zone_number = zoneData['zone_number'];
20
-
21
- this.min_temp = zoneData['lower_conf_limit'];
22
- this.max_temp = zoneData['upper_conf_limit'];
14
+ this.id = parseInt(zoneData['zoneID']);
15
+ this.name = zoneData.hasOwnProperty('name') ? zoneData['name'] : '';
16
+ this.min_temp = zoneData['minTemp'];
17
+ this.max_temp = zoneData['maxTemp'];
23
18
 
24
- this.path = path+'.'+this.name;
19
+ this.path = path+'.Zone'+this.id;
25
20
  await this.adapter.setObjectNotExistsAsync(this.path, {
26
21
  type: 'device',
27
22
  common: {
28
- name: 'Zone_'+this.name,
23
+ name: 'Zone_'+this.id,
29
24
  type: 'object',
30
25
  read: true,
31
26
  write: false,
@@ -33,18 +28,22 @@ class Zone {
33
28
  native: {},
34
29
  });
35
30
 
36
- await this.adapter.createPropertyAndInit(this.path, 'id', 'string', true, false, this.id, 'text');
31
+ var unitRaw = zoneData['units'];
32
+ var unitName = Constants.UNIT_CONVERTER[unitRaw]["name"];
33
+ var unitUnit = Constants.UNIT_CONVERTER[unitRaw]["unit"];
34
+
35
+ await this.adapter.createPropertyAndInit(this.path, 'id', 'number', true, false, this.id, 'number');
37
36
  await this.adapter.createPropertyAndInit(this.path, 'name', 'string', true, false, this.name, 'text');
38
37
  await this.adapter.createPropertyAndInit(this.path, 'min_temp', 'number', true, false, this.min_temp, 'value.min');
39
38
  await this.adapter.createPropertyAndInit(this.path, 'max_temp', 'number', true, false, this.max_temp, 'value.max');
40
- await this.adapter.createUnitProperty(this.path, 'current_temperature', 'number', 0, 100, '°C', true, false, 'value.temperature');
41
- await this.adapter.createUnitProperty(this.path, 'current_humidity', 'number', 0, 100, '%', true, false, 'value.humidity');
42
- await this.adapter.createUnitProperty(this.path, 'target_temperature', 'number', this.min_temp, this.max_temp, '°C', true, true, 'state');
39
+ await this.adapter.createPropertyAndInit(this.path, 'unitRaw', 'string', true, false, unitRaw, 'number');
40
+ await this.adapter.createPropertyAndInit(this.path, 'unitName', 'string', true, false, unitName, 'text');
41
+ await this.adapter.createPropertyAndInit(this.path, 'unit', 'string', true, false, unitUnit, 'text');
43
42
  await this.adapter.createProperty(this.path, 'is_on', 'boolean', true, false, 'switch.power');
44
- await this.adapter.createProperty(this.path, 'mode_raw', 'number', true, false, 'value');
45
- await this.adapter.createProperty(this.path, 'mode', 'string', true, false, 'text');
46
- await this.adapter.createProperty(this.path, 'mode_description', 'string', true, false, 'text');
47
-
43
+ await this.adapter.createUnitProperty(this.path, 'current_temperature', 'number', 0, 100, unitUnit, true, false, 'value.temperature');
44
+ await this.adapter.createUnitProperty(this.path, 'current_humidity', 'number', 0, 100, '%', true, false, 'value.humidity');
45
+ await this.adapter.createUnitProperty(this.path, 'target_temperature', 'number', this.min_temp, this.max_temp, unitUnit, true, true, 'value.temperature');
46
+
48
47
  // Register callbacks to react on value changes
49
48
  this.adapter.subscribeState(this.path+'.target_temperature', this, this.reactToTargetTemperatureChange);
50
49
  this.adapter.subscribeState(this.path+'.is_on', this, this.reactToIsOnChange);
@@ -57,25 +56,16 @@ class Zone {
57
56
  */
58
57
  async updateData(zoneData)
59
58
  {
60
- this.current_temperature = zoneData['temp'];
59
+ this.current_temperature = zoneData['roomTemp'];
61
60
  await this.adapter.updatePropertyValue(this.path, 'current_temperature', this.current_temperature);
62
61
 
63
62
  this.current_humidity = zoneData['humidity'];
64
63
  await this.adapter.updatePropertyValue(this.path, 'current_humidity', this.current_humidity);
65
64
 
66
- this.target_temperature = zoneData['consign'];
65
+ this.target_temperature = zoneData['setpoint'];
67
66
  await this.adapter.updatePropertyValue(this.path, 'target_temperature', this.target_temperature);
68
-
69
- this.mode_raw = zoneData['mode'];
70
- await this.adapter.updatePropertyValue(this.path, 'mode_raw', this.mode_raw);
71
-
72
- this.mode = Constants.MODES_CONVERTER[this.mode_raw]['name'];
73
- await this.adapter.updatePropertyValue(this.path, 'mode', this.mode);
74
67
 
75
- this.mode_description = Constants.MODES_CONVERTER[this.mode_raw]['description'];
76
- await this.adapter.updatePropertyValue(this.path, 'mode_description', this.mode_description);
77
-
78
- this.is_on = zoneData['state'] == '1' && this.mode_raw > 0;
68
+ this.is_on = zoneData['on'] == '1';
79
69
  await this.adapter.updatePropertyValue(this.path, 'is_on', this.is_on);
80
70
  }
81
71
 
@@ -89,16 +79,13 @@ class Zone {
89
79
  if(self.max_temp != undefined && temperature > self.max_temp)
90
80
  temperature = self.max_temp;
91
81
 
92
- await self.sendEvent('consign', temperature);
82
+ await self.sendEvent('setpoint', temperature);
93
83
  }
94
84
 
95
85
  /**
96
86
  * Is called when the state of is_on was changed
97
87
  */
98
88
  async reactToIsOnChange(self, id, state) {
99
- if(self.mode_raw < 1)
100
- return;
101
-
102
89
  if(state.val)
103
90
  await self.turn_on();
104
91
  else
@@ -109,31 +96,21 @@ class Zone {
109
96
  * Send event to the airzone cloud
110
97
  */
111
98
  async sendEvent(option, value) {
112
- var payload = {
113
- 'event': {
114
- 'cgi': 'modzona',
115
- 'device_id': this.device_id,
116
- 'system_number': this.system_number,
117
- 'zone_number': this.zone_number,
118
- 'option': option,
119
- 'value': value,
120
- }
121
- };
122
- await this.airzone.sendEvent(payload);
99
+ await this.localApi.sendUpdate(this.id, option, value)
123
100
  }
124
101
 
125
102
  /**
126
103
  * Turn zone on
127
104
  */
128
105
  async turn_on() {
129
- await this.sendEvent('state', 1);
106
+ await this.sendEvent('on', 1);
130
107
  }
131
108
 
132
109
  /**
133
110
  * Turn zone off
134
111
  */
135
112
  async turn_off() {
136
- await this.sendEvent('state', 0);
113
+ await this.sendEvent('on', 0);
137
114
  }
138
115
  }
139
116
  module.exports = Zone;
package/README.md CHANGED
@@ -7,15 +7,22 @@ Control and monitor airzone devices with ioBroker.
7
7
  [![Downloads](https://img.shields.io/npm/dm/iobroker.airzone.svg)](https://www.npmjs.com/package/iobroker.airzone)
8
8
  [![Stable](http://iobroker.live/badges/airzone-stable.svg)](http://iobroker.live/badges/airzone-stable.svg)
9
9
  [![installed](http://iobroker.live/badges/airzone-installed.svg)](http://iobroker.live/badges/airzone-installed.svg)
10
- [![Dependency Status](https://img.shields.io/david/SilentPhoenix11/iobroker.airzone.svg)](https://david-dm.org/SilentPhoenix11/iobroker.airzone)
11
10
  [![Known Vulnerabilities](https://snyk.io/test/github/SilentPhoenix11/ioBroker.airzone/badge.svg)](https://snyk.io/test/github/SilentPhoenix11/ioBroker.airzone)
12
11
  [![Build Status](https://travis-ci.com/SilentPhoenix11/ioBroker.airzone.svg?branch=master)](https://travis-ci.com/github/SilentPhoenix11/ioBroker.airzone)
13
12
 
14
- [![NPM](https://nodei.co/npm/iobroker.airzone.png?downloads=true)](https://nodei.co/npm/iobroker.airzone/)
13
+ ## Changelog
14
+ ### 2.0.3
15
+ * (SilentPhoenix11) Small fixes
15
16
 
16
- **Tests:**
17
+ ### 2.0.2
18
+ * (SilentPhoenix11) Small fixes
19
+
20
+ ### 2.0.1
21
+ * (SilentPhoenix11) Small fixes
22
+
23
+ ### 2.0.0
24
+ * (SilentPhoenix11) Using the local API instead of cloud web service.
17
25
 
18
- ## Changelog
19
26
  ### 1.0.4
20
27
  * (SilentPhoenix11) Small fixes
21
28
 
@@ -34,6 +34,37 @@ class AsyncRequest {
34
34
  return JSON.parse(result);
35
35
  }
36
36
 
37
+ static async jsonPutRequest(url, data) {
38
+
39
+ const response = await asyncRequest({
40
+ method: 'PUT',
41
+ uri: url,
42
+ headers: {
43
+ 'Content-Type': 'application/json'
44
+ },
45
+ body: data
46
+ });
47
+
48
+ var result;
49
+
50
+ if(response.error)
51
+ {
52
+ result = JSON.stringify({statusCode:response.statusCode,errors:error});
53
+ }
54
+ else
55
+ {
56
+ var body = response.body;
57
+ var errorMsg = JSON.parse(body)["errors"];
58
+ if(errorMsg)
59
+ result = JSON.stringify({statusCode:response.statusCode,errors:errorMsg});
60
+ else
61
+ result = JSON.stringify({statusCode:response.statusCode,body:response.body});
62
+ }
63
+
64
+ return JSON.parse(result);
65
+ }
66
+
67
+
37
68
  static async jsonGetRequest(url) {
38
69
 
39
70
  const response = await asyncRequest({
package/admin/Airzone.png CHANGED
Binary file
Binary file
@@ -67,34 +67,30 @@
67
67
 
68
68
  <div class="row">
69
69
  <div class="col s12 m4 l2">
70
- <img src="Airzone.png" class="logo" style="width: 150px;">
70
+ <img src="Airzone.png" class="logo" style="width: 198px;">
71
71
  </div>
72
72
  </div>
73
73
 
74
74
  <div class="row">
75
- <div class="col s6 input-field">
76
- <input placeholder="mail@me.com" type="text" class="value" id="username"/>
77
- <label for="username" class="translate">Username</label>
78
- </div>
79
-
80
- <div class="col s6 input-field">
81
- <input type="password" class="value" id="password" />
82
- <label for="password" class="translate">Password</label>
83
- </div>
75
+ <div class="input-field col s12 m4 l3">
76
+ <input placeholder="192.168.178.1" type="text" minlength="7" maxlength="15" size="15" pattern="^((\d{1,2}|1\d\d|2[0-4]\d|25[0-5])\.){3}(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])$" class="value validate" id="local_ip">
77
+ <label data-error="invalid format" data-success="ok" for="local_ip" class="translate">Local IP</label>
78
+ </div>
84
79
  </div>
85
- <div class="row">
86
- <div class="col s6 input-field">
87
- <input placeholder="https://airzonecloud.com" type="text" class="value" id="base_url" />
88
- <label for="base_url" class="translate">Base url</label><span class="helper-text translate">(default is https://airzonecloud.com)</span>
80
+ <div class="row">
81
+ <div class="input-field col s12 m4 l3">
82
+ <input placeholder="1" type="number" class="value validate" id="system_id" min="1"/>
83
+ <label data-error="numbers only" data-success="ok" class="translate active" for="system_id" class="translate">System Id</label>
84
+ <span class="helper-text translate">(default is 1)</span>
89
85
  </div>
90
-
86
+ </div>
87
+ <div class="row">
91
88
  <div class="input-field col s12 m4 l3">
92
- <input placeholder="30" name="synchronization" type="number" id="sync_time" class="value validate" />
93
- <label data-error="numbers only" data-success="ok" class="translate active" for="sync_time" class="translate">Sync time</label><span class="helper-text translate">s (default is 30 s, minimum is 15 s)</span>
89
+ <input placeholder="5" type="number" id="sync_time" class="value validate" min="1"/>
90
+ <label data-error="numbers only" data-success="ok" class="translate active" for="sync_time" class="translate">Sync time</label>
91
+ <span class="helper-text translate">(default is 5 s, minimum is 1 s)</span>
94
92
  </div>
95
-
96
93
  </div>
97
-
98
94
  </div>
99
95
 
100
96
  </body>
package/admin/words.js CHANGED
@@ -3,53 +3,41 @@
3
3
 
4
4
  systemDictionary = {
5
5
  "Adapter settings for airzone cloud": {
6
- "en": "Adapter settings for airzone cloud",
7
- "de": "Adaptereinstellungen für die Luftzonenwolke",
8
- "ru": "Настройки адаптера для облака airzone",
9
- "pt": "Configurações do adaptador para nuvem airzone",
10
- "nl": "Adapterinstellingen voor Airzone Cloud",
11
- "fr": "Paramètres de l'adaptateur pour le cloud airzone",
12
- "it": "Impostazioni dell'adattatore per airzone cloud",
13
- "es": "Configuración del adaptador para airzone cloud",
14
- "pl": "Ustawienia adaptera dla chmury airzone",
15
- "zh-cn": "Airzone Cloud的适配器设置"
6
+ "en": "Adapter settings for airzone local api",
7
+ "de": "Adaptereinstellungen für airzone local api",
8
+ "ru": "Настройки адаптера для airzone local api",
9
+ "pt": "Ajustes do adaptador para api local de zona aérea",
10
+ "nl": "Adapter instellingen voor airzone local api",
11
+ "fr": "Paramètres de l'adaptateur pour l'api local d'airzone",
12
+ "it": "Impostazioni dell'adattatore per airzone local api",
13
+ "es": "Configuración del adaptador para la api local de airzone",
14
+ "pl": "Ustawienia adaptera dla airzone local api",
15
+ "zh-cn": "Airzone本地api的适配器设置"
16
16
  },
17
- "username": {
18
- "en": "username",
19
- "de": "Benutzername",
20
- "ru": "имя пользователя",
21
- "pt": "nome do usuário",
22
- "nl": "gebruikersnaam",
23
- "fr": "Nom d'utilisateur",
24
- "it": "nome utente",
25
- "es": "nombre de usuario",
26
- "pl": "Nazwa Użytkownika",
27
- "zh-cn": "用户名"
28
- },
29
- "password": {
30
- "en": "password",
31
- "de": "Passwort",
32
- "ru": "пароль",
33
- "pt": "senha",
34
- "nl": "wachtwoord",
35
- "fr": "le mot de passe",
36
- "it": "parola d'ordine",
37
- "es": "contraseña",
38
- "pl": "hasło",
39
- "zh-cn": "密码"
40
- },
41
- "base_url": {
42
- "en": "Base url",
43
- "de": "Basis-URL",
44
- "ru": "Базовый URL",
45
- "pt": "Url de base",
46
- "nl": "Basis-URL",
47
- "fr": "URL de base",
48
- "it": "URL di base",
49
- "es": "URL base",
50
- "pl": "Podstawowy adres URL",
51
- "zh-cn": "基本网址"
52
- },
17
+ "local_ip": {
18
+ "en": "Local IP address",
19
+ "de": "Lokale IP-Adresse",
20
+ "ru": "Локальный IP-адрес",
21
+ "pt": "Endereço IP local",
22
+ "nl": "Lokaal IP-adres",
23
+ "fr": "Adresse IP locale",
24
+ "it": "Indirizzo IP locale",
25
+ "es": "Dirección IP local",
26
+ "pl": "Lokalny adres IP",
27
+ "zh-cn": "本地IP地址"
28
+ },
29
+ "system_id": {
30
+ "en": "Unique ID of the airzone system.",
31
+ "de": "Eindeutige ID des airzone systems.",
32
+ "ru": "Уникальный идентификатор системы воздушной зоны.",
33
+ "pt": "Identificação única do sistema de zonas aéreas.",
34
+ "nl": "Unieke ID van het airzone-systeem.",
35
+ "fr": "ID unique du système airzone.",
36
+ "it": "ID unico del sistema airzone.",
37
+ "es": "ID único del sistema de zonas aéreas.",
38
+ "pl": "Unikalny identyfikator systemu stref powietrza.",
39
+ "zh-cn": "气区系统的唯一ID。"
40
+ },
53
41
  "sync_time": {
54
42
  "en": "Sync time",
55
43
  "de": "Synchronisierungszeit",