diff --git a/.gitignore b/.gitignore index f067ec86e..5ac6a3f93 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ npm-debug.log .SyncTrash storage/* .idea +.history node_modules blub *.gitignore diff --git a/CHANGELOG.md b/CHANGELOG.md index 7315ee057..6ef445bb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,242 @@ +#10.10.2018 v2.3.8 +Features: +* remove uploaded room images +* new base modules added that combine or enhance functionalities of already well known modules, which are working without Alpca JS. + These new modules can be find and configured in SHUI under the Automation menu (gear wheel): + * Hazard Notification (combination from Leakage Protection and Fire Protection modules, ready for more) + * Rules (combination of If>Then (simple mode) and Logical Rule (expert mode) modules) + * Scenes (enhancement of Light Scene module) + * Schedules (enhancement of Scheduled Scene module) + * Security (refactored and scheduling enhanced) + * Heating (enhanced Climate Control module) + * transformation added that will optionally transform LogicalRules/IfThen into Rules, LightScene into Scene, ScheduledScene into Schedules instances and deactivate all source instances - so the transformation can be rolled back easily (using http://IP:8083/ZAutomation/api/v1/modules/transform/reverse will remove the transformation flag from all modules) + +Changes: +* node id to vdev of zway devices added +* add remove location image api +* add prepareHTTPResponse function to AutomationModule.js +* ignore list of restore/backup functions refactored +* ZAutomation history API removed (now it comes from the module itself) +* storage function enhanced to remove not existing and cleared filenames from list +* add PNG and GIF to img type check +* QR-Code isn't stored on z-way-server anymore, it's generated on demand in UI instead (Devices > Mobile > Add) +* add restriction to QR code API - admin can request all QR codes, users can only request their own +* logical helper functions from Rules moved and centralized into AutomationModule.js, so also other modules can use them +* postfix.json updated (changed or added): + * added: + * Steinel XLED home 2 + * Steinel Senor-Switched Outdoor LightScene + * Foxx Project Door/Window Sensor + * Foxx Project Flood Sensor + * Everspring/TechniSat RM1 + * Heiman Smart Smoke Sensor + * Aeotec NanoMote One + * Aeotec NanoMote Quad + * Aeotec TriSensor + * TKBHome single/dual wall switch + * TKBHOME single/dual dimmer switch + * TKB Home Energy Plug In Switch + * Everspring Plug + * Popp Power Plug + * MCO Home Fan Coil Thermostat (2-pipe) V3.0 + * MCO Home CO2 Monitor + * Qubino 3-Phase Smart Meter + * Qubino Smart Plug + + * changed: + * Philio 4 in 1 Multisensor + * Philio Relay Insert Blind + * Philio Double Relay Insert 2*1.5 kW with Metering Function + * Foxx Project Smart Switch Gen5 + * POPP Flood / Water Leakage Sensor + * TKBHome two channel switch TZX7 + * TKB Plug Dimmer + * Qubino Smart Meter + * Qubino Flush Thermostats + * Qubino On/Off thermostat + * Aeotec Home Energy Meter - Gen5 + * PAN16 Smart Energy Plug In Switch + * Everspring Lamp Holder + * Poly Control Dana Lock V3 + * OOMI Door Window Sensor + * MCO Home - Water Heating Thermostat with humidity sensor + * MCO Home - Electrical Heating Thermostat with humidity sensor + * Sensative Strips Comfort / Drips + * Hank Flood Sensor + +Fixes: +* ZWave v2.3.0 + * update of failed status in zway vdevs fixed + * thermostat min/max fixed + * Public Z-Wave API with Expert UI fixed +* postfix update fixed +* Object.keys() error if cc.data is null fixed +* download URL of skins fixed +* Cannot select none image for room fixed +* icon upload and add uppercase extension (GIF,PNG,JPG,JPEG) support fixed +* missing transformation of main_sensor during update leads to indexOf undefined error - fixed + +Modules: +* ZWave v2.3.0 + * Gas Alarm (V7) 0x12 support added + * new alarm type - gas added + * to not polute the global namespace area a var was added to postfix logic + * Optimized a bit the F/W update code and added support of 40196 bootloader for upgrade UZB 5.07->5.27 +* HTTPDevice v2.2.0 + * enhancement: can set method GET/POST for update command + * helper fixed +* DeviceHistory v2.0.0 + * new api HistoryAPI added (moved from ZAutomation and AutomationController to module) +* BindDevices v1.0.2 + * sensorMultilevel support added +* MailNotifier v1.2.0 + * change logic to handle also different mail adresses in e-mail outgoing +* BatteryPolling v2.2.0 + * notifications support module added +* MobileAppSupport v1.2.7 + * table of undefined bug fixed + +#23.03.2018 v2.3.7 +Features: +* Added emulateOff postfix to Sensor Binary +* support for wifiplugs added (TP-Link HS100, TP-Link HS110, EDIMAX SP1101, EDIMAX SP2101) +* allow probeType postfix in ZWave module +* IP-address api added + +Changes: +* Set thermostat value only if they are not equal * this will prevent ClimateControl app to set values twice +* add ip address to qrcode +* stored QR code is removed, now it is only produced in UI temporarely against successful authorization +* module categories "system" and "wifiplug" added +* rework the ZAutomation/api/v1/icons/upload API to return a name +* language files refactored +* postfix.json updated (changed or added): + * added: + * Popp Z-Weather + * Z-Wave.me Dimmer + * Danfoss Hydronic Controller 10 + * Aeotec RGBW Bulb + * Aeotec Dual Nano Switch with Meter + * Aeotec Indoor Siren Gen5 + * Aeotec Door Window Sensor 6 + * Aeotec Dual Nano Switch + * Aeotec Water Sensor 6 + * Secure 7 Day Programmable Thermostat + * Secure Temperature Sensor + * Secure Wall Thermostat with LCD Display + * Secure Z-Wave controlled Boiler Actuator (2 Channels) + * Secure Receiver with Relay SSR303 + * Philio Smart Dimmer Socket PAD02 + * Philio Double Relay Insert 2*1.5 kW with Metering Function + * OOMI Mote + * OOMI Plug + * OOMI Range Extender + * OOMI In-Wall Switch + * OOMI In-Wall Dimmer + * OOMI Colorstripe + * OOMI WATERSENSOR + * OOMI Multisensor + * Everspring Temperature and Humidity Sensor + * Everspring Wall Plug Dimmer + * Everspring ST812 Flood Detector + * Everspring SE812 Indoor Siren + * Devolo Alarmsirene + * Devolo Luftfeuchtemelder + * Devolo Wassermelder + * Devolo Bewegungsmelder + * Devolo Funkschalter + * Devolo Radiator Thermostat + * Devolo Rauchmelder + * Devolo KFOB + * Devolo Wall Plug 2.0 + * TKBHOME single/dual dimmer switch + * SCHWAIGER * 4 in 1 Multi Sensor Outdoor IP43 + * Fibaro Door/Window Sensor G5 * RU + * Fibaro Single Switch * 1*2.5 kW + * Fibaro Heat Controller + * MCO Home PM2.5 Sensor + * MCO Home Water Heating Thermostat with humidity sensor + * MCO Home CO2 Monitor + * MCO Home Glass Touch Switch (4 Buttons) British Standard + * MCO Home Glass Touch Switch (2 Buttons) British Standard + * MCO Home Glass Touch Switch (1 Button) + * MCO Home Glass Touch Switch (2 Buttons) + * MCO Home * Electrical Heating Thermostat with humidity sensor + * Sensative Stripe Multisensor Drip + * BeNext Wall Plug with Dimmer Function + * Hank Four-Key Scene Contoller + * Hank One-Key Scene Contoller + * Hank Flood Sensor + * Hank Motion Sensor + * Hank Smart Plug + * NEO Coolcam Door / Window Sensor + * NEO Coolcam Motion Sensor + * NEO Coolcam Siren + * Steinel Indoor Light + * Steinel Motion Sensor + * changed: + * Fibaro Double Relay Switch FGS-222 + * Fibaro Door/Window Sensor G5 + * Fibaro Dimmer 2 + * Danfoss Hydronic Controller 5 + * Devolo Door/Window Contact + * Aeotec Nano Switch 1 Relay + * Aeotec Range Extender 6 + * Aeotec Door Window Sensor 6 + * Aeotec Recessed Door G5 + * Aeotec Multisensor Gen 6 + * Qubino On/Off thermostat + * Qubino Flush Thermostat + * Qubino Relay Insert 1*2,3 kW + * Qubino PWM Thermostat + * TKB Plug Dimmer French + * WiDom Universal Double Switch + * Philio 4 in 1 Multisensor + * Philio PST02-5B Motion Sensor + * Philio Motionsensor + * Philio Relay Insert Blind + * Philio Double Relay Insert 2x1,5KW + * Philio PAN16 Smart Energy Plug In Switch + +Fixes: +* Fix not working time-zone set via shui +* remote triggered on Z-Way startup +* fix setDefaultLang function +* allow probeType postfix in ZWave module +* save probeType in vdevInfo + +Modules: +* TP-Link HS100 v1.0.0 + * added to support this wifi plug +* TP-Link HS110 v1.0.0 + * added to support this wifi plug +* EDIMAX SP1101 v1.0.0 + * added to support this wifi plug +* EDIMAX SP2101 v1.0.0 + * added to support this wifi plug +* Sonos v1.2.3: + * fixes + * icon changed +* MobileAppSupport v1.2.5: + * bugfix catches undefined-error occurred during adding first mobile device + * bugfix removing mobile device deletes LOCAL widget + * update IOS token + * remove devices manually +* ScheduledScene v2.2.0: + * can start scene many times in day + * defined default times: 00:00 and 12:00 + * devices on new path, as example switch: this.config.devices.switches + * compatibility with old config + * Bugfix: Dimmers, Lock and scenes didn't run. +* IfThen v2.5.1: + * allow negative values for multilevel sensors +* LightScene v1.1.1: + * icon changed +* AutoLock v1.2: + * added switchBinary support and checkbox Don't send Lock command if doorlock already closed +* TamperAutoOff v1.1.0: + * fix to SETDATA + #04.10.2017 v2.3.6 Changes: * show more logs on ZWaveBinding error diff --git a/README.md b/README.md index d5d1b2b43..a0afff368 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Z-Way Home Automation Engine v2.3.6 +Z-Way Home Automation Engine v2.3.8 ==================================== # Introduction # @@ -12,12 +12,11 @@ Z-Way Home Automation Engine v2.3.6 For a better integration during running developments, please use [Home Automation develop branch](https://github.com/Z-Wave-Me/home-automation/tree/develop) for your pull requests. ## New complete Documentation: -* [Z-Way Essentials](https://github.com/Z-Wave-Me/Z-Way-Manual/raw/3.0/ZWayManual.pdf) (PDF) +* [Z-Way Essentials](https://new.z-wave.me/essentials) (PDF Download) ## Home Automation Documentation's : * https://github.com/Z-Wave-Me/home-automation/wiki -* [old Z-Wave.Me Developers Documentation](https://github.com/Z-Wave-Me/Z-Way-Manual/raw/master/zwayDev.pdf) (PDF) -* see also documentations on [http://razberry.z-wave.me](http://razberry.z-wave.me/index.php?id=5) for more information +* see also documentations on [https://z-wave.me/support/](https://z-wave.me/support/) for more information ## API Documentation: #### ZAutomation: @@ -32,13 +31,13 @@ For a better integration during running developments, please use [Home Automatio ## Available Z-Way Server Downloads: Downloads are available for Raspbian (POPP Hub 1, also complete images), Debian, Ubuntu, Voyage, Dune HD Plugin (mips), Windows -* http://razberry.z-wave.me/z-way-server/ +* https://storage.z-wave.me/z-way-server/ ## Current changelog's: * [Home Automation changelog](https://github.com/Z-Wave-Me/home-automation/blob/master/CHANGELOG.md) * [Smart Home UI changelog](https://github.com/Z-Wave-Me/zwave-smarthome/blob/master/README.md) * [Expert UI changelog](https://github.com/Z-Wave-Me/ExpertUI/blob/master/README.md) -* [Full z-way-server changelog on http://razberry.z-wave.me](http://razberry.z-wave.me/z-way-server/ChangeLog) +* [Full z-way-server changelog on https://z-wave.me](https://storage.z-wave.me/z-way-server/ChangeLog) ## Z-Wave.Me - Developer Console (App Store) Create an account and upload your own z-way home automation apps, share them with a special group or verify them for public access. @@ -53,5 +52,4 @@ You can also find a list of all [currently published apps](https://developer.z-w ## Disscussion platforms: * https://forum.z-wave.me/ (discussions available in en/de/ru/fr/it/cn, posts about: RaZberry, Z-Uno ...) * http://www.zwave.de/forum (de) -* http://www.z-wave.info/forum (en) - +* http://www.z-wave.info/forum (en) \ No newline at end of file diff --git a/Utils.js b/Utils.js index 28bd09d4f..c329fd997 100644 --- a/Utils.js +++ b/Utils.js @@ -315,3 +315,42 @@ function retBoolean(boolean) { return false; } }; + +/* +* find the smallest not assigned value (integer) of a specific key within array objects +*/ +function findSmallestNotAssignedIntegerValue (array, key) { + var value = 1, + maxValue = null, + listValues = []; + + listValues = array.map(function(entry) { + return Number.isInteger(entry[key])? entry[key] : parseInt(entry[key],10); + }); + + maxValue = Math.max.apply(null, listValues); + + for (var i = 1; i <= maxValue; i++) { + if (listValues.indexOf(i) < 0) { + value = i; + break; + } else if (i == maxValue) { + value = i + 1; + } + } + + return value; +}; + +/* + * transform the publicKey into usual dsk format: xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx-xxxxx + */ + function transformPublicKeyToDSK (publicKey) { + var dsk = ''; + + if (_.isArray(publicKey) && publicKey.length > 0) { + dsk = publicKey.map(function(x, i, a) { if (i % 2 == 0) return x * 256 + a[i + 1]; }).filter(function(x) { return x != undefined; }).map(function(x) { return ("00000" + x).slice(-5); }).slice(0, 8).join('-'); + } + + return dsk; + } \ No newline at end of file diff --git a/ZAutomationAPIProvider.js b/ZAutomationAPIProvider.js index b49fab077..06559a2cb 100644 --- a/ZAutomationAPIProvider.js +++ b/ZAutomationAPIProvider.js @@ -13,11 +13,11 @@ Copyright: (c) ZWave.Me, 2013 executeFile("router.js"); -function ZAutomationAPIWebRequest (controller) { +function ZAutomationAPIWebRequest(controller) { ZAutomationAPIWebRequest.super_.call(this); this.ROLE = controller.auth.ROLE; - + this.router = new Router("/v1"); this.controller = controller; this.res = { @@ -47,8 +47,6 @@ _.extend(ZAutomationAPIWebRequest.prototype, { this.router.get("/notifications", this.ROLE.USER, this.exposeNotifications); this.router.put("/notifications", this.ROLE.ADMIN, this.redeemNotifications); this.router.del("/notifications", this.ROLE.ADMIN, this.deleteNotifications); - this.router.get("/history", this.ROLE.USER, this.exposeHistory); - this.router.del("/history", this.ROLE.USER, this.exposeHistory); this.router.get("/devices", this.ROLE.USER, this.listDevices); this.router.get("/restart", this.ROLE.ADMIN, this.restartController); this.router.get("/locations", this.ROLE.USER, this.listLocations); @@ -61,7 +59,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { this.router.get("/locations/update", this.ROLE.ADMIN, this.updateLocation); this.router.get("/modules", this.ROLE.ADMIN, this.listModules); this.router.get("/modules/categories", this.ROLE.ADMIN, this.listModulesCategories); - + // module installation / update this.router.post("/modules/install", this.ROLE.ADMIN, this.installModule); this.router.post("/modules/update", this.ROLE.ADMIN, this.updateModule); @@ -84,6 +82,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { this.router.get("/locations/:location_id/namespaces/:namespace_id", this.ROLE.ADMIN, this.getLocationNamespacesFunc); this.router.get("/locations/:location_id/namespaces", this.ROLE.ADMIN, this.getLocationNamespacesFunc); + this.router.del("/locations/image/:location_id", this.ROLE.ADMIN, this.removeLocationImage, [parseInt]); this.router.del("/locations/:location_id", this.ROLE.ADMIN, this.removeLocation, [parseInt]); this.router.put("/locations/:location_id", this.ROLE.ADMIN, this.updateLocation, [parseInt]); this.router.get("/locations/:location_id", this.ROLE.ADMIN, this.getLocationFunc); @@ -92,6 +91,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { this.router.del("/notifications/:notification_id", this.ROLE.USER, this.deleteNotifications, [parseInt]); this.router.put("/notifications/:notification_id", this.ROLE.USER, this.redeemNotifications, [parseInt]); + this.router.post("/profiles/qrcode/:profile_id", this.ROLE.USER, this.getQRCodeString, [parseInt]); this.router.del("/profiles/:profile_id", this.ROLE.ADMIN, this.removeProfile, [parseInt]); this.router.put("/profiles/:profile_id", this.ROLE.USER, this.updateProfile, [parseInt]); this.router.get("/profiles/:profile_id", this.ROLE.USER, this.listProfiles, [parseInt]); @@ -109,12 +109,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { this.router.post("/modules/reset/:module_id", this.ROLE.ADMIN, this.resetModule); this.router.del("/modules/delete/:module_id", this.ROLE.ADMIN, this.deleteModule); - + // reinitialize apps from /modules or /userModules directory this.router.get("/modules/reinitialize/:module_id", this.ROLE.ADMIN, this.reinitializeModule); - + this.router.get("/modules/categories/:category_id", this.ROLE.ADMIN, this.getModuleCategoryFunc); - + this.router.get("/modules/transform/reverse", this.ROLE.ADMIN, this.revertTransformModuleFlag); + this.router.post("/modules/transform", this.ROLE.ADMIN, this.transformModule); + this.router.get("/modules/:module_id", this.ROLE.ADMIN, this.getModuleFunc); this.router.get("/namespaces/:namespace_id", this.ROLE.ADMIN, this.getNamespaceFunc); @@ -123,7 +125,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { this.router.del("/history/:dev_id", this.ROLE.USER, this.getDevHist); this.router.get("/load/modulemedia/:module_name/:file_name", this.ROLE.ANONYMOUS, this.loadModuleMedia); - + this.router.get("/load/image/:img_name", this.ROLE.ANONYMOUS, this.loadImage); this.router.get("/backup", this.ROLE.ADMIN, this.backup); @@ -157,28 +159,29 @@ _.extend(ZAutomationAPIWebRequest.prototype, { this.router.get("system/time/ntp/:action", this.ROLE.ADMIN, this.configNtp); this.router.get("/system/remote-id", this.ROLE.ANONYMOUS, this.getRemoteId); + this.router.get("/system/ip-address", this.ROLE.ANONYMOUS, this.getIPAddress); this.router.get("/system/first-access", this.ROLE.ANONYMOUS, this.getFirstLoginInfo); this.router.get("/system/info", this.ROLE.ANONYMOUS, this.getSystemInfo); this.router.post("/system/wifi/settings", this.ROLE.ADMIN, this.setWifiSettings); this.router.get("/system/wifi/settings", this.ROLE.ADMIN, this.getWifiSettings); - this.router.post("/system/certfxAuth",this.ROLE.ANONYMOUS,this.certfxAuth); - this.router.post("/system/certfxAuthForwarding",this.ROLE.ADMIN,this.certfxSetAuthForwarding); - this.router.get("/system/certfxAuthForwarding",this.ROLE.ADMIN,this.certfxGetAuthForwarding); - this.router.post("/system/certfxUnregister",this.ROLE.ADMIN,this.certfxUnregister); - this.router.post("/system/certfxUpdateIdentifier",this.ROLE.ADMIN,this.certfxUpdateIdentifier); + this.router.post("/system/certfxAuth", this.ROLE.ANONYMOUS, this.certfxAuth); + //this.router.post("/system/certfxAuthForwarding", this.ROLE.ADMIN, this.certfxSetAuthForwarding); + //this.router.get("/system/certfxAuthForwarding", this.ROLE.ADMIN, this.certfxGetAuthForwarding); + this.router.post("/system/certfxUnregister", this.ROLE.ADMIN, this.certfxUnregister); + this.router.post("/system/certfxUpdateIdentifier", this.ROLE.ADMIN, this.certfxUpdateIdentifier); - this.router.get("/system/zwave/deviceInfoGet",this.ROLE.ADMIN, this.zwaveDeviceInfoGet); - this.router.get("/system/zwave/deviceInfoUpdate",this.ROLE.ADMIN, this.zwaveDeviceInfoUpdate); - this.router.get("/system/zwave/vendorsInfoGet",this.ROLE.ADMIN, this.zwaveVendorsInfoGet); - this.router.get("/system/zwave/vendorsInfoUpdate",this.ROLE.ADMIN, this.zwaveVendorsInfoUpdate); + this.router.get("/system/zwave/deviceInfoGet", this.ROLE.ADMIN, this.zwaveDeviceInfoGet); + this.router.get("/system/zwave/deviceInfoUpdate", this.ROLE.ADMIN, this.zwaveDeviceInfoUpdate); + this.router.get("/system/zwave/vendorsInfoGet", this.ROLE.ADMIN, this.zwaveVendorsInfoGet); + this.router.get("/system/zwave/vendorsInfoUpdate", this.ROLE.ADMIN, this.zwaveVendorsInfoUpdate); this.router.put("/devices/reorder", this.ROLE.ADMIN, this.reorderDevices); }, // Used by the android app to request server status - statusReport: function () { + statusReport: function() { var currentDateTime = new Date(), reply = { error: null, @@ -191,7 +194,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.data = null; reply.code = 503; reply.message = "Service Unavailable"; - } + } return reply; }, @@ -222,10 +225,10 @@ _.extend(ZAutomationAPIWebRequest.prototype, { data: resProfile, code: 200, headers: { - "Set-Cookie": "ZWAYSession=" + sid + "; Path=/; HttpOnly"// set cookie - it will duplicate header just in case client prefers cookies + "Set-Cookie": "ZWAYSession=" + sid + "; Path=/; HttpOnly" // set cookie - it will duplicate header just in case client prefers cookies } }; - }, + }, // Method to return a 401 to the user denyLogin: function(error) { return { @@ -240,18 +243,27 @@ _.extend(ZAutomationAPIWebRequest.prototype, { // Returns user session information for the smarthome UI verifySession: function() { var auth = controller.auth.resolve(this.req, 2); - + if (!auth) { return this.denyLogin("No valid user session found"); } - - var profile = _.find(this.controller.profiles, function (profile) { + + var profile = _.find(this.controller.profiles, function(profile) { return profile.id === auth.user; }); - - return this.setLogin(profile); + + res = _.extend(this.getProfileResponse(profile), {sid: controller.auth.getSessionId(this.req)}); + + return { + error: null, + data: res, + code: 200, + headers: { + "Set-Cookie": "ZWAYSession=" + res.sid + "; Path=/; HttpOnly" // set cookie - it will duplicate header just in case client prefers cookies + } + }; }, - // Check if login exists and password is correct + // Check if login exists and password is correct verifyLogin: function() { var reqObj; @@ -266,7 +278,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }; } - var profile = _.find(this.controller.profiles, function (profile) { + var profile = _.find(this.controller.profiles, function(profile) { return profile.login === reqObj.login; }); @@ -286,11 +298,6 @@ _.extend(ZAutomationAPIWebRequest.prototype, { // - registered cit & login forwarding is active //if ((!checkBoxtype('cit') && pwd_check) || (this.authCIT() && (pwd_check || this.controller.allowLoginForwarding(this.req)))) { // deactivate forwarding if ((!checkBoxtype('cit') && pwd_check) || (this.authCIT() && pwd_check)) { - - // set qr code only box is no CIT - if(!checkBoxtype('cit') && !profile.hasOwnProperty('qrcode') || profile.qrcode === "") { - this.controller.addQRCode(profile, reqObj); - } return this.setLogin(profile); } else { return this.denyLogin(); @@ -299,7 +306,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return this.denyLogin(); } }, - + doLogout: function() { var reply = { error: null, @@ -308,7 +315,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { headers: null }, session; - + var sessionId = this.controller.auth.getSessionId(this.req); if (sessionId) { @@ -327,11 +334,11 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 404; reply.error = 'Could not logout. No session found.'; } - + return reply; }, // Devices - listDevices: function () { + listDevices: function() { var nowTS = Math.floor(new Date().getTime() / 1000), reply = { error: null, @@ -343,20 +350,24 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }, since = this.req.query.hasOwnProperty("since") ? parseInt(this.req.query.since, 10) : 0; - reply.data.structureChanged = this.controller.lastStructureChangeTime >= since ? true : false; - reply.data.devices = this.devicesByUser(this.req.user, function (dev) { return dev.get("updateTime") >= (reply.data.structureChanged ? 0 : since); }); + reply.data.structureChanged = this.controller.lastStructureChangeTime >= since && since? true : false; + reply.data.devices = this.devicesByUser(this.req.user, function(dev) { + return dev.get("updateTime") >= (reply.data.structureChanged ? 0 : since); + }); if (Boolean(this.req.query.pagination)) { reply.data.total_count = devices.length; } return reply; }, - getVDevFunc: function (vDevId) { + getVDevFunc: function(vDevId) { var reply = { error: null, data: null }, - device = _.find(this.devicesByUser(this.req.user), function(device) { return device.id === vDevId; }); + device = _.find(this.devicesByUser(this.req.user), function(device) { + return device.id === vDevId; + }); if (device) { reply.code = 200; @@ -367,7 +378,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } return reply; }, - setVDevFunc: function (vDevId) { + setVDevFunc: function(vDevId) { var reqObj, device = null, reply = { @@ -384,10 +395,12 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; } - if(this.req.query.hasOwnProperty('icon')) { + if (this.req.query.hasOwnProperty('icon')) { device = this.controller.devices.get(vDevId); - if(device) { - device.set('customIcons', reqObj.customicons, {silent:true}); + if (device) { + device.set('customIcons', reqObj.customicons, { + silent: true + }); reply.data = "OK"; result = true; } @@ -399,7 +412,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } } - if(result) { + if (result) { reply.code = 200; } else { reply.code = 404; @@ -407,7 +420,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } return reply; }, - performVDevCommandFunc: function (vDevId, commandId) { + performVDevCommandFunc: function(vDevId, commandId) { var reply = { error: null, data: null, @@ -415,7 +428,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }, result_execution_command, vDev = this.deviceByUser(vDevId, this.req.user); - + if (vDev) { result_execution_command = vDev.performCommand.call(vDev, commandId, this.req.query); reply.data = !!result_execution_command ? result_execution_command : null; @@ -427,7 +440,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, // Notifications - exposeNotifications: function (notificationId) { + exposeNotifications: function(notificationId) { var notifications, reply = { error: null, @@ -438,22 +451,24 @@ _.extend(ZAutomationAPIWebRequest.prototype, { since = this.req.query.hasOwnProperty("since") ? parseInt(this.req.query.since, 10) : 0, to = (this.req.query.hasOwnProperty("to") ? parseInt(this.req.query.to, 10) : 0) || timestamp, profile = this.profileByUser(this.req.user), - devices = this.devicesByUser(this.req.user).map(function(device) { return device.id; }), + devices = this.devicesByUser(this.req.user).map(function(device) { + return device.id; + }), test = function(n) { return ((profile.hide_system_events === false && n.level !== 'device-info') || // hide_system_events = false - (profile.hide_all_device_events === false && n.level === 'device-info')) && // hide_device_events = false + (profile.hide_all_device_events === false && n.level === 'device-info')) && // hide_device_events = false (!profile.hide_single_device_events || profile.hide_single_device_events.indexOf(n.source) === -1) && // remove events from devices to hide - ((n.level !== 'device-info' && devices.indexOf(n.source) === -1) || (n.level === 'device-info' && devices.indexOf(n.source) > -1));// filter device by user + ((n.level !== 'device-info' && devices.indexOf(n.source) === -1) || (n.level === 'device-info' && devices.indexOf(n.source) > -1)); // filter device by user }; if (notificationId) { - notification = this.controller.notifications.get().filter(function (notification) { + notification = this.controller.notifications.get().filter(function(notification) { return notification.id === notificationId && // filter by id - test(notification); // check against 2nd filter + test(notification); // check against 2nd filter }); - if (notification.length > 0 ) { + if (notification.length > 0) { reply.data = notification[0]; } else { reply.code = 404; @@ -461,19 +476,19 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } } else { - notifications = this.controller.notifications.get().filter(function (notification) { - return notification.id >= since && notification.id <= to && // filter by time - test(notification); // check against 2nd filter + notifications = this.controller.notifications.get().filter(function(notification) { + return notification.id >= since && notification.id <= to && // filter by time + test(notification); // check against 2nd filter }); reply.data = { - updateTime: Math.floor(timestamp/1000), + updateTime: Math.floor(timestamp / 1000), notifications: notifications }; } if (Boolean(this.req.query.pagination)) { - reply.data.total_count= notifications.length; + reply.data.total_count = notifications.length; // !!! fix pagination notifications = notifications.slice(); } @@ -481,7 +496,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, // delete single notifications or all privious by a choosen timestamp - deleteNotifications: function (notificationId) { + deleteNotifications: function(notificationId) { var id = notificationId ? parseInt(notificationId) : 0, reply = { @@ -495,7 +510,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { redeemed = this.req.query.hasOwnProperty("allRedeemed") ? Boolean(this.req.query.allRedeemed) : false; if (!redeemed) { - this.controller.deleteNotifications(id, before, function (notice) { + this.controller.deleteNotifications(id, before, function(notice) { if (notice) { reply.code = 204; reply.data = null; @@ -507,7 +522,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } }); } else { - this.controller.deleteAllRedeemedNotifications(function (notice) { + this.controller.deleteAllRedeemedNotifications(function(notice) { if (notice) { reply.code = 204; reply.data = null; @@ -519,7 +534,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, // redeem single or all notifications (true/false) - redeemNotifications: function (notificationId) { + redeemNotifications: function(notificationId) { var id = notificationId ? parseInt(notificationId) : 0, reply = { @@ -528,11 +543,11 @@ _.extend(ZAutomationAPIWebRequest.prototype, { error: "Something went wrong." }; - redeemed = this.req.body.hasOwnProperty("set_redeemed")? retBoolean(this.req.body.set_redeemed) : false; - all = this.req.body.hasOwnProperty("all")? retBoolean(this.req.body.all) : false; + redeemed = this.req.body.hasOwnProperty("set_redeemed") ? retBoolean(this.req.body.set_redeemed) : false; + all = this.req.body.hasOwnProperty("all") ? retBoolean(this.req.body.all) : false; if (!all) { - this.controller.redeemNotification(id, redeemed, function (notice) { + this.controller.redeemNotification(id, redeemed, function(notice) { if (notice) { reply.code = 204; reply.data = null; @@ -544,7 +559,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } }); } else { - this.controller.redeemAllNotifications(redeemed, function (notice) { + this.controller.redeemAllNotifications(redeemed, function(notice) { if (notice) { reply.code = 204; reply.data = null; @@ -556,7 +571,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, //locations - listLocations: function () { + listLocations: function() { var reply = { data: null, error: null @@ -576,14 +591,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, // get location - getLocationFunc: function (locationId) { + getLocationFunc: function(locationId) { var reply = { data: null, error: null }, locations = this.locationsByUser(this.req.user), _location = [], - locationId = !isNaN(locationId)? parseInt(locationId, 10) : locationId; + locationId = !isNaN(locationId) ? parseInt(locationId, 10) : locationId; _location = this.controller.getLocation(locations, locationId); @@ -599,14 +614,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, //filter location namespaces - getLocationNamespacesFunc: function (locationId, namespaceId) { + getLocationNamespacesFunc: function(locationId, namespaceId) { var reply = { data: null, error: null }, locations = this.locationsByUser(this.req.user), _location = [], - locationId = !isNaN(locationId)? parseInt(locationId, 10) : locationId; + locationId = !isNaN(locationId) ? parseInt(locationId, 10) : locationId; _location = this.controller.getLocation(locations, locationId); @@ -629,12 +644,12 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } } else { reply.code = 404; - reply.error = "Location " + locationId === 0? 'globalRoom' : locationId + " not found"; + reply.error = "Location " + locationId === 0 ? 'globalRoom' : locationId + " not found"; } return reply; }, - addLocation: function () { + addLocation: function() { var title, reply = { error: null, @@ -644,9 +659,9 @@ _.extend(ZAutomationAPIWebRequest.prototype, { locProps = {}; if (this.req.method === 'GET') { - + reqObj = this.req.query; - + } else if (this.req.method === 'POST') { // POST try { reqObj = JSON.parse(this.req.body); @@ -657,13 +672,13 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } for (var property in reqObj) { - if ( property !== 'id') { + if (property !== 'id') { locProps[property] = reqObj[property] ? reqObj[property] : null; } } if (!!locProps.title) { - this.controller.addLocation(locProps, function (data) { + this.controller.addLocation(locProps, function(data) { if (data) { reply.code = 201; reply.data = data; @@ -679,7 +694,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - removeLocation: function (locationId) { + removeLocation: function(locationId) { var id, reqObj, reply = { @@ -703,7 +718,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (!!id) { if (id !== 0) { - this.controller.removeLocation(id, function (result) { + this.controller.removeLocation(id, function(result) { if (result) { reply.code = 204; reply.data = null; @@ -723,7 +738,74 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - updateLocation: function (locationId) { + removeLocationImage: function(locationId) { + var id, + user_img, + reqObj, + reply = { + error: null, + data: null, + code: 200 + }; + + if (this.req.method === 'DELETE' && locationId === undefined) { + try { + reqObj = JSON.parse(this.req.body); + } catch (ex) { + reply.error = ex.message; + } + id = reqObj.id; + user_img = reqObj.user_img; + + } else if (this.req.method === 'DELETE' && locationId !== undefined) { + id = locationId; + user_img = this.req.query.user_img; + } + + if (!!id || user_img !== "") { + if (id !== 0) { + var location = this.controller.getLocation(this.controller.locations, id); + if (location) { + // check custom image exists + if (!loadObject(user_img)) { + reply.code = 404; + reply.error = "Location image " + user_img + " doesn't exist or already deleted."; + } else { + // delete custom room image + saveObject(user_img, null); + if (location.user_img == user_img && location.img_type == 'user') { + location.user_img = ''; + location.img_type = ''; + location.show_background = false; + } else if (location.user_img == user_img) { + location.user_img = ''; + } + // update affected location + this.controller.updateLocation(location.id, location.title, location.user_img, location.default_img, location.img_type, location.show_background, location.main_sensors, function(data) { + if (data) { + reply.data = data; + } else { + reply.code = 404; + reply.error = "Location " + id + " doesn't exist."; + } + }); + } + } else { + reply.code = 404; + reply.error = "Location " + id + " doesn't exist."; + } + } else { + reply.code = 403; + reply.error = "Permission denied."; + } + } else { + reply.code = 400; + reply.error = "Argument id, user_img is required."; + } + + return reply; + }, + updateLocation: function(locationId) { var id, title, user_img, @@ -737,8 +819,8 @@ _.extend(ZAutomationAPIWebRequest.prototype, { code: 200 }, reqObj; - - if(locationId !== 0){ + + if (locationId !== 0) { if (this.req.method === 'GET') { id = parseInt(this.req.query.id); title = this.req.query.title; @@ -750,7 +832,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } id = locationId || reqObj.id; title = reqObj.title; - user_img =reqObj.user_img || ''; + user_img = reqObj.user_img || ''; default_img = reqObj.default_img || ''; img_type = reqObj.img_type || ''; show_background = reqObj.show_background || false; @@ -758,7 +840,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } if (!!title && title.length > 0) { - this.controller.updateLocation(id, title, user_img, default_img, img_type,show_background, main_sensors, function (data) { + this.controller.updateLocation(id, title, user_img, default_img, img_type, show_background, main_sensors, function(data) { if (data) { reply.data = data; } else { @@ -770,16 +852,16 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 400; reply.error = "Arguments id & title are required"; } - }else { + } else { reply.code = 403; reply.error = "Permission denied."; } - + return reply; }, // modules - listModules: function () { + listModules: function() { var reply = { error: null, data: [], @@ -787,17 +869,19 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }, module = null; - Object.keys(this.controller.modules).sort().forEach(function (className) { + Object.keys(this.controller.modules).sort().forEach(function(className) { module = this.controller.getModuleData(className); module.className = className; - if(module.location === ('userModules/' + className) && fs.list('modules/'+ className)) { + if (module.location === ('userModules/' + className) && fs.list('modules/' + className)) { module.hasReset = true; } else { module.hasReset = false; } - if (module.singleton && _.any(this.controller.instances, function (instance) { return instance.moduleId === module.id; })) { + if (module.singleton && _.any(this.controller.instances, function(instance) { + return instance.moduleId === module.id; + })) { module.created = true; } else { module.created = false; @@ -807,12 +891,12 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - getModuleFunc: function (moduleId) { + getModuleFunc: function(moduleId) { var reply = { error: null, data: null, code: null - }, + }, moduleData; if (!this.controller.modules.hasOwnProperty(moduleId)) { @@ -822,7 +906,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { // get module data moduleData = this.controller.getModuleData(moduleId); - if(moduleData.location === ('userModules/' + moduleId) && fs.list('modules/'+ moduleId)) { + if (moduleData.location === ('userModules/' + moduleId) && fs.list('modules/' + moduleId)) { moduleData.hasReset = true; } else { moduleData.hasReset = false; @@ -832,27 +916,27 @@ _.extend(ZAutomationAPIWebRequest.prototype, { // replace namspace filters reply.data = this.controller.replaceNamespaceFilters(moduleData); } - + return reply; }, // modules categories - listModulesCategories: function () { + listModulesCategories: function() { var reply = { - error: null, - data: null, - code: 200 - }; + error: null, + data: null, + code: 200 + }; reply.data = this.controller.getListModulesCategories(); return reply; }, - getModuleCategoryFunc: function (categoryId) { + getModuleCategoryFunc: function(categoryId) { var reply = { - error: null, - data: null, - code: 500 - }; + error: null, + data: null, + code: 500 + }; category = this.controller.getListModulesCategories(categoryId); @@ -866,8 +950,74 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, + transformModule: function() { + var reply = { + error: 'Something went wrong.', + data: null, + code: 500 + }, + reqObj = parseToObject(this.req.body), + sources = ['IfThen', 'LogicalRules', 'ScheduledScene', 'LightScene'], + targets = ['Rules', 'Schedules', 'Scenes'], + source = reqObj.source && ['IfThen', 'LogicalRules', 'ScheduledScene', 'LightScene'].indexOf(reqObj.source) > -1 ? reqObj.source : null, + target = reqObj.target && ['Rules', 'Schedules', 'Scenes'].indexOf(reqObj.target) > -1 ? reqObj.target : null, + pairing = false, + resultList = []; + + try { + pairing = (target === 'Rules' && (source === 'IfThen' || source === 'LogicalRules')) || + (target === 'Schedules' && source === 'ScheduledScene') || + (target === 'Scenes' && source === 'LightScene'); + + if (pairing) { + resultList = this.controller.transformIntoNewInstance(source); + + reply.code = 200; + reply.data = resultList; + reply.error = null; + } else { + reply.code = 400; + reply.error = 'Bad Request. Following transformations are allowed: IfThen/LogicalRules > Rules, ScheduledScene > Schedules, LightScene > Scenes'; + } + + } catch (e) { + reply.error += ' Error: ' + e.toString(); + } + + return reply; + }, + revertTransformModuleFlag: function() { + var self = this, + reply = { + error: 'Something went wrong.', + data: null, + code: 500 + }, + transformationsDone = false; + + try { + + _.forEach(this.controller.instances, function(instance) { + if (instance.params.moduleAPITransformed) { + // remove transformed flag + delete instance.params.moduleAPITransformed; + self.controller.reconfigureInstance(instance.id, instance); + transformationsDone = true; + } + }); + + reply.code = 200; + reply.data = transformationsDone ? 'successfull' : 'No transformations found.'; + reply.error = null; + + } catch (e) { + reply.error += ' Error: ' + e.toString(); + } + + return reply; + }, // install module - installModule: function () { + installModule: function() { var reply = { error: { key: null, @@ -884,15 +1034,15 @@ _.extend(ZAutomationAPIWebRequest.prototype, { moduleId = moduleUrl.split(/[\/]+/).pop().split(/[.]+/).shift(); if (!this.controller.modules[moduleId]) { - + // download and install the module result = this.controller.installModule(moduleUrl, moduleId); if (result === "done") { - + loadSuccessfully = this.controller.loadInstalledModule(moduleId, 'userModules/', false); - if(loadSuccessfully){ + if (loadSuccessfully) { reply.code = 201; reply.data.key = "app_installation_successful"; // send language key as response } else { @@ -910,7 +1060,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } return reply; }, - updateModule: function () { + updateModule: function() { var reply = { error: { key: null, @@ -935,7 +1085,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { loadSuccessfully = this.controller.reinitializeModule(moduleId, 'userModules/'); - if(loadSuccessfully){ + if (loadSuccessfully) { reply.code = 200; reply.data.key = "app_update_successful"; // send language key as response } else { @@ -953,7 +1103,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } return reply; }, - deleteModule: function (moduleId) { + deleteModule: function(moduleId) { var reply = { error: { key: null @@ -963,7 +1113,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { appendix: null }, code: 500 - }, + }, uninstall = false; if (this.controller.modules[moduleId]) { @@ -976,37 +1126,37 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } else { reply.code = 500; reply.error.key = 'app_failed_to_delete'; - } + } } else { reply.code = 404; reply.error.key = 'app_not_exist'; } return reply; }, - resetModule: function (moduleId) { + resetModule: function(moduleId) { var reply = { error: {}, data: {}, code: 500 - }, + }, unload; - + var result = "in progress"; if (this.controller.modules[moduleId]) { - if (this.controller.modules[moduleId].location === ('userModules/' + moduleId) && fs.list('modules/' + moduleId)){ + if (this.controller.modules[moduleId].location === ('userModules/' + moduleId) && fs.list('modules/' + moduleId)) { uninstall = this.controller.uninstallModule(moduleId, true); if (uninstall) { reply.code = 200; reply.data.key = 'app_reset_successful_to_version'; - reply.data.appendix = this.controller.modules[moduleId].meta.version; + reply.data.appendix = this.controller.modules[moduleId].meta.version; } else { reply.code = 500; reply.error = 'There was an error during resetting the app ' + moduleId + '. Maybe a server restart could solve this problem.'; - } + } } else { reply.code = 412; reply.error.key = 'app_is_still_reseted'; @@ -1017,23 +1167,23 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } return reply; }, - getModuleTokens: function () { + getModuleTokens: function() { var reply = { - error: null, - data: null, - code: 500 - }, - tokenObj = { - tokens: [] - }, - getTokens = function () { - return loadObject('moduleTokens.json'); - }; + error: null, + data: null, + code: 500 + }, + tokenObj = { + tokens: [] + }, + getTokens = function() { + return loadObject('moduleTokens.json'); + }; if (getTokens() === null) { saveObject('moduleTokens.json', tokenObj); } - + if (!!getTokens()) { reply.data = getTokens(); reply.code = 200; @@ -1043,14 +1193,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - storeModuleToken: function () { + storeModuleToken: function() { var reply = { - error: null, - data: null, - code: 500 - }, - reqObj = parseToObject(this.req.body), - tokenObj = loadObject('moduleTokens.json'); + error: null, + data: null, + code: 500 + }, + reqObj = parseToObject(this.req.body), + tokenObj = loadObject('moduleTokens.json'); if (tokenObj === null) { saveObject('moduleTokens.json', tokenObj); @@ -1079,14 +1229,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - deleteModuleToken: function () { + deleteModuleToken: function() { var reply = { - error: null, - data: null, - code: 500 - }, - reqObj = parseToObject(this.req.body), - tokenObj = loadObject('moduleTokens.json'); + error: null, + data: null, + code: 500 + }, + reqObj = parseToObject(this.req.body), + tokenObj = loadObject('moduleTokens.json'); if (reqObj && reqObj.token && !!tokenObj && tokenObj.tokens) { if (tokenObj.tokens.indexOf(reqObj.token) > -1) { @@ -1120,11 +1270,11 @@ _.extend(ZAutomationAPIWebRequest.prototype, { location = [], loadSuccessfully = 0; - if(fs.list('modules/' + moduleId)) { + if (fs.list('modules/' + moduleId)) { location.push('modules/'); } - if(fs.list('userModules/' + moduleId)) { + if (fs.list('userModules/' + moduleId)) { location.push('userModules/'); } @@ -1134,8 +1284,8 @@ _.extend(ZAutomationAPIWebRequest.prototype, { _.forEach(location, function(loc) { loadSuccessfully += this.controller.reinitializeModule(moduleId, loc); }); - - if(loadSuccessfully > 0){ + + if (loadSuccessfully > 0) { reply.data = 'Reinitialization of app "' + moduleId + '" successfull.'; reply.code = 200; } @@ -1146,28 +1296,28 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 404; reply.error = "App not found."; } - + return reply; }, // instances - listInstances: function () { + listInstances: function() { var reply = { - error: null, - data: null, - code: 200 - }, + error: null, + data: null, + code: 200 + }, instances = this.controller.listInstances(); - if(instances){ + if (instances) { reply.data = instances; } else { reply.code = 500; reply.error = 'Could not list Instances.'; } - + return reply; }, - createInstance: function () { + createInstance: function() { var reply = { error: null, data: null, @@ -1192,17 +1342,21 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - getInstanceFunc: function (instanceId) { + getInstanceFunc: function(instanceId) { var reply = { - error: null, - data: null, - code: 500 - }; - - if(isNaN(instanceId)){ - instance = _.filter(this.controller.instances, function (i) { return instanceId === i.moduleId; }); + error: null, + data: null, + code: 500 + }; + + if (isNaN(instanceId)) { + instance = _.filter(this.controller.instances, function(i) { + return instanceId === i.moduleId; + }); } else { - instance = _.find(this.controller.instances, function (i) { return parseInt(instanceId) === i.id; }); + instance = _.find(this.controller.instances, function(i) { + return parseInt(instanceId) === i.id; + }); } if (!Boolean(instance) || instance.length === 0) { @@ -1215,7 +1369,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - reconfigureInstanceFunc: function (instanceId) { + reconfigureInstanceFunc: function(instanceId) { var reply = { error: null, data: null @@ -1223,7 +1377,9 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reqObj = this.req.reqObj, instance; - if (!_.any(this.controller.instances, function (instance) { return instanceId === instance.id; })) { + if (!_.any(this.controller.instances, function(instance) { + return instanceId === instance.id; + })) { reply.code = 404; reply.error = "Instance " + instanceId + " doesn't exist"; } else { @@ -1239,14 +1395,16 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - deleteInstanceFunc: function (instanceId) { + deleteInstanceFunc: function(instanceId) { var reply = { error: null, data: null, code: 200 }; - if (!_.any(this.controller.instances, function (instance) { return instance.id === instanceId; })) { + if (!_.any(this.controller.instances, function(instance) { + return instance.id === instanceId; + })) { reply.code = 404; reply.error = "Instance " + instanceId + " not found"; } else { @@ -1254,11 +1412,11 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.data = null; this.controller.deleteInstance(instanceId); } - + return reply; }, // profiles - listProfiles: function (profileId) { + listProfiles: function(profileId) { var reply = { error: null, data: null, @@ -1291,14 +1449,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (!!getProfile && (this.req.role === this.ROLE.ADMIN || (this.req.role === this.ROLE.USER && this.req.user === getProfile.id))) { // do not send password (also role if user is no admin) - if(this.req.role === this.ROLE.ADMIN){ + if (this.req.role === this.ROLE.ADMIN) { excl = ["password"]; } else { excl = ["password", "role"]; - } - + } + for (var property in getProfile) { - if(excl.indexOf(property) === -1){ + if (excl.indexOf(property) === -1) { filteredProfile[property] = getProfile[property]; } } @@ -1313,7 +1471,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - createProfile: function () { + createProfile: function() { var reply = { error: null, data: null, @@ -1331,11 +1489,11 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.error = ex.message; } - uniqueEmail = _.filter(this.controller.profiles, function (p) { + uniqueEmail = _.filter(this.controller.profiles, function(p) { return p.email !== '' && p.email === reqObj.email; }); - uniqueLogin = _.filter(this.controller.profiles, function (p) { + uniqueLogin = _.filter(this.controller.profiles, function(p) { return p.login !== '' && p.login === reqObj.login; }); @@ -1349,12 +1507,12 @@ _.extend(ZAutomationAPIWebRequest.prototype, { _.defaults(reqObj, { role: null, name: 'User', - email:'', + email: '', lang: 'en', color: '#dddddd', dashboard: [], interval: 2000, - rooms: reqObj.role === 1? [0] : [], + rooms: reqObj.role === 1 ? [0] : [], expert_view: false, hide_all_device_events: false, hide_system_events: false, @@ -1363,7 +1521,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }); reqObj = _.omit(reqObj, 'passwordConfirm'); - + profile = this.controller.createProfile(reqObj); if (profile !== undefined && profile.id !== undefined) { reply.data = resProfile; @@ -1373,10 +1531,10 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.error = "Profile creation error"; } } - + return reply; }, - updateProfile: function (profileId) { + updateProfile: function(profileId) { var reply = { error: null, data: null, @@ -1385,7 +1543,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reqObj, profile = this.controller.getProfile(profileId), uniqueProfProps = []; - + if (profile && (this.req.role === this.ROLE.ADMIN || (this.req.role === this.ROLE.USER && this.req.user === profile.id))) { reqObj = JSON.parse(this.req.body); @@ -1393,9 +1551,9 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 403; reply.error = "Revoking self Admin priviledge is not allowed."; } else { - uniqueProfProps = _.filter(this.controller.profiles, function (p) { - return (p.email !== '' && p.email === reqObj.email) && - p.id !== profileId; + uniqueProfProps = _.filter(this.controller.profiles, function(p) { + return (p.email !== '' && p.email === reqObj.email) && + p.id !== profileId; }); if (uniqueProfProps.length === 0) { @@ -1403,14 +1561,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (this.req.role === this.ROLE.ADMIN) { // id is never changeable // login is changed by updateProfileAuth() - profile.role = reqObj.role; - profile.rooms = reqObj.rooms.indexOf(0) === -1 && reqObj.role === 1? reqObj.rooms.push(0) : reqObj.rooms; + profile.role = reqObj.role; + profile.rooms = reqObj.rooms.indexOf(0) === -1 && reqObj.role === 1 ? reqObj.rooms.push(0) : reqObj.rooms; profile.expert_view = reqObj.expert_view; try { // update email adress on initial update if (profile.login === 'admin' && this.controller.config.initial && reqObj.email !== '') { - var emailMe = _.findIndex(this.controller.instances, function (instance){ + var emailMe = _.findIndex(this.controller.instances, function(instance) { return instance.moduleId === 'MailNotifier' && instance.params.mail_to_input === '' && instance.params.mail_to_select === '' @@ -1422,7 +1580,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } } } catch (e) { - this.controller.addNotification('error', 'Failed to set email address: ' + e.toString(),'core','ZAutomationAPI'); + this.controller.addNotification('error', 'Failed to set email address: ' + e.toString(), 'core', 'ZAutomationAPI'); } } // could be changed by user role @@ -1435,9 +1593,10 @@ _.extend(ZAutomationAPIWebRequest.prototype, { profile.dashboard = reqObj.dashboard; profile.hide_single_device_events = reqObj.hide_single_device_events; profile.email = reqObj.email; - + profile.night_mode = reqObj.night_mode; + profile = this.controller.updateProfile(profile, profile.id); - + if (profile !== undefined && profile.id !== undefined) { reply.data = this.getProfileResponse(profile); reply.code = 200; @@ -1458,7 +1617,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, // different pipe for updating authentication values - updateProfileAuth: function (profileId) { + updateProfileAuth: function(profileId) { var self = this, reply = { error: null, @@ -1468,9 +1627,9 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reqObj, profile = this.controller.getProfile(profileId), uniqueLogin = [], - reqToken = this.req.reqObj.hasOwnProperty("token")? this.req.reqObj.token : null, + reqToken = this.req.reqObj.hasOwnProperty("token") ? this.req.reqObj.token : null, tokenObj = {}; - + if (typeof this.req.body !== 'object') { reqObj = JSON.parse(this.req.body); } else { @@ -1482,7 +1641,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (profile && (this.req.role === this.ROLE.ADMIN || (this.req.role === this.ROLE.USER && this.req.user === profile.id))) { - uniqueLogin = _.filter(this.controller.profiles, function (p) { + uniqueLogin = _.filter(this.controller.profiles, function(p) { if (self.req.role === self.ROLE.ADMIN && self.req.user !== parseInt(reqObj.id, 10)) { return p.login !== '' && p.login === reqObj.login && p.id !== parseInt(reqObj.id, 10); } else { @@ -1492,7 +1651,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (uniqueLogin.length < 1) { profile = this.controller.updateProfileAuth(reqObj, profileId); - + if (!!profile && profile.id !== undefined) { reply.data = this.getProfileResponse(profile); reply.code = 200; @@ -1506,14 +1665,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } } else if (this.req.role === this.ROLE.ANONYMOUS && profileId && !!reqToken) { tokenObj = self.controller.auth.getForgottenPwdToken(reqToken); - + if (tokenObj && !!tokenObj) { profile = this.controller.updateProfileAuth(reqObj, profileId); if (!!profile && profile.id !== undefined) { // remove forgotten token self.controller.auth.removeForgottenPwdEntry(reqToken); - + reply.code = 200; } else { reply.code = 500; @@ -1530,56 +1689,60 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - restorePassword: function (profileId) { + restorePassword: function(profileId) { var self = this, reply = { error: null, data: null, code: 500 }, - reqObj = typeof this.req.body !== 'object'? JSON.parse(this.req.body): this.req.body, - reqToken = this.req.query.hasOwnProperty("token")? this.req.query.token : null, + reqObj = typeof this.req.body !== 'object' ? JSON.parse(this.req.body) : this.req.body, + reqToken = this.req.query.hasOwnProperty("token") ? this.req.query.token : null, profile, emailExists = [], - tokenObj; + tokenObj; if (reqObj.email) { - emailExists = _.filter(self.controller.profiles, function (profile) { + emailExists = _.filter(self.controller.profiles, function(profile) { return profile.email !== '' && profile.email === reqObj.email; }); } if (reqToken === null && emailExists.length > 0 && !profileId) { - + try { var tkn = crypto.guid(), success = self.controller.auth.forgottenPwd(reqObj.email, tkn); - + if (success) { - reply.data = { token: tkn }; + reply.data = { + token: tkn + }; reply.code = 200; } else { reply.error = "Token request for e-mail already exists."; reply.code = 409; - } - + } + } catch (e) { reply.code = 500; reply.error = "Internal server error."; } - } else if (!!reqToken && emailExists.length < 1 && !profileId){ + } else if (!!reqToken && emailExists.length < 1 && !profileId) { try { tokenObj = self.controller.auth.getForgottenPwdToken(reqToken); - + if (tokenObj && !!tokenObj) { - profile = _.filter(self.controller.profiles,function(p) { - return p.email === tokenObj.email; + profile = _.filter(self.controller.profiles, function(p) { + return p.email === tokenObj.email; }); - if(profile[0]) { + if (profile[0]) { reply.code = 200; - reply.data = { userId: profile[0].id }; + reply.data = { + userId: profile[0].id + }; } else { reply.code = 404; reply.error = "User not found."; @@ -1587,7 +1750,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } else { reply.code = 404; reply.error = "Token not found."; - } + } } catch (e) { reply.code = 500; reply.error = "Internal server error."; @@ -1595,7 +1758,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } else if (!!reqToken && emailExists.length < 1 && profileId) { profile = self.controller.updateProfileAuth(reqObj, profileId); - + if (!!profile && profile.id !== undefined) { reply.code = 200; } else { @@ -1609,14 +1772,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - removeProfile: function (profileId) { + removeProfile: function(profileId) { var reply = { - error: null, - data: null, - code: 500 + error: null, + data: null, + code: 500 }, - profile = this.controller.getProfile(profileId); - + profile = this.controller.getProfile(profileId); + if (profile) { // It is not possible to delete own profile if (profile.id !== this.req.user) { @@ -1634,157 +1797,89 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - // namespaces - listNamespaces: function () { + getQRCodeString: function(profileId) { var reply = { - error: null, - data: null, - code: 500 - }, - nspc; - - nspc = this.controller.namespaces; - - if (_.isArray(nspc) && nspc.length > 0) { - reply.data = nspc; - reply.code = 200; - } else { - reply.code = 404; - reply.error = "Namespaces array is null"; - } - - return reply; - }, - getNamespaceFunc: function (namespaceId) { - var reply = { - error: null, - data: null, - code: 500 - }, - namespace; - - namespace = this.controller.getListNamespaces(namespaceId, this.controller.namespaces); - if (!namespace || (_.isArray(namespace) && namespace.length < 1)) { - reply.code = 404; - reply.error = "No namespaces found with this path: " + namespaceId; - } else { - reply.data = namespace; - reply.code = 200; - } - - return reply; - }, - // History - exposeHistory: function () { - var history, - reply = { - code: 500, error: null, - data: null - }; - - history = this.controller.listHistories(); + data: null, + code: 500 + }, + profile = this.controller.getProfile(profileId); - if(history){ - if (this.req.method === "GET") { - reply.data = { - updateTime: Math.floor(new Date().getTime() / 1000), - history: history - }; - reply.code = 200; + try { + var reqObj = parseToObject(this.req.body); + } catch (e) { + return reply.error = e.message; + } - } else if (this.req.method === "DELETE") { - success = this.controller.deleteDevHistory(); + if (profile) { + if (this.req.role === this.ROLE.ADMIN || (this.req.role === this.ROLE.USER && this.req.user === profileId)) { + var pwd_check = reqObj.password ? (!profile.salt && profile.password === reqObj.password) || (profile.salt && profile.password === hashPassword(reqObj.password, profile.salt)) : false; + if (pwd_check) { + var qrcode_str = this.controller.getQRCodeData(profile, reqObj.password); - if (success) { - reply.code = 204; + if (qrcode_str !== undefined) { + reply.code = 200; + reply.data = qrcode_str; + } else { + reply.code = 500; + } } else { - reply.error = "Something went wrong." + reply.error = "wrong_password"; + reply.code = 500; } } else { - reply.code = 400; - reply.error = "Bad request."; + reply.error = "Forbidden"; + reply.code = 403; } } else { reply.code = 404; - reply.error = "No device histories found."; + reply.error = "Profile not found"; } - + return reply; }, - // get or delete histories of devices - getDevHist: function (vDevId) { - var history, - dev, - reply = { - code: 500, + // namespaces + listNamespaces: function() { + var reply = { error: null, - data: null + data: null, + code: 500 }, - since, - show, - sinceDevHist, - view = [288,96,48,24,12,6]; - - if (this.deviceByUser(vDevId, this.req.user) !== null) { - - history = this.controller.listHistories(); - - if (history) { - - hash = this.controller.hashCode(vDevId); - dev = history.filter(function(x) { - return x.h === hash || x.id === vDevId; - }); + nspc; - if (dev.length > 0) { - if (this.req.method === "GET") { - show = this.req.query.hasOwnProperty("show")? (view.indexOf(parseInt(this.req.query.show, 10)) > -1 ? parseInt(this.req.query.show, 10) : 0) : 0; - since = this.req.query.hasOwnProperty("since") ? parseInt(this.req.query.since, 10) : 0; - - sinceDevHist = this.controller.getDevHistory(dev, since, show); - - if (dev && sinceDevHist) { - reply.code = 200; - reply.data = { - id: vDevId, - since: since, - deviceHistory: sinceDevHist - }; - } else { - reply.code = 200; - reply.data = dev; - } - - } else if (this.req.method === "DELETE") { - success = this.controller.deleteDevHistory(vDevId); + nspc = this.controller.namespaces; - if (success) { - reply.code = 204; - } else { - reply.error = "Something went wrong." - } - } else { - reply.code = 400; - reply.error = "Bad request."; - } - } else { - reply.code = 404; - reply.error = "History of device " + vDevId + " doesn't exist"; - } - } else { - reply.code = 404; - reply.error = "No device histories found. Please check if app '24 Hours Device History' is active."; - } + if (_.isArray(nspc) && nspc.length > 0) { + reply.data = nspc; + reply.code = 200; } else { reply.code = 404; - reply.error = "Device not found."; + reply.error = "Namespaces array is null"; + } + + return reply; + }, + getNamespaceFunc: function(namespaceId) { + var reply = { + error: null, + data: null, + code: 500 + }, + namespace; + + namespace = this.controller.getListNamespaces(namespaceId, this.controller.namespaces); + if (!namespace || (_.isArray(namespace) && namespace.length < 1)) { + reply.code = 404; + reply.error = "No namespaces found with this path: " + namespaceId; + } else { + reply.data = namespace; + reply.code = 200; } - + return reply; }, // restart - restartController: function (profileId) { + restartController: function(profileId) { var reply = { error: null, data: null, @@ -1792,7 +1887,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }; this.controller.restart(); - return reply; + return reply; }, loadModuleMedia: function(moduleName, fileName) { var reply = { @@ -1803,17 +1898,17 @@ _.extend(ZAutomationAPIWebRequest.prototype, { obj; if ((moduleName !== '' || !!moduleName || moduleName) && (fileName !== '' || !!fileName || fileName)) { - obj = this.controller.loadModuleMedia(moduleName,fileName); - + obj = this.controller.loadModuleMedia(moduleName, fileName); + if (!this.controller.modules[moduleName]) { reply.code = 404; - reply.error = "Can't load file from app because app '" + moduleName + "' was not found." ; - + reply.error = "Can't load file from app because app '" + moduleName + "' was not found."; + return reply; } else if (obj !== null) { this.res.status = 200; - this.res.headers = { + this.res.headers = { "Content-Type": obj.ct, "Connection": "keep-alive" }; @@ -1822,14 +1917,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return null; // let handleRequest take this.res as is } else { reply.code = 500; - reply.error = "Failed to load file from module." ; - + reply.error = "Failed to load file from module."; + return reply; - } + } } else { reply.code = 400; - reply.error = "Incorrect app or file name" ; - + reply.error = "Incorrect app or file name"; + return reply; } }, @@ -1842,20 +1937,20 @@ _.extend(ZAutomationAPIWebRequest.prototype, { data; data = this.controller.loadImage(imageName); - + if (data !== null) { this.res.status = 200; - this.res.headers = { - "Content-Type": "image/(png|jpeg|gif)", - "Connection": "keep-alive" + this.res.headers = { + "Content-Type": "image/(png|jpeg|gif)", + "Connection": "keep-alive" }; this.res.body = data; return null; // let handleRequest take this.res as is } else { reply.code = 500; - reply.error = "Failed to load file." ; - + reply.error = "Failed to load file."; + return reply; } }, @@ -1868,20 +1963,20 @@ _.extend(ZAutomationAPIWebRequest.prototype, { file; if (this.req.method === "POST" && this.req.body) { - - for (prop in this.req.body){ - if(this.req.body[prop]['content']) { + + for (prop in this.req.body) { + if (this.req.body[prop]['content']) { file = this.req.body[prop]; } } - + if (_.isArray(file)) { file = file[0]; } - + if (file && file.name && file.content || (_.isArray(file) && file.length > 0)) { - if(~file.name.indexOf('.csv') && typeof Papa === 'object'){ + if (~file.name.indexOf('.csv') && typeof Papa === 'object') { var csv = null; Papa.parse(file.content, { header: true, @@ -1891,7 +1986,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } }); - if(!!csv) { + if (!!csv) { saveObject(file.name, csv); } } else { @@ -1904,15 +1999,15 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } else { reply.code = 500; - reply.error = "Failed to upload file" ; + reply.error = "Failed to upload file"; } } else { reply.code = 400; - reply.error = "Invalid request" ; + reply.error = "Invalid request"; } return reply; }, - backup: function () { + backup: function() { var self = this, reply = { error: null, @@ -1928,7 +2023,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { var backupJSON = self.controller.createBackup(); - reply.headers= { + reply.headers = { "Content-Type": "application/octet-stream", // application/x-download octet-stream "Content-Disposition": "attachment; filename=z-way-backup-" + ts + ".zab", "Connection": "keep-alive" @@ -1936,14 +2031,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 200; reply.data = Base64.encode(JSON.stringify(backupJSON)); - } catch(e) { + } catch (e) { reply.code = 500; reply.error = e.toString(); } return reply; }, - restore: function () { + restore: function() { var self = this, reqObj, reply = { @@ -1953,36 +2048,43 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }, result = "", langfile = this.controller.loadMainLang(), - waitForInstallation = function (allreadyInstalled, reqKey) { - var d = (new Date()).valueOf() + 300000; // wait not more than 5 min - - while ((new Date()).valueOf() < d && allreadyInstalled.length <= reqObj.data[reqKey].length) { - - if (allreadyInstalled.length === reqObj.data[reqKey].length) { - break; - } - - processPendingCallbacks(); - } + dontSave = this.controller.getIgnoredStorageFiles([ + "__ZWay", + "__EnOcean", + "__userModules", + "__userSkins" + ]); + + function waitForInstallation(allreadyInstalled, reqKey) { + var d = (new Date()).valueOf() + 300000; // wait not more than 5 min + + while ((new Date()).valueOf() < d && allreadyInstalled.length <= reqObj.data[reqKey].length) { if (allreadyInstalled.length === reqObj.data[reqKey].length) { - // success - reply.code = 200; + break; } - }; + + processPendingCallbacks(); + } + + if (allreadyInstalled.length === reqObj.data[reqKey].length) { + // success + reply.code = 200; + } + } // get flag that network information should be overwritten allowTopoRestore = this.req.body.hasOwnProperty("overwriteNetwork") ? retBoolean(this.req.body.overwriteNetwork) : false; try { function utf8Decode(bytes) { - var chars = []; - - for(var i = 0; i < bytes.length; i++) { + var chars = []; + + for (var i = 0; i < bytes.length; i++) { chars[i] = bytes.charCodeAt(i); } - - return chars; + + return chars; } reqObj = parseToObject(this.req.body.backupFile.content); @@ -2006,30 +2108,8 @@ _.extend(ZAutomationAPIWebRequest.prototype, { // stop the controller this.controller.stop(); + for (var obj in reqObj.data) { - var dontSave = [ - "__ZWay", - "__EnOcean", - "__userModules", - "notifications", - "8084AccessTimeout", - "__userSkins", - "rssidata.json", - "reorgLog", - "incomingPacket.json", - "outgoingPacket.json", - "originPackets.json", - "zway_incomingPacket.json", - "zway_outgoingPacket.json", - "zway_originPackets.json", - "zway_parsedPackets.json", - "zway_reorgLog", - "zway_rssidata.json", - "de.devices.json", - "en.devices.json", - "zwave_vendors.json", - "history" - ]; // objects that should be ignored if (dontSave.indexOf(obj) === -1) { saveObject(obj, reqObj.data[obj]); @@ -2039,7 +2119,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { // start controller with reload flag to apply config.json this.controller.start(true); - + // restore Z-Wave and EnOcean !!reqObj.data["__ZWay"] && Object.keys(reqObj.data["__ZWay"]).forEach(function(zwayName) { var zwayData = utf8Decode(reqObj.data["__ZWay"][zwayName]); @@ -2051,7 +2131,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { // global.EnOcean[zenoName] && global.EnOcean[zenoName].zeno.Restore(reqObj.data["__EnOcean"][zenoName]); }); */ - + // install userModules if (reqObj.data["__userModules"]) { var installedModules = []; @@ -2059,24 +2139,24 @@ _.extend(ZAutomationAPIWebRequest.prototype, { _.forEach(reqObj.data["__userModules"], function(entry) { http.request({ - url:'https://developer.z-wave.me/?uri=api-module-archive/'+ entry.name, - method:'GET', + url: 'https://developer.z-wave.me/?uri=api-module-archive/' + entry.name, + method: 'GET', async: true, - success: function(res){ + success: function(res) { var archiv = [], item = { name: entry.name }, - location = 'modules/'+ entry.name, + location = 'modules/' + entry.name, overwriteCoreModule = false; if (res.data.data && res.data.data.length > 0) { - archiv = _.filter(res.data.data, function (appEntry){ + archiv = _.filter(res.data.data, function(appEntry) { return appEntry.version === entry.version.toString(); }) // check if already loaded module is a core module - coreModule = self.controller.modules[entry.name] && self.controller.modules[entry.name].meta? (self.controller.modules[entry.name].meta.location === location) : false; + coreModule = self.controller.modules[entry.name] && self.controller.modules[entry.name].meta ? (self.controller.modules[entry.name].meta.location === location) : false; // check if version of core module isn't higher than the restored one if (coreModule) { @@ -2086,25 +2166,25 @@ _.extend(ZAutomationAPIWebRequest.prototype, { // if achive was found try to download it if (archiv.length > 0 && (!coreModule || (coreModule && overwriteCoreModule))) { - console.log('Restore userModule', archiv[0].modulename, 'v'+archiv[0].version); - result = self.controller.installModule('https://developer.z-wave.me/archiv/'+ archiv[0].archiv, archiv[0].modulename); + console.log('Restore userModule', archiv[0].modulename, 'v' + archiv[0].version); + result = self.controller.installModule('https://developer.z-wave.me/archiv/' + archiv[0].archiv, archiv[0].modulename); item.status = result; if (result === "done") { loadSuccessfully = self.controller.reinitializeModule(entry.name, 'userModules/', true); - if(!loadSuccessfully){ - self.controller.addNotification("warning", langfile.zaap_war_restart_necessary + ' :: ' + entry.name + ' ' + 'v'+archiv[0].version, "core", "AppInstaller"); + if (!loadSuccessfully) { + self.controller.addNotification("warning", langfile.zaap_war_restart_necessary + ' :: ' + entry.name + ' ' + 'v' + archiv[0].version, "core", "AppInstaller"); } } else { - self.controller.addNotification("warning", langfile.zaap_err_app_install + ' :: ' + entry.name + ' ' + 'v'+archiv[0].version, "core", "AppInstaller"); + self.controller.addNotification("warning", langfile.zaap_err_app_install + ' :: ' + entry.name + ' ' + 'v' + archiv[0].version, "core", "AppInstaller"); } } else { // downlaod latest if it isn't already there if (overwriteCoreModule) { - - console.log(entry.name+':','No archive with this version found. Install latest ...'); - result = self.controller.installModule('https://developer.z-wave.me/modules/'+ entry.name +'.tar.gz', entry.name); + + console.log(entry.name + ':', 'No archive with this version found. Install latest ...'); + result = self.controller.installModule('https://developer.z-wave.me/modules/' + entry.name + '.tar.gz', entry.name); item.status = result; @@ -2118,7 +2198,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { self.controller.addNotification("warning", langfile.zaap_war_core_app_is_newer + ' :: ' + entry.name, "core", "AppInstaller"); item.status = 'failed'; } - } + } } else { self.controller.addNotification("error", langfile.zaap_err_no_archives + ' :: ' + entry.name, "core", "AppInstaller"); item.status = 'failed'; @@ -2126,17 +2206,17 @@ _.extend(ZAutomationAPIWebRequest.prototype, { installedModules.push(item); }, - error: function(res){ + error: function(res) { self.controller.addNotification("error", langfile.zaap_err_server + ' :: ' + entry.name + '::' + res.statusText, "core", "AppInstaller"); installedModules.push({ name: entry.name, status: 'failed' }); - } + } }); }); - waitForInstallation(installedModules,"__userModules"); + waitForInstallation(installedModules, "__userModules"); } @@ -2147,10 +2227,10 @@ _.extend(ZAutomationAPIWebRequest.prototype, { http.request({ // get online list of all existing modules first - url:'http://hrix.net/developer-console/?uri=api-skins', - method:'GET', + url: 'https://developer.z-wave.me/?uri=api-skins', + method: 'GET', async: true, - success: function(res){ + success: function(res) { if (res.data.data) { remoteSkins = res.data.data; @@ -2167,8 +2247,8 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (remSkinObj[0]) { - index = _.findIndex(self.controller.skins, function(skin) { - return skin.name === entry.name; + index = _.findIndex(self.controller.skins, function(skin) { + return skin.name === entry.name; }); try { @@ -2178,22 +2258,22 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } catch (e) { self.controller.addNotification("error", langfile.zaap_err_no_archives + ' :: ' + entry.name, "core", "SkinInstaller"); } - } + } installedSkins.push(item); }); - } + } }, - error: function(res){ + error: function(res) { self.controller.addNotification("error", langfile.zaap_err_server + ' :: ' + res.statusText, "core", "SkinInstaller"); - } + } }); - waitForInstallation(installedSkins,"__userSkins"); + waitForInstallation(installedSkins, "__userSkins"); } - + // success reply.code = 200; reply.data = { @@ -2210,14 +2290,25 @@ _.extend(ZAutomationAPIWebRequest.prototype, { resetToFactoryDefault: function() { var self = this, langFile = this.controller.loadMainLang(); - reply = { + reply = { error: null, data: null, code: 500 - }, - backupCfg = loadObject("backupConfig"), + }; + + if (checkBoxtype('popphub3')) { + reply.code = 200; + + saveObject('performReset','1'); + + setTimeout(function() { + self.doLogout(); + }, 3000); + + } else { + var backupCfg = loadObject("backupConfig"), storageContentList = loadObject("__storageContent"), - defaultConfigExists = fs.stat('defaultConfigs/config.json'), // will be added during build - build depending + defaultConfigExists = fs.stat('defaultConfigs/config.json'), // will be added during build - build depending defaultConfig = {}, defaultSkins = [{ name: "default", @@ -2231,138 +2322,139 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }], now = new Date(); - try{ + try { - if (defaultConfigExists && defaultConfigExists.type !== 'dir' && defaultConfigExists.size > 0){ - defaultConfig = fs.loadJSON('defaultConfigs/config.json'); - } + if (defaultConfigExists && defaultConfigExists.type !== 'dir' && defaultConfigExists.size > 0) { + defaultConfig = fs.loadJSON('defaultConfigs/config.json'); + } - if (!!defaultConfig && !_.isEmpty(defaultConfig)) { + if (!!defaultConfig && !_.isEmpty(defaultConfig)) { + + if (zway) { + var ts = now.getFullYear() + "-"; + ts += ("0" + (now.getMonth() + 1)).slice(-2) + "-"; + ts += ("0" + now.getDate()).slice(-2) + "-"; + ts += ("0" + now.getHours()).slice(-2) + "-"; + ts += ("0" + now.getMinutes()).slice(-2); + + console.log('Backup config ...'); + // make backup of current config.json + saveObject('backupConfig' + ts, loadObject('config.json')); + + // remove all active instances of moduleId + this.controller.instances.forEach(function(instance) { + if (instance.moduleId !== 'ZWave') { + self.controller.deleteInstance(instance.id); + } + }); - if (zway) { - var ts = now.getFullYear() + "-"; - ts += ("0" + (now.getMonth()+1)).slice(-2) + "-"; - ts += ("0" + now.getDate()).slice(-2) + "-"; - ts += ("0" + now.getHours()).slice(-2) + "-"; - ts += ("0" + now.getMinutes()).slice(-2); + // reset z-way controller + console.log('Reset Controller ...'); + var d = (new Date()).valueOf() + 15000; // wait not more than 15 sec - console.log('Backup config ...'); - // make backup of current config.json - saveObject('backupConfig' + ts, loadObject('config.json')); + zway.controller.SetDefault(); - // remove all active instances of moduleId - this.controller.instances.forEach(function (instance) { - if (instance.moduleId !== 'ZWave') { - self.controller.deleteInstance(instance.id); + while ((new Date()).valueOf() < d && zway.controller.data.controllerState.value === 20) { + processPendingCallbacks(); } - }); - // reset z-way controller - console.log('Reset Controller ...'); - var d = (new Date()).valueOf() + 15000; // wait not more than 15 sec + // remove instances of ZWave at least + // filter for instances of ZWave + zwInstances = this.controller.instances.filter(function(instance) { + return instance.moduleId === 'ZWave'; + }).map(function(instance) { + return instance.id; + }); + + // remove instance of ZWave + if (zwInstances.length > 0) { + zwInstances.forEach(function(instanceId) { + console.log('Remove ZWave instance: ' + instanceId); + self.controller.deleteInstance(instanceId); + }); + } - zway.controller.SetDefault(); + console.log('Remove and unload userModules apps ...'); + // unload and remove modules + Object.keys(this.controller.modules).forEach(function(className) { + var meta = self.controller.modules[className], + unload = '', + locPath = meta.location.split('/'), + success = false; - while ((new Date()).valueOf() < d && zway.controller.data.controllerState.value === 20) { - processPendingCallbacks(); - } + if (locPath[0] === 'userModules') { + console.log(className + ' remove it ...'); - // remove instances of ZWave at least - // filter for instances of ZWave - zwInstances = this.controller.instances.filter(function (instance) { - return instance.moduleId === 'ZWave'; - }).map(function (instance) { - return instance.id; - }); + success = self.controller.uninstallModule(className); + + if (success) { + console.log(className + ' has been successfully removed.'); + } else { + console.log('Cannot remove app: ' + className); + self.addNotification("warning", langFile.zaap_err_uninstall_mod + ' ' + className, "core", "AutomationController"); + } + } - // remove instance of ZWave - if (zwInstances.length > 0) { - zwInstances.forEach(function (instanceId) { - console.log('Remove ZWave instance: ' + instanceId); - self.controller.deleteInstance(instanceId); }); - } - console.log('Remove and unload userModules apps ...'); - // unload and remove modules - Object.keys(this.controller.modules).forEach( function(className) { - var meta = self.controller.modules[className], - unload = '', - locPath = meta.location.split('/'), - success = false; - - if (locPath[0] === 'userModules'){ - console.log(className + ' remove it ...'); - - success = self.controller.uninstallModule(className); - - if (success) { - console.log(className + ' has been successfully removed.'); - } else { - console.log('Cannot remove app: ' + className); - self.addNotification("warning", langFile.zaap_err_uninstall_mod + ' ' + className, "core", "AutomationController"); + // remove skins + _.forEach(this.controller.skins, function(skin) { + if (skin.name !== 'default') { + self.controller.uninstallSkin(skin.name); } - } + }); - }); + // stop the controller + this.controller.stop(); - // remove skins - _.forEach(this.controller.skins, function(skin) { - if (skin.name !== 'default') { - self.controller.uninstallSkin(skin.name); + // clean up storage + for (var ind in storageContentList) { + if (storageContentList[ind].indexOf('backupConfig') < 0 && !!storageContentList[ind]) { + saveObject(storageContentList[ind], null); + } } - }); - // stop the controller - this.controller.stop(); - - // clean up storage - for (var ind in storageContentList) { - if(storageContentList[ind].indexOf('backupConfig') < 0 && !!storageContentList[ind]){ - saveObject(storageContentList[ind], null); + // clean up storageContent + if (__storageContent.length > 0) { + __saveObject("__storageContent", []); + __storageContent = []; } - } - // clean up storageContent - if (__storageContent.length > 0) { - __saveObject("__storageContent", []); - __storageContent = []; - } + // set back to default config + saveObject('config.json', defaultConfig); + saveObject('userSkins.json', defaultSkins); - // set back to default config - saveObject('config.json', defaultConfig); - saveObject('userSkins.json', defaultSkins); + // start controller with reload flag to apply config.json + this.controller.start(true); - // start controller with reload flag to apply config.json - this.controller.start(true); + reply.code = 200; - reply.code = 200; + setTimeout(function() { + self.doLogout(); + }, 3000); + } else { + reply.code = 404; + reply.error = 'Unable to reset controller. Z-Way not found.'; + } - setTimeout(function(){ - self.doLogout(); - }, 3000); } else { reply.code = 404; - reply.error = 'Unable to reset controller. Z-Way not found.'; + reply.error = 'No default configuration file found.'; } - - } else { - reply.code = 404; - reply.error = 'No default configuration file found.'; + } catch (e) { + reply.error = 'Something went wrong. Error: ' + e.toString(); } - } catch (e) { - reply.error = 'Something went wrong. Error: ' + e.toString(); } return reply; }, - getSkins: function () { + getSkins: function() { var reply = { - error: null, - data: null, - code: 500 - }; - + error: null, + data: null, + code: 500 + }; + if (this.controller.skins) { reply.data = this.controller.skins; reply.code = 200; @@ -2372,16 +2464,16 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - getSkin: function (skinName) { + getSkin: function(skinName) { var reply = { - error: null, - data: null, - code: 500 - }; - + error: null, + data: null, + code: 500 + }; + if (this.controller.skins) { - index = _.findIndex(this.controller.skins, function(skin) { - return skin.name === skinName; + index = _.findIndex(this.controller.skins, function(skin) { + return skin.name === skinName; }); if (index > -1) { @@ -2397,16 +2489,16 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - getActiveSkin: function () { + getActiveSkin: function() { var reply = { - error: null, - data: null, - code: 500 - }; - + error: null, + data: null, + code: 500 + }; + if (this.controller.skins) { - index = _.findIndex(this.controller.skins, function(skin) { - return skin.active === true; + index = _.findIndex(this.controller.skins, function(skin) { + return skin.active === true; }); if (index > -1) { @@ -2422,12 +2514,12 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - activateOrDeactivateSkin: function (skinName) { + activateOrDeactivateSkin: function(skinName) { var reply = { - error: null, - data: null, - code: 500 - }, + error: null, + data: null, + code: 500 + }, reqObj = parseToObject(this.req.body), skin = null; @@ -2448,7 +2540,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - addOrUpdateSkin: function (skinName) { + addOrUpdateSkin: function(skinName) { var reply = { error: 'skin_failed_to_install', data: null, @@ -2460,21 +2552,21 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (skName !== 'default') { - index = _.findIndex(this.controller.skins, function(skin) { - return skin.name === skName; + index = _.findIndex(this.controller.skins, function(skin) { + return skin.name === skName; }); - if ((index < 0 && this.req.method === 'POST') || - (index > -1 && this.req.method === 'PUT' && skinName)) { - + if ((index < 0 && this.req.method === 'POST') || + (index > -1 && this.req.method === 'PUT' && skinName)) { + // download and install the skin result = this.controller.installSkin(reqObj, skName, index); if (result === "done") { reply.code = 200; - reply.data = this.req.method === 'POST'? "skin_installation_successful" : "skin_update_successful"; // send language key as response + reply.data = this.req.method === 'POST' ? "skin_installation_successful" : "skin_update_successful"; // send language key as response reply.error = null; - } + } } else if (this.req.method === 'POST' && !skinName) { reply.code = 409; @@ -2487,10 +2579,10 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 403; reply.error = 'No Permission'; } - + return reply; }, - deleteSkin: function (skinName) { + deleteSkin: function(skinName) { var reply = { error: 'skin_failed_to_delete', data: null, @@ -2499,8 +2591,8 @@ _.extend(ZAutomationAPIWebRequest.prototype, { uninstall = false; if (skinName !== 'default') { - index = _.findIndex(this.controller.skins, function(skin) { - return skin.name === skinName; + index = _.findIndex(this.controller.skins, function(skin) { + return skin.name === skinName; }); if (index > -1) { @@ -2512,7 +2604,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 200; reply.data = "skin_delete_successful"; // send language key as response reply.error = null; - } + } } else { reply.code = 404; reply.error = 'skin_not_exists'; @@ -2521,56 +2613,56 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 403; reply.error = 'No Permission'; } - + return reply; }, - setDefaultSkin: function () { + setDefaultSkin: function() { var self = this, reply = { error: null, data: null, code: 500 }; - - try { - // deactivate all skins and set default skin to active: true - _.forEach(this.controller.skins, function (skin) { - skin.active = skin.name === 'default'? true : false; - }) + try { + + // deactivate all skins and set default skin to active: true + _.forEach(this.controller.skins, function(skin) { + skin.active = skin.name === 'default' ? true : false; + }) - saveObject("userSkins.json", this.controller.skins); + saveObject("userSkins.json", this.controller.skins); - reply.data = "Skin reset was successfull. You'll be logged out in 3, 2, 1 ..."; - reply.code = 200; - // do logout - setTimeout(function(){ - self.doLogout(); - }, 3000); - } catch (e) { - reply.error = "Something went wrong."; - reply.message = e.message; - } + reply.data = "Skin reset was successfull. You'll be logged out in 3, 2, 1 ..."; + reply.code = 200; + // do logout + setTimeout(function() { + self.doLogout(); + }, 3000); + } catch (e) { + reply.error = "Something went wrong."; + reply.message = e.message; + } return reply; }, - getSkinTokens: function () { + getSkinTokens: function() { var reply = { - error: null, - data: null, - code: 500 - }, - tokenObj = loadObject('skinTokens.json'); + error: null, + data: null, + code: 500 + }, + tokenObj = loadObject('skinTokens.json'); if (tokenObj === null) { tokenObj = { - skinTokens: [] + skinTokens: [] }; saveObject('skinTokens.json', tokenObj); } - + if (!!tokenObj) { reply.data = tokenObj; reply.code = 200; @@ -2580,14 +2672,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - storeSkinToken: function () { + storeSkinToken: function() { var reply = { - error: null, - data: null, - code: 500 - }, - reqObj = parseToObject(this.req.body), - tokenObj = loadObject('skinTokens.json'); + error: null, + data: null, + code: 500 + }, + reqObj = parseToObject(this.req.body), + tokenObj = loadObject('skinTokens.json'); if (reqObj && reqObj.token) { @@ -2625,14 +2717,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - deleteSkinToken: function () { + deleteSkinToken: function() { var reply = { - error: null, - data: null, - code: 500 - }, - reqObj = parseToObject(this.req.body), - tokenObj = loadObject('skinTokens.json'); + error: null, + data: null, + code: 500 + }, + reqObj = parseToObject(this.req.body), + tokenObj = loadObject('skinTokens.json'); if (reqObj && reqObj.token && !!tokenObj && tokenObj.skinTokens) { if (tokenObj.skinTokens.indexOf(reqObj.token) > -1) { @@ -2656,7 +2748,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - getIcons: function () { + getIcons: function() { var reply = { error: null, data: null, @@ -2679,8 +2771,8 @@ _.extend(ZAutomationAPIWebRequest.prototype, { code: 500 }; - for (prop in this.req.body){ - if(this.req.body[prop]['content']) { + for (prop in this.req.body) { + if (this.req.body[prop]['content']) { file = this.req.body[prop]; } @@ -2689,7 +2781,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { function utf8Decode(bytes) { var chars = []; - for(var i = 0; i < bytes.length; i++) { + for (var i = 0; i < bytes.length; i++) { chars[i] = bytes.charCodeAt(i); } @@ -2740,9 +2832,9 @@ _.extend(ZAutomationAPIWebRequest.prototype, { var data, bytes = new Uint8Array(utf8Decode(file.content)), re = /(?:\.([^.]+))?$/; - ext = re.exec(file.name)[1]; + ext = re.exec(file.name)[1]; - if(ext === 'gz') { + if (ext === 'gz') { var gunzip = new Zlib.Gunzip(bytes); data = gunzip.decompress(); } else { @@ -2753,10 +2845,10 @@ _.extend(ZAutomationAPIWebRequest.prototype, { result = this.controller.installIcon('local', file, 'custom', 'icon'); - if (result === "done") { + if (result.message === "done") { reply.code = 200; - reply.data = "icon_installation_successful"; + reply.data = result.files; reply.error = null; } @@ -2774,18 +2866,18 @@ _.extend(ZAutomationAPIWebRequest.prototype, { id = reqObj.id; index = _.findIndex(this.controller.icons, function(icon) { - return icon.source === icName+"_"+id; + return icon.source === icName + "_" + id; }); - if (index === -1 ) { + if (index === -1) { // download and install the icon result = this.controller.installIcon('remote', reqObj, icName, reqObj.id); - if (result === "done") { + if (result.message === "done") { reply.code = 200; - reply.data = "icon_installation_successful"; + reply.data = result.files; reply.error = null; } } else { @@ -2817,7 +2909,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - getTime: function () { + getTime: function() { var reply = { error: null, data: null, @@ -2829,19 +2921,19 @@ _.extend(ZAutomationAPIWebRequest.prototype, { try { var sys = system('cat /etc/timezone'); sys.forEach(function(i) { - if(typeof i === 'string') { - tz = i.replace(/\n/g, ''); - return; - }; + if (typeof i === 'string') { + tz = i.replace(/\n/g, ''); + return; + }; }); - } catch(e) {} + } catch (e) {} if (now) { reply.code = 200; reply.data = { localTimeUT: Math.round((now.getTime() + (now.getTimezoneOffset() * -60000)) / 1000), // generate timestamp with correct timezone offset localTimeString: now.toLocaleString(), - localTimeZoneOffset: now.getTimezoneOffset() /60, + localTimeZoneOffset: now.getTimezoneOffset() / 60, localTimeZone: tz }; } else { @@ -2853,7 +2945,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { setTimezone: function() { var self = this, langfile = this.controller.loadMainLang(); - reply = { + reply = { error: null, data: null, code: 500 @@ -2877,7 +2969,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { saveObject('8084AccessTimeout', 10); var res = http.request(req); - if(res.status === 200) { + if (res.status === 200 || res.status === 303) { reply.code = 200; reply.data = res.statusText; @@ -2899,7 +2991,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { return reply; }, - getRemoteId: function () { + getRemoteId: function() { var self = this, reply = { error: null, @@ -2907,21 +2999,41 @@ _.extend(ZAutomationAPIWebRequest.prototype, { code: 500 }; - try { - reply.code = 200; - reply.data = { - remote_id: self.controller.getRemoteId() - }; + try { + reply.code = 200; + reply.data = { + remote_id: self.controller.getRemoteId() + }; - } catch (e) { - if(e.name === "service-not-available") { - reply.code = 503; - reply.error = e.message; - } else { - reply.code = 500; - reply.error = e.message; - } + } catch (e) { + if (e.name === "service-not-available") { + reply.code = 503; + reply.error = e.message; + } else { + reply.code = 500; + reply.error = e.message; } + } + return reply; + }, + getIPAddress: function() { + var self = this, + reply = { + error: null, + data: null, + code: 500 + }, + ip = self.controller.getIPAddress(); + + if (ip) { + reply.code = 200; + reply.data = { + ip_address: ip + }; + } else { + reply.code = 500; + reply.error = "syscommad-not-set"; + } return reply; }, // set a timout for accessing firmware update tab of 8084 @@ -2965,7 +3077,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { defaultProfile = [], setLogin = {}; try { - defaultProfile = _.filter(this.controller.profiles, function (profile) { + defaultProfile = _.filter(this.controller.profiles, function(profile) { return profile.login === 'admin' && profile.password === 'admin'; }); @@ -2978,10 +3090,12 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 200; } else { - reply.data = { firstaccess: false }; + reply.data = { + firstaccess: false + }; reply.code = 200; } - } catch (e){ + } catch (e) { reply.data = null; reply.error = e.message; } @@ -2995,19 +3109,19 @@ _.extend(ZAutomationAPIWebRequest.prototype, { code: 500 }, versionArr = []; - + try { versionArr = zway.controller.data.softwareRevisionVersion.value.substring(1).split('-'); - version = versionArr[0]? versionArr[0] : null; - majurity = versionArr[1]? versionArr[1] : null; + version = versionArr[0] ? versionArr[0] : null; + majurity = versionArr[1] ? versionArr[1] : null; - reply.data = { - first_start_up: this.controller.config.first_start_up, + reply.data = { + first_start_up: this.controller.config.first_start_up, count_of_reconnects: this.controller.config.count_of_reconnects, current_firmware: version, current_firmware_majurity: majurity, remote_id: this.controller.getRemoteId(), - firstaccess: this.controller.config.hasOwnProperty('firstaccess')? this.controller.config.firstaccess : true + firstaccess: this.controller.config.hasOwnProperty('firstaccess') ? this.controller.config.firstaccess : true }; // add more information if box is cit @@ -3015,12 +3129,12 @@ _.extend(ZAutomationAPIWebRequest.prototype, { _.extend(reply.data, { cit_identifier: this.controller.config.cit_identifier || '', cit_authorized: this.controller.config.cit_authorized || false, - cit_license_countDown: zway && zway.controller.data.countDown? zway.controller.data.countDown.value : null, + cit_license_countDown: zway && zway.controller.data.countDown ? zway.controller.data.countDown.value : null, cit_server_reachable: checkInternetConnection('https://findcit.z-wavealliance.org') }); if (this.controller.config.forwardCITAuth === true && this.controller.config.cit_authorized) { - profile = _.filter(this.controller.profiles, function(p){ + profile = _.filter(this.controller.profiles, function(p) { return p.name === 'CIT Administrator'; }); @@ -3037,7 +3151,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } reply.code = 200; - } catch (e){ + } catch (e) { reply.data = null; reply.error = e.message; } @@ -3047,30 +3161,30 @@ _.extend(ZAutomationAPIWebRequest.prototype, { rebootBox: function() { var self = this, langfile = this.controller.loadMainLang(); - reply = { - error: null, - data: null, - code: 500 - }; + reply = { + error: null, + data: null, + code: 500 + }; - // if reboot has flag firstaccess=true add showWelcome to controller config - if(this.req.query.hasOwnProperty('firstaccess') && this.req.query.firstaccess) { - this.controller.config.showWelcome = true; - this.controller.saveConfig(); - } + // if reboot has flag firstaccess=true add showWelcome to controller config + if (this.req.query.hasOwnProperty('firstaccess') && this.req.query.firstaccess) { + this.controller.config.showWelcome = true; + this.controller.saveConfig(); + } - // reboot after 5 seconds - setTimeout(function() { - try { - console.log("Rebooting system ..."); - system("reboot"); // reboot the box - } catch (e){ - self.controller.addNotification("error", langfile.zaap_err_reboot, "core", "RebootBox"); - } - }, 5000); + // reboot after 5 seconds + setTimeout(function() { + try { + console.log("Rebooting system ..."); + system("reboot"); // reboot the box + } catch (e) { + self.controller.addNotification("error", langfile.zaap_err_reboot, "core", "RebootBox"); + } + }, 5000); - reply.code = 200; - reply.data = "System is rebooting ..."; + reply.code = 200; + reply.data = "System is rebooting ..."; return reply; }, @@ -3088,8 +3202,8 @@ _.extend(ZAutomationAPIWebRequest.prototype, { try { reqObj = parseToObject(this.req.body); - if(reqObj.password !== '') { - if(reqObj.password.length >= 8 && reqObj.password.length <= 63) { + if (reqObj.password !== '') { + if (reqObj.password.length >= 8 && reqObj.password.length <= 63) { retPp = system("sh automation/lib/configAP.sh setPp " + reqObj.password); } else { reply.error = "Password must between 8 and 63 characters long."; @@ -3099,22 +3213,22 @@ _.extend(ZAutomationAPIWebRequest.prototype, { retPp[1] = ""; } - if(reqObj.ssid !== '') { + if (reqObj.ssid !== '') { retSsid = system("sh automation/lib/configAP.sh setSsid " + reqObj.ssid); } else { retSsid[1] = ""; } - if((retSsid[1].indexOf("successfull") !== -1 || retPp[1].indexOf("successfull") !== -1) || (retSsid[1].indexOf("successfull") !== -1 && retPp[1].indexOf("successfull") !== -1)) { + if ((retSsid[1].indexOf("successfull") !== -1 || retPp[1].indexOf("successfull") !== -1) || (retSsid[1].indexOf("successfull") !== -1 && retPp[1].indexOf("successfull") !== -1)) { retR = system("sh automation/lib/configAP.sh reload"); - if(retR[1].indexOf("Done") !== -1 ) { + if (retR[1].indexOf("Done") !== -1) { reply.error = null; reply.data = "OK"; reply.code = 200; } } - } catch(e) { + } catch (e) { console.log(e.toString()); reply.error = 'Internal Server Error. ' + e.toString(); } @@ -3127,10 +3241,10 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }, getWifiSettings: function() { var reply = { - error: null, - data: null, - code: 500 - }; + error: null, + data: null, + code: 500 + }; if (fs.stat('lib/configAP.sh')) { try { @@ -3139,9 +3253,11 @@ _.extend(ZAutomationAPIWebRequest.prototype, { var ssid = retSsid[1].replace(' 0', '').replace(/\n/g, ''); reply.code = 200; - reply.data = {"ssid": ssid}; + reply.data = { + "ssid": ssid + }; - } catch(e) { + } catch (e) { console.log(e.toString()); reply.error = 'Internal Server Error. ' + e.toString(); } @@ -3158,7 +3274,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { data: null, code: 500 }, - actions = ["status","stop","start","restart","disable","enable","reconfigure","setDateTime"], + actions = ["status", "stop", "start", "restart", "disable", "enable", "reconfigure", "setDateTime"], dt_regex = /[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1]) (2[0-3]|[01][0-9]):[0-5][0-9]/; if (fs.stat('lib/ntp.sh')) { @@ -3167,12 +3283,12 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (actions.indexOf(action) > -1 || (action == 'setDateTime' && reqObj.dateTime && dt_regex.exec(reqObj.dateTime))) { - res = system("./automation/lib/ntp.sh " + action + (action == 'setDateTime'? " '" + reqObj.dateTime + "'" : "")); + res = system("./automation/lib/ntp.sh " + action + (action == 'setDateTime' ? " '" + reqObj.dateTime + "'" : "")); if (action === 'status' && res[1]) { reply.data = JSON.parse(res[1]); } else { - reply.data = res[1]? res[1] : res; + reply.data = res[1] ? res[1] : res; } reply.code = 200; @@ -3182,7 +3298,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.error = 'Bad Request. Allowed are: ' + actions.toString() + '?dateTime=yyyy-mm-dd hh:mm'; reply.code = 400; } - } catch(e) { + } catch (e) { console.log(e.toString()); reply.error = 'Internal Server Error. ' + e.toString(); } @@ -3203,9 +3319,9 @@ _.extend(ZAutomationAPIWebRequest.prototype, { response = 'in progress', cit_server_reachable = checkInternetConnection('https://certxfer.z-wavealliance.org:8443'), reqObj = parseToObject(this.req.body), - user = reqObj.user && reqObj.user !== ''? reqObj.user : undefined, - pass = reqObj.pass && reqObj.pass !== ''? reqObj.pass : undefined, - identifier = reqObj.cit_identifier && reqObj.cit_identifier !== ''? reqObj.cit_identifier : (self.controller.config.cit_identifier && self.controller.config.cit_authorized? self.controller.config.cit_identifier : undefined); + user = reqObj.user && reqObj.user !== '' ? reqObj.user : undefined, + pass = reqObj.pass && reqObj.pass !== '' ? reqObj.pass : undefined, + identifier = reqObj.cit_identifier && reqObj.cit_identifier !== '' ? reqObj.cit_identifier : (self.controller.config.cit_identifier && self.controller.config.cit_authorized ? self.controller.config.cit_identifier : undefined); try { // check controller vendor (cit) @@ -3218,7 +3334,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (user && pass && identifier) { // access to alliance if server is reachable - if (cit_server_reachable){ + if (cit_server_reachable) { var uuid = zway.controller.data.uuid.value; var d = (new Date()).valueOf() + 15000; // wait not more than 15 sec var cit_user = user.toLowerCase(); @@ -3227,7 +3343,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { http.request({ url: encodeURI("https://certxfer.z-wavealliance.org:8443/CITAuth/Reg.aspx?UID=" + uuid + "&user=" + user + "&pass=" + pass + "&desc=" + identifier), async: true, - success: function (resp) { + success: function(resp) { r = parseToObject(resp); res = r.data ? parseToObject(r.data) : null; @@ -3242,7 +3358,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (res.result) { self.controller.config.cit_identifier = identifier; - if (self.controller.profiles.filter(function (p) { + if (self.controller.profiles.filter(function(p) { return p.login === cit_user; }).length === 0) { @@ -3297,14 +3413,16 @@ _.extend(ZAutomationAPIWebRequest.prototype, { response = 'done'; if (res.hasOwnProperty('result') && !res.result) { - _.extend(res, {key: 'cit_initialize_login_failed'}); + _.extend(res, { + key: 'cit_initialize_login_failed' + }); } reply.code = r.status; reply.error = null; reply.data = res; }, - error: function (resp) { + error: function(resp) { r = parseToObject(resp); response = 'failed'; @@ -3318,7 +3436,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { http.request({ url: encodeURI("https://certxfer.z-wavealliance.org:8443/CITAuth/Auth.aspx?UID=" + uuid + "&user=" + user + "&pass=" + pass), async: true, - success: function (resp) { + success: function(resp) { r = parseToObject(resp); res = r.data ? parseToObject(r.data) : null; @@ -3327,7 +3445,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { // update cit profile if auth is ok if (res.result) { // update default admin profile - prof = _.filter(self.controller.profiles, function (p) { + prof = _.filter(self.controller.profiles, function(p) { return p.login === cit_user; }); @@ -3345,14 +3463,16 @@ _.extend(ZAutomationAPIWebRequest.prototype, { response = 'done'; if (res.hasOwnProperty('result') && !res.result) { - _.extend(res, {key: 'cit_login_failed'}); + _.extend(res, { + key: 'cit_login_failed' + }); } reply.code = r.status; reply.error = null; reply.data = res; }, - error: function (resp) { + error: function(resp) { r = parseToObject(resp); response = 'failed'; @@ -3375,7 +3495,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.code = 504; reply.error = 'Gateway Time-out: No response from https://certxfer.z-wavealliance.org'; } - // forward local login if server isn't reachable but cit is registrated + // forward local login if server isn't reachable but cit is registrated } else { reply.code = 504; reply.error = 'Gateway Time-out: No response from https://certxfer.z-wavealliance.org'; @@ -3388,7 +3508,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.error = 'Not Implemented: This function is not supported by controller.'; reply.code = 501; } - } catch(e) { + } catch (e) { console.log(e.toString()); reply.error = 'Internal Server Error. ' + e.toString(); } @@ -3479,8 +3599,8 @@ _.extend(ZAutomationAPIWebRequest.prototype, { response = 'in progress', req_user = this.profileByUser(this.req.user), reqObj = parseToObject(this.req.body), - user = reqObj.user && reqObj.user !== ''? reqObj.user : undefined, - pass = reqObj.pass && reqObj.pass !== ''? reqObj.pass : undefined; + user = reqObj.user && reqObj.user !== '' ? reqObj.user : undefined, + pass = reqObj.pass && reqObj.pass !== '' ? reqObj.pass : undefined; try { // check controller vendor (cit) @@ -3500,7 +3620,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { async: true, success: function(resp) { r = parseToObject(resp); - res = r.data? parseToObject(r.data) : null; + res = r.data ? parseToObject(r.data) : null; // check if CertXFer auth is ok if (!!res && res.result) { @@ -3514,7 +3634,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.message = "Your CIT was successfully unregistered. You'll be logged out in 3, 2, 1 ..."; - setTimeout(function(){ + setTimeout(function() { self.doLogout(); }, 3000); } @@ -3531,7 +3651,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { response = 'failed'; reply.code = r.status; - reply.error = r.error? r.error : r.status + ' ' + r.statusText; + reply.error = r.error ? r.error : r.status + ' ' + r.statusText; reply.data = r.data; } }); @@ -3559,7 +3679,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.error = 'Not Implemented: This function is not supported by controller.'; reply.code = 501; } - } catch(e) { + } catch (e) { console.log(e.toString()); reply.error = 'Internal Server Error. ' + e.toString(); } @@ -3576,7 +3696,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { response = 'in progress', reqObj = parseToObject(this.req.body), req_user = this.profileByUser(this.req.user), - identifier = reqObj.cit_identifier && reqObj.cit_identifier !== ''? reqObj.cit_identifier : undefined; + identifier = reqObj.cit_identifier && reqObj.cit_identifier !== '' ? reqObj.cit_identifier : undefined; try { // check controller vendor (cit) @@ -3596,7 +3716,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { async: true, success: function(resp) { r = parseToObject(resp); - res = r.data? parseToObject(r.data) : null; + res = r.data ? parseToObject(r.data) : null; // check if CertXFer auth is ok if (!!res && res.result) { @@ -3617,7 +3737,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { response = 'failed'; reply.code = r.status; - reply.error = r.error? r.error : r.status + ' ' + r.statusText; + reply.error = r.error ? r.error : r.status + ' ' + r.statusText; reply.data = r.data; } }); @@ -3641,7 +3761,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.error = 'Not Implemented: This function is not supported by controller.'; reply.code = 501; } - } catch(e) { + } catch (e) { console.log(e.toString()); reply.error = 'Internal Server Error. ' + e.toString(); } @@ -3654,20 +3774,20 @@ _.extend(ZAutomationAPIWebRequest.prototype, { code: 500, data: null }, - l = ['en','de'], //this.controller.availableLang + l = ['en', 'de'], //this.controller.availableLang devInfo = {}, - reqObj = !this.req.query? undefined : parseToObject(this.req.query); + reqObj = !this.req.query ? undefined : parseToObject(this.req.query); try { - devID = reqObj && reqObj.id? reqObj.id : null; - language = reqObj && reqObj.lang && l.indexOf(reqObj.lang) > -1? reqObj.lang : 'en'; + devID = reqObj && reqObj.id ? reqObj.id : null; + language = reqObj && reqObj.lang && l.indexOf(reqObj.lang) > -1 ? reqObj.lang : 'en'; if (reqObj && reqObj.lang && l.indexOf(reqObj.lang) === -1) { reply.message = 'Language not found. English is used instead.'; } - devInfo = loadObject(language +'.devices.json'); //this.controller.defaultLang + devInfo = loadObject(language + '.devices.json'); //this.controller.defaultLang if (devInfo === null) { @@ -3684,7 +3804,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { if (!reply.data) { reply.code = 404; - reply.error = 'No Z-Wave device with ' + devID + ' found.'; + reply.error = 'No Z-Wave device with ' + devID + ' found.'; reply.data = null; } } @@ -3698,7 +3818,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { zwaveDeviceInfoUpdate: function() { var self = this, result = [], - l = ['en','de'], //this.controller.availableLang, + l = ['en', 'de'], //this.controller.availableLang, reply = { error: null, code: 500, @@ -3729,14 +3849,14 @@ _.extend(ZAutomationAPIWebRequest.prototype, { list.zwave_devices.push(data[index]); } - saveObject(lang +'.devices.json', list); + saveObject(lang + '.devices.json', list); obj[lang] = true; } result.push(obj); }, error: function() { - self.controller.addNotification('Z-Wave device list for lang:' + lang + ' not found.'); + self.controller.addNotification('error', 'Z-Wave device list for lang:' + lang + ' not found.', 'core', 'ZAutomationAPI'); result.push(obj); } }); @@ -3746,13 +3866,13 @@ _.extend(ZAutomationAPIWebRequest.prototype, { processPendingCallbacks(); } - if(result) { + if (result) { reply.code = 200; reply.data = result; } } catch (e) { - this.controller.addNotification('Error has occured during updating the Z-Wave devices list'); + this.controller.addNotification('error', 'Error has occured during updating the Z-Wave devices list', 'core', 'ZAutomationAPI'); reply.error = 'Something went wrong:' + e.message; } @@ -3765,11 +3885,11 @@ _.extend(ZAutomationAPIWebRequest.prototype, { data: null }, devInfo = {}, - reqObj = !this.req.query? undefined : parseToObject(this.req.query); + reqObj = !this.req.query ? undefined : parseToObject(this.req.query); try { - vendorID = reqObj && reqObj.id? reqObj.id : null; + vendorID = reqObj && reqObj.id ? reqObj.id : null; devInfo = loadObject('zwave_vendors.json'); @@ -3784,7 +3904,7 @@ _.extend(ZAutomationAPIWebRequest.prototype, { reply.data = devInfo.zwave_vendors[vendorID]; } else if (reqObj.id) { reply.code = 404; - reply.error = 'No Z-Wave vendor with id "' + vendorID + '" found.'; + reply.error = 'No Z-Wave vendor with id "' + vendorID + '" found.'; reply.data = null; } } @@ -3807,9 +3927,9 @@ _.extend(ZAutomationAPIWebRequest.prototype, { try { // update postfix JSON var list = { - updateTime: '', - zwave_vendors: {} - }; + updateTime: '', + zwave_vendors: {} + }; http.request({ url: "http://manuals-backend.z-wave.info/make.php?mode=brand", @@ -3828,8 +3948,8 @@ _.extend(ZAutomationAPIWebRequest.prototype, { } }, error: function(e) { - var msg = 'Z-Wave vendors list could not be updated. Error: ' +e.toString(); - self.controller.addNotification(msg); + var msg = 'Z-Wave vendors list could not be updated. Error: ' + e.toString(); + self.controller.addNotification('error', msg, 'core', 'ZAutomationAPI'); result = 'failed'; @@ -3843,12 +3963,12 @@ _.extend(ZAutomationAPIWebRequest.prototype, { processPendingCallbacks(); } - if(result === 'in progress') { + if (result === 'in progress') { result = 'failed'; } } catch (e) { - this.controller.addNotification('Error has occured during updating the Z-Wave devices list'); + this.controller.addNotification('error', 'Error has occured during updating the Z-Wave devices list', 'core', 'ZAutomationAPI'); reply.error = 'Something went wrong:' + e.message; } @@ -3857,7 +3977,9 @@ _.extend(ZAutomationAPIWebRequest.prototype, { }); ZAutomationAPIWebRequest.prototype.profileByUser = function(userId) { - return _.find(this.controller.profiles, function(profile) { return profile.id === userId }); + return _.find(this.controller.profiles, function(profile) { + return profile.id === userId + }); }; ZAutomationAPIWebRequest.prototype.devicesByUser = function(userId, filter) { @@ -3867,7 +3989,7 @@ ZAutomationAPIWebRequest.prototype.devicesByUser = function(userId, filter) { if (!profile) { return []; } - + if (profile.role === this.ROLE.ADMIN) { return devices; } else { @@ -3883,19 +4005,21 @@ ZAutomationAPIWebRequest.prototype.devicesByUser = function(userId, filter) { }; ZAutomationAPIWebRequest.prototype.deviceByUser = function(vDevId, userId) { - if (this.devicesByUser(userId).filter(function (device) { return device.id === vDevId }).length) { + if (this.devicesByUser(userId).filter(function(device) { + return device.id === vDevId + }).length) { return this.controller.devices.get(vDevId); } - return null; + return null; }; ZAutomationAPIWebRequest.prototype.locationsByUser = function(userId) { var profile = this.profileByUser(userId); - + if (!profile) { return []; } - + if (profile.role === this.ROLE.ADMIN) { return this.controller.locations; } else { @@ -3909,20 +4033,20 @@ ZAutomationAPIWebRequest.prototype.locationsByUser = function(userId) { } }; -ZAutomationAPIWebRequest.prototype.authCIT = function () { +ZAutomationAPIWebRequest.prototype.authCIT = function() { var license = true; // check for license countdown if (typeof zway !== 'undefined' && zway.controller.data.countDown) { - license = zway.controller.data.countDown.value > 0? true : false; + license = zway.controller.data.countDown.value > 0 ? true : false; } return checkBoxtype('cit') && this.controller.config.cit_authorized && license; }; -ZAutomationAPIWebRequest.prototype.getProfileResponse = function (profileObj) { +ZAutomationAPIWebRequest.prototype.getProfileResponse = function(profileObj) { var self = this, resProfile = {}; - Object.keys(profileObj).forEach(function(value){ + Object.keys(profileObj).forEach(function(value) { if (!~self.exclFromProfileRes.indexOf(value)) { resProfile[value] = profileObj[value]; } @@ -3931,7 +4055,7 @@ ZAutomationAPIWebRequest.prototype.getProfileResponse = function (profileObj) { return resProfile; }; -ZAutomationAPIWebRequest.prototype.Unauthorized = function () { +ZAutomationAPIWebRequest.prototype.Unauthorized = function() { return { error: 'Not logged in', data: null, @@ -3939,7 +4063,7 @@ ZAutomationAPIWebRequest.prototype.Unauthorized = function () { }; } -ZAutomationAPIWebRequest.prototype.Forbidden = function () { +ZAutomationAPIWebRequest.prototype.Forbidden = function() { return { error: 'Permission denied', data: null, @@ -3947,7 +4071,7 @@ ZAutomationAPIWebRequest.prototype.Forbidden = function () { }; } -ZAutomationAPIWebRequest.prototype.dispatchRequest = function (method, url) { +ZAutomationAPIWebRequest.prototype.dispatchRequest = function(method, url) { var self = this, handlerFunc = this.NotFound, // Default handler is NotFound validParams; @@ -3968,16 +4092,19 @@ ZAutomationAPIWebRequest.prototype.dispatchRequest = function (method, url) { // fill user field this.req.user = auth.user; this.req.role = auth.role; - + if (matched.params.length) { - validParams = _.every(matched.params), function(p) { return !!p; }; + validParams = _.every(matched.params), + function(p) { + return !!p; + }; if (validParams) { - handlerFunc = function () { + handlerFunc = function() { return matched.handler.apply(this, matched.params); } } } else { - handlerFunc = matched.handler? matched.handler : handlerFunc; + handlerFunc = matched.handler ? matched.handler : handlerFunc; } // --- Proceed to checkout =) @@ -3994,7 +4121,7 @@ ZAutomationAPIWebRequest.prototype.dispatchRequest = function (method, url) { } }; -ZAutomationAPIWebRequest.prototype.reorderDevices = function () { +ZAutomationAPIWebRequest.prototype.reorderDevices = function() { var self = this, reply = { error: "Internal Server Error", @@ -4007,7 +4134,7 @@ ZAutomationAPIWebRequest.prototype.reorderDevices = function () { var data = reqObj.data, // ordered list of devices action = reqObj.action; // Dasboard, Elelements, Room(location) - if(self.controller.reoderDevices(data, action)) { + if (self.controller.reoderDevices(data, action)) { reply.error = ""; reply.data = "OK"; reply.code = 200; @@ -4027,5 +4154,4 @@ ZAutomationAPIWebRequest.prototype.reorderDevices = function () { return reply; -} - +} \ No newline at end of file diff --git a/classes/AuthController.js b/classes/AuthController.js index 7d2ddb1be..e4c267a06 100644 --- a/classes/AuthController.js +++ b/classes/AuthController.js @@ -136,38 +136,42 @@ AuthController.prototype.checkIn = function(profile, sid) { AuthController.prototype.forgottenPwd = function(email, token) { var self = this, - success; + success = true, + setToken = function(){ + self.forgottenPwdCollector[token] = { + email: email, + expTime: Math.floor(new Date().getTime() / 1000) + 600 // 10 min + }; + }; - if ( Object.keys(this.forgottenPwdCollector).length > 0) { + if (Object.keys(this.forgottenPwdCollector).length > 0) { Object.keys(this.forgottenPwdCollector).forEach(function(t){ if (self.forgottenPwdCollector[t].email === email) { - console.log('Tokenrequest already exists for e-mail:', email); success = false; } }); + if (success) { + setToken(); + } } else { - this.forgottenPwdCollector[token] = { - email: email, - expTime: Math.floor(new Date().getTime() / 1000) + 3600 - }; - - success = true; + setToken(); } - if (!self.expireTokens) { + if (!this.expireTokens) { this.expireTokens = setInterval(function() { var expirationTime = Math.floor(new Date().getTime() / 1000); - Object.keys(self.forgottenPwdCollector).forEach(function(tkn, i) { - if (tkn.expTime < expirationTime) { - self.removeForgottenPwdEntry(i); + Object.keys(self.forgottenPwdCollector).forEach(function(tkn) { + if (self.forgottenPwdCollector[tkn].expTime < expirationTime) { + self.removeForgottenPwdEntry(tkn); } }); - if (self.forgottenPwdCollector.size === 0 && self.expireTokens) { + if (Object.keys(self.forgottenPwdCollector).length < 1 && self.expireTokens) { clearInterval(self.expireTokens); + self.expireTokens = null; } - }, 600 * 1000); + }, 60 * 1000); // check each minute } return success; diff --git a/classes/AutomationController.js b/classes/AutomationController.js index 07d076453..200ed99bf 100644 --- a/classes/AutomationController.js +++ b/classes/AutomationController.js @@ -42,22 +42,21 @@ function AutomationController() { this.devices = new DevicesCollection(this); this.notifications = []; - this.history = []; this.lastStructureChangeTime = 0; this._loadedSingletons = []; - + this.auth = new AuthController(this); this.skins = loadObject('userSkins.json') || [{ - name: "default", - title: "Default", - description: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.", - version: "1.0.3", - icon: true, - author: "Martin Vach", - homepage: "http://www.zwave.eu", - active: true - }]; + name: "default", + title: "Default", + description: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.", + version: "1.0.3", + icon: true, + author: "Martin Vach", + homepage: "http://www.zwave.eu", + active: true + }]; this.icons = loadObject('userIcons.json') || []; } @@ -65,41 +64,41 @@ function AutomationController() { inherits(AutomationController, EventEmitter2); function wrap(self, func) { - return function () { + return function() { func.apply(self, arguments); }; } -AutomationController.prototype.init = function () { +AutomationController.prototype.init = function() { var self = this; function pushNamespaces(device, locationNspcOnly) { - self.generateNamespaces(function (namespaces) { + self.generateNamespaces(function(namespaces) { ws.push('me.z-wave.namespaces.update', JSON.stringify(namespaces)); }, device, locationNspcOnly); } - self.loadModules(function () { + self.loadModules(function() { self.emit("core.init"); - + // update namespaces if device title has changed - self.devices.on('change:metrics:title', function (device) { + self.devices.on('change:metrics:title', function(device) { ws.push("me.z-wave.devices.title_update", JSON.stringify(device.toJSON())); pushNamespaces(device, false); }); // update only location namespaces if device location has changed - self.devices.on('change:location', function (device) { + self.devices.on('change:location', function(device) { ws.push("me.z-wave.devices.location_update", JSON.stringify(device.toJSON())); pushNamespaces(device, true); var id = device.get('id'); - locationId = device.get('location'), + locationId = device.get('location'), order = device.get('order'), count = 0; self.devices.forEach(function(dev) { - if(dev.get('location') == locationId) { + if (dev.get('location') == locationId) { count++; } }); @@ -107,65 +106,65 @@ AutomationController.prototype.init = function () { order.room = (count - 1); device.set('order', order); - var location = _.find(self.locations, function(loc) { - if(loc.id !== 0) { - var index = loc.main_sensors.indexOf(id); - if(index > -1) { - return loc; - } - } - }); - - if(location !== undefined) { - location.main_sensors.splice(index, 1); - self.updateLocation(location.id, location.title, location.user_img, location.default_img, location.img_type, location.show_background, location.main_sensors, function (data) { - if (!data) { - console.log("Error location not exists"); - } - }); - } + var location = _.find(self.locations, function(loc) { + if (loc.id !== 0) { + var index = loc.main_sensors.indexOf(id); + if (index > -1) { + return loc; + } + } + }); + + if (location !== undefined && device.get('location') !== location.id) { + location.main_sensors.splice(index, 1); + self.updateLocation(location.id, location.title, location.user_img, location.default_img, location.img_type, location.show_background, location.main_sensors, function(data) { + if (!data) { + console.log("Error location not exists"); + } + }); + } }); // update namespaces if device permanently_hidden status has changed - self.devices.on('change:permanently_hidden', function (device) { + self.devices.on('change:permanently_hidden', function(device) { ws.push("me.z-wave.devices.visibility_update", JSON.stringify(device.toJSON())); pushNamespaces(device, false); }); // update namespaces if device removed status has changed - self.devices.on('change:metrics:removed', function (device) { + self.devices.on('change:metrics:removed', function(device) { ws.push("me.z-wave.devices.visibility_update", JSON.stringify(device.toJSON())); pushNamespaces(device, false); }); // update namespaces if structure of devices collection changed - self.devices.on('created', function (device) { + self.devices.on('created', function(device) { ws.push("me.z-wave.devices.add", JSON.stringify(device.toJSON())); pushNamespaces(device); }); - self.devices.on('destroy', function (device) { + self.devices.on('destroy', function(device) { ws.push("me.z-wave.devices.destroy", JSON.stringify(device.toJSON())); }); - self.devices.on('removed', function (device) { + self.devices.on('removed', function(device) { pushNamespaces(device); var id = device.get('id'); var locationId = device.get('location'); - if(locationId !== 0) { - var location = _.find(self.locations, function (location) { + if (locationId !== 0) { + var location = _.find(self.locations, function(location) { return location.id === locationId; }); - if(typeof location !== 'undefined') { - if(location.hasOwnProperty("main_sensors")) { + if (typeof location !== 'undefined') { + if (location.hasOwnProperty("main_sensors")) { var index = location.main_sensors.indexOf(id); if (index > -1) { location.main_sensors.splice(index, 1); - self.updateLocation(location.id, location.title, location.user_img, location.default_img, location.img_type, location.show_background, location.main_sensors, function (data) { + self.updateLocation(location.id, location.title, location.user_img, location.default_img, location.img_type, location.show_background, location.main_sensors, function(data) { if (!data) { console.log("Error location not exists"); } @@ -176,30 +175,30 @@ AutomationController.prototype.init = function () { } }); - self.on("notifications.push", function (notice) { + self.on("notifications.push", function(notice) { ws.push("me.z-wave.notifications.add", JSON.stringify(notice)); }); }); }; -AutomationController.prototype.setDefaultLang = function (lang) { +AutomationController.prototype.setDefaultLang = function(lang) { var self = this, oldLang = self.defaultLang; self.defaultLang = self.availableLang.indexOf(lang) === -1 ? 'en' : lang; - - if(self.defaultLang !== oldLang) { + + if (self.defaultLang !== oldLang) { this.emit('language.changed', self.defaultLang); } }; -AutomationController.prototype.saveConfig = function () { +AutomationController.prototype.saveConfig = function() { // do clean up of location namespaces - cleanupLocations = function (locations) { + cleanupLocations = function(locations) { var newLoc = []; - locations.forEach(function(loc){ + locations.forEach(function(loc) { newLoc.push(_.omit(loc, 'namespaces')); }); @@ -216,33 +215,33 @@ AutomationController.prototype.saveConfig = function () { try { saveObject("config.json", cfgObject); - } catch(e) { + } catch (e) { console.log("Error: can not write back config to storage: ", e); } }; -AutomationController.prototype.saveFiles = function () { +AutomationController.prototype.saveFiles = function() { saveObject("files.json", this.files); }; -AutomationController.prototype.start = function (reload) { +AutomationController.prototype.start = function(reload) { var restore = restore || false; // if reload flag is true, overwrite config values first - if (reload){ + if (reload) { console.log("Reload config..."); // Reload config this.reloadConfig(); this.skins = loadObject('userSkins.json') || [{ - name: "default", - title: "Default", - description: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.", - version: "1.0.3", - icon: true, - author: "Martin Vach", - homepage: "http://www.zwave.eu", - active: true - }]; + name: "default", + title: "Default", + description: "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem.", + version: "1.0.3", + icon: true, + author: "Martin Vach", + homepage: "http://www.zwave.eu", + active: true + }]; this.icons = loadObject('userIcons.json') || []; } @@ -253,8 +252,11 @@ AutomationController.prototype.start = function (reload) { console.log("Loading modules..."); this.instantiateModules(); - ZAutomation = function () { - return {status: 400, body: "Invalid ZAutomation request"}; + ZAutomation = function() { + return { + status: 400, + body: "Invalid ZAutomation request" + }; }; ws.allowExternalAccess("ZAutomation", this.auth.ROLE.ANONYMOUS); @@ -272,7 +274,7 @@ AutomationController.prototype.start = function (reload) { this.emit("core.start"); }; -AutomationController.prototype.reloadConfig = function () { +AutomationController.prototype.reloadConfig = function() { var reloadedConfig = loadObject("config.json"); // overwrite variables with restored data @@ -283,7 +285,7 @@ AutomationController.prototype.reloadConfig = function () { this.vdevInfo = reloadedConfig.vdevInfo || config.vdevInfo; }; -AutomationController.prototype.stop = function () { +AutomationController.prototype.stop = function() { var self = this, modWithoutDep = []; @@ -297,15 +299,15 @@ AutomationController.prototype.stop = function () { // Clean instances console.log("Stopping instances with dependencies ..."); - self.instances.forEach(function (instance) { + self.instances.forEach(function(instance) { // first stop instances with dependencies if (self.modules[instance.moduleId]) { if ((instance.active === true || instance.active === 'true') && - _.isArray(self.modules[instance.moduleId].meta.dependencies) && - self.modules[instance.moduleId].meta.dependencies.length > 0) { + _.isArray(self.modules[instance.moduleId].meta.dependencies) && + self.modules[instance.moduleId].meta.dependencies.length > 0) { self.removeInstance(instance.id); - } else if ((instance.active === true || instance.active === 'true')){ + } else if ((instance.active === true || instance.active === 'true')) { modWithoutDep.push(instance.id) } } @@ -329,7 +331,7 @@ AutomationController.prototype.stop = function () { this.emit("core.stop"); }; -AutomationController.prototype.restart = function () { +AutomationController.prototype.restart = function() { this.stop(); this.start(); @@ -338,15 +340,15 @@ AutomationController.prototype.restart = function () { this.addNotification("warning", langFile.ac_warn_restart, "core", "AutomationController"); }; -AutomationController.prototype.loadModules = function (callback) { +AutomationController.prototype.loadModules = function(callback) { console.log("--- Loading ZAutomation classes"); var self = this; - fs.list("modules/").forEach(function (moduleClassName) { + fs.list("modules/").forEach(function(moduleClassName) { self.loadModuleFromFolder(moduleClassName, "modules/"); }); - (fs.list("userModules/") || []).forEach(function (moduleClassName) { + (fs.list("userModules/") || []).forEach(function(moduleClassName) { self.loadModuleFromFolder(moduleClassName, "userModules/"); }); @@ -355,12 +357,12 @@ AutomationController.prototype.loadModules = function (callback) { } }; -AutomationController.prototype.loadModuleFromFolder = function (moduleClassName, folder, ignoreVersion) { +AutomationController.prototype.loadModuleFromFolder = function(moduleClassName, folder, ignoreVersion) { var self = this, langFile = self.loadMainLang(), - values, + values, addModule = false, - ignoreVersion = ignoreVersion? ignoreVersion : false; + ignoreVersion = ignoreVersion ? ignoreVersion : false; var moduleMetaFilename = folder + moduleClassName + "/module.json", _st; @@ -380,7 +382,7 @@ AutomationController.prototype.loadModuleFromFolder = function (moduleClassName, try { var moduleMeta = fs.loadJSON(moduleMetaFilename); } catch (e) { - values = moduleMetaFilename + ": " + e.toString(); + values = moduleMetaFilename + ": " + e.toString(); self.addNotification("error", langFile.ac_err_load_mod_json + values, "core", "AutomationController"); console.log(e.stack); @@ -399,11 +401,11 @@ AutomationController.prototype.loadModuleFromFolder = function (moduleClassName, moduleMeta.location = folder + moduleClassName; // check version before overwriting the already existing module - if (self.modules[moduleClassName] && - self.modules[moduleClassName].meta && - self.modules[moduleClassName].meta.version && - moduleMeta.version) { - + if (self.modules[moduleClassName] && + self.modules[moduleClassName].meta && + self.modules[moduleClassName].meta.version && + moduleMeta.version) { + var existingVersion = self.modules[moduleClassName].meta.version.toString(), currentVersion = moduleMeta.version.toString(); @@ -431,9 +433,9 @@ AutomationController.prototype.loadModuleFromFolder = function (moduleClassName, }; -AutomationController.prototype.instantiateModule = function (instanceModel) { +AutomationController.prototype.instantiateModule = function(instanceModel) { var self = this, - module = _.find(self.modules, function (module) { + module = _.find(self.modules, function(module) { return instanceModel.moduleId === module.meta.id; }), instance = null, @@ -447,7 +449,7 @@ AutomationController.prototype.instantiateModule = function (instanceModel) { } if (module.failed) { - self.addNotification("error", langFile.ac_err_load_failure + (module.meta && module.meta.id? ': ' + module.meta.id : ''), "core", "AutomationController"); + self.addNotification("error", langFile.ac_err_load_failure + (module.meta && module.meta.id ? ': ' + module.meta.id : ''), "core", "AutomationController"); return null; // not loaded } @@ -470,7 +472,7 @@ AutomationController.prototype.instantiateModule = function (instanceModel) { console.log("Instantiating module", instanceModel.id, "from class", module.meta.id); cntExistInst = _.filter(self.instances, function(inst) { - return module.meta.id === inst.moduleId; + return module.meta.id === inst.moduleId; }); if (module.meta.singleton) { @@ -489,8 +491,8 @@ AutomationController.prototype.instantiateModule = function (instanceModel) { // remove singleton entry of broken instance if (module.meta.singleton) { var index = self._loadedSingletons.indexOf(module.meta.id); - - if(index > -1) { + + if (index > -1) { self._loadedSingletons.splice(index, 1); } } @@ -514,7 +516,7 @@ AutomationController.prototype.instantiateModule = function (instanceModel) { } }; -AutomationController.prototype.loadModule = function (module, rootModule, instancesCount) { +AutomationController.prototype.loadModule = function(module, rootModule, instancesCount) { var langFile = this.loadMainLang(), values; @@ -546,10 +548,10 @@ AutomationController.prototype.loadModule = function (module, rootModule, instan return false; } - if (!this.loadedModules.some(function (x) { + if (!this.loadedModules.some(function(x) { return x.meta.id === dep; })) { - + this.addNotification("error", langFile.ac_err_dep_not_init + values, "dependency", module.meta.id); module.failed = true; return false; @@ -578,7 +580,7 @@ AutomationController.prototype.loadModule = function (module, rootModule, instan } // Monkey-patch module with basePath method - _module.prototype.moduleBasePath = function () { + _module.prototype.moduleBasePath = function() { return module.location; }; @@ -589,9 +591,9 @@ AutomationController.prototype.loadModule = function (module, rootModule, instan // Loading instances var count = 0; - this.instances.filter(function (x) { + this.instances.filter(function(x) { return x.moduleId === module.meta.id; - }).forEach(function (x) { + }).forEach(function(x) { if (this.instantiateModule(x) !== null) { count++; } @@ -603,33 +605,33 @@ AutomationController.prototype.loadModule = function (module, rootModule, instan return true; }; -AutomationController.prototype.unloadModule = function (moduleId) { +AutomationController.prototype.unloadModule = function(moduleId) { var self = this, activeInstances = [], result = 'failed'; try { // filter for instances of moduleId - activeInstances = self.instances.filter(function (instance) { + activeInstances = self.instances.filter(function(instance) { return instance.moduleId === moduleId; - }).map(function (instance) { + }).map(function(instance) { return instance.id; }); // remove all instances of moduleId if (activeInstances.length > 0) { - activeInstances.forEach(function (instanceId) { + activeInstances.forEach(function(instanceId) { self.deleteInstance(instanceId); }); } //remove from loaded Modules - self.loadedModules = self.loadedModules.filter(function (module) { + self.loadedModules = self.loadedModules.filter(function(module) { return module.meta.id !== moduleId; }); // remove from modules list - if (self.modules[moduleId]){ + if (self.modules[moduleId]) { delete self.modules[moduleId]; result = 'success'; @@ -651,20 +653,21 @@ AutomationController.prototype.installModule = function(moduleUrl, moduleName) { moduleUrl, moduleName, function() { - result = "done"; - }, function() { - result = "failed"; + result = "done"; + }, + function() { + result = "failed"; } ); - + var d = (new Date()).valueOf() + 20000; // wait not more than 20 seconds - - while ((new Date()).valueOf() < d && result === "in progress") { - processPendingCallbacks(); + + while ((new Date()).valueOf() < d && result === "in progress") { + processPendingCallbacks(); } if (result === "in progress") { - result = "failed"; + result = "failed"; } } @@ -674,7 +677,7 @@ AutomationController.prototype.installModule = function(moduleUrl, moduleName) { AutomationController.prototype.uninstallModule = function(moduleId, reset) { var langFile = this.loadMainLang(), uninstall = false, - reset = reset? reset : false, + reset = reset ? reset : false, unload = this.unloadModule(moduleId), result = "in progress"; @@ -683,32 +686,33 @@ AutomationController.prototype.uninstallModule = function(moduleId, reset) { installer.remove( moduleId, function() { - result = "done"; - }, function() { - result = "failed"; + result = "done"; + }, + function() { + result = "failed"; } ); - + var d = (new Date()).valueOf() + 20000; // wait not more than 20 seconds - - while ((new Date()).valueOf() < d && result === "in progress") { - processPendingCallbacks(); + + while ((new Date()).valueOf() < d && result === "in progress") { + processPendingCallbacks(); } - + if (result === "in progress") { - result = "failed"; + result = "failed"; } if (result === "done") { - if(reset) { + if (reset) { loadSuccessfully = this.loadInstalledModule(moduleId, 'modules/', true); uninstall = loadSuccessfully; } else { uninstall = true; - } - + } + } } catch (e) { console.log('Uninstalling or reseting of app "' + moduleId + '" has failed. ERROR:', e); @@ -719,72 +723,72 @@ AutomationController.prototype.uninstallModule = function(moduleId, reset) { return uninstall; }; -AutomationController.prototype.loadInstalledModule = function (moduleId, rootDirectory, ignoreVersion) { +AutomationController.prototype.loadInstalledModule = function(moduleId, rootDirectory, ignoreVersion) { var self = this, successful = false, - ignoreVersion = ignoreVersion? ignoreVersion : false; + ignoreVersion = ignoreVersion ? ignoreVersion : false; - try{ - if(fs.list(rootDirectory + moduleId) && fs.list(rootDirectory + moduleId).indexOf('index.js') !== -1){ + try { + if (fs.list(rootDirectory + moduleId) && fs.list(rootDirectory + moduleId).indexOf('index.js') !== -1) { console.log('Load app "' + moduleId + '" from folder ...'); successful = self.loadModuleFromFolder(moduleId, rootDirectory, ignoreVersion); - if(successful && self.modules[moduleId]){ + if (successful && self.modules[moduleId]) { self.loadModule(self.modules[moduleId]); successful = true; } } - } catch (e){ + } catch (e) { console.log('Load app "' + moduleId + '" has failed. ERROR:', e); } return successful; }; -AutomationController.prototype.reinitializeModule = function (moduleId, rootDirectory, ignoreVersion) { +AutomationController.prototype.reinitializeModule = function(moduleId, rootDirectory, ignoreVersion) { var self = this, successful = false, existingInstances = [], - ignoreVersion = ignoreVersion? ignoreVersion : false; + ignoreVersion = ignoreVersion ? ignoreVersion : false; // filter for active instances of moduleId - existingInstances = self.instances.filter(function (instance) { + existingInstances = self.instances.filter(function(instance) { return instance.moduleId === moduleId; }); this.unloadModule(moduleId); // try to reinitialize app - try{ - if(fs.list(rootDirectory + moduleId) && fs.list(rootDirectory + moduleId).indexOf('index.js') !== -1){ + try { + if (fs.list(rootDirectory + moduleId) && fs.list(rootDirectory + moduleId).indexOf('index.js') !== -1) { console.log('Load app "' + moduleId + '" from folder ...'); successful = self.loadModuleFromFolder(moduleId, rootDirectory, ignoreVersion); - if(successful && self.modules[moduleId]){ + if (successful && self.modules[moduleId]) { self.loadModule(self.modules[moduleId], undefined, existingInstances.length); // add and start instances of moduleId again - existingInstances.forEach(function (instance) { + existingInstances.forEach(function(instance) { self.createInstance(instance); }); successful = true; } } - } catch (e){ + } catch (e) { console.log('Load app "' + moduleId + '" has failed. ERROR:', e); } return successful; }; -AutomationController.prototype.instantiateModules = function () { +AutomationController.prototype.instantiateModules = function() { var self = this, langFile = this.loadMainLang(), modules = Object.getOwnPropertyNames(this.modules), - requiredBaseModules = ["Cron","ZWave"], + requiredBaseModules = ["Cron", "ZWave"], requiredWithDep = []; this.loadedModules = []; @@ -793,19 +797,19 @@ AutomationController.prototype.instantiateModules = function () { modules.splice(modules.indexOf('ZWave'), 1); // get all required modules from dependencies - Object.getOwnPropertyNames(this.modules).forEach(function (m) { - if (this.modules[m].meta && - this.modules[m].meta.dependencies && - _.isArray(this.modules[m].meta.dependencies) && - this.modules[m].meta.dependencies.length > 0) { + Object.getOwnPropertyNames(this.modules).forEach(function(m) { + if (this.modules[m].meta && + this.modules[m].meta.dependencies && + _.isArray(this.modules[m].meta.dependencies) && + this.modules[m].meta.dependencies.length > 0) { // load if it exists in modules list - requiredBaseModules = _.uniq(requiredBaseModules.concat(_.filter(this.modules[m].meta.dependencies, function(dep){ + requiredBaseModules = _.uniq(requiredBaseModules.concat(_.filter(this.modules[m].meta.dependencies, function(dep) { return self.modules[dep]; }))); // remove all required base modules from modules list - this.modules[m].meta.dependencies.forEach(function(mod){ + this.modules[m].meta.dependencies.forEach(function(mod) { if (modules.indexOf(mod) > -1) { modules.splice(modules.indexOf(mod), 1); } @@ -817,19 +821,19 @@ AutomationController.prototype.instantiateModules = function () { requiredBaseModules.forEach(function(mod) { // prepare base modules with dependencies - if (this.modules[mod].meta && - this.modules[mod].meta.dependencies && - _.isArray(this.modules[mod].meta.dependencies) && - this.modules[mod].meta.dependencies.length > 0) { - + if (this.modules[mod].meta && + this.modules[mod].meta.dependencies && + _.isArray(this.modules[mod].meta.dependencies) && + this.modules[mod].meta.dependencies.length > 0) { + // cache required modules with dependencies - if (requiredWithDep.indexOf(mod) < 0){ + if (requiredWithDep.indexOf(mod) < 0) { requiredWithDep.push(mod); } } else { // load base modules without dependencies first this.loadModule(this.modules[mod]); - } + } }, this); // instantiate all required with dependencies @@ -843,11 +847,11 @@ AutomationController.prototype.instantiateModules = function () { }, this); }; -AutomationController.prototype.moduleInstance = function (instanceId) { +AutomationController.prototype.moduleInstance = function(instanceId) { return this.instances.hasOwnProperty(instanceId) ? this.instances[instanceId] : null; }; -AutomationController.prototype.registerInstance = function (instance) { +AutomationController.prototype.registerInstance = function(instance) { var self = this, langFile = this.loadMainLang(); @@ -866,18 +870,18 @@ AutomationController.prototype.registerInstance = function (instance) { } }; -AutomationController.prototype.listInstances = function (){ +AutomationController.prototype.listInstances = function() { var self = this, expInstances = []; - if(self.instances) { - self.instances.forEach(function (instance){ + if (self.instances) { + self.instances.forEach(function(instance) { var moduleJSON = self.getModuleData(instance.moduleId); expInstances.push(_.extend(instance, { // use category from module and use it's title as fallback ... - category : moduleJSON && moduleJSON.category || null, - title : (!instance.title || instance.title === '') ? ((moduleJSON.defaults && moduleJSON.defaults.title) ? moduleJSON.defaults.title : "?") : instance.title + category: moduleJSON && moduleJSON.category || null, + title: (!instance.title || instance.title === '') ? ((moduleJSON.defaults && moduleJSON.defaults.title) ? moduleJSON.defaults.title : "?") : instance.title })); }); } else { @@ -887,24 +891,14 @@ AutomationController.prototype.listInstances = function (){ return expInstances; } -AutomationController.prototype.createInstance = function (reqObj) { - - getNextId = function() { - var id = 0; - self.instances.forEach(function (instance) { - if (instance.id > id) { - id = instance.id; - } - }); - return id+1; - } +AutomationController.prototype.createInstance = function(reqObj) { //var instance = this.instantiateModule(id, className, config), var self = this, langFile = this.loadMainLang(), - id = getNextId(), + id = findSmallestNotAssignedIntegerValue(self.instances, 'id'), instance = null, - module = _.find(self.modules, function (module) { + module = _.find(self.modules, function(module) { return module.meta.id === reqObj.moduleId; }), result, @@ -913,29 +907,29 @@ AutomationController.prototype.createInstance = function (reqObj) { if (!!module) { if (reqObj.id) { - alreadyExisting = _.filter(this.instances, function(inst){ + alreadyExisting = _.filter(this.instances, function(inst) { return inst.id === reqObj.id }); } - instance = _.extend(reqObj, { - id: alreadyExisting[0]? alreadyExisting[0].id : id, - active: reqObj.active === 'true' || reqObj.active? true : false + instance = _.extend(reqObj, { + id: alreadyExisting[0] ? reqObj.id : id, + active: reqObj.active === 'true' || reqObj.active ? true : false }); self.instances.push(instance); self.saveConfig(); - self.emit('core.instanceCreated', id); + self.emit('core.instanceCreated', instance.id); result = self.instantiateModule(instance); // remove instance from list if broken if (result === null) { var currIndex = self.instances.length ? self.instances.length - 1 : 0; - + self.instances.splice(currIndex, 1); self.saveConfig(); self.emit('core.instanceDeleted', id); - } + } } else { self.emit('core.error', new Error(langFile.ac_err_create_instance + reqObj.moduleId + " :: " + id)); result = false; @@ -944,7 +938,7 @@ AutomationController.prototype.createInstance = function (reqObj) { return result; }; -AutomationController.prototype.stopInstance = function (instance) { +AutomationController.prototype.stopInstance = function(instance) { var self = this, langFile = this.loadMainLang(), instId = instance.id, @@ -955,15 +949,15 @@ AutomationController.prototype.stopInstance = function (instance) { delete this.registerInstances[instId]; // get all devices created by instance - instDevices = _.map(this.devices.filter(function (dev) { + instDevices = _.map(this.devices.filter(function(dev) { return dev.get('creatorId') === instId; - }), function (dev) { + }), function(dev) { return dev.id; }); // cleanup devices if (instDevices.length > 0) { - instDevices.forEach(function (id) { + instDevices.forEach(function(id) { // check for device entry again if (!!self.devices.get(id)) { self.devices.remove(id); @@ -980,10 +974,10 @@ AutomationController.prototype.stopInstance = function (instance) { } }; -AutomationController.prototype.reconfigureInstance = function (id, instanceObject) { +AutomationController.prototype.reconfigureInstance = function(id, instanceObject) { var langFile = this.loadMainLang(), register_instance = this.registerInstances[id], - instance = _.find(this.instances, function (model) { + instance = _.find(this.instances, function(model) { return model.id === id; }), index = this.instances.indexOf(instance), @@ -995,23 +989,23 @@ AutomationController.prototype.reconfigureInstance = function (id, instanceObjec this.stopInstance(register_instance); } - if(instanceObject.hasOwnProperty('params')){ - if(Object.keys(instanceObject.params).length === 0){ + if (instanceObject.hasOwnProperty('params')) { + if (Object.keys(instanceObject.params).length === 0) { config = instanceObject.params; - }else { + } else { for (var property in instance.params) { - config[property] = instanceObject.params.hasOwnProperty(property) && instanceObject.params[property] !== instance.params[property]? instanceObject.params[property] :instance.params[property]; + config[property] = instanceObject.params.hasOwnProperty(property) && instanceObject.params[property] !== instance.params[property] ? instanceObject.params[property] : instance.params[property]; } } } _.extend(this.instances[index], { - title: instanceObject.hasOwnProperty('title')? instanceObject.title : instance.title, - description: instanceObject.hasOwnProperty('description')? instanceObject.description : instance.description, - active: instanceObject.hasOwnProperty('active')? instanceObject.active : instance.active, - params: config !== {}? config : instance.params + title: instanceObject.hasOwnProperty('title') ? instanceObject.title : instance.title, + description: instanceObject.hasOwnProperty('description') ? instanceObject.description : instance.description, + active: instanceObject.hasOwnProperty('active') ? instanceObject.active : instance.active, + params: config !== {} ? config : instance.params }); - + if (!!register_instance) { if (this.instances[index].active) { // here we read new config instead of existing register_instance.init(config); @@ -1036,13 +1030,13 @@ AutomationController.prototype.reconfigureInstance = function (id, instanceObjec return result; }; -AutomationController.prototype.removeInstance = function (id) { +AutomationController.prototype.removeInstance = function(id) { var instance = this.registerInstances[id], getInstFromList = []; - getInstFromList = this.instances.filter(function (model) { - return id === model.id; - }); + getInstFromList = this.instances.filter(function(model) { + return id === model.id; + }); if (!!instance) { this.stopInstance(instance); @@ -1061,22 +1055,22 @@ AutomationController.prototype.removeInstance = function (id) { } }; -AutomationController.prototype.deleteInstance = function (id) { +AutomationController.prototype.deleteInstance = function(id) { var instDevices = [], self = this; - + // get all devices created by instance instDevices = this.devices.filterByCreatorId(id); - + this.removeInstance(id); - this.instances = this.instances.filter(function (model) { + this.instances = this.instances.filter(function(model) { return id !== model.id; }); // cleanup if (instDevices.length > 0) { - instDevices.forEach(function (vDev) { + instDevices.forEach(function(vDev) { // check for vDevInfo entry if (self.vdevInfo[vDev.id]) { self.devices.remove(vDev.id); @@ -1093,31 +1087,32 @@ AutomationController.prototype.installSkin = function(reqObj, skinName, index) { var result = "in progress"; if (reqObj.file_path) { - + console.log('Installing skin', skinName, '...'); skininstaller.install( reqObj.file_path, skinName, function() { - result = "done"; - }, function() { - result = "failed"; + result = "done"; + }, + function() { + result = "failed"; } ); - + var d = (new Date()).valueOf() + 20000; // wait not more than 20 seconds - - while ((new Date()).valueOf() < d && result === "in progress") { - processPendingCallbacks(); + + while ((new Date()).valueOf() < d && result === "in progress") { + processPendingCallbacks(); } if (result === "in progress") { - result = "failed"; + result = "failed"; } if (result === 'done') { - if (index < 0){ + if (index < 0) { // add new skin newSkin = { name: '', @@ -1164,14 +1159,15 @@ AutomationController.prototype.uninstallSkin = function(skinName) { skinName, function() { result = "done"; - }, function() { + }, + function() { result = "failed"; } ); var d = (new Date()).valueOf() + 20000; // wait not more than 20 seconds - while ((new Date()).valueOf() < d && result === "in progress") { + while ((new Date()).valueOf() < d && result === "in progress") { processPendingCallbacks(); } @@ -1181,7 +1177,7 @@ AutomationController.prototype.uninstallSkin = function(skinName) { //if (result === "done") { - this.skins = _.filter(this.skins, function (skin) { + this.skins = _.filter(this.skins, function(skin) { return skin.name !== skinName; }); @@ -1208,15 +1204,15 @@ AutomationController.prototype.setSkinState = function(skinName, reqObj) { if (reqObj.hasOwnProperty('active')) { - _.forEach(this.skins, function (skin) { + _.forEach(this.skins, function(skin) { if (reqObj.active === true || reqObj.active === 'true') { // activate target skin and deactivate all others - skin.active = skin.name === skinName? true : false; - res = skin.name === skinName? skin : res; + skin.active = skin.name === skinName ? true : false; + res = skin.name === skinName ? skin : res; } else { // deactivate all skins and set default skin to active: true - skin.active = skin.name === 'default'? true : false; - res = skin.name === 'default'? skin : res; + skin.active = skin.name === 'default' ? true : false; + res = skin.name === 'default' ? skin : res; } }) @@ -1227,18 +1223,31 @@ AutomationController.prototype.setSkinState = function(skinName, reqObj) { }; AutomationController.prototype.installIcon = function(option, reqObj, iconName, id) { - var result = "in progress", + var reply = { + message: "in progress", + files: [] + }, filelist = [], input = "", name = "", update = false; - switch(option) { + extensionToLower = function(name) { + var arr = name.split("."); + arr[arr.length - 1] = arr[arr.length - 1].toLowerCase(); + if (arr[arr.length - 1] == "gz" && arr.length > 2 && arr[arr.length - 2].toLowerCase() == "tar") { + arr[arr.length - 2] == arr[arr.length - 2].toLowerCase(); + } + return arr.join("."); + }; + + switch (option) { case 'remote': input = reqObj.file_path; name = iconName break; case 'local': + reqObj.name = extensionToLower(reqObj.name); input = JSON.stringify(reqObj); name = reqObj.name; break; @@ -1253,35 +1262,38 @@ AutomationController.prototype.installIcon = function(option, reqObj, iconName, id, function(success) { filelist = parseToObject(success); - result = "done"; - }, function() { - result = "failed"; + reply.message = "done"; + }, + function() { + reply.message = "failed"; } ); var d = (new Date()).valueOf() + 20000; // wait not more than 20 seconds - while ((new Date()).valueOf() < d && result === "in progress") { + while ((new Date()).valueOf() < d && reply.message === "in progress") { processPendingCallbacks(); } - if (result === "in progress") { - result = "failed"; + if (reply.message === "in progress") { + reply.message = "failed"; } - if (result === 'done') { + if (reply.message === 'done') { for (var file in filelist) { if (filelist[file].filename && filelist[file].orgfilename) { var icon = { 'file': filelist[file].filename, 'orgfile': filelist[file].orgfilename, - 'source': iconName+"_"+id, + 'source': iconName + "_" + id, 'name': iconName, 'id': id, 'timestamp': Math.floor(new Date().getTime() / 1000), - 'source_title': option === "local" ? iconName+" "+id : reqObj.title + 'source_title': option === "local" ? iconName + " " + id : reqObj.title }; + reply.files.push(filelist[file].filename); + this.icons.push(icon); update = true; } @@ -1292,7 +1304,12 @@ AutomationController.prototype.installIcon = function(option, reqObj, iconName, } } } - return result; + + if (reply.files.length == 1) { + reply.files = reply.files[0]; + } + + return reply; }; AutomationController.prototype.listIcons = function() { @@ -1312,15 +1329,15 @@ AutomationController.prototype.listIcons = function() { var d = (new Date()).valueOf() + 20000; // wait not more than 20 seconds - while ((new Date()).valueOf() < d && result === "in progress") { + while ((new Date()).valueOf() < d && result === "in progress") { processPendingCallbacks(); } - if(result == "in progress") { + if (result == "in progress") { result = "failed"; } - } catch(e) { + } catch (e) { console.log(e) } @@ -1336,8 +1353,9 @@ AutomationController.prototype.uninstallIcon = function(iconName) { iconName, function(success) { result = "done"; - }, function(error) { - if(error == "No such icon.") { + }, + function(error) { + if (error == "No such icon.") { result = "done"; } else { result = "failed"; @@ -1347,7 +1365,7 @@ AutomationController.prototype.uninstallIcon = function(iconName) { var d = (new Date()).valueOf() + 20000; // wait not more than 20 seconds - while ((new Date()).valueOf() < d && result === "in progress") { + while ((new Date()).valueOf() < d && result === "in progress") { processPendingCallbacks(); } @@ -1356,14 +1374,14 @@ AutomationController.prototype.uninstallIcon = function(iconName) { } //if (result === "done") { - this.icons = _.filter(this.icons, function (icon) { + this.icons = _.filter(this.icons, function(icon) { return icon.file !== iconName; }); saveObject("userIcons.json", this.icons); //} - } catch(e) { + } catch (e) { console.log('Uninstalling or reseting of icon "' + iconName + '" has failed. ERROR:', e); this.addNotification("error", langFile.ac_err_uninstall_icon + ': ' + iconName, "core", "AutomationController"); } @@ -1374,26 +1392,30 @@ AutomationController.prototype.uninstallIcon = function(iconName) { AutomationController.prototype.deleteCustomicon = function(iconName) { self = this; self.devices.each(function(dev) { - if(!_.isEmpty(dev.get('customIcons'))) { + if (!_.isEmpty(dev.get('customIcons'))) { var customIcon = dev.get('customIcons'); _.each(customIcon, function(value, key) { - if(typeof value !== "object") { - if(value === iconName) { + if (typeof value !== "object") { + if (value === iconName) { customIcon = {}; - dev.set('customIcons', customIcon, {silent:true}); + dev.set('customIcons', customIcon, { + silent: true + }); return false; } } else { _.each(value, function(icon, level) { - if(icon === iconName) { + if (icon === iconName) { delete customIcon[key][level]; } }); - if(_.isEmpty(customIcon[key])) { + if (_.isEmpty(customIcon[key])) { customIcon = {}; } - dev.set('customIcons', customIcon, {silent:true}); + dev.set('customIcons', customIcon, { + silent: true + }); } }); } @@ -1403,54 +1425,56 @@ AutomationController.prototype.deleteCustomicon = function(iconName) { AutomationController.prototype.deleteAllCustomicons = function() { self = this; self.devices.each(function(dev) { - dev.set('customIcons', {}, {silent:true}); + dev.set('customIcons', {}, { + silent: true + }); }); } -AutomationController.prototype.deviceExists = function (vDevId) { +AutomationController.prototype.deviceExists = function(vDevId) { return Object.keys(this.devices).indexOf(vDevId) >= 0; }; -AutomationController.prototype.getVdevInfo = function (id) { +AutomationController.prototype.getVdevInfo = function(id) { return this.vdevInfo[id] || {}; }; -AutomationController.prototype.setVdevInfo = function (id, device) { - this.vdevInfo[id] = _.pick(device, - "deviceType", - "metrics", - "location", - "tags", - "permanently_hidden", - "creationTime", - "customIcons", - "order", - "visibility", - "hasHistory"); +AutomationController.prototype.setVdevInfo = function(id, device) { + this.vdevInfo[id] = _.pick(device, + "deviceType", + "metrics", + "location", + "tags", + "permanently_hidden", + "creationTime", + "customIcons", + "order", + "visibility", + "hasHistory"); this.saveConfig(); return this.vdevInfo[id]; }; -AutomationController.prototype.clearVdevInfo = function (id) { +AutomationController.prototype.clearVdevInfo = function(id) { delete this.vdevInfo[id]; this.saveConfig(); }; -AutomationController.prototype.loadNotifications = function () { +AutomationController.prototype.loadNotifications = function() { //this.notifications = loadObject("notifications") || []; this.notifications = new LimitedArray( loadObject("notifications") || [], - function (arr) { + function(arr) { saveObject('notifications', arr); }, 25, // check it every 25 notifications 2500, // save up to 2500 notifications - function (notification){ + function(notification) { var now = new Date(), - startOfDay = now.setHours(0,0,0,0), - s_tsSevenDaysBefore = Math.floor(startOfDay /1000) - 86400*6, // fallback for older versions - ms_tsSevenDaysBefore = Math.floor(startOfDay) - 86400000*6; + startOfDay = now.setHours(0, 0, 0, 0), + s_tsSevenDaysBefore = Math.floor(startOfDay / 1000) - 86400 * 6, // fallback for older versions + ms_tsSevenDaysBefore = Math.floor(startOfDay) - 86400000 * 6; return (notification.id.toString().length <= 10 && notification.id >= s_tsSevenDaysBefore) || (notification.id.toString().length > 10 && notification.id >= ms_tsSevenDaysBefore); @@ -1458,21 +1482,46 @@ AutomationController.prototype.loadNotifications = function () { ); }; -AutomationController.prototype.addNotification = function (severity, message, type, source) { +AutomationController.prototype.addNotification = function(severity, message, type, source) { + var self = this; + + this.prepareMessage = function(message) { + var regex = /<<[^:>>]*\:[^:<<]*>>/; + count = 0; + do { + search = regex.exec(message); + if (search) { + var replace = ''; + var dev = search[0].substring(2,search[0].length-2).split(':'); + if (dev[0] != '' && (dev[1] == 'level' || dev[1] == 'title' || dev[1] == 'scaleTitle')) { + var vDev = self.devices.get(dev[0]); + if (vDev) { + replace = vDev.get('metrics:'+dev[1]); + } + } + if (replace == '') + replace = 'unknown'; + message = message.replace(search[0],replace); + } + count++; + } while(search && count < 50); + return message; + } + var now = new Date(), notice = { id: Math.floor(now.getTime()), timestamp: now.toISOString(), level: severity, - message: message, + message: this.prepareMessage(message), type: type || 'device', source: source, redeemed: false }; - if(typeof message === 'object'){ + if (typeof message === 'object') { msg = JSON.stringify(message); - }else{ + } else { msg = message; } @@ -1482,19 +1531,19 @@ AutomationController.prototype.addNotification = function (severity, message, ty console.log("Notification:", severity, "(" + type + "):", msg); }; -AutomationController.prototype.deleteNotifications = function (ts, before, callback) { - var before = Boolean(before)|| false, +AutomationController.prototype.deleteNotifications = function(ts, before, callback) { + var before = Boolean(before) || false, newNotificationList = [], ts = parseInt(ts) || 0; if (ts !== 0) { if (before) { - newNotificationList = this.notifications.get().filter(function (notification) { + newNotificationList = this.notifications.get().filter(function(notification) { return notification.id >= ts; }); console.log('---------- all notifications before ' + ts + ' deleted ----------'); } else { - newNotificationList = this.notifications.get().filter(function (notification) { + newNotificationList = this.notifications.get().filter(function(notification) { return notification.id !== ts; }); console.log('---------- notification with id ' + ts + ' deleted ----------'); @@ -1508,7 +1557,7 @@ AutomationController.prototype.deleteNotifications = function (ts, before, callb } }; -AutomationController.prototype.deleteAllRedeemedNotifications = function (callback) { +AutomationController.prototype.deleteAllRedeemedNotifications = function(callback) { try { this.notifications.set(this.notifications.get().filter(function(notification) { return !notification.redeemed; @@ -1526,13 +1575,15 @@ AutomationController.prototype.deleteAllRedeemedNotifications = function (callba } }; -AutomationController.prototype.redeemNotification = function (id, redeemed, callback) { - var r = redeemed !== undefined? redeemed : true; - id = id || 0; +AutomationController.prototype.redeemNotification = function(id, redeemed, callback) { + var r = redeemed !== undefined ? redeemed : true; + id = id || 0; if (id > 0) { var notifications = this.notifications.get(), - index= _.findIndex(notifications, { id: id }); + index = _.findIndex(notifications, { + id: id + }); notifications[index].redeemed = r; @@ -1548,13 +1599,13 @@ AutomationController.prototype.redeemNotification = function (id, redeemed, call } }; -AutomationController.prototype.redeemAllNotifications = function (redeemed, callback) { - var r = redeemed !== undefined? redeemed : true; +AutomationController.prototype.redeemAllNotifications = function(redeemed, callback) { + var r = redeemed !== undefined ? redeemed : true; try { var notifications = this.notifications.get(); - _.forEach(notifications, function (notification) { + _.forEach(notifications, function(notification) { notification.redeemed = r; }); @@ -1563,32 +1614,34 @@ AutomationController.prototype.redeemAllNotifications = function (redeemed, call if (typeof callback === 'function') { callback(true); } - } catch(e) { + } catch (e) { if (typeof callback === 'function') { callback(false); } } }; -AutomationController.prototype.getLocation = function (locations, locationId) { +AutomationController.prototype.getLocation = function(locations, locationId) { var location = [], nspc = null; - location = locations.filter(function (location) { + location = locations.filter(function(location) { if (locationId === 'globalRoom') { return location.id === 0 && - location.title === locationId; + location.title === locationId; } else { return location.id === locationId; } }); - return location[0]? location[0] : null; + return location[0] ? location[0] : null; } -AutomationController.prototype.addLocation = function (locProps, callback) { - var id = this.locations.length > 0 ? Math.max.apply(null, this.locations.map(function (location) { return location.id; })) + 1 : 1; // changed after adding global room with id=0 || old: this.locations.length ? this.locations[this.locations.length - 1].id + 1 : 1; - var locations = this.locations.filter(function (location) { +AutomationController.prototype.addLocation = function(locProps, callback) { + var id = this.locations.length > 0 ? Math.max.apply(null, this.locations.map(function(location) { + return location.id; + })) + 1 : 1; // changed after adding global room with id=0 || old: this.locations.length ? this.locations[this.locations.length - 1].id + 1 : 1; + var locations = this.locations.filter(function(location) { return location.id === id; }); @@ -1605,35 +1658,35 @@ AutomationController.prototype.addLocation = function (locProps, callback) { default_img: locProps.default_img || '', img_type: locProps.img_type || '', show_background: locProps.show_background || false, - main_sensors: locProps.main_sensors || [] + main_sensors: locProps.main_sensors || [] }; - + this.locations.push(location); - + if (typeof callback === 'function') { callback(location); } - + this.saveConfig(); this.emit('location.added', id); } }; -AutomationController.prototype.removeLocation = function (id, callback) { +AutomationController.prototype.removeLocation = function(id, callback) { var self = this, langFile = this.loadMainLang(), - locations = this.locations.filter(function (location) { - return location.id === id; - }); + locations = this.locations.filter(function(location) { + return location.id === id; + }); if (locations.length > 0) { - Object.keys(this.devices).forEach(function (vdevId) { + Object.keys(this.devices).forEach(function(vdevId) { var vdev = self.devices[vdevId]; if (vdev.location === id) { vdev.location = 0; } }); - this.locations = this.locations.filter(function (location) { + this.locations = this.locations.filter(function(location) { return location.id !== id; }); @@ -1650,9 +1703,9 @@ AutomationController.prototype.removeLocation = function (id, callback) { } }; -AutomationController.prototype.updateLocation = function (id, title, user_img, default_img, img_type,show_background, main_sensors, callback) { +AutomationController.prototype.updateLocation = function(id, title, user_img, default_img, img_type, show_background, main_sensors, callback) { var langFile = this.loadMainLang(), - locations = this.locations.filter(function (location) { + locations = this.locations.filter(function(location) { return location.id === id; }); @@ -1662,13 +1715,13 @@ AutomationController.prototype.updateLocation = function (id, title, user_img, d location.title = title; location.show_background = show_background; location.main_sensors = main_sensors; - if (typeof user_img === 'string' && user_img.length > 0) { + if (typeof user_img === 'string') { location.user_img = user_img; } - if (typeof default_img === 'string' && default_img.length > 0) { + if (typeof default_img === 'string') { location.default_img = default_img; } - if (typeof img_type === 'string' && img_type.length > 0) { + if (typeof img_type === 'string') { location.img_type = img_type; } if (typeof callback === 'function') { @@ -1685,34 +1738,34 @@ AutomationController.prototype.updateLocation = function (id, title, user_img, d } }; -AutomationController.prototype.listNotifications = function (since, to) { +AutomationController.prototype.listNotifications = function(since, to) { var now = new Date(); - + since = parseInt(since) || 0; - to = parseInt(to) || Math.floor(now.getTime() /1000); + to = parseInt(to) || Math.floor(now.getTime() / 1000); - return this.notifications.get().filter(function (notification) { + return this.notifications.get().filter(function(notification) { return notification.id >= since && notification.id <= to; }); }; -AutomationController.prototype.getNotification = function (id) { - var filteredNotifications = this.notifications.get().filter(function (notification) { +AutomationController.prototype.getNotification = function(id) { + var filteredNotifications = this.notifications.get().filter(function(notification) { return parseInt(notification.id) === parseInt(id); }); return filteredNotifications[0] || null; }; -AutomationController.prototype.updateNotification = function (id, object, callback) { - var filteredNotifications = _.find(this.notifications.get(), function (notification) { +AutomationController.prototype.updateNotification = function(id, object, callback) { + var filteredNotifications = _.find(this.notifications.get(), function(notification) { return parseInt(notification.id) === parseInt(id); }), index = this.notifications.get().indexOf(filteredNotifications); if (object.hasOwnProperty('redeemed')) { this.notifications.get()[index].redeemed = object.redeemed; - + if (typeof callback === 'function') { callback(this.notifications.get()[index]); } @@ -1723,127 +1776,54 @@ AutomationController.prototype.updateNotification = function (id, object, callba } }; -AutomationController.prototype.listHistories = function () { - return this.history; -}; - -AutomationController.prototype.setHistory = function () { - this.history = loadObject('history') || []; - return this.history; -}; - -AutomationController.prototype.deleteDevHistory = function (vDevId) { - var self = this, - vDevId = vDevId || null; - success = false; - - if (!!vDevId) { - //clear entries of single vDev - index = _.findIndex(self.history, { id: vDevId }); - - if (index > -1) { - self.history[index].mH = []; - - success = true; - - console.log('History of ' + vDevId + ' successful deleted ...'); - } - } else { - //clear all entries - self.history.forEach(function (devHist) { - devHist.mH = []; - }); - - success = true; - - console.log('All histories successful deleted ...'); - } - - //save history - if (success) { - saveObject('history', self.history); - } - - return success; -}; - -AutomationController.prototype.getDevHistory = function (dev, since, show) { - var filteredEntries = [], - averageEntries = [], - entries = [], - now = Math.floor(new Date().getTime() / 1000), - l = 0, - cnt = 0, - metric = {}, - since = since? since : 0, - items = show? show : 0, - sec = 0; - - // create output with n (= show) values - 288, 96, 48, 24, 12, 6 - if(items > 0 && items <= 288){ - sec = 86400 / show; // calculate seconds of range - - // calculate averaged value of all meta values between 'sec' range - for (i = 0; i < items; i++){ - from = now - sec*(items - i); - to = now - sec*(items - (i+1)); - - // filter values between from and to - range = dev[0]['mH'].filter(function (metric){ - return metric.id >= from && metric.id <= to; - }); - - cnt = range.length; - - // calculate level - if(cnt > 0){ - - for(j=0; j < cnt; j++){ - l += range[j]['l']; - } - - l = l /cnt; - - if(l === +l && l !== (l|0)) { // round to one position after '.' - l = l.toFixed(1); - } - } else { - l = null; - } - - metric = { - id: to, - l: parseFloat(l) - } - - averageEntries.push(metric); - - l = 0; - metric = {}; - } - - entries = averageEntries; - } else { - entries = dev[0]['mH']; +// get list of files from storage that should be ignored during e.g. backup, restore +AutomationController.prototype.getIgnoredStorageFiles = function(list) { + var dontSave = [ + "notifications", + "8084AccessTimeout", + "expertconfig.json", + "de.devices.json", + "en.devices.json", + "history", + "postfix.json" + ], // objects that should be ignored + dynamicMatches = [ + "incomingPacket.json", + "outgoingPacket.json", + "originPackets.json", + "parsedPackets.json", + "reorgLog", + "rssidata.json", + "vendors.json", + "history_" + ], + storageList = __storageContent; + matches = []; + + // add additional list of ignored storage files + dontSave = list && _.isArray(list) ? _.uniq(dontSave.concat(list)) : dontSave; + + // apply list of dynamic matches to ignore list + if (storageList) { + _.forEach(dynamicMatches, function(match) { + matches = _.uniq(matches.concat(_.filter(storageList, function(name) { + return name.indexOf(match) > -1; + }))); + }); } - // filter meta entries by since - filteredEntries = since > 0? entries.filter(function (metric) { - return metric.id >= since; - }) : entries; - - return filteredEntries; + return _.uniq(dontSave.concat(matches)); }; -AutomationController.prototype.getListProfiles = function () { +AutomationController.prototype.getListProfiles = function() { var getProfiles = []; - this.profiles.forEach(function (profile){ + this.profiles.forEach(function(profile) { var prof = {}, excl = ["login", "password", "role"]; - + for (var property in profile) { - if(excl.indexOf(property) === -1){ + if (excl.indexOf(property) === -1) { prof[property] = profile[property]; } } @@ -1853,26 +1833,26 @@ AutomationController.prototype.getListProfiles = function () { return getProfiles; }; -AutomationController.prototype.getProfile = function (id) { - return _.find(this.profiles, function (profile) { - return profile.id === parseInt(id); - }) || null; +AutomationController.prototype.getProfile = function(id) { + return _.find(this.profiles, function(profile) { + return profile.id === parseInt(id); + }) || null; }; -AutomationController.prototype.createProfile = function (profile) { +AutomationController.prototype.createProfile = function(profile) { var id = 0, globalRoom = [0], - profileIds = _.map(this.profiles, function(pro){ - return parseInt(pro.id); - }); + profileIds = _.map(this.profiles, function(pro) { + return parseInt(pro.id); + }); // create latest id - id = profileIds.length > 0? Math.max.apply(null, profileIds) + 1 : 1; - + id = profileIds.length > 0 ? Math.max.apply(null, profileIds) + 1 : 1; + profile.id = id; if (profile.role === 1) { - profile.rooms = profile.rooms.indexOf(0) > -1? profile.rooms : profile.rooms.concat(globalRoom); + profile.rooms = profile.rooms.indexOf(0) > -1 ? profile.rooms : profile.rooms.concat(globalRoom); } profile.salt = generateSalt(); @@ -1884,28 +1864,28 @@ AutomationController.prototype.createProfile = function (profile) { return profile; }; -AutomationController.prototype.updateProfile = function (object, id) { - var profile = _.find(this.profiles, function (profile) { +AutomationController.prototype.updateProfile = function(object, id) { + var profile = _.find(this.profiles, function(profile) { return profile.id === parseInt(id); }), index; - + if (profile) { index = this.profiles.indexOf(profile); - + for (var property in object) { if (object.hasOwnProperty(property) && profile.hasOwnProperty(property)) { this.profiles[index][property] = object[property]; } } } - + this.saveConfig(); return this.profiles[index]; }; -AutomationController.prototype.updateProfileAuth = function (object, id) { - var profile = _.find(this.profiles, function (profile) { +AutomationController.prototype.updateProfileAuth = function(object, id) { + var profile = _.find(this.profiles, function(profile) { return profile.id === parseInt(id); }), index; @@ -1914,7 +1894,7 @@ AutomationController.prototype.updateProfileAuth = function (object, id) { index = this.profiles.indexOf(profile); p = this.profiles[index]; - + if (object.hasOwnProperty('password') && object.password !== '' && !!object.password) { p.salt = generateSalt(); p.password = hashPassword(object.password, p.salt); @@ -1923,21 +1903,17 @@ AutomationController.prototype.updateProfileAuth = function (object, id) { p.login = object.login; } - if (!checkBoxtype('cit')){ - this.addQRCode(p, object); - } - this.saveConfig(); - + return p; } else { return null; } }; -AutomationController.prototype.removeProfile = function (profileId) { +AutomationController.prototype.removeProfile = function(profileId) { var that = this; - this.profiles = this.profiles.filter(function (profile) { + this.profiles = this.profiles.filter(function(profile) { return profile.id !== profileId; }); @@ -1970,10 +1946,23 @@ AutomationController.prototype.removeProfile = function (profileId) { return forward; };*/ -AutomationController.prototype.addQRCode = function(profile, obj) { - var typeNumber = 15, - errorCorrectionLevel = 'H', - data = { +AutomationController.prototype.getIPAddress = function() { + var ip = false; + try { + if (checkBoxtype('poppbox')) { + ip = system(". /lib/functions/network.sh; network_get_ipaddr ip wan; echo $ip")[1].replace(/[\s\n]/g, ''); + } else { + ip = system("ip a s dev eth0 | sed -n 's/.*inet \\([0-9.]*\\)\\/.*/\\1/p' | head -n 1")[1].replace(/[\s\n]/g, ''); + } + } catch (e) { + console.log(e); + } + + return ip; +} + +AutomationController.prototype.getQRCodeData = function(profile, password) { + var data = { id: "", login: "", service: "find.z-wave.me", @@ -1982,67 +1971,60 @@ AutomationController.prototype.addQRCode = function(profile, obj) { wpa: "", passwd: "" }, - url = ""; + url = "", + ip = ""; - data.passwd = obj.password; + data.passwd = password; data.login = profile.login; data.id = this.getRemoteId(); - var qr = qrcode(typeNumber, errorCorrectionLevel); + ip = this.getIPAddress(); + if (ip) { + data.ip = ip; + } - var url = Object.keys(data).map(function(key){ + url = Object.keys(data).map(function(key) { return encodeURIComponent(key) + '=' + encodeURIComponent(data[key]); }).join('&'); - qr.addData(url); - qr.make(); - - var qrcodeBase64 = qr.createImgTag(3); - - var file = ""; - if(!profile.hasOwnProperty('qrcode') || profile.qrcode === "") { - file = data.login + new Date().getTime()+ ".gif"; //Loginname + timespame + file extension(gif) - } else { - file = profile.qrcode - } + url = Base64.encode(url); - profile.qrcode = file; - saveObject(file ,qrcodeBase64.toString()); + return url; } // namespaces -AutomationController.prototype.generateNamespaces = function (callback, device, locationNspcOnly) { +AutomationController.prototype.generateNamespaces = function(callback, device, locationNspcOnly) { var that = this, devStillExists = that.devices.get(device.id), - locationNspcOnly = locationNspcOnly? locationNspcOnly : false, + locationNspcOnly = locationNspcOnly ? locationNspcOnly : false, nspcArr = [], locNspcArr = [], devLocation = device.get('location'), location = that.getLocation(that.locations, devLocation), devHidden = device.get('permanently_hidden') || device.get('metrics:removed'); - if (!!location && !location.namespaces) { - location.namespaces = []; - } + if (!!location && !location.namespaces) { + location.namespaces = []; + } if (device) { - this.genNspc = function (nspc,vDev) { + this.genNspc = function(nspc, vDev) { var devTypeEntry = 'devices_' + vDev.get('deviceType'), devProbeType = vDev.get('probeType'), devEntry = { deviceId: vDev.id, deviceName: vDev.get('metrics:title') }, - addRemoveEntry = function (entryArr) { + addRemoveEntry = function(entryArr) { var exists = []; - + // check if entry already exists exists = _.filter(entryArr, function(entry) { return entry.deviceId === devEntry.deviceId; }); - if (!!devStillExists && exists.length < 1 && !devHidden){ + if (!!devStillExists && exists.length < 1 && !devHidden) { // add entry entryArr.push(devEntry); } else if (!!devStillExists && exists[0] && !devHidden) { @@ -2059,7 +2041,7 @@ AutomationController.prototype.generateNamespaces = function (callback, device, return entryArr; }, - deleteEmptyProp = function (object, key) { + deleteEmptyProp = function(object, key) { // delete empty CC type entries if ((_.isArray(object[key]) && object[key].length < 1) || (!_.isArray(object[key]) && Object.keys(object[key]).length < 1)) { delete object[key]; @@ -2071,39 +2053,39 @@ AutomationController.prototype.generateNamespaces = function (callback, device, paramEntry; // check for type entry - typeEntryExists = _.filter(nspc, function(typeEntry){ + typeEntryExists = _.filter(nspc, function(typeEntry) { return typeEntry.id === devTypeEntry; }); if (typeEntryExists.length > 0) { - paramEntry = typeEntryExists[0] && typeEntryExists[0].params? typeEntryExists[0].params : paramEntry; + paramEntry = typeEntryExists[0] && typeEntryExists[0].params ? typeEntryExists[0].params : paramEntry; } // generate probetype entries if (devProbeType !== '') { - cutType = devProbeType === 'general_purpose'? devProbeType.split() : devProbeType.split('_'), - // sub type includes whole probeType after first '_' - cutSubType = devProbeType.substr(cutType[0].length + 1); + cutType = devProbeType === 'general_purpose' ? devProbeType.split() : devProbeType.split('_'), + // sub type includes whole probeType after first '_' + cutSubType = devProbeType.substr(cutType[0].length + 1); - paramEntry = paramEntry? paramEntry : {}; + paramEntry = paramEntry ? paramEntry : {}; // create 'none' entry to get an object with array of 'none probetype' entries - if (_.isArray(paramEntry)){ + if (_.isArray(paramEntry)) { paramEntry = { none: paramEntry }; } // check for CC sub type and add device namespaces - if(cutType.length > 1){ + if (cutType.length > 1) { // add CC type - if(!paramEntry[cutType[0]]){ + if (!paramEntry[cutType[0]]) { paramEntry[cutType[0]] = {}; } // add subtype - if(!paramEntry[cutType[0]][cutSubType]){ + if (!paramEntry[cutType[0]][cutSubType]) { paramEntry[cutType[0]][cutSubType] = []; } @@ -2112,10 +2094,10 @@ AutomationController.prototype.generateNamespaces = function (callback, device, // delete empty suptype entries paramEntry[cutType[0]] = deleteEmptyProp(paramEntry[cutType[0]], cutSubType); - - // add CC type + + // add CC type } else { - if(!paramEntry[cutType[0]]){ + if (!paramEntry[cutType[0]]) { paramEntry[cutType[0]] = []; } @@ -2128,15 +2110,15 @@ AutomationController.prototype.generateNamespaces = function (callback, device, } else { // add entries to type entries - paramEntry = paramEntry? paramEntry : []; + paramEntry = paramEntry ? paramEntry : []; // create 'none' entry to get an object with array of 'none probetype' entries - if (_.isArray(paramEntry)){ + if (_.isArray(paramEntry)) { paramEntry = { none: paramEntry }; - } else if(!_.isArray(paramEntry) && devProbeType === ''){ - paramEntry['none'] = paramEntry['none']? paramEntry['none'] : []; + } else if (!_.isArray(paramEntry) && devProbeType === '') { + paramEntry['none'] = paramEntry['none'] ? paramEntry['none'] : []; } paramEntry.none = addRemoveEntry(paramEntry.none); } @@ -2173,7 +2155,7 @@ AutomationController.prototype.generateNamespaces = function (callback, device, if (!locationNspcOnly) { // add to location namespaces - if(!!location){ + if (!!location) { _.forEach(that.locations, function(l) { if (l.id === devLocation) { // add to namespace @@ -2189,16 +2171,16 @@ AutomationController.prototype.generateNamespaces = function (callback, device, callback(nspcArr); } - // if location of device has changed - // on device: change:location - // update namespaces for location if necessary + // if location of device has changed + // on device: change:location + // update namespaces for location if necessary } else { _.forEach(that.locations, function(l) { if (!l.namespaces) { l.namespaces = []; } - if(l.id === devLocation) { + if (l.id === devLocation) { // if device is assigned to location add to namespace devStillExists = that.devices.get(device.id); l.namespaces = that.genNspc(l.namespaces, device); @@ -2221,7 +2203,7 @@ AutomationController.prototype.generateNamespaces = function (callback, device, } }; -AutomationController.prototype.getListNamespaces = function (path, namespacesObj, setLocationTitle) { +AutomationController.prototype.getListNamespaces = function(path, namespacesObj, setLocationTitle) { var self = this, result = [], namespaces = namespacesObj, @@ -2229,20 +2211,20 @@ AutomationController.prototype.getListNamespaces = function (path, namespacesObj pathArr = [], namespacesPath = '', nspc, - v = setLocationTitle? setLocationTitle : false; + v = setLocationTitle ? setLocationTitle : false; this.getNspcDevAll = function(nspcObj) { var devicesAll = [], obj = {}; - if (_.isArray(nspcObj)){ + if (_.isArray(nspcObj)) { nspcObj.forEach(function(nspcEntry) { - if (!~devicesAll.indexOf(nspcEntry)){ + if (!~devicesAll.indexOf(nspcEntry)) { devicesAll.push(nspcEntry); } }); } else { - for ( var prop in nspcObj) { + for (var prop in nspcObj) { devicesAll = devicesAll.concat(self.getNspcDevAll(nspcObj[prop])); } } @@ -2252,16 +2234,16 @@ AutomationController.prototype.getListNamespaces = function (path, namespacesObj // map all entries (deviceName, deviceId) // if deviceName add also location title - mapEntries = function (list){ + mapEntries = function(list) { // return list of entries - return _.map(list, function(entry){ + return _.map(list, function(entry) { var locationTitle = ''; if (setLocationTitle) { var vDev = self.devices.get(entry.deviceId), location = self.getLocation(self.locations, vDev.get('location')); - locationTitle = currPath === 'deviceName' && !!location && location && location.title !== 'globalRoom'? location.title.toUpperCase() + ' - ' : ''; + locationTitle = currPath === 'deviceName' && !!location && location && location.title !== 'globalRoom' ? location.title.toUpperCase() + ' - ' : ''; } return locationTitle + entry[currPath]; @@ -2276,9 +2258,9 @@ AutomationController.prototype.getListNamespaces = function (path, namespacesObj if (pathArr.length > 1 && pathArr.indexOf('params') === -1) { pathArr.splice(1, 0, 'params'); } - + // filter for type - nspc = namespaces.filter(function (namespace) { + nspc = namespaces.filter(function(namespace) { return namespace.id === pathArr[0]; })[0]; @@ -2291,19 +2273,19 @@ AutomationController.prototype.getListNamespaces = function (path, namespacesObj var currPath = pathArr[i + shift], obj = {}, lastPath = ['deviceId', 'deviceName']; - - if(nspc[currPath]) { + + if (nspc[currPath]) { nspc = nspc[currPath]; result = nspc; - + } else if (!nspc[currPath] && ~lastPath.indexOf(currPath)) { result = self.getNspcDevAll(nspc); result = mapEntries(result); - // add backward compatibility + // add backward compatibility } else if (~lastPath.indexOf(currPath)) { - if (_.isArray(nspc)){ + if (_.isArray(nspc)) { // map all device id's or device names result = mapEntries(nspc); } @@ -2322,9 +2304,9 @@ AutomationController.prototype.getListNamespaces = function (path, namespacesObj } } } else { - result = nspc && nspc['params']? nspc['params'] : nspc; // if not return undefined - } - + result = nspc && nspc['params'] ? nspc['params'] : nspc; // if not return undefined + } + } else { result = namespaces; } @@ -2332,7 +2314,7 @@ AutomationController.prototype.getListNamespaces = function (path, namespacesObj return result; }; -AutomationController.prototype.setNamespace = function (id, namespacesArr, data) { +AutomationController.prototype.setNamespace = function(id, namespacesArr, data) { var result = null, namespace, index; @@ -2340,10 +2322,12 @@ AutomationController.prototype.setNamespace = function (id, namespacesArr, data) id = id || null; if (id && this.getListNamespaces(id, namespacesArr)) { - namespace = _.findWhere(namespacesArr, {id : id}); + namespace = _.findWhere(namespacesArr, { + id: id + }); if (!!namespace) { index = namespacesArr.indexOf(namespace); - + // remove entry if data is empty if (~index && (_.isArray(data) && data.length < 1) || ((!_.isArray(data)) && Object.keys(data).length < 1)) { namespacesArr = namespacesArr.splice(index, 1); @@ -2364,12 +2348,12 @@ AutomationController.prototype.setNamespace = function (id, namespacesArr, data) return result; }; -AutomationController.prototype.getListModulesCategories = function (id) { +AutomationController.prototype.getListModulesCategories = function(id) { var result = null, categories = this.modules_categories; if (Boolean(id)) { - result = _.find(categories, function (category) { + result = _.find(categories, function(category) { return category.id === id; }); } else { @@ -2379,33 +2363,33 @@ AutomationController.prototype.getListModulesCategories = function (id) { return result; }; -AutomationController.prototype.getModuleData = function (moduleName) { +AutomationController.prototype.getModuleData = function(moduleName) { var self = this, defaultLang = self.defaultLang, moduleMeta = self.modules[moduleName] && self.modules[moduleName].meta || null, languageFile = self.loadModuleLang(moduleName), data = {}; - + if (!self.modules[moduleName]) { return {}; // module not found (deleted from filesystem or broken?), return empty meta } - + try { metaStringify = JSON.stringify(moduleMeta); - } catch(e){ + } catch (e) { try { metaStringify = JSON.stringify(fs.loadJSON('modules/' + moduleName + '/module.json')); - } catch(e){ + } catch (e) { try { metaStringify = JSON.stringify(fs.loadJSON('userModules/' + moduleName + '/module.json')); - } catch(e){ + } catch (e) { console.log('Cannot load lang file from module ' + moduleName + '. ERROR: ' + e); } } } if (languageFile !== null) { - Object.keys(languageFile).forEach(function (key) { + Object.keys(languageFile).forEach(function(key) { var regExp = new RegExp('__' + key + '__', 'g'); if (languageFile[key]) { metaStringify = metaStringify.replace(regExp, languageFile[key]); @@ -2419,22 +2403,22 @@ AutomationController.prototype.getModuleData = function (moduleName) { return data; }; -AutomationController.prototype.replaceNamespaceFilters = function (moduleMeta) { +AutomationController.prototype.replaceNamespaceFilters = function(moduleMeta) { var self = this, moduleMeta = moduleMeta || null, langFile = this.loadMainLang(); // loop through object - function replaceNspcFilters (moduleMeta, obj, keys) { + function replaceNspcFilters(moduleMeta, obj, keys) { var objects = []; for (var i in obj) { - if (obj && !obj[i]){ + if (obj && !obj[i]) { continue; } if ((i === 'properties' || i === 'fields') && typeof obj[i] === 'object' && obj[i]['room'] && obj[i]['devicesByRoom']) { var k = _.keys(obj[i]) - newObj = {}; + newObj = {}; try { // overwrite old key with new namespaces array @@ -2442,34 +2426,34 @@ AutomationController.prototype.replaceNamespaceFilters = function (moduleMeta) { console.log("Room - Device relation found, try to preparate JSON's schema structure ..."); var dSRoom = _.extend({ - "type":"", - "field":"", - "datasource":"", - "enum":"", - "title":"" + "type": "", + "field": "", + "datasource": "", + "enum": "", + "title": "" }, obj[i]['room']), dSDevByRoom = _.extend({ - "type":"", - "datasource":"", - "enum":"", - "title":"", - "dependencies":"" + "type": "", + "datasource": "", + "enum": "", + "title": "", + "dependencies": "" }, obj[i]['devicesByRoom']); - if (dSRoom['enum'] && !_.isArray(dSRoom['enum'])){ + if (dSRoom['enum'] && !_.isArray(dSRoom['enum'])) { dSRoom['enum'] = getNspcFromFilters(moduleMeta, dSRoom['enum']); obj[i]['room'] = dSRoom; } - if (dSDevByRoom['enum'] && !_.isArray(dSDevByRoom['enum']) && _.isArray(dSRoom['enum'])){ + if (dSDevByRoom['enum'] && !_.isArray(dSDevByRoom['enum']) && _.isArray(dSRoom['enum'])) { var path = dSDevByRoom['enum'].substring(21).replace(/:/gi, '.'); - if(k.length > 0) { + if (k.length > 0) { k.forEach(function(key) { - if(key === 'devicesByRoom') { + if (key === 'devicesByRoom') { dSRoom['enum'].forEach(function(roomId, index) { var locNspc = [], - nspc =[]; + nspc = []; location = self.getLocation(self.locations, roomId); @@ -2477,7 +2461,7 @@ AutomationController.prototype.replaceNamespaceFilters = function (moduleMeta) { nspc = self.getListNamespaces(path, location.namespaces); } - dSDevByRoom['enum'] = nspc && nspc.length > 0? nspc: [langFile.no_devices_found]; + dSDevByRoom['enum'] = nspc && nspc.length > 0 ? nspc : [langFile.no_devices_found]; dSDevByRoom['dependencies'] = "room"; newObj['devicesByRoom_' + roomId] = _.clone(dSDevByRoom); @@ -2497,43 +2481,47 @@ AutomationController.prototype.replaceNamespaceFilters = function (moduleMeta) { console.log("Room - Device relation found, try to preparate JSON's options structure ..."); var dSRoom = _.extend({ - "type":"", - "field":"", - "optionLabels":"" - },obj[i]['room']), + "type": "", + "field": "", + "optionLabels": "" + }, obj[i]['room']), dSDevByRoom = _.extend({ "dependencies": {}, - "type":"", - "field":"", - "optionLabels":"" - },obj[i]['devicesByRoom']); + "type": "", + "field": "", + "optionLabels": "" + }, obj[i]['devicesByRoom']); - if (dSRoom['optionLabels'] && !_.isArray(dSRoom['optionLabels'])){ + if (dSRoom['optionLabels'] && !_.isArray(dSRoom['optionLabels'])) { dSRoom['optionLabels'] = getNspcFromFilters(moduleMeta, dSRoom['optionLabels']); obj[i]['room'] = dSRoom; } - if (dSDevByRoom['optionLabels'] && !_.isArray(dSDevByRoom['optionLabels']) && _.isArray(dSRoom['optionLabels'])){ + if (dSDevByRoom['optionLabels'] && !_.isArray(dSDevByRoom['optionLabels']) && _.isArray(dSRoom['optionLabels'])) { var path = dSDevByRoom['optionLabels'].substring(21).replace(/:/gi, '.'); - if(k.length > 0) { + if (k.length > 0) { k.forEach(function(key) { - if(key === 'devicesByRoom') { + if (key === 'devicesByRoom') { dSRoom['optionLabels'].forEach(function(roomName, index) { - + var locNspc = [], nspc = [], locationId; - location = self.locations.filter(function(location){ return location.title === roomName }); - locationId = location[0]? location[0].id : location.id; + location = self.locations.filter(function(location) { + return location.title === roomName + }); + locationId = location[0] ? location[0].id : location.id; if (location[0]) { nspc = self.getListNamespaces(path, location[0].namespaces); } - - dSDevByRoom['optionLabels'] = nspc && nspc.length > 0? nspc: [langFile.no_devices_found]; - dSDevByRoom['dependencies'] = { "room" : locationId }; + + dSDevByRoom['optionLabels'] = nspc && nspc.length > 0 ? nspc : [langFile.no_devices_found]; + dSDevByRoom['dependencies'] = { + "room": locationId + }; newObj['devicesByRoom_' + locationId] = _.clone(dSDevByRoom); @@ -2555,10 +2543,10 @@ AutomationController.prototype.replaceNamespaceFilters = function (moduleMeta) { console.log('Cannot prepare Room-Device related JSON structure. ERROR: ' + e); self.addNotification('warning', langFile.err_preparing_room_dev_structure, 'module', moduleMeta.id); } - + // try to replace the other stuff for (var key in obj[i]) { - _.each(obj[i][key], function(p, index){ + _.each(obj[i][key], function(p, index) { if (~keys.indexOf(index) && !_.isArray(p)) { obj[i][key][index] = getNspcFromFilters(moduleMeta, obj[i][key][index]); } @@ -2576,10 +2564,10 @@ AutomationController.prototype.replaceNamespaceFilters = function (moduleMeta) { }; // generate namespace arry from filter string - function getNspcFromFilters (moduleMeta, nspcfilters) { + function getNspcFromFilters(moduleMeta, nspcfilters) { var namespaces = [], filters = nspcfilters.split(','), - apis = ['locations','namespaces','loadFunction'], + apis = ['locations', 'namespaces', 'loadFunction'], filteredDev = []; try { @@ -2589,41 +2577,41 @@ AutomationController.prototype.replaceNamespaceFilters = function (moduleMeta) { } // do it for each filter - _.forEach(filters, function (flr,i){ + _.forEach(filters, function(flr, i) { var id = flr.split(':'), path; - if(apis.indexOf(id[0]) > -1){ - + if (apis.indexOf(id[0]) > -1) { + // get location ids or titles - except location 0/globalRoom - 'locations:id' or 'locations:title' // should allow dynamic filtering per location if (id[0] === 'locations' && (id[1] === 'id' || id[1] === 'title')) { namespaces = _.filter(self.locations, function(location) { return location[id[1]] !== 0 && location[id[1]] !== 'globalRoom'; - }).map(function(location) { - return location[id[1]]; + }).map(function(location) { + return location[id[1]]; }); - - // get namespaces of devices per location - 'locations:locationId:filterPath' - } else if (id[0] === 'locations' && id[1] === 'locationId'){ + + // get namespaces of devices per location - 'locations:locationId:filterPath' + } else if (id[0] === 'locations' && id[1] === 'locationId') { // don't replace set filters instead namespaces = nspcfilters; - // load function from file + // load function from file } else if (id[0] === 'loadFunction') { var filePath = moduleMeta.location + '/htdocs/js/' + id[1], jsFile = fs.stat(filePath); - + if (id[1] && jsFile && jsFile.type === 'file') { jsFile = fs.load(filePath); if (!!jsFile) { - //compress string - namespaces = jsFile.replace(/\s\s+|\t/g,' '); + //compress string + namespaces = jsFile.replace(/\s\s+|\t/g, ' '); } } - - // get namespaces of devices ignoring locations + + // get namespaces of devices ignoring locations } else { // cut path path = flr.substring(id[0].length + 1).replace(/:/gi, '.'); @@ -2641,23 +2629,23 @@ AutomationController.prototype.replaceNamespaceFilters = function (moduleMeta) { } catch (e) { console.log('Cannot parse filters > ' + nspcfilters + ' < from namespaces. ERROR: ' + e); self.addNotification('warning', langFile.err_parsing_npc_filters, 'module', moduleMeta.id); - + return namespaces; } }; if (!!moduleMeta) { var params = { - schema: ['enum'], - options: ['optionLabels', 'onFieldChange', 'click'], - postRender : '' - }; - + schema: ['enum'], + options: ['optionLabels', 'onFieldChange', 'click'], + postRender: '' + }; + // transform filters for (var property in params) { - if (property === 'postRender' && moduleMeta[property] && !_.isArray(moduleMeta[property])) { + if (property === 'postRender' && moduleMeta[property] && !_.isArray(moduleMeta[property])) { moduleMeta[property] = getNspcFromFilters(moduleMeta, moduleMeta[property]); - } else if (moduleMeta[property]) { + } else if (moduleMeta[property]) { moduleMeta[property] = replaceNspcFilters(moduleMeta, moduleMeta[property], params[property]); } } @@ -2667,24 +2655,24 @@ AutomationController.prototype.replaceNamespaceFilters = function (moduleMeta) { }; // load module lang folder -AutomationController.prototype.loadModuleLang = function (moduleId) { +AutomationController.prototype.loadModuleLang = function(moduleId) { var moduleMeta = this.modules[moduleId] && this.modules[moduleId].meta || null, languageFile = null; - if(!!moduleMeta){ - languageFile = this.loadMainLang(moduleMeta.location + '/'); - } + if (!!moduleMeta) { + languageFile = this.loadMainLang(moduleMeta.location + '/'); + } return languageFile; }; // load lang folder with given prefix -AutomationController.prototype.loadMainLang = function (pathPrefix) { +AutomationController.prototype.loadMainLang = function(pathPrefix) { var self = this, languageFile = null, prefix; - if(pathPrefix === undefined || pathPrefix === null) { + if (pathPrefix === undefined || pathPrefix === null) { prefix = ''; } else { prefix = pathPrefix; @@ -2692,7 +2680,7 @@ AutomationController.prototype.loadMainLang = function (pathPrefix) { try { languageFile = fs.loadJSON(prefix + "lang/" + self.defaultLang + ".json"); - } catch (e) { + } catch (e) { try { languageFile = fs.loadJSON(prefix + "lang/en.json"); } catch (e) { @@ -2703,11 +2691,11 @@ AutomationController.prototype.loadMainLang = function (pathPrefix) { return languageFile; }; -AutomationController.prototype.loadModuleMedia = function(moduleName,fileName) { - var img = ["png","jpg","jpeg","JPG","JPEG","gif"], - text = ["css","js","txt","rtf","xml"], - html = ["htm","html","shtml"], - video = ["mpeg","mpg","mpe","qt","mov","viv","vivo","avi","movie","mp4"], +AutomationController.prototype.loadModuleMedia = function(moduleName, fileName) { + var img = ["png", "PNG", "jpg", "jpeg", "JPG", "JPEG", "gif", "GIF"], + text = ["css", "js", "txt", "rtf", "xml"], + html = ["htm", "html", "shtml"], + video = ["mpeg", "mpg", "mpe", "qt", "mov", "viv", "vivo", "avi", "movie", "mp4"], fe, resObject = { data: null, @@ -2715,35 +2703,35 @@ AutomationController.prototype.loadModuleMedia = function(moduleName,fileName) { }; try { - + fe = fileName.split(".").pop(); - if(img.indexOf(fe) > -1){ + if (img.indexOf(fe) > -1) { resObject.ct = "image/(png|jpeg|gif)"; } - - if(text.indexOf(fe) > -1){ + + if (text.indexOf(fe) > -1) { resObject.ct = "text/(css|html|javascript|plain|rtf|xml)"; } - if(html.indexOf(fe) > -1){ + if (html.indexOf(fe) > -1) { resObject.ct = "text/html"; } - if(video.indexOf(fe) > -1){ + if (video.indexOf(fe) > -1) { resObject.ct = "video/(mpeg|quicktime|vnd.vivo|x-msvideo|x-sgi-movie|mp4)"; } - - try{ + + try { resObject.data = fs.load('userModules/' + moduleName + '/htdocs/' + fileName); - } catch(e){ + } catch (e) { resObject.data = fs.load('modules/' + moduleName + '/htdocs/' + fileName); } - }catch(e){ + } catch (e) { resObject = null; } - + return resObject; }; @@ -2751,22 +2739,23 @@ AutomationController.prototype.loadImage = function(fileName) { var data = null, img = loadObject(fileName); - if (!!img) { - data = Base64.decode(img); - } - - return data; + if (!!img) { + data = Base64.decode(img); + } + + return data; }; AutomationController.prototype.hashCode = function(str) { - var hash = 0, i, chr, len; + var hash = 0, + i, chr, len; if (this.length === 0) { return hash; } for (i = 0, len = str.length; i < len; i++) { - chr = str.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; - hash = hash & hash; // Convert to 32bit integer + chr = str.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; + hash = hash & hash; // Convert to 32bit integer } return hash; }; @@ -2778,43 +2767,25 @@ AutomationController.prototype.createBackup = function() { skins = [], result = null; - var list = loadObject("__storageContent"), - excludedFiles = [ - "notifications", - "8084AccessTimeout", - "expertconfig.json", - "rssidata.json", - "reorgLog", - "incomingPacket.json", - "outgoingPacket.json", - "originPackets.json", - "zway_incomingPacket.json", - "zway_outgoingPacket.json", - "zway_originPackets.json", - "zway_parsedPackets.json", - "zway_reorgLog", - "zway_rssidata.json", - "de.devices.json", - "en.devices.json", - "zwave_vendors.json", - "history" - ]; + var list = __storageContent, + excludedFiles = this.getIgnoredStorageFiles(); try { // save all objects in storage for (var ind in list) { - //if (list[ind] !== "notifications" && list[ind] !== "8084AccessTimeout") { // don't create backup of 8084 and notifications if (excludedFiles.indexOf(list[ind]) === -1) { backupJSON[list[ind]] = loadObject(list[ind]); } } // add list of current userModules - _.forEach(fs.list('userModules')|| [], function(moduleName) { - if (fs.stat('userModules/' + moduleName).type === 'dir' && !_.findWhere(userModules, {name: moduleName})) { + _.forEach(fs.list('userModules') || [], function(moduleName) { + if (fs.stat('userModules/' + moduleName).type === 'dir' && !_.findWhere(userModules, { + name: moduleName + })) { userModules.push({ name: moduleName, - version: self.modules[moduleName]? self.modules[moduleName].meta.version : '' + version: self.modules[moduleName] ? self.modules[moduleName].meta.version : '' }); } }); @@ -2875,7 +2846,7 @@ AutomationController.prototype.createBackup = function() { var bcp = "", data = new Uint8Array(global.ZWave[zwayName].zway.controller.Backup()); - for(var i = 0; i < data.length; i++) { + for (var i = 0; i < data.length; i++) { bcp += String.fromCharCode(data[i]); } @@ -2893,7 +2864,7 @@ AutomationController.prototype.createBackup = function() { */ result = backupJSON; - } catch(e) { + } catch (e) { throw e.toString(); } @@ -2908,18 +2879,18 @@ AutomationController.prototype.getRemoteId = function() { try { zbw = new ZBWConnect(); // find zbw by path or use (raspberry) location /etc/zbw as default - if(!!zbw) { - checkIfTypeError = zbw.getUserId() instanceof TypeError? true : false; + if (!!zbw) { + checkIfTypeError = zbw.getUserId() instanceof TypeError ? true : false; } - } catch(e) { + } catch (e) { try { zbw = new ZBWConnect('./zbw'); - checkIfTypeError = zbw.getUserId() instanceof TypeError? true : false; + checkIfTypeError = zbw.getUserId() instanceof TypeError ? true : false; } catch (er) { console.log('Something went wrong. Reading remote id has failed. Error:' + er.message); } } - if(checkIfTypeError) { + if (checkIfTypeError) { console.log('Something went wrong. Reading remote id has failed.'); } else { result = zbw.getUserId(); @@ -2932,7 +2903,11 @@ AutomationController.prototype.getRemoteId = function() { }; AutomationController.prototype.getInstancesByModuleName = function(moduleName) { - return Object.keys(this.registerInstances).map(function(id) { return controller.registerInstances[id]; }).filter(function(i) { return i.meta.id === moduleName; }); + return Object.keys(this.registerInstances).map(function(id) { + return controller.registerInstances[id]; + }).filter(function(i) { + return i.meta.id === moduleName; + }); }; AutomationController.prototype.reoderDevices = function(list, action) { @@ -2948,7 +2923,7 @@ AutomationController.prototype.reoderDevices = function(list, action) { vDev.set('order', order); }); result = true; - } catch(e) { + } catch (e) { console.log(e); } @@ -2956,18 +2931,423 @@ AutomationController.prototype.reoderDevices = function(list, action) { }; AutomationController.prototype.vDevFailedDetection = function(nodeId, isFailed, zwayName) { - var nodeId = nodeId, - getNodeVDevs = []; + this.devices.filterByNode(nodeId, zwayName).forEach(function(vDev) { + if (vDev.get('metrics:isFailed') !== isFailed) { // don't trigger events if values is not changing to minimize the number of events + vDev.set('metrics:isFailed', isFailed); + } + }); +}; + +AutomationController.prototype.transformIntoNewInstance = function(moduleName) { + + if (['IfThen', 'LogicalRules', 'ScheduledScene', 'LightScene'].indexOf(moduleName) < 0) { + return null; + } + + var self = this, + instances = [], + newInstances = [], + result = []; + + var moduleMeta = this.modules[moduleName] && this.modules[moduleName].meta || null; + + instances = _.filter(this.instances, function(i) { + return moduleName === i.moduleId && !i.params.transformed; + }); + + if (instances.length && moduleMeta) { + instances.forEach(function(instance) { + var newInstance = {}; + + switch (moduleName) { + case 'IfThen': + case 'LogicalRules': + newInstance = { + moduleId: 'Rules', + active: false, + title: '', + params: { + simple: { + triggerEvent: {}, + triggerDelay: 0, + targetElements: [], + sendNotifications: [], + reverseDelay: 0 + }, + advanced: { + active: false, + triggerOnDevicesChange: false, + triggerScenes: [], + triggerDelay: 0, + logicalOperator: 'and', + tests: [], + targetElements: [], + sendNotifications: [], + reverseDelay: 0 + }, + reverse: false + } + } + break; + case 'ScheduledScene': + + newInstance = { + moduleId: 'Schedules', + active: false, + title: '', + params: { + weekdays: [], + times: [], + devices: [] + } + }; + + break; + case 'LightScene': + + newInstance = { + moduleId: 'Scenes', + active: false, + title: '', + params: { + devices: [], + customIcon: { + table: [{ + icon: '' + }] + } + } + }; + + break; + } + + if ((moduleName === 'LogicalRules' && has_higher_version(moduleMeta.version, '1.4.0')) || + (moduleName === 'IfThen' && has_higher_version(moduleMeta.version, '2.5.0')) || + (moduleName === 'ScheduledScene' && has_higher_version(moduleMeta.version, '2.2.1')) || + (moduleName === 'LightScene' && has_higher_version(moduleMeta.version, '1.1.0'))) { + + // adjust title and instance state + newInstance.active = instance.active || false; + newInstance.title = moduleName + ' - ' + instance.title || 'Automatically transformed ' + newInstance.moduleId + ' instance from ' + moduleName + ' instance #' + instance.id; + + if (moduleName === 'LogicalRules') { - getNodeVDevs = this.devices.filterByNode(nodeId, zwayName); + // transform into advanced Rule + newInstance.params.advanced = self.transformIntoRule('advanced', instance, newInstance.params.advanced); - // set vDev isFailed state - getNodeVDevs.forEach(function(vDev) { - vDev.set('metrics:isFailed', isFailed); + } else if (moduleName === 'IfThen') { - if (!isFailed) { - // bind on last receive - zway.devices[nodeId].data.lastReceived.unbind(this.vDevFailedDetection); + // transform into simple Rule + newInstance.params.simple = self.transformIntoRule('simple', instance, newInstance.params.simple); + + } else if (moduleName === 'ScheduledScene') { + + // update params and instance + newInstance.params.devices = self.concatDeviceListEntries(instance.params.devices); + newInstance.params.times = instance.params.times; + newInstance.params.weekdays = instance.params.weekdays; + + } else if (moduleName === 'LightScene') { + + // update params and instance + newInstance.params.devices = self.concatDeviceListEntries(instance.params); + } + + newInstances.push(newInstance); + + // stop old instance + instance.active = instance.active || instance.active === 'true' ? false : instance.active; + // set transformed flag + instance.params.moduleAPITransformed = true; + self.reconfigureInstance(instance.id, instance); + } + }); + + // create new instances + newInstances.forEach(function(inst, index) { + + var active = inst.active; + if (!active) { + inst.active = true; + } + + addedInst = self.createInstance(inst); + + result.push({ + id: addedInst.id, + title: inst.title, + old_moduleId: moduleName + }); + + // stop old instance + if (addedInst && (!active || active === 'false')) { + + inst.active = false; + self.reconfigureInstance(addedInst.id, inst); + } + }); + } + + return result; +}; + +AutomationController.prototype.transformIntoRule = function(type, instance, object) { + var self = this, + targetDevices = [], + tests = [], + oldParams = instance.params ? instance.params : null, + newParams = object ? object : null; + + /* + newParams = { + active: true, + triggerOnDevicesChange: false, + triggerScenes: [], + triggerDelay: 0, + logicalOperator: 'and', + tests: [], + targetElements: [], + sendNotifications: [], + reverseDelay: 0 + } + */ + + // transform old structure to new + if (oldParams && type === 'advanced' && newParams) { + + object.active = true; + object.triggerOnDevicesChange = oldParams.triggerOnDevicesChange || false; + object.logicalOperator = oldParams.logicalOperator || 'and'; + object.tests = oldParams.test || []; + object.targetElements = oldParams.action || []; + + // concat all tests to one list + oldParams.tests.forEach(function(test) { + + if (Object.keys(test)[1]) { + var entry = test[Object.keys(test)[1]]; + + if (test['testType'] === 'time') { + tests.push({ + type: "time", + operator: entry.testOperator, + level: entry.testValue + }); + } else if (test['testType'] === 'nested') { + var nested = { + logicalOperator: test['testNested']['logicalOperator'], + tests: [] + } + + test['testNested']['tests'].forEach(function(nestedTest) { + var nestedTests = []; + + if (nestedTest['testType'] === 'time') { + nested.tests.push({ + type: "time", + operator: nestedTest.testOperator, + level: nestedTest.testValue + }); + } else { + nested.tests.push(self.transformAdvancedEntry('test', nestedTest)); + } + }); + + tests.push(nested); + + // + } else { + tests.push(self.transformAdvancedEntry('test', entry)); + } + } + }); + + // concat actions to one list + Object.keys(oldParams.action).forEach(function(key) { + + oldParams.action[key].forEach(function(entry) { + if (entry.device || (key === 'scenes' && entry)) { + if (key === 'scenes') { + targetDevices.push({ + deviceId: entry, + deviceType: 'toggleButton', + level: 'on' + }); + } else if (key === 'notification') { + newParams.sendNotifications.push(entry); + } else { + targetDevices.push(self.transformAdvancedEntry('action', entry)); + } + } + }); + }); + + // set new params + newParams.tests = tests; + newParams.targetElements = targetDevices; + + } else if (oldParams && type === 'simple' && newParams) { + + /* + newParams = { + triggerEvent: {}, + triggerDelay: 0, + targetElements: [], + sendNotifications: [], + reverseDelay: 0 + } + */ + + // transform trigger event + if (Object.keys(oldParams.sourceDevice)[1]) { + var entry = oldParams.sourceDevice[Object.keys(oldParams.sourceDevice)[1]]; + + newParams.triggerEvent = self.transformSimpleEntry(entry); + } + + // concat actions to one list + oldParams.targets.forEach(function(key) { + var targetEntry = key[Object.keys(key)[1]]; + + if (key['notification']) { + newParams.sendNotifications.push(targetEntry); + } else if (key['scene']) { + newParams.targetElements.push({ + deviceId: targetEntry.target, + deviceType: 'toggleButton', + level: 'on' + }); + } else { + newParams.targetElements.push(self.transformSimpleEntry(targetEntry)); + } + }); + } + + return newParams; +} + +AutomationController.prototype.transformSimpleEntry = function(entry) { + //console.log('simple entry:', JSON.stringify(entry)); + var vdevId = entry && entry.device ? entry.device : entry.target, + vDev = this.devices.get(vdevId), + lvl = entry.status && ['color', 'level'].indexOf(entry.status) < 0 ? entry.status : (entry.level ? entry.level : (entry.color ? { + r: entry.color.red, + g: entry.color.green, + b: entry.color.blue + } : 0)); + + if (vDev) { + /* transform each single entry to the new format: switches, thermostats, dimmers, locks, scenes + { + deviceId: '', + deviceType: '', + level: '', // color: { r: 0, g: 0, b: 0}, on, off, open, close, color + sendAction: true, || false >> don't do this if level is already triggered + operator: '', + reverseLevel: "off" // set reverse level on or off + } + */ + return { + deviceId: vdevId, + deviceType: vDev ? vDev.get('deviceType') : '', + level: lvl, + sendAction: entry.sendAction || false, + operator: entry.operator, + reverseLevel: "off" + }; + } +}; + +AutomationController.prototype.transformAdvancedEntry = function(transformation, entry) { + //console.log('advanced entry:', JSON.stringify(entry)); + var vDev = this.devices.get(entry.device); + + if (vDev) { + if (transformation === 'test') { + /* transform each single entry to the new format: switches, thermostats, dimmers, locks, scenes + { + deviceId: '', + type: '', + level: '', // color: { r: 0, g: 0, b: 0}, on, off, open, close, color + operator: '' + } + */ + return { + deviceId: vDev.id, + type: vDev ? vDev.get('deviceType') : '', + level: entry['testValue'], + operator: entry['testOperator'] ? entry['testOperator'] : undefined + } + + } else if (transformation === 'action') { + /* transform each single entry to the new format: switches, thermostats, dimmers, locks, scenes + { + deviceId: '', + deviceType: '', + level: '', // color: { r: 0, g: 0, b: 0}, on, off, open, close, color + sendAction: true || false >> don't do this if level is already triggered + } + */ + return { + deviceId: entry.device, + deviceType: vDev ? vDev.get('deviceType') : '', + level: entry.status && ['color', 'level'].indexOf(entry.status) < 0 ? entry.status : (entry.level ? entry.level : (entry.color ? { + r: entry.color.red, + g: entry.color.green, + b: entry.color.blue + } : 0)), + sendAction: entry.sendAction || false + } + } + } +}; + +AutomationController.prototype.concatDeviceListEntries = function(devices) { + var self = this, + newDevArr = [], + keys = ['switches', 'dimmers', 'thermostats', 'locks', 'scenes']; + + // concat all lists to one + Object.keys(devices).forEach(function(key) { + /* transform each single entry to the new format: switches, thermostats, dimmers, locks, scenes + { + deviceId: '', + deviceType: '', + level: '', // color: { r: 0, g: 0, b: 0}, on, off, open, close, color + sendAction: true || false >> don't do this if level is already triggered + } + */ + if (_.isArray(devices[key]) && keys.indexOf(key) >= 0) { + devices[key].forEach(function(entry) { + + var vDev = null; + if (entry.device || (key === 'scenes' && entry)) { + if (key === 'scenes') { + newDevArr.push({ + deviceId: entry, + deviceType: 'toggleButton', + level: 'on' + }); + } else { + vDev = self.devices.get(entry.device); + + newDevArr.push({ + deviceId: entry.device, + deviceType: vDev ? vDev.get('deviceType') : '', + level: entry.status && ['color', 'level'].indexOf(entry.status) < 0 ? entry.status : (entry.level ? entry.level : (entry.color ? { + r: entry.color.red, + g: entry.color.green, + b: entry.color.blue + } : 0)), + sendAction: entry.sendAction + }); + } + } + }); } }); + + // update params and instance + return newDevArr; }; \ No newline at end of file diff --git a/classes/AutomationModule.js b/classes/AutomationModule.js index d93ad85c2..d67ca7009 100644 --- a/classes/AutomationModule.js +++ b/classes/AutomationModule.js @@ -7,7 +7,7 @@ Copyright: (c) ZWave.Me, 2013 ******************************************************************************/ -AutomationModule = function (id, controller) { +AutomationModule = function(id, controller) { var self = this; this.id = id; @@ -21,18 +21,18 @@ AutomationModule = function (id, controller) { this.config = {}; }; -AutomationModule.prototype.defaultConfig = function (config) { +AutomationModule.prototype.defaultConfig = function(config) { var result = {}, self = this; if (this.meta.hasOwnProperty("defaults") && _.isObject(this.meta.defaults)) { - Object.keys(_.omit(this.meta.defaults, 'title', 'description')).forEach(function (key) { + Object.keys(_.omit(this.meta.defaults, 'title', 'description')).forEach(function(key) { result[key] = self.meta.defaults[key]; }); } if (!!config) { - Object.keys(config).forEach(function (key) { + Object.keys(config).forEach(function(key) { result[key] = config[key]; }); } @@ -40,7 +40,7 @@ AutomationModule.prototype.defaultConfig = function (config) { return result; }; -AutomationModule.prototype.init = function (config) { +AutomationModule.prototype.init = function(config) { console.log("--- Starting module " + this.meta.defaults.title); if (!!config) { this.saveNewConfig(config); @@ -49,30 +49,32 @@ AutomationModule.prototype.init = function (config) { } }; -AutomationModule.prototype.saveNewConfig = function (config) { +AutomationModule.prototype.saveNewConfig = function(config) { if (!!config) { this.config = this.defaultConfig(config); this.saveConfig(); } }; -AutomationModule.prototype.stop = function () { +AutomationModule.prototype.stop = function() { console.log("--- Stopping module " + this.meta.defaults.title); }; -AutomationModule.prototype.loadConfig = function () { +AutomationModule.prototype.loadConfig = function() { var self = this; - var cfg = loadObject("cfg"+this.id); + var cfg = loadObject("cfg" + this.id); if ("object" === typeof cfg) { - Object.keys(cfg).forEach(function (key) { + Object.keys(cfg).forEach(function(key) { self.config[key] = cfg[key]; }); } }; -AutomationModule.prototype.saveConfig = function (config) { +AutomationModule.prototype.saveConfig = function(config) { var that = this, - index = this.controller.instances.indexOf(_.find(this.controller.instances, function (model) { return model.id === that.id; })); + index = this.controller.instances.indexOf(_.find(this.controller.instances, function(model) { + return model.id === that.id; + })); this.controller.instances[index].params = config || this.config; this.controller.saveConfig(); @@ -83,7 +85,7 @@ AutomationModule.prototype.getName = function() { }; // This method returns JSON representation -AutomationModule.prototype.toJSON = function () { +AutomationModule.prototype.toJSON = function() { return { module: this.getName(), id: this.id, @@ -91,39 +93,242 @@ AutomationModule.prototype.toJSON = function () { }; }; -AutomationModule.prototype.runAction = function (actionId, args, callback) { +AutomationModule.prototype.runAction = function(actionId, args, callback) { // Run action function with actionId on instance if exists if (this.actionFuncs.hasOwnProperty(actionId)) { this.actionFuncs[actionId].call(this, args, callback); } }; -AutomationModule.prototype.getMeta = function () { +AutomationModule.prototype.getMeta = function() { if (!this.meta) { - this.meta = this.controller.getModuleData(this.constructor.name); + this.meta = this.controller.getModuleData(this.constructor.name); this.meta.id = this.constructor.name; } return this.meta; }; -AutomationModule.prototype.loadModuleJSON = function (filename) { +AutomationModule.prototype.loadModuleJSON = function(filename) { return fs.loadJSON(this.meta.location + "/" + filename); }; -AutomationModule.prototype.getInstanceTitle = function () { +AutomationModule.prototype.getInstanceTitle = function() { var instanceId = this.id; - var instanceTitle = this.controller.instances.filter(function (instance){ + var instanceTitle = this.controller.instances.filter(function(instance) { return instance.id === instanceId; }); - return instanceTitle[0] && instanceTitle[0].title? instanceTitle[0].title : this.constructor.name + ' ' + instanceId; + return instanceTitle[0] && instanceTitle[0].title ? instanceTitle[0].title : this.constructor.name + ' ' + instanceId; }; -AutomationModule.prototype.loadModuleLang = function () { +AutomationModule.prototype.loadModuleLang = function() { return this.controller.loadModuleLang(this.getName()); }; -AutomationModule.prototype.addNotification = function (severity, message, type) { +AutomationModule.prototype.addNotification = function(severity, message, type) { this.controller.addNotification(severity, message, type, this.getName()); }; + +AutomationModule.prototype.prepareHTTPResponse = function(body) { + var result = {}, + ret = { + status: 500, + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "Authorization", + "Content-Type": "application/json", + "Connection": "keep-alive" + }, + body: { + code: 500, + message: "500 Something went wrong.", + error: null, + data: null + } + }; + + return body ? _.extend(ret, { + status: body.code ? body.code : ret.status, + body: body + }) : ret; +} + +// ---------------------------------------------------------------------------- +// --- Logical methods (Rules, Scenes, Schedules, HazardNotification) +// ---------------------------------------------------------------------------- + +/* compare old and new level to avoid unnecessary updates + + vDev [string] // device object + valNew [string/number] // device level like: {color: { r: 0, g: 0, b: 0}}, 'on', 'off', 'open', 'close', [level] +*/ +AutomationModule.prototype.newValueNotEqualsOldValue = function(vDev, valNew) { + if (vDev) { + var vO = ''; + + vN = _.isNaN(parseFloat(valNew)) ? valNew : parseFloat(valNew); + + switch (vDev.get('deviceType')) { + case 'switchRGBW': + + vO = typeof vN !== 'string' ? vDev.get('metrics:color') : vDev.get('metrics:level'); + + if (valNew !== 'string') { + return !_.isEqual(vO, vN); + } + case 'switchControl': + if (_.contains(['on', 'off'], vN) || _.isNumber(vN)) { + vO = vDev.get('metrics:level'); + } else { + vO = vDev.get('metrics:change'); + } + default: + vO = vDev.get('metrics:level'); + } + return vO !== vN; + } else { + return false; + } +}; + +/* + set a new device state + + vDev [string] // device object + new_level [string/number] // device level like: {color: { r: 0, g: 0, b: 0}}, 'on', 'off', 'open', 'close', [level] +*/ +AutomationModule.prototype.setNewDeviceState = function(vDev, new_level) { + if (vDev) { + switch (vDev.get('deviceType')) { + case 'doorlock': + case 'switchBinary': + vDev.performCommand(new_level); + break; + case 'switchMultilevel': + case 'thermostat': + _.contains(['on', 'off'], new_level) ? vDev.performCommand(new_level) : vDev.performCommand("exact", { + level: new_level + }); + break; + case 'switchRGBW': + if (_.contains(["on", "off"], new_level)) { + vDev.performCommand(new_level); + } else { + vDev.performCommand("exact", { + red: new_level.r, + green: new_level.g, + blue: new_level.b + }); + } + break; + case 'switchControl': + if (_.contains(["on", "off"], new_level)) { + vDev.performCommand(new_level); + } else if (_.contains(["upstart", "upstop", "downstart", "downstop"], new_level)) { + vDev.performCommand("exact", { + change: new_level + }); + } else { + vDev.performCommand("exact", { + level: new_level + }); + } + break; + case 'toggleButton': + vDev.performCommand('on'); + break; + default: + vDev.performCommand(new_level); + } + } +}; + +/* + execute a device action based on compareLevels flag + + compareLevels [boolean] // flag if device level should be checked first before triggering the command + vDev [string] // device object + targetValue [string] // target value +*/ +AutomationModule.prototype.executeActions = function(compareLevels, vDev, targetValue) { + return (!compareLevels || (compareLevels && this.newValueNotEqualsOldValue(vDev, targetValue))); +}; + +/* + simply compare two values with help of logical operators + + dval [string] // device value that should be check against condition + op [string] // operator for comparisation: '=', '<', '>', '<=', '>=', '!=' + val [string] // condition value +*/ +AutomationModule.prototype.op = function(dval, op, val) { + if (op === "=") { + return dval === val; + } else if (op === "!=") { + return dval !== val; + } else if (op === ">") { + return dval > val; + } else if (op === "<") { + return dval < val; + } else if (op === ">=") { + return dval >= val; + } else if (op === "<=") { + return dval <= val; + } + + return null; // error!! +}; + +/* + simply compare switchControl level or changes + + vDev [object] // device object + targetValue [string] // target value that should be switched +*/ +AutomationModule.prototype.compareSwitchControl = function(vDev, targetValue) { + if (vDev) { + return (_.contains(["on", "off"], targetValue) && vDev.get('metrics:level') === targetValue) || (_.contains(["upstart", "upstop", "downstart", "downstop"], targetValue) && vDev.get("metrics:change") === targetValue) + } else { + return false; + } +}; + +/* + simply compare times in format HH:mm with current time + + time [string] // format HH:mm + operator [string] // operators for comparisation: '<=', '>=' (others doesn't make sence) +*/ +AutomationModule.prototype.compareTime = function(time, operator) { + var curTime = new Date(), + time_arr = time.split(":").map(function(x) { + return parseInt(x, 10); + }); + + return this.op(curTime.getHours() * 60 + curTime.getMinutes(), operator, time_arr[0] * 60 + time_arr[1]); +}; + +/* switches a vDev based on it's known object stored in modules config + + reverseLevel [boolean] // don't do this if level is already triggered + el = { + deviceId: '', [string], MUST // device ID + deviceType: '', [string], MUST // device type + level: '', [string/number], MUST // device level like: {color: { r: 0, g: 0, b: 0}}, 'on', 'off', 'open', 'close', [level] + sendAction: false, [boolean], MUST // don't do this if level is already triggered + reverseLevel: '', [string/number], OPTIONAL // device reverse level like: {color: { r: 0, g: 0, b: 0}}, 'on', 'off', 'open', 'close', [level] + operator: '' [string], OPTIONAL // operator for comparisation: '=', '<', '>', '<=', '>=', '!=' + } +*/ +AutomationModule.prototype.shiftDevice = function(el, reverse) { + var vDev = this.controller.devices.get(el.deviceId), + lvl = reverse && el.reverseLevel !== undefined && !!el.reverseLevel? el.reverseLevel : el.level, + set = this.executeActions(el.sendAction, vDev, lvl); + + // check if levels are equal and if active don't trigger new state + if (vDev && set) { + this.setNewDeviceState(vDev, lvl); + } +}; \ No newline at end of file diff --git a/classes/VirtualDevice.js b/classes/VirtualDevice.js index 13ec8f277..e55ec2b85 100644 --- a/classes/VirtualDevice.js +++ b/classes/VirtualDevice.js @@ -14,7 +14,11 @@ VirtualDevice = function (options) { visibility = options.defaults.hasOwnProperty('visibility')? options.defaults.visibility : true, customicons = options.defaults.hasOwnProperty('customIcons') ? options.defaults.customIcons : {}, removed = this.metrics && this.metrics.hasOwnProperty('removed')? this.metrics.removed : false, - isFailed = this.metrics && this.metrics.hasOwnProperty('isFailed')? this.metrics.isFailed : false; + isFailed = this.metrics && this.metrics.hasOwnProperty('isFailed')? this.metrics.isFailed : false, + tags = options.defaults.hasOwnProperty('tags')? options.defaults.tags : [], + order = options.defaults.hasOwnProperty('order') ? options.defaults.order : { rooms: 0, elements: 0, dashboard: 0 }, + location = options.defaults.hasOwnProperty('location')? options.defaults.location : 0, + creationTime = options.defaults.hasOwnProperty('creationTime')? options.defaults.creationTime : 0; _.extend(this, options, { id: options.deviceId, @@ -33,7 +37,8 @@ VirtualDevice = function (options) { 'creationTime', 'probeType', 'customIcons', - 'order' + 'order', + 'nodeId' ], collection: options.controller.devices, metrics: {}, @@ -47,20 +52,16 @@ VirtualDevice = function (options) { isFailed: isFailed, removed: removed }), - tags: [], + tags: tags, permanently_hidden: permHidden, - location: 0, + location: location, h: options.controller.hashCode(options.deviceId), hasHistory: false, visibility: visibility, - creationTime: 0, + creationTime: creationTime, probeType: probeType, customIcons: customicons, - order: { - rooms: 0, - elements: 0, - dashboard: 0 - } + order: order }, changed: {}, overlay: options.overlay || {}, @@ -82,6 +83,16 @@ VirtualDevice = function (options) { this.attributes.creatorId = options.moduleId; } + if (this.attributes.id.indexOf('ZWayVDev') > -1) { + var shifting = this.attributes.id.indexOf('RemoteHA') > -1? 4 : 2; + var idArr = this.attributes.id.split('_'); + var nodeId = idArr[shifting].split('-').shift(); + + this.attributes.nodeId = shifting === 4 && nodeId? 'R-'+ idArr[1] +'_'+ idArr[3] +'_'+ nodeId : (nodeId? parseInt(nodeId, 10) : undefined); // Remote is flagged by R-[moduleID]_[zwayName]_[nodeId] + } else { + delete this.attributes.nodeId; + } + this.initialize.apply(this, arguments); return this; }; diff --git a/defaultConfigs/config.json_ttyACM0 b/defaultConfigs/config.json_ttyACM0 index 8525927d9..78c58062c 100644 --- a/defaultConfigs/config.json_ttyACM0 +++ b/defaultConfigs/config.json_ttyACM0 @@ -26,7 +26,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -43,7 +43,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/defaultConfigs/config.json_ttyACM0_ZBW-WD b/defaultConfigs/config.json_ttyACM0_ZBW-WD index f1bb5cd22..6f95f1fe4 100644 --- a/defaultConfigs/config.json_ttyACM0_ZBW-WD +++ b/defaultConfigs/config.json_ttyACM0_ZBW-WD @@ -24,7 +24,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -41,7 +41,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/defaultConfigs/config.json_ttyACM0_ZBW-no b/defaultConfigs/config.json_ttyACM0_ZBW-no index 27fea4b50..3ffbe8df7 100644 --- a/defaultConfigs/config.json_ttyACM0_ZBW-no +++ b/defaultConfigs/config.json_ttyACM0_ZBW-no @@ -24,7 +24,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -41,7 +41,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/defaultConfigs/config.json_ttyAMA0 b/defaultConfigs/config.json_ttyAMA0 index b7f458fbc..680dea217 100644 --- a/defaultConfigs/config.json_ttyAMA0 +++ b/defaultConfigs/config.json_ttyAMA0 @@ -24,7 +24,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -41,7 +41,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/defaultConfigs/config.json_ttyAMA0_NonExpert b/defaultConfigs/config.json_ttyAMA0_NonExpert index 5b4e8d04f..8153a9a10 100644 --- a/defaultConfigs/config.json_ttyAMA0_NonExpert +++ b/defaultConfigs/config.json_ttyAMA0_NonExpert @@ -24,7 +24,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -41,7 +41,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/defaultConfigs/config.json_ttyS0 b/defaultConfigs/config.json_ttyS0 index 90c006fed..554771014 100644 --- a/defaultConfigs/config.json_ttyS0 +++ b/defaultConfigs/config.json_ttyS0 @@ -24,7 +24,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -41,7 +41,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/defaultConfigs/config.json_ttyS0-JBox b/defaultConfigs/config.json_ttyS0-JBox index b8c73b676..4e5266e96 100644 --- a/defaultConfigs/config.json_ttyS0-JBox +++ b/defaultConfigs/config.json_ttyS0-JBox @@ -24,7 +24,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -41,7 +41,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/defaultConfigs/config.json_ttyS1 b/defaultConfigs/config.json_ttyS1 index 6cc3ff9ef..36977993d 100644 --- a/defaultConfigs/config.json_ttyS1 +++ b/defaultConfigs/config.json_ttyS1 @@ -24,7 +24,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -41,7 +41,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/defaultConfigs/config.json_ttyUSB0_ZBW-no_vDev-no b/defaultConfigs/config.json_ttyUSB0_ZBW-no_vDev-no index 7029fc90c..439d77080 100644 --- a/defaultConfigs/config.json_ttyUSB0_ZBW-no_vDev-no +++ b/defaultConfigs/config.json_ttyUSB0_ZBW-no_vDev-no @@ -24,7 +24,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -41,7 +41,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/defaultConfigs/config.json_windows b/defaultConfigs/config.json_windows index a842781f2..7407a0b3e 100644 --- a/defaultConfigs/config.json_windows +++ b/defaultConfigs/config.json_windows @@ -24,7 +24,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }, { "id": 2, "role": 3, @@ -41,7 +41,7 @@ "hide_system_events": false, "hide_single_device_events": [], "skin": "default", - "qrcode": "" + "night_mode": false }], "instances": [{ "id": 1, diff --git a/lang/cn.json b/lang/cn.json new file mode 100644 index 000000000..3fed0bd82 --- /dev/null +++ b/lang/cn.json @@ -0,0 +1,36 @@ +{ + "ac_err_create_instance": "Cannot create instance. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Dependency not found for module: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Failed to load module because dependency was not loaded: [DEP]::[MODUL] = ", + "ac_err_file_load": "Cannot load ", + "ac_err_init_module": "Cannot instantiate module: ", + "ac_err_init_module_not_found": "Cannot instantiate module: module not found in the list of all modules.", + "ac_err_instance_already_exists": "Can't register duplicate module instance: ", + "ac_err_instance_empty": "Can't register empty module instance: ", + "ac_err_invalid_module": "Invalid module ", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Cannot load modules.json from ", + "ac_err_location_not_found": "Location doesn't exist.", + "ac_err_refonfigure_instance": "Cannot reconfigure instance with id: ", + "ac_err_stop_mod": "Cannot stop module ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Unable to uninstall or reset app.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Automation Controller is restarted.", + "err_parsing_npc_filters": "Cannot parse filters from namespaces.", + "err_preparing_room_dev_structure": "Cannot prepare Room-Device relation.", + "no_devices_found": "No devices found", + "profile_descr": "This is the admin profile. Admin profile created automatically.", + "profile_name": "Administrator", + "vd_err_virtual_dev": "Error during perform command execution: ", + "zaap_err_app_install": "An error has occurred during the app installation. The installation was cancelled.", + "zaap_err_no_archives": "This app has no archive. The installation was cancelled.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "The download server is not reachable or responses with an error. Please try again later.", + "zaap_err_uninstall_mod": "Unable to uninstall app.", + "zaap_err_unload_mod": "Unable to unload app.", + "zaap_war_app_installed_corrupt_instance": "There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_core_app_is_newer": "This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_restart_necessary": "The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization." +} \ No newline at end of file diff --git a/lang/cz.json b/lang/cz.json index d715df049..c0d7cfd3b 100644 --- a/lang/cz.json +++ b/lang/cz.json @@ -1,32 +1,36 @@ { - "ac_warn_restart": "Automatizační Kontrolér je restartován.", - "ac_err_init_module_not_found": "Nelze vytvořit instanci modulu: modul nenalezen v seznamu všech modulů.", - "ac_err_init_module": "Nelze vytvořit instanci modulu:", - "ac_err_dep_not_found": "Nebyla nalezena závislost modulu: [DEP] :: [MODUL] =", - "ac_err_dep_not_loaded": "Načtení modulu selhalo, protože závislost modulu nebyla načtení: [DEP] :: [MODUL] =", - "ac_err_dep_not_init": "Načtení modulu selhalo, protože pro závislost modulu nebyla vytvořena instance: [DEP] :: [MODUL] =", - "ac_err_file_load": "Nelze načíst", - "ac_err_invalid_module": "Neplatný modul", - "ac_err_load_mod_json": "Nelze načíst modules.json z", - "ac_err_stop_mod": "Nelze zastavit modul", - "ac_err_uninstall_mod":"Unable to uninstall or reset app.", - "ac_err_uninstall_skin":"Unable to uninstall skin.", - "ac_err_instance_already_exists":"Can't register duplicate module instance: ", - "ac_err_instance_empty":"Can't register empty module instance: ", - "ac_err_create_instance":"Cannot create instance. [APP] :: [INSTANCE ID] = ", - "ac_err_refonfigure_instance":"Cannot reconfigure instance with id: ", - "ac_err_location_not_found":"Location doesn't exist.", - "ac_err_load_failure":"An error has occurred while the app was instantiated. Please reload or reinstall the app", - "profile_name": "Administrátor", - "profile_descr": "Toto je profil admina. Profil admina je vytvořen automaticky.", - "vd_err_virtual_dev": "Chyba, během provádění příkazu:", - "zaap_err_uninstall_mod":"Unable to uninstall app.", - "zaap_err_unload_mod":"Unable to unload app.", - "zaap_war_restart_necessary":"The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization.", - "zaap_err_app_install":"An error has occurred during the app installation. The installation was cancelled.", - "zaap_war_app_installed_corrupt_instance":"There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", - "zaap_war_core_app_is_newer":"This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", - "zaap_err_no_archives":"This app has no archive. The installation was cancelled.", - "zaap_err_server":"The download server is not reachable or responses with an error. Please try again later.", - "zaap_err_reboot":"Reboot command is not supported on your platform, please unplug the power or follow the controller manual." + "ac_err_create_instance": "Cannot create instance. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Nebyla nalezena závislost modulu: [DEP] :: [MODUL] =", + "ac_err_dep_not_init": "Načtení modulu selhalo, protože pro závislost modulu nebyla vytvořena instance: [DEP] :: [MODUL] =", + "ac_err_dep_not_loaded": "Načtení modulu selhalo, protože závislost modulu nebyla načtení: [DEP] :: [MODUL] =", + "ac_err_file_load": "Nelze načíst", + "ac_err_init_module": "Nelze vytvořit instanci modulu:", + "ac_err_init_module_not_found": "Nelze vytvořit instanci modulu: modul nenalezen v seznamu všech modulů.", + "ac_err_instance_already_exists": "Can't register duplicate module instance: ", + "ac_err_instance_empty": "Can't register empty module instance: ", + "ac_err_invalid_module": "Neplatný modul", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Nelze načíst modules.json z", + "ac_err_location_not_found": "Location doesn't exist.", + "ac_err_refonfigure_instance": "Cannot reconfigure instance with id: ", + "ac_err_stop_mod": "Nelze zastavit modul", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Unable to uninstall or reset app.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Automatizační Kontrolér je restartován.", + "err_parsing_npc_filters": "Cannot parse filters from namespaces.", + "err_preparing_room_dev_structure": "Cannot prepare Room-Device relation.", + "no_devices_found": "No devices found", + "profile_descr": "Toto je profil admina. Profil admina je vytvořen automaticky.", + "profile_name": "Administrátor", + "vd_err_virtual_dev": "Chyba, během provádění příkazu:", + "zaap_err_app_install": "An error has occurred during the app installation. The installation was cancelled.", + "zaap_err_no_archives": "This app has no archive. The installation was cancelled.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "The download server is not reachable or responses with an error. Please try again later.", + "zaap_err_uninstall_mod": "Unable to uninstall app.", + "zaap_err_unload_mod": "Unable to unload app.", + "zaap_war_app_installed_corrupt_instance": "There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_core_app_is_newer": "This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_restart_necessary": "The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization." } \ No newline at end of file diff --git a/lang/de.json b/lang/de.json index 572fd31fa..81e421457 100644 --- a/lang/de.json +++ b/lang/de.json @@ -1,36 +1,36 @@ { - "ac_warn_restart":"Der Automation Controller wurde neu gestartet.", - "ac_err_init_module_not_found":"Das Modul kann nicht instanziiert werden: Das Modul wurde in der Modulliste nicht gefunden.", - "ac_err_init_module":"Das Modul kann nicht instanziiert werden: ", - "ac_err_dep_not_found":"Die Dependency wurde für das Modul nicht gefunden: [DEP]::[MODUL] = ", - "ac_err_dep_not_loaded":"Das Modul kann nicht geladen werden, da folgende Dependency nicht geladen wurde: [DEP]::[MODUL] = ", - "ac_err_dep_not_init":"Das Modul kann nicht geladen werden, da die Dependency nicht instanziiert wurde: [DEP]::[MODUL] = ", - "ac_err_file_load":"Fehler beim Laden von ", - "ac_err_invalid_module":"Nicht valides Modul: ", - "ac_err_load_mod_json":"modules.json kann nicht geladen werden. Modul: ", - "ac_err_stop_mod":"Das folgende Modul kann nicht gestoppt werden: ", - "ac_err_uninstall_mod":"Die App konnte nicht deinstalliert oder wiederhergestellt werden.", - "ac_err_uninstall_skin":"Das Skin konnte nicht deinstalliert werden.", - "ac_err_uninstall_skin":"Das Icon konnte nicht deinstalliert werden.", - "ac_err_instance_already_exists":"Instanzierung fehlgeschlagen. Folgende Instanz Id wird bereits verwendet: ", - "ac_err_instance_empty":"Leere Instanzen können nicht instanziert werden: ", - "ac_err_create_instance":"Es konnte keine neue Instanz von der App erzeugt werden. [APP] :: [INSTANZ ID] = ", - "ac_err_refonfigure_instance":"Folgende Instanz konnte nicht geupdatet werden: ", - "ac_err_location_not_found":"Der Raum existiert nicht.", - "ac_err_load_failure":"Es trat ein Fehler während der Initialisierung der App auf. Bitte installieren oder laden sie die folgende App erneut", - "profile_name": "Administrator", - "profile_descr": "Dies ist das Administratorprofil. Dieses wird automatisch angelegt.", - "vd_err_virtual_dev":"Fehler beim Ausführen des Befehls: ", - "no_devices_found":"Keine Geräte gefunden", - "err_preparing_room_dev_structure":"Die Raum-Geräte Beziehung konnte nicht erzeugt werden", - "err_parsing_npc_filters":"Die Namespaces-Filter konnten nicht gefunden/gelesen werden.", - "zaap_err_uninstall_mod":"Die App konnte nicht deinstalliert werden.", - "zaap_err_unload_mod":"Die App konnte nicht gelöscht werden.", - "zaap_war_restart_necessary":"Die App wurde erfolgreich herunter geladen, aber die Initialisierung ist fehlgeschlagen. Bitte starten Sie den Server neu um die Initialisierung abzuschließen.", - "zaap_err_app_install":"Während der Installation ist ein Fehler aufgetreten. Die Installation wurde abgebrochen.", - "zaap_war_app_installed_corrupt_instance":"Es stand keine App im Archiv mit dieser Version zur Verfügung. Stattdessen wurde die aktuellste Version installiert. Es kann vorkommen, dass bereits aktive Instanzen dieser App anschließend nicht mehr funktionieren. Bitte entfernen Sie diese von den Anwendungen > Aktiv und fügen Sie diese neu hinzu unter Anwendungen > Lokal.", - "zaap_war_core_app_is_newer":"Die App ist bereits mit der selben oder einer höheren auf ihrem System vorinstalliert. Die Installation wurde abgebrochen. Es kann vorkommen, dass bereits aktive Instanzen dieser App anschließend nicht mehr funktionieren. Bitte entfernen Sie diese von den Anwendungen > Aktiv und fügen Sie diese neu hinzu unter Anwendungen > Lokal.", - "zaap_err_no_archives":"Diese App hat kein Archiv. Die Installation wurde abgebrochen.", - "zaap_err_server":"Der Download-Server ist nicht erreichbar oder hat mit einem Fehler geantwortet. Versuchen Sie es später erneut.", - "zaap_err_reboot":"Die Systemneustart wird auf Ihrem Gerät nicht unterstützt. Starten Sie ihr System neu, in dem Sie kurz die Stromversorgung trennen oder die Anweisungen aus dem Handbuch befolgen." + "ac_err_create_instance": "Es konnte keine neue Instanz von der App erzeugt werden. [APP] :: [INSTANZ ID] = ", + "ac_err_dep_not_found": "Die Dependency wurde für das Modul nicht gefunden: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Das Modul kann nicht geladen werden, da die Dependency nicht instanziiert wurde: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Das Modul kann nicht geladen werden, da folgende Dependency nicht geladen wurde: [DEP]::[MODUL] = ", + "ac_err_file_load": "Fehler beim Laden von ", + "ac_err_init_module": "Das Modul kann nicht instanziiert werden: ", + "ac_err_init_module_not_found": "Das Modul kann nicht instanziiert werden: Das Modul wurde in der Modulliste nicht gefunden.", + "ac_err_instance_already_exists": "Instanzierung fehlgeschlagen. Folgende Instanz Id wird bereits verwendet: ", + "ac_err_instance_empty": "Leere Instanzen können nicht instanziert werden: ", + "ac_err_invalid_module": "Nicht valides Modul: ", + "ac_err_load_failure": "Es trat ein Fehler während der Initialisierung der App auf. Bitte installieren oder laden sie die folgende App erneut", + "ac_err_load_mod_json": "modules.json kann nicht geladen werden. Modul: ", + "ac_err_location_not_found": "Der Raum existiert nicht.", + "ac_err_refonfigure_instance": "Folgende Instanz konnte nicht geupdatet werden: ", + "ac_err_stop_mod": "Das folgende Modul kann nicht gestoppt werden: ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Die App konnte nicht deinstalliert oder wiederhergestellt werden.", + "ac_err_uninstall_skin": "Das Icon konnte nicht deinstalliert werden.", + "ac_warn_restart": "Der Automation Controller wurde neu gestartet.", + "err_parsing_npc_filters": "Die Namespaces-Filter konnten nicht gefunden\/gelesen werden.", + "err_preparing_room_dev_structure": "Die Raum-Geräte Beziehung konnte nicht erzeugt werden", + "no_devices_found": "Keine Geräte gefunden", + "profile_descr": "Dies ist das Administratorprofil. Dieses wird automatisch angelegt.", + "profile_name": "Administrator", + "vd_err_virtual_dev": "Fehler beim Ausführen des Befehls: ", + "zaap_err_app_install": "Während der Installation ist ein Fehler aufgetreten. Die Installation wurde abgebrochen.", + "zaap_err_no_archives": "Diese App hat kein Archiv. Die Installation wurde abgebrochen.", + "zaap_err_reboot": "Die Systemneustart wird auf Ihrem Gerät nicht unterstützt. Starten Sie ihr System neu, in dem Sie kurz die Stromversorgung trennen oder die Anweisungen aus dem Handbuch befolgen.", + "zaap_err_server": "Der Download-Server ist nicht erreichbar oder hat mit einem Fehler geantwortet. Versuchen Sie es später erneut.", + "zaap_err_uninstall_mod": "Die App konnte nicht deinstalliert werden.", + "zaap_err_unload_mod": "Die App konnte nicht gelöscht werden.", + "zaap_war_app_installed_corrupt_instance": "Es stand keine App im Archiv mit dieser Version zur Verfügung. Stattdessen wurde die aktuellste Version installiert. Es kann vorkommen, dass bereits aktive Instanzen dieser App anschließend nicht mehr funktionieren. Bitte entfernen Sie diese von den Anwendungen > Aktiv und fügen Sie diese neu hinzu unter Anwendungen > Lokal.", + "zaap_war_core_app_is_newer": "Die App ist bereits mit der selben oder einer höheren auf ihrem System vorinstalliert. Die Installation wurde abgebrochen. Es kann vorkommen, dass bereits aktive Instanzen dieser App anschließend nicht mehr funktionieren. Bitte entfernen Sie diese von den Anwendungen > Aktiv und fügen Sie diese neu hinzu unter Anwendungen > Lokal.", + "zaap_war_restart_necessary": "Die App wurde erfolgreich herunter geladen, aber die Initialisierung ist fehlgeschlagen. Bitte starten Sie den Server neu um die Initialisierung abzuschließen." } \ No newline at end of file diff --git a/lang/en.json b/lang/en.json index 5be93ccbc..3fed0bd82 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,36 +1,36 @@ -{ - "ac_warn_restart":"Automation Controller is restarted.", - "ac_err_init_module_not_found":"Cannot instantiate module: module not found in the list of all modules.", - "ac_err_init_module":"Cannot instantiate module: ", - "ac_err_dep_not_found":"Dependency not found for module: [DEP]::[MODUL] = ", - "ac_err_dep_not_loaded":"Failed to load module because dependency was not loaded: [DEP]::[MODUL] = ", - "ac_err_dep_not_init":"Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", - "ac_err_file_load":"Cannot load ", - "ac_err_invalid_module":"Invalid module ", - "ac_err_load_mod_json":"Cannot load modules.json from ", - "ac_err_stop_mod":"Cannot stop module ", - "ac_err_uninstall_mod":"Unable to uninstall or reset app.", - "ac_err_uninstall_skin":"Unable to uninstall skin.", - "ac_err_uninstall_icon":"Unable to uninstall icon.", - "ac_err_instance_already_exists":"Can't register duplicate module instance: ", - "ac_err_instance_empty":"Can't register empty module instance: ", - "ac_err_create_instance":"Cannot create instance. [APP] :: [INSTANCE ID] = ", - "ac_err_refonfigure_instance":"Cannot reconfigure instance with id: ", - "ac_err_location_not_found":"Location doesn't exist.", - "ac_err_load_failure":"An error has occurred while the app was instantiated. Please reload or reinstall the app", - "profile_name": "Administrator", - "profile_descr": "This is the admin profile. Admin profile created automatically.", - "vd_err_virtual_dev":"Error during perform command execution: ", - "no_devices_found":"No devices found", - "err_preparing_room_dev_structure":"Cannot prepare Room-Device relation.", - "err_parsing_npc_filters":"Cannot parse filters from namespaces.", - "zaap_err_uninstall_mod":"Unable to uninstall app.", - "zaap_err_unload_mod":"Unable to unload app.", - "zaap_war_restart_necessary":"The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization.", - "zaap_err_app_install":"An error has occurred during the app installation. The installation was cancelled.", - "zaap_war_app_installed_corrupt_instance":"There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", - "zaap_war_core_app_is_newer":"This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", - "zaap_err_no_archives":"This app has no archive. The installation was cancelled.", - "zaap_err_server":"The download server is not reachable or responses with an error. Please try again later.", - "zaap_err_reboot":"Reboot command is not supported on your platform, please unplug the power or follow the controller manual." +{ + "ac_err_create_instance": "Cannot create instance. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Dependency not found for module: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Failed to load module because dependency was not loaded: [DEP]::[MODUL] = ", + "ac_err_file_load": "Cannot load ", + "ac_err_init_module": "Cannot instantiate module: ", + "ac_err_init_module_not_found": "Cannot instantiate module: module not found in the list of all modules.", + "ac_err_instance_already_exists": "Can't register duplicate module instance: ", + "ac_err_instance_empty": "Can't register empty module instance: ", + "ac_err_invalid_module": "Invalid module ", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Cannot load modules.json from ", + "ac_err_location_not_found": "Location doesn't exist.", + "ac_err_refonfigure_instance": "Cannot reconfigure instance with id: ", + "ac_err_stop_mod": "Cannot stop module ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Unable to uninstall or reset app.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Automation Controller is restarted.", + "err_parsing_npc_filters": "Cannot parse filters from namespaces.", + "err_preparing_room_dev_structure": "Cannot prepare Room-Device relation.", + "no_devices_found": "No devices found", + "profile_descr": "This is the admin profile. Admin profile created automatically.", + "profile_name": "Administrator", + "vd_err_virtual_dev": "Error during perform command execution: ", + "zaap_err_app_install": "An error has occurred during the app installation. The installation was cancelled.", + "zaap_err_no_archives": "This app has no archive. The installation was cancelled.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "The download server is not reachable or responses with an error. Please try again later.", + "zaap_err_uninstall_mod": "Unable to uninstall app.", + "zaap_err_unload_mod": "Unable to unload app.", + "zaap_war_app_installed_corrupt_instance": "There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_core_app_is_newer": "This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_restart_necessary": "The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization." } \ No newline at end of file diff --git a/lang/es.json b/lang/es.json index 535c6c0b1..e9589be80 100644 --- a/lang/es.json +++ b/lang/es.json @@ -1,36 +1,36 @@ -{ - "ac_warn_restart":"El controlador de automatización se ha reiniciado.", - "ac_err_init_module_not_found":"No se ha podido instanciar el módulo: módulo no encontrado en la lista de todos los módulos.", - "ac_err_init_module":"No se ha podido instanciar el módulo: ", - "ac_err_dep_not_found":"Dependencia no encontrada para el módulo: [DEP]::[MODUL] = ", - "ac_err_dep_not_loaded":"No se ha podido cargar el módulo porque la dependencia no se ha cargado: [DEP]::[MODUL] = ", - "ac_err_dep_not_init":"No se ha podido cargar el módulo porque la dependencia no se ha instanciado: [DEP]::[MODUL] = ", - "ac_err_file_load":"No se ha podido cargar ", - "ac_err_invalid_module":"Módulo no válido ", - "ac_err_load_mod_json":"No se han podido cargar los módulos .json desde ", - "ac_err_stop_mod":"No se ha podido detener el módulo ", - "ac_err_uninstall_mod":"No se ha podido desinstalar ni reiniciar la aplicación.", - "ac_err_uninstall_skin":"No se ha podido desinstalar el tapiz.", - "ac_err_uninstall_icon":"No se ha podido desinstalar el icono.", - "ac_err_instance_already_exists":"No se pueden registrar instancias de módulo duplicadas: ", - "ac_err_instance_empty":"No se pueden registrar instancias de módulo vacías: ", - "ac_err_create_instance":"No se ha podido crear la instancia. [APP] :: [INSTANCE ID] = ", - "ac_err_refonfigure_instance":"No se ha podido reconfigurar la instancia con el id.: ", - "ac_err_location_not_found":"La ubicación no existe.", - "ac_err_load_failure":"Se ha producido un fallo mientras la aplicación se instanciaba. Vuelva a cargar o a instalar la aplicación", - "profile_name": "Administrador", - "profile_descr": "Este es el perfil del administrador. Perfil del administrador creado automáticamente.", - "vd_err_virtual_dev":"Error durante la ejecución del comando: ", - "no_devices_found":"No se ha encontrado ningún dispositivo", - "err_preparing_room_dev_structure":"No se ha podido preparar la relación Habitación-Dispositivo.", - "err_parsing_npc_filters":"No se han podido analizar los filtros a partir de los espacios nominales.", - "zaap_err_uninstall_mod":"No se ha podido desinstalar la aplicación.", - "zaap_err_unload_mod":"No se ha podido descargar la aplicación.", - "zaap_war_restart_necessary":"La aplicación se ha descargado correctamente pero la inicialización no se ha podido llevar a cabo. Reinicie el servidor para concluir la inicialización.", - "zaap_err_app_install":"Se ha producido un error durante la instalación de la aplicación. La instalación se ha cancelado.", - "zaap_war_app_installed_corrupt_instance":"No se ha encontrado ninguna aplicación con esta versión en el archivo disponible. En su lugar se ha instalado la versión más reciente. Puede que las instancias de esta aplicación existentes ya no funcionen. Elimínelas de Apps > Active y añada las nuevas a Apps > Local.", - "zaap_war_core_app_is_newer":"Esta aplicación ya está instalada con la misma versión o una más reciente. La instalación se ha cancelado. Puede que las instancias existentes de esta aplicación ya no funcionen. Elimínelas de Apps > Active y añada las nuevas a Apps > Local.", - "zaap_err_no_archives":"Esta aplicación no tiene archivo. La instalación se ha cancelado.", - "zaap_err_server":"No se ha podido acceder al servidor de descarga o este responde con un error. Inténtelo de nuevo más tarde.", - "zaap_err_reboot":"Su plataforma no admite el comando de reinicio, desconecte la alimentación o siga las indicaciones del manual del controlador." +{ + "ac_err_create_instance": "No se ha podido crear la instancia. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Dependencia no encontrada para el módulo: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "No se ha podido cargar el módulo porque la dependencia no se ha instanciado: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "No se ha podido cargar el módulo porque la dependencia no se ha cargado: [DEP]::[MODUL] = ", + "ac_err_file_load": "No se ha podido cargar ", + "ac_err_init_module": "No se ha podido instanciar el módulo: ", + "ac_err_init_module_not_found": "No se ha podido instanciar el módulo: módulo no encontrado en la lista de todos los módulos.", + "ac_err_instance_already_exists": "No se pueden registrar instancias de módulo duplicadas: ", + "ac_err_instance_empty": "No se pueden registrar instancias de módulo vacías: ", + "ac_err_invalid_module": "Módulo no válido ", + "ac_err_load_failure": "Se ha producido un fallo mientras la aplicación se instanciaba. Vuelva a cargar o a instalar la aplicación", + "ac_err_load_mod_json": "No se han podido cargar los módulos .json desde ", + "ac_err_location_not_found": "La ubicación no existe.", + "ac_err_refonfigure_instance": "No se ha podido reconfigurar la instancia con el id.: ", + "ac_err_stop_mod": "No se ha podido detener el módulo ", + "ac_err_uninstall_icon": "No se ha podido desinstalar el icono.", + "ac_err_uninstall_mod": "No se ha podido desinstalar ni reiniciar la aplicación.", + "ac_err_uninstall_skin": "No se ha podido desinstalar el tapiz.", + "ac_warn_restart": "El controlador de automatización se ha reiniciado.", + "err_parsing_npc_filters": "No se han podido analizar los filtros a partir de los espacios nominales.", + "err_preparing_room_dev_structure": "No se ha podido preparar la relación Habitación-Dispositivo.", + "no_devices_found": "No se ha encontrado ningún dispositivo", + "profile_descr": "Este es el perfil del administrador. Perfil del administrador creado automáticamente.", + "profile_name": "Administrador", + "vd_err_virtual_dev": "Error durante la ejecución del comando: ", + "zaap_err_app_install": "Se ha producido un error durante la instalación de la aplicación. La instalación se ha cancelado.", + "zaap_err_no_archives": "Esta aplicación no tiene archivo. La instalación se ha cancelado.", + "zaap_err_reboot": "Su plataforma no admite el comando de reinicio, desconecte la alimentación o siga las indicaciones del manual del controlador.", + "zaap_err_server": "No se ha podido acceder al servidor de descarga o este responde con un error. Inténtelo de nuevo más tarde.", + "zaap_err_uninstall_mod": "No se ha podido desinstalar la aplicación.", + "zaap_err_unload_mod": "No se ha podido descargar la aplicación.", + "zaap_war_app_installed_corrupt_instance": "No se ha encontrado ninguna aplicación con esta versión en el archivo disponible. En su lugar se ha instalado la versión más reciente. Puede que las instancias de esta aplicación existentes ya no funcionen. Elimínelas de Apps > Active y añada las nuevas a Apps > Local.", + "zaap_war_core_app_is_newer": "Esta aplicación ya está instalada con la misma versión o una más reciente. La instalación se ha cancelado. Puede que las instancias existentes de esta aplicación ya no funcionen. Elimínelas de Apps > Active y añada las nuevas a Apps > Local.", + "zaap_war_restart_necessary": "La aplicación se ha descargado correctamente pero la inicialización no se ha podido llevar a cabo. Reinicie el servidor para concluir la inicialización." } \ No newline at end of file diff --git a/lang/fi.json b/lang/fi.json new file mode 100644 index 000000000..3fed0bd82 --- /dev/null +++ b/lang/fi.json @@ -0,0 +1,36 @@ +{ + "ac_err_create_instance": "Cannot create instance. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Dependency not found for module: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Failed to load module because dependency was not loaded: [DEP]::[MODUL] = ", + "ac_err_file_load": "Cannot load ", + "ac_err_init_module": "Cannot instantiate module: ", + "ac_err_init_module_not_found": "Cannot instantiate module: module not found in the list of all modules.", + "ac_err_instance_already_exists": "Can't register duplicate module instance: ", + "ac_err_instance_empty": "Can't register empty module instance: ", + "ac_err_invalid_module": "Invalid module ", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Cannot load modules.json from ", + "ac_err_location_not_found": "Location doesn't exist.", + "ac_err_refonfigure_instance": "Cannot reconfigure instance with id: ", + "ac_err_stop_mod": "Cannot stop module ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Unable to uninstall or reset app.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Automation Controller is restarted.", + "err_parsing_npc_filters": "Cannot parse filters from namespaces.", + "err_preparing_room_dev_structure": "Cannot prepare Room-Device relation.", + "no_devices_found": "No devices found", + "profile_descr": "This is the admin profile. Admin profile created automatically.", + "profile_name": "Administrator", + "vd_err_virtual_dev": "Error during perform command execution: ", + "zaap_err_app_install": "An error has occurred during the app installation. The installation was cancelled.", + "zaap_err_no_archives": "This app has no archive. The installation was cancelled.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "The download server is not reachable or responses with an error. Please try again later.", + "zaap_err_uninstall_mod": "Unable to uninstall app.", + "zaap_err_unload_mod": "Unable to unload app.", + "zaap_war_app_installed_corrupt_instance": "There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_core_app_is_newer": "This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_restart_necessary": "The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization." +} \ No newline at end of file diff --git a/lang/fr.json b/lang/fr.json index 6d979f904..7369ccafc 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -1,33 +1,36 @@ { - "ac_warn_restart" : "Votre contrôleur redémarre.", - "ac_err_init_module_not_found" : "Impossible d'appliquer le module: le module est introuvable parmis la liste des modules.", - "ac_err_init_module" : "Impossible d'appliquer le module: ", - "ac_err_dep_not_found" : "Dépendance du module introuvable: [DEP]::[MODUL] = ", - "ac_err_dep_not_loaded" : "Échec du chargement du module car une dépendance n'est pas chargé: [DEP]::[MODUL] = ", - "ac_err_dep_not_init" : "Échec du chargement du module car une dépendance n'est pas initialisé: [DEP]::[MODUL] = ", - "ac_err_file_load" : "Chargement impossible ", - "ac_err_invalid_module" : "Module invalide ", - "ac_err_load_mod_json" : "Chargement impossible de modules.json en provenance de ", - "ac_err_stop_mod" : "Impossible d'arrêter le module ", - "ac_err_uninstall_mod" : "Impossible d'arrêter ou de redémarrer l'application.", - "ac_err_instance_already_exists" : "Impossible d'enregistrer une autre instance du module: ", - "ac_err_instance_empty" : "Impossible d'enregistrer une instance vide du module: ", - "ac_err_create_instance" : "Création d'une instance impossible. [APP] :: [INSTANCE ID] = ", - "ac_err_refonfigure_instance" : "Impossible de reconfigurer l'instance avec l'identifiant: ", - "ac_err_location_not_found" : "Localité inexistante.", - "profile_name" : "Administrateur", - "profile_descr" : "Ceci est le profil admin. le profil admin est créé automatiquement.", - "vd_err_virtual_dev" : "Erreur lors de l'exécution de la commande: ", - "no_devices_found" : "Aucun dispositif trouvé", - "err_preparing_room_dev_structure" : "Impossible de préparer le lien entre la pièce et le dispositif.", - "err_parsing_npc_filters" : "Impossible de filtrer à partir de l'espace de noms.", - "zaap_err_uninstall_mod" : "Impossible de désinataller l'application.", - "zaap_err_unload_mod" : "Impossible de décharger l'application.", - "zaap_war_restart_necessary" : "L'application a été téléchargé mais l'initialisation a échoué. Veuillez redémarrer le serveur afin de terminer l'initialisation.", - "zaap_err_app_install" : "Une erreur s'est produite lors de l'installation de l'application. L'installation a été annulé.", - "zaap_war_app_installed_corrupt_instance" : "Aucune application avec cette version dans les archives disponibles. La dernière version a été installé en remplacement. Il est possible que des instances existantes de cette application cessent de fonctionner. Veuillez les retirer dans Applications > Actives et ajouté les nouvelles sous Applications > Application locale", - "zaap_war_core_app_is_newer" : "Cette application est déjà installé avec une version supérieur ou identique. L'installation a été annulée. Il est possible que des instances existantes de cette application cessent de fonctionner. Veuillez les retirer dans Applications > Actives et ajouté les nouvelles sous Applications > Application locale.", - "zaap_err_no_archives" : "Cette application n'a pas d'archive. L'installation a été annulée.", - "zaap_err_server" : "Le serveur de téléchargement n'est pas accessible ou ne répond pas. Veuillez réessayer plus tard.", - "zaap_err_reboot":"Reboot command is not supported on your platform, please unplug the power or follow the controller manual." -} + "ac_err_create_instance": "Création d'une instance impossible. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Dépendance du module introuvable: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Échec du chargement du module car une dépendance n'est pas initialisé: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Échec du chargement du module car une dépendance n'est pas chargé: [DEP]::[MODUL] = ", + "ac_err_file_load": "Chargement impossible ", + "ac_err_init_module": "Impossible d'appliquer le module: ", + "ac_err_init_module_not_found": "Impossible d'appliquer le module: le module est introuvable parmis la liste des modules.", + "ac_err_instance_already_exists": "Impossible d'enregistrer une autre instance du module: ", + "ac_err_instance_empty": "Impossible d'enregistrer une instance vide du module: ", + "ac_err_invalid_module": "Module invalide ", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Chargement impossible de modules.json en provenance de ", + "ac_err_location_not_found": "Localité inexistante.", + "ac_err_refonfigure_instance": "Impossible de reconfigurer l'instance avec l'identifiant: ", + "ac_err_stop_mod": "Impossible d'arrêter le module ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Impossible d'arrêter ou de redémarrer l'application.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Votre contrôleur redémarre.", + "err_parsing_npc_filters": "Impossible de filtrer à partir de l'espace de noms.", + "err_preparing_room_dev_structure": "Impossible de préparer le lien entre la pièce et le dispositif.", + "no_devices_found": "Aucun dispositif trouvé", + "profile_descr": "Ceci est le profil admin. le profil admin est créé automatiquement.", + "profile_name": "Administrateur", + "vd_err_virtual_dev": "Erreur lors de l'exécution de la commande: ", + "zaap_err_app_install": "Une erreur s'est produite lors de l'installation de l'application. L'installation a été annulé.", + "zaap_err_no_archives": "Cette application n'a pas d'archive. L'installation a été annulée.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "Le serveur de téléchargement n'est pas accessible ou ne répond pas. Veuillez réessayer plus tard.", + "zaap_err_uninstall_mod": "Impossible de désinataller l'application.", + "zaap_err_unload_mod": "Impossible de décharger l'application.", + "zaap_war_app_installed_corrupt_instance": "Aucune application avec cette version dans les archives disponibles. La dernière version a été installé en remplacement. Il est possible que des instances existantes de cette application cessent de fonctionner. Veuillez les retirer dans Applications > Actives et ajouté les nouvelles sous Applications > Application locale", + "zaap_war_core_app_is_newer": "Cette application est déjà installé avec une version supérieur ou identique. L'installation a été annulée. Il est possible que des instances existantes de cette application cessent de fonctionner. Veuillez les retirer dans Applications > Actives et ajouté les nouvelles sous Applications > Application locale.", + "zaap_war_restart_necessary": "L'application a été téléchargé mais l'initialisation a échoué. Veuillez redémarrer le serveur afin de terminer l'initialisation." +} \ No newline at end of file diff --git a/lang/it.json b/lang/it.json new file mode 100644 index 000000000..3fed0bd82 --- /dev/null +++ b/lang/it.json @@ -0,0 +1,36 @@ +{ + "ac_err_create_instance": "Cannot create instance. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Dependency not found for module: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Failed to load module because dependency was not loaded: [DEP]::[MODUL] = ", + "ac_err_file_load": "Cannot load ", + "ac_err_init_module": "Cannot instantiate module: ", + "ac_err_init_module_not_found": "Cannot instantiate module: module not found in the list of all modules.", + "ac_err_instance_already_exists": "Can't register duplicate module instance: ", + "ac_err_instance_empty": "Can't register empty module instance: ", + "ac_err_invalid_module": "Invalid module ", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Cannot load modules.json from ", + "ac_err_location_not_found": "Location doesn't exist.", + "ac_err_refonfigure_instance": "Cannot reconfigure instance with id: ", + "ac_err_stop_mod": "Cannot stop module ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Unable to uninstall or reset app.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Automation Controller is restarted.", + "err_parsing_npc_filters": "Cannot parse filters from namespaces.", + "err_preparing_room_dev_structure": "Cannot prepare Room-Device relation.", + "no_devices_found": "No devices found", + "profile_descr": "This is the admin profile. Admin profile created automatically.", + "profile_name": "Administrator", + "vd_err_virtual_dev": "Error during perform command execution: ", + "zaap_err_app_install": "An error has occurred during the app installation. The installation was cancelled.", + "zaap_err_no_archives": "This app has no archive. The installation was cancelled.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "The download server is not reachable or responses with an error. Please try again later.", + "zaap_err_uninstall_mod": "Unable to uninstall app.", + "zaap_err_unload_mod": "Unable to unload app.", + "zaap_war_app_installed_corrupt_instance": "There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_core_app_is_newer": "This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_restart_necessary": "The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization." +} \ No newline at end of file diff --git a/lang/pt.json b/lang/pt.json new file mode 100644 index 000000000..3fed0bd82 --- /dev/null +++ b/lang/pt.json @@ -0,0 +1,36 @@ +{ + "ac_err_create_instance": "Cannot create instance. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Dependency not found for module: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Failed to load module because dependency was not loaded: [DEP]::[MODUL] = ", + "ac_err_file_load": "Cannot load ", + "ac_err_init_module": "Cannot instantiate module: ", + "ac_err_init_module_not_found": "Cannot instantiate module: module not found in the list of all modules.", + "ac_err_instance_already_exists": "Can't register duplicate module instance: ", + "ac_err_instance_empty": "Can't register empty module instance: ", + "ac_err_invalid_module": "Invalid module ", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Cannot load modules.json from ", + "ac_err_location_not_found": "Location doesn't exist.", + "ac_err_refonfigure_instance": "Cannot reconfigure instance with id: ", + "ac_err_stop_mod": "Cannot stop module ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Unable to uninstall or reset app.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Automation Controller is restarted.", + "err_parsing_npc_filters": "Cannot parse filters from namespaces.", + "err_preparing_room_dev_structure": "Cannot prepare Room-Device relation.", + "no_devices_found": "No devices found", + "profile_descr": "This is the admin profile. Admin profile created automatically.", + "profile_name": "Administrator", + "vd_err_virtual_dev": "Error during perform command execution: ", + "zaap_err_app_install": "An error has occurred during the app installation. The installation was cancelled.", + "zaap_err_no_archives": "This app has no archive. The installation was cancelled.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "The download server is not reachable or responses with an error. Please try again later.", + "zaap_err_uninstall_mod": "Unable to uninstall app.", + "zaap_err_unload_mod": "Unable to unload app.", + "zaap_war_app_installed_corrupt_instance": "There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_core_app_is_newer": "This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_restart_necessary": "The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization." +} \ No newline at end of file diff --git a/lang/ru.json b/lang/ru.json index f3c716901..3fed0bd82 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -1,32 +1,36 @@ -{ - "ac_warn_restart":"Automation Controller is restarted.", - "ac_err_init_module_not_found":"Cannot instantiate module: module not found in the list of all modules.", - "ac_err_init_module":"Cannot instantiate module: ", - "ac_err_dep_not_found":"Dependency not found for module: [DEP]::[MODUL] = ", - "ac_err_dep_not_loaded":"Failed to load module because dependency was not loaded: [DEP]::[MODUL] = ", - "ac_err_dep_not_init":"Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", - "ac_err_file_load":"Cannot load ", - "ac_err_invalid_module":"Invalid module ", - "ac_err_load_mod_json":"Cannot load modules.json from ", - "ac_err_stop_mod":"Cannot stop module ", - "ac_err_uninstall_mod":"Unable to uninstall or reset app.", - "ac_err_uninstall_skin":"Unable to uninstall skin.", - "ac_err_instance_already_exists":"Can't register duplicate module instance: ", - "ac_err_instance_empty":"Can't register empty module instance: ", - "ac_err_create_instance":"Cannot create instance. [APP] :: [INSTANCE ID] = ", - "ac_err_refonfigure_instance":"Cannot reconfigure instance with id: ", - "ac_err_location_not_found":"Location doesn't exist.", - "ac_err_load_failure":"An error has occurred while the app was instantiated. Please reload or reinstall the app", - "profile_name": "Administrator", - "profile_descr": "This is the admin profile. Admin profile created automatically.", - "vd_err_virtual_dev":"Error during perform command execution: ", - "zaap_err_uninstall_mod":"Unable to uninstall app.", - "zaap_err_unload_mod":"Unable to unload app.", - "zaap_war_restart_necessary":"The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization.", - "zaap_err_app_install":"An error has occurred during the app installation. The installation was cancelled.", - "zaap_war_app_installed_corrupt_instance":"There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", - "zaap_war_core_app_is_newer":"This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", - "zaap_err_no_archives":"This app has no archive. The installation was cancelled.", - "zaap_err_server":"The download server is not reachable or responses with an error. Please try again later.", - "zaap_err_reboot":"Reboot command is not supported on your platform, please unplug the power or follow the controller manual." +{ + "ac_err_create_instance": "Cannot create instance. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Dependency not found for module: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Failed to load module because dependency was not loaded: [DEP]::[MODUL] = ", + "ac_err_file_load": "Cannot load ", + "ac_err_init_module": "Cannot instantiate module: ", + "ac_err_init_module_not_found": "Cannot instantiate module: module not found in the list of all modules.", + "ac_err_instance_already_exists": "Can't register duplicate module instance: ", + "ac_err_instance_empty": "Can't register empty module instance: ", + "ac_err_invalid_module": "Invalid module ", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Cannot load modules.json from ", + "ac_err_location_not_found": "Location doesn't exist.", + "ac_err_refonfigure_instance": "Cannot reconfigure instance with id: ", + "ac_err_stop_mod": "Cannot stop module ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Unable to uninstall or reset app.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Automation Controller is restarted.", + "err_parsing_npc_filters": "Cannot parse filters from namespaces.", + "err_preparing_room_dev_structure": "Cannot prepare Room-Device relation.", + "no_devices_found": "No devices found", + "profile_descr": "This is the admin profile. Admin profile created automatically.", + "profile_name": "Administrator", + "vd_err_virtual_dev": "Error during perform command execution: ", + "zaap_err_app_install": "An error has occurred during the app installation. The installation was cancelled.", + "zaap_err_no_archives": "This app has no archive. The installation was cancelled.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "The download server is not reachable or responses with an error. Please try again later.", + "zaap_err_uninstall_mod": "Unable to uninstall app.", + "zaap_err_unload_mod": "Unable to unload app.", + "zaap_war_app_installed_corrupt_instance": "There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_core_app_is_newer": "This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_restart_necessary": "The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization." } \ No newline at end of file diff --git a/lang/sk.json b/lang/sk.json index 8b7901e00..5897a1c58 100644 --- a/lang/sk.json +++ b/lang/sk.json @@ -1,32 +1,36 @@ -{ - "ac_warn_restart":"Automatizačný Kontrolér je reštartovaný.", - "ac_err_init_module_not_found":"Nie je možné vytvoriť inštanciu modulu: modul nenájdený v zozname všetkých modulov.", - "ac_err_init_module":"Nie je možné vytvoriť inštanciu modulu: ", - "ac_err_dep_not_found":"Nebola nájdená závislosť modulu: [DEP]::[MODUL] = ", - "ac_err_dep_not_loaded":"Načítanie modulu zlyhalo, pretože závislosť modulu nebola načítana: [DEP]::[MODUL] = ", - "ac_err_dep_not_init":"Načítanie modulu zlyhalo, pretože pre závislosť modulu nebola vytvorená inštancia: [DEP]::[MODUL] = ", - "ac_err_file_load":"Nedá sa načítať ", - "ac_err_invalid_module":"Neplatný modul ", - "ac_err_load_mod_json":"Nedá sa načítať modules.json z ", - "ac_err_stop_mod":"Nedá sa zastaviť modul ", - "ac_err_uninstall_mod":"Unable to uninstall or reset app.", - "ac_err_uninstall_skin":"Unable to uninstall skin.", - "ac_err_instance_already_exists":"Can't register duplicate module instance: ", - "ac_err_instance_empty":"Can't register empty module instance: ", - "ac_err_create_instance":"Cannot create instance. [APP] :: [INSTANCE ID] = ", - "ac_err_refonfigure_instance":"Cannot reconfigure instance with id: ", - "ac_err_location_not_found":"Location doesn't exist.", - "ac_err_load_failure":"An error has occurred while the app was instantiated. Please reload or reinstall the app", - "profile_name": "Administrátor", - "profile_descr": "Toto je profil admina. Profil admina je vytvorený automaticky.", - "vd_err_virtual_dev":"Chyba, počas vykonávania príkazu: ", - "zaap_err_uninstall_mod":"Unable to uninstall app.", - "zaap_err_unload_mod":"Unable to unload app.", - "zaap_war_restart_necessary":"The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization.", - "zaap_err_app_install":"An error has occurred during the app installation. The installation was cancelled.", - "zaap_war_app_installed_corrupt_instance":"There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", - "zaap_war_core_app_is_newer":"This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", - "zaap_err_no_archives":"This app has no archive. The installation was cancelled.", - "zaap_err_server":"The download server is not reachable or responses with an error. Please try again later.", - "zaap_err_reboot":"Reboot command is not supported on your platform, please unplug the power or follow the controller manual." +{ + "ac_err_create_instance": "Cannot create instance. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Nebola nájdená závislosť modulu: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Načítanie modulu zlyhalo, pretože pre závislosť modulu nebola vytvorená inštancia: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Načítanie modulu zlyhalo, pretože závislosť modulu nebola načítana: [DEP]::[MODUL] = ", + "ac_err_file_load": "Nedá sa načítať ", + "ac_err_init_module": "Nie je možné vytvoriť inštanciu modulu: ", + "ac_err_init_module_not_found": "Nie je možné vytvoriť inštanciu modulu: modul nenájdený v zozname všetkých modulov.", + "ac_err_instance_already_exists": "Can't register duplicate module instance: ", + "ac_err_instance_empty": "Can't register empty module instance: ", + "ac_err_invalid_module": "Neplatný modul ", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Nedá sa načítať modules.json z ", + "ac_err_location_not_found": "Location doesn't exist.", + "ac_err_refonfigure_instance": "Cannot reconfigure instance with id: ", + "ac_err_stop_mod": "Nedá sa zastaviť modul ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Unable to uninstall or reset app.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Automatizačný Kontrolér je reštartovaný.", + "err_parsing_npc_filters": "Cannot parse filters from namespaces.", + "err_preparing_room_dev_structure": "Cannot prepare Room-Device relation.", + "no_devices_found": "No devices found", + "profile_descr": "Toto je profil admina. Profil admina je vytvorený automaticky.", + "profile_name": "Administrátor", + "vd_err_virtual_dev": "Chyba, počas vykonávania príkazu: ", + "zaap_err_app_install": "An error has occurred during the app installation. The installation was cancelled.", + "zaap_err_no_archives": "This app has no archive. The installation was cancelled.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "The download server is not reachable or responses with an error. Please try again later.", + "zaap_err_uninstall_mod": "Unable to uninstall app.", + "zaap_err_unload_mod": "Unable to unload app.", + "zaap_war_app_installed_corrupt_instance": "There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_core_app_is_newer": "This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_restart_necessary": "The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization." } \ No newline at end of file diff --git a/lang/sv.json b/lang/sv.json new file mode 100644 index 000000000..3fed0bd82 --- /dev/null +++ b/lang/sv.json @@ -0,0 +1,36 @@ +{ + "ac_err_create_instance": "Cannot create instance. [APP] :: [INSTANCE ID] = ", + "ac_err_dep_not_found": "Dependency not found for module: [DEP]::[MODUL] = ", + "ac_err_dep_not_init": "Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", + "ac_err_dep_not_loaded": "Failed to load module because dependency was not loaded: [DEP]::[MODUL] = ", + "ac_err_file_load": "Cannot load ", + "ac_err_init_module": "Cannot instantiate module: ", + "ac_err_init_module_not_found": "Cannot instantiate module: module not found in the list of all modules.", + "ac_err_instance_already_exists": "Can't register duplicate module instance: ", + "ac_err_instance_empty": "Can't register empty module instance: ", + "ac_err_invalid_module": "Invalid module ", + "ac_err_load_failure": "An error has occurred while the app was instantiated. Please reload or reinstall the app", + "ac_err_load_mod_json": "Cannot load modules.json from ", + "ac_err_location_not_found": "Location doesn't exist.", + "ac_err_refonfigure_instance": "Cannot reconfigure instance with id: ", + "ac_err_stop_mod": "Cannot stop module ", + "ac_err_uninstall_icon": "Unable to uninstall icon.", + "ac_err_uninstall_mod": "Unable to uninstall or reset app.", + "ac_err_uninstall_skin": "Unable to uninstall skin.", + "ac_warn_restart": "Automation Controller is restarted.", + "err_parsing_npc_filters": "Cannot parse filters from namespaces.", + "err_preparing_room_dev_structure": "Cannot prepare Room-Device relation.", + "no_devices_found": "No devices found", + "profile_descr": "This is the admin profile. Admin profile created automatically.", + "profile_name": "Administrator", + "vd_err_virtual_dev": "Error during perform command execution: ", + "zaap_err_app_install": "An error has occurred during the app installation. The installation was cancelled.", + "zaap_err_no_archives": "This app has no archive. The installation was cancelled.", + "zaap_err_reboot": "Reboot command is not supported on your platform, please unplug the power or follow the controller manual.", + "zaap_err_server": "The download server is not reachable or responses with an error. Please try again later.", + "zaap_err_uninstall_mod": "Unable to uninstall app.", + "zaap_err_unload_mod": "Unable to unload app.", + "zaap_war_app_installed_corrupt_instance": "There was no app with this version in the archive available. The lastest version was installed instead. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_core_app_is_newer": "This app is already installed with a newer or same version. The installation was cancelled. It could be that already existing instances of this app do not work anymore. Please remove them from Apps > Active and add new ones under Apps > Local.", + "zaap_war_restart_necessary": "The app was downloaded successfully but the initialization has failed. Please restart the server to finalize the initialization." +} \ No newline at end of file diff --git a/main.js b/main.js index 9ae2428e9..11f0e9e6b 100644 --- a/main.js +++ b/main.js @@ -25,11 +25,26 @@ executeFile("updateBackendConfig.js"); // overload saveObject to allow backup/restore of all JSON files in storage __saveObject = saveObject; __storageContent = loadObject("__storageContent") || []; + +// check against storage if listed files really exists +__storageContent = __storageContent.filter(function(name) { + return !!loadObject(name); +}); + saveObject = function(name, object) { + // add entry to __storageContent if it does not already exist if (__storageContent.indexOf(name) === -1 && !!name) { __storageContent.push(name); __saveObject("__storageContent", __storageContent); + + // remove entry from __storageContent if deleted + } else if (!!name && object === null) { + __storageContent = _.filter(__storageContent, function(fileName){ + return fileName !== name; + }); + __saveObject("__storageContent", __storageContent); } + __saveObject(name, object); }; @@ -64,7 +79,6 @@ if (!config && config === null) { executeFile(config.libPath + "/base64.js"); executeFile(config.libPath + "/qrcode.js"); executeFile(config.libPath + "/LimitedArray.js"); - //--- Load Automation subsystem classes executeFile(config.classesPath + "/VirtualDevice.js"); executeFile(config.classesPath + "/DevicesCollection.js"); diff --git a/modules/Alexa/htdocs/icon.png b/modules/Alexa/htdocs/icon.png new file mode 100644 index 000000000..85564ac24 Binary files /dev/null and b/modules/Alexa/htdocs/icon.png differ diff --git a/modules/Alexa/index.js b/modules/Alexa/index.js new file mode 100644 index 000000000..49f50b136 --- /dev/null +++ b/modules/Alexa/index.js @@ -0,0 +1,1414 @@ +/*** Alexa Z-Way HA module ******************************************* + + Version: 1.1.0 + (c) Z-Wave.Me, 2016 + ----------------------------------------------------------------------------- + Author: Michael Hensche + Description: Supports Alexa SmartHome Skill API version 2 + + ******************************************************************************/ + +function Alexa (id, controller) { + // Call superconstructor first (AutomationModule) + Alexa.super_.call(this, id, controller); + + // namespaces + this.NAMESPACE_CONTROL = "Alexa.ConnectedHome.Control"; + this.NAMESPACE_DISCOVERY = "Alexa.ConnectedHome.Discovery"; + this.NAMESPACE_QUERY = "Alexa.ConnectedHome.Query"; + + // discovery + this.REQUEST_DISCOVER = "DiscoverAppliancesRequest"; + this.RESPONSE_DISCOVER = "DiscoverAppliancesResponse"; + + // control + this.REQUEST_TURN_ON = "TurnOnRequest"; + this.RESPONSE_TURN_ON = "TurnOnConfirmation"; + this.REQUEST_TURN_OFF = "TurnOffRequest"; + this.RESPONSE_TURN_OFF = "TurnOffConfirmation"; + this.REQUEST_SET_PERCENTAGE = "SetPercentageRequest"; + this.RESPONSE_SET_PERCENTAGE = "SetPercentageConfirmation"; + this.REQUEST_INCREMENT_PERCENTAGE = "IncrementPercentageRequest"; + this.RESPONSE_INCREMENT_PERCENTAGE = "IncrementPercentageConfirmation"; + this.REQUEST_DECREMENT_PERCENTAGE = "DecrementPercentageRequest"; + this.RESPONSE_DECREMENT_PERCENTAGE = "DecrementPercentageConfirmation"; + this.REQUEST_SET_TARGET_TEMPERATURE = "SetTargetTemperatureRequest"; + this.RESPONSE_SET_TARGET_TEMPERATURE = "SetTargetTemperatureConfirmation"; + this.REQUEST_INCREMENT_TARGET_TEMPERATURE = "IncrementTargetTemperatureRequest"; + this.RESPONSE_INCREMENT_TARGET_TEMPERATURE = "IncrementTargetTemperatureConfirmation"; + this.REQUEST_DECREMENT_TARGET_TEMPERATURE = "DecrementTargetTemperatureRequest"; + this.RESPONSE_DECREMENT_TARGET_TEMPERATURE = "DecrementTargetTemperatureConfirmation"; + this.REQUEST_SET_LOCK_STATE = "SetLockStateRequest"; + this.RESPONSE_SET_LOCK_STATE = "SetLockStateConfirmation"; + this.REQUEST_SET_COLOR = "SetColorRequest"; + this.RESPONSE_SET_COLOR = "SetColorConfirmation"; + + // query + this.REQUEST_LOCK_STATE = "GetLockStateRequest"; + this.RESPONSE_GET_LOCK_STATE = "GetLockStateResponse"; + this.REQUEST_TARGET_TEMPERATURE = "GetTargetTemperatureRequest"; + this.RESPONSE_TARGET_TEMPERATURE = "GetTargetTemperatureResponse"; + this.REQUEST_TARGET_READING_TEMPERATURE = "GetTemperatureReadingRequest"; + this.RESPONSE_TARGET_READING_TEMPERATURE = "GetTemperatureReadingResponse"; + + // errors + this.ERROR_UNSUPPORTED_OPERATION = "UnsupportedOperationError"; + this.ERROR_UNEXPECTED_INFO = "UnexpectedInformationReceivedError"; + this.ERROR_NO_SUCH_TARGET = "NoSuchTargetError"; + this.ERROR_TARGET_OFFLINE = "TargetOfflineError"; + this.ERROR_VALUE_OUT_OF_RANGE = "ValueOutOfRangeError"; + + + this.whiteListDeviceType = [{"sensorMultilevel":["temperature"]}, {"switchBinary":[]}, {"toggleButton":[]}, {"switchMultilevel":[]}, {"thermostat":[]}, {"doorlock":[]}]; +} + +inherits(Alexa, AutomationModule); + +_module = Alexa; + +Alexa.prototype.init = function(config) { + var self = this; + + Alexa.super_.prototype.init.call(this, config); + + this.defineHandlers(); + this.externalAPIAllow(); + global["AlexaAPI"] = this.AlexaAPI; +}; + +Alexa.prototype.stop = function() { + var self = this; + + delete global["AlexaAPI"]; + + Alexa.super_.prototype.stop.call(this); +}; + +Alexa.prototype.handleDiscovery = function(event) { + var self = this, + appliances = self.buildAppliances(), + header = self.createHeader(self.NAMESPACE_DISCOVERY, self.RESPONSE_DISCOVER), + payload = { + "discoveredAppliances": appliances + }; + + return self.createDirective(header, payload); +}; + +Alexa.prototype.handleControl = function(event) { + var self = this, + response = null, + requestedName = event.header.name; + + switch (requestedName) { + case self.REQUEST_TURN_ON : + response = self.handleControlTurnOn(event); + break; + case self.REQUEST_TURN_OFF : + response = self.handleControlTurnOff(event); + break; + case self.REQUEST_SET_PERCENTAGE : + response = self.handleControlSetPercentage(event); + break; + case self.REQUEST_INCREMENT_PERCENTAGE : + response = self.handleControlIncrementPercentage(event); + break; + case self.REQUEST_DECREMENT_PERCENTAGE : + response = self.handleControlDecrementPercentage(event); + break; + case self.REQUEST_SET_TARGET_TEMPERATURE : + response = self.handleControlSetTargetTemperature(event); + break; + case self.REQUEST_INCREMENT_TARGET_TEMPERATURE : + response = self.handleControlIncrementTargetTemperature(event); + break; + case self.REQUEST_DECREMENT_TARGET_TEMPERATURE : + response = self.handleControlDecrementTargetTemperature(event); + break; + case self.REQUEST_SET_LOCK_STATE : + response = self.handleControlSetLockState(event); + break; + case self.REQUEST_SET_COLOR: + response = self.handleControlSetColor(event); + break; + default: + console.log("Error", "Unsupported operation" + requestedName); + response = self.handleUnsupportedOperation(); + break; + } + return response; +}; + + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "TurnOnRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "TurnOnConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": {} + * } + */ +Alexa.prototype.handleControlTurnOn = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_TURN_ON); + vDev.performCommand("on"); + } + + return self.createDirective(header, {}); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "TurnOffRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "TurnOffConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": {} + * } + */ +Alexa.prototype.handleControlTurnOff = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_TURN_OFF) + vDev.performCommand("off"); + } + + return self.createDirective(header, {}); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "SetPercentageRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * }, + * "percentageState": { + * "value": 50.0 + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "SetPercentageConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": {} + * } + */ +Alexa.prototype.handleControlSetPercentage = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_SET_PERCENTAGE); + var level = event.payload.percentageState.value; + vDev.performCommand("exact", {level: level}); + } + + return self.createDirective(header, {}); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "IncrementPercentageRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * }, + * "deltaPercentage": { + * "value": 10.0 + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "IncrementPercentageConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": {} + * } + */ +Alexa.prototype.handleControlIncrementPercentage = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + maxLevel = 99, + minLevel = 0, + newLevel = 0, + delta = event.payload.deltaPercentage.value, + response = {}; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_INCREMENT_PERCENTAGE); + var curLevel = vDev.get("metrics:level"); + if(curLevel + delta <= maxLevel) { + newLevel = curLevel + delta; + } else { + newLevel = maxLevel; + } + + vDev.performCommand("exact", {level: newLevel}); + } + + return self.createDirective(header, response); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "DecrementPercentageRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * }, + * "deltaPercentage": { + * "value": 10.0 + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "DecrementPercentageConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": {} + * } + */ +Alexa.prototype.handleControlDecrementPercentage = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + minLevel = 0, + newLevel = 0, + delta = event.payload.deltaPercentage.value; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_DECREMENT_PERCENTAGE); + var curLevel = vDev.get("metrics:level"); + + if(curLevel - delta >= minLevel) { + newLevel = curLevel - delta; + } else { + newLevel = minLevel; + } + + vDev.performCommand("exact", {level: newLevel}); + } + + return self.createDirective(header, {}); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "SetTargetTemperatureRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * }, + * "targetTemperature": { + * "value": 25.0 + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "SetTargetTemperatureConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "targetTemperature": { + * "value": 25.0 + * }, + * "temperatureMode": { + * "value" "AUTO" + * }, + * "previousState": { + * "targetTemperature": { + * "value": 21.0 + * }, + * "mode": { + * "value": "AUTO" + * } + * } + * } + * } + */ +Alexa.prototype.handleControlSetTargetTemperature = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + temperature = event.payload.targetTemperature.value, + response = {}; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + var maxTemp = vDev.get("metrics:max"), + minTemp = vDev.get("metrics:min"), + prevTemp = vDev.get("metrics:level"); + + if (temperature < minTemp || temperature > maxTemp) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_VALUE_OUT_OF_RANGE); + response = { + "minimumValue": minTemp, + "maximumValue": maxTemp + }; + } else { + header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_SET_TARGET_TEMPERATURE); + vDev.performCommand("exact", {level: temperature}); + + response = { + "targetTemperature": { + "value": temperature + }, + "temperatureMode": { + "value": "AUTO" + }, + "previousState": { + "targetTemperature": { + "value": prevTemp + }, + "mode": { + "value": "AUTO" + } + } + }; + } + } + return self.createDirective(header, response); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "IncrementTargetTemperatureRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * }, + * "deltaTemperature": { + * "value": 3.6 + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "IncrementTargetTemperatureConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "previousState": { + * "targetTemperature": { + * "value": 21.0 + * }, + * "mode": { + * "value": "AUTO" + * } + * } + * "targetTemperature": { + * "value": 24.6 + * }, + * "temperatureMode": { + * "value" "AUTO" + * } + * } + * } + */ +Alexa.prototype.handleControlIncrementTargetTemperature = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + newTemp = 0, + temperature = event.payload.deltaTemperature.value, + response = {}; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + var maxTemp = vDev.get("metrics:max"), + minTemp = vDev.get("metrics:min"), + curTemp = vDev.get("metrics:level"); + + if(curTemp + temperature > maxTemp) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_VALUE_OUT_OF_RANGE); + response = { + "minimumValue": minTemp, + "maximumValue": maxTemp + }; + } else { + header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_INCREMENT_TARGET_TEMPERATURE) + + newTemp = curTemp + temperature; + + vDev.performCommand("exact", {level: newTemp}); + + response = { + "previousState": { + "mode": { + "value": "AUTO" + }, + "targetTemperature": { + "value": curTemp + } + }, + "targetTemperature": { + "value": newTemp + }, + "temperatureMode": { + "value": "AUTO" + } + }; + } + } + + return self.createDirective(header, response); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "DecrementTargetTemperatureRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * }, + * "deltaTemperature": { + * "value": 2 + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "DecrementTargetTemperatureConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "previousState": { + * "targetTemperature": { + * "value": 23.0 + * }, + * "mode": { + * "value": "AUTO" + * } + * } + * "targetTemperature": { + * "value": 21.0 + * }, + * "temperatureMode": { + * "value" "AUTO" + * } + * } + * } + */ +Alexa.prototype.handleControlDecrementTargetTemperature = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + newTemp = 0, + temperature = event.payload.deltaTemperature.value, + response = {}; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + var minTemp = vDev.get("metrics:min"), + maxTemp = vDev.get("metrics:max"), + curTemp = vDev.get("metrics:level"); + + if(curTemp - temperature < minTemp) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_VALUE_OUT_OF_RANGE); + response = { + "minimumValue": minTemp, + "maximumValue": maxTemp + }; + } else { + + newTemp = curTemp - temperature; + + header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_DECREMENT_TARGET_TEMPERATURE); + vDev.performCommand("exact", {level: newTemp}); + + response = { + "previousState": { + "mode": { + "value": "AUTO" + }, + "targetTemperature": { + "value": curTemp + } + }, + "targetTemperature": { + "value": newTemp + }, + "temperatureMode": { + "value": "AUTO" + } + }; + } + } + + return self.createDirective(header, response); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "SetLockStateRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * }, + * "lockState": "LOCKED" + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "SetLockStateConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "lockState": "LOCKED" + * } + * } + */ +Alexa.prototype.handleControlSetLockState = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + lockState = event.payload.targetTemperature; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_SET_LOCK_STATE) + var newLevel = lockState == "LOCKED" ? "close" : "open"; + + vDev.performCommand("exact", {level: newLevel}); + + var response = { + "lockState": lockState + }; + } + + return self.createDirective(header, response); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "SetColorRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * }, + * "color": { + * "hue": 0.0, + * "saturation": 1.0000, + * "brightness": 1.0000 + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "SetColorConfirmation", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "achievedState": { + * "color": { + * "hue": 0.0, + * "saturation": 1.0000, + * "brightness": 1.0000 + * } + * } + * } + * } + */ +Alexa.prototype.handleControlSetColor = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + response = {}, + hue = event.payload.color.hue, + saturation = event.payload.color.saturation, + brightness = event.payload.color.brightness, + color = hsvToRgb(hue, saturation, brightness); + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + var header = self.createHeader(self.NAMESPACE_CONTROL, self.RESPONSE_SET_COLOR); + response = { + "achievedState": { + "color": { + "hue": hue, + "saturation": saturation, + "brightness": brightness + } + } + } + vDev.performCommand("exact", {red: color.r, green: color.g, blue: color.b}); + } + + return self.createDirective(header, response); +} + +Alexa.prototype.handleQuery = function(event) { + var self = this, + response = null, + requestedName = event.header.name; + + //console.log("Handle Query: ", JSON.stringify(event)); + + switch (requestedName) { + case self.REQUEST_LOCK_STATE : + response = self.handleQueryLockState(event); + break; + case self.REQUEST_TARGET_TEMPERATURE : + response = self.handleQueryTargetTemperature(event); + break; + case self.REQUEST_TARGET_READING_TEMPERATURE : + response = self.handleQueryTargetReadingTemperature(event); + break; + default: + console.log("Error", "Unsupported operation" + requestedName); + response = self.handleUnsupportedOperation(); + break; + } + return response; +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "GetLockStateRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "GetLockStateResponse", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "lockSate": "LOCKED", + * "applianceResponseTimestamp": "2017-01-12T23:20:50.52Z" + * } + * } + */ +Alexa.prototype.handleQueryLockState = function(event) { + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + response = {}; + + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + var curLevel = vDev.get("metrics:level"), + updateTime = new Date(vDev.get("updateTime") * 1000), + lockState = curLevel == "open" ? "UNLOCKED" : "LOCKED"; + + header = self.createHeader(self.NAMESPACE_QUERY, self.RESPONSE_GET_LOCK_STATE); + + response = { + "lockState": lockState, + "applianceResponseTimestamp": self.ISODateString(updateTime) + }; + } + + return self.createDirective(header, response); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "GetTargetTemperatureRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "GetTargetTemperatureResponse ", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "targetTemperature": { + * "value": 23.00, + * "scale": "CELSIUS" + * }, + * "applianceResponseTimestamp": "2017-01-12T23:20:50.52Z", + * "temperatureMode": { + * "value": "HEAT", + * "friendlyName": "" + * } + * } + * } + */ +Alexa.prototype.handleQueryTargetTemperature = function(event){ + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + response = {}; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + var curTemp = Math.round(vDev.get("metrics:level")), + updateTime = new Date(vDev.get("updateTime") * 1000); + + header = self.createHeader(self.NAMESPACE_QUERY, self.RESPONSE_TARGET_TEMPERATURE), + + response = { + "targetTemperature": { + "value": curTemp, + "scale": "CELSIUS" + }, + "applianceResponseTimestamp": self.ISODateString(updateTime), + "temperatureMode": { + "value": "AUTO", + "friendlyName": "" + } + }; + } + + return self.createDirective(header, response); +}; + +/** + * + * @param event + * { + * "header": { + * "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688", + * "name": "GetTemperatureReadingRequest", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "accessToken": "[OAuth token here]", + * "appliance": { + * "additionalApplianceDetails": { + * {"device": "[Z-Way Device ID]"} + * }, + * "applianceId": "[Device ID]" + * } + * } + * } + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "GetTemperatureReadingResponse ", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "temperatureReading": { + * "value": 23.00, + * "scale": "CELSIUS" + * }, + * "applianceResponseTimestamp": "2017-01-12T23:20:50.52Z" + * } + * } + */ +Alexa.prototype.handleQueryTargetReadingTemperature = function(event){ + var self = this, + vDev = self.getvDev(event.payload.appliance.additionalApplianceDetails.device), + header = {}, + response = {}; + + if(!vDev) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_NO_SUCH_TARGET); + } else if(vDev.get("metrics:isFailed")) { + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_TARGET_OFFLINE); + } else { + var curTemp = Math.round(vDev.get("metrics:level")), + updateTime = new Date(vDev.get("updateTime") * 1000); + + header = self.createHeader(self.NAMESPACE_QUERY, self.RESPONSE_TARGET_READING_TEMPERATURE) + + response = { + "temperatureReading": { + "value": curTemp, + "scale": "CELSIUS" + }, + "applianceResponseTimestamp": self.ISODateString(updateTime) + }; + } + + return self.createDirective(header, response); +}; + + +/** + * + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "UnsupportedOperationError", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": {} + * } + */ +Alexa.prototype.handleUnsupportedOperation = function() { + var self = this, + header = self.createHeader(self.NAMESPACE_CONTROL, self.ERROR_UNSUPPORTED_OPERATION); + return self.createDirective(header, {}); +}; + + +/** + * @param fault + * @return {{}} + * { + * "header": { + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "UnexpectedInformationReceivedError", + * "namespace": "Alexa.ConnectedHome.Control", + * "payloadVersion": "2" + * }, + * "payload": { + * "faultingParameter": "[FAULT]" + * } + * } + */ +Alexa.prototype.handleUnexpectedInfo = function(fault) { + var self = this, + header = self.createHeader(NAMESPACE_CONTROL, ERROR_UNEXPECTED_INFO); + + var payload = { + "faultingParameter" : fault + }; + + return createDirective(header,payload); +}; + +Alexa.prototype.getvDev = function(deviceId) { + var self = this, + vDev = self.controller.devices.get(deviceId); + + return vDev; +}; + +/** + * @param namespace + * @param name + * @return {{}} + * { + * + * "messageId": "26fa11a8-accb-4f66-a272-8b1ff7abd722", + * "name": "[name]", + * "namespace": "[namespace]", + * "payloadVersion": "2" + * + * } + */ +Alexa.prototype.createHeader = function(namespace, name) { + var self = this; + return { + "messageId": self.createMessageId(), + "namespace": namespace, + "name": name, + "payloadVersion": "2" + }; +}; + +/** + * @param header + * @param payload + * @return {{}} + * { + * "header": { + * ["header"] + * }, + * "payload": { + * ["payload"] + * } + * } + */ +Alexa.prototype.createDirective = function(header, payload) { + return { + "header" : header, + "payload" : payload + }; +}; + +Alexa.prototype.buildAppliances = function() { + var self = this, + devices = self.controller.devices, + locations = self.controller.locations, + moduleName = "Alexa", + langFile = self.controller.loadModuleLang(moduleName), + instances = self.controller.instances, + active_devices = self.config.devices.map(function(dev) {return dev.id}); + + var appliances = devices.filter(function(device) { + var vDev = self.controller.devices.get(device.id), + pos = active_devices.indexOf(device.id); + + if(pos != -1) { + return vDev; + } + }).map(function(vDev) { + var appliance = { + "applianceId": "", + "friendlyDescription": "undefined", + "friendlyName": "undefined", + "isReachable": true, + "manufacturerName": "undefined", + "modelName": "undefined", + "version": "undefined", + "additionalApplianceDetails": {}, + "actions": [], + "applianceTypes": [] + }; + + switch(vDev.get("deviceType")) { + case "switchBinary": + appliance.actions.push("turnOn", "turnOff"); + break; + case "switchMultilevel": + appliance.actions.push("turnOn", "turnOff", "setPercentage", "incrementPercentage", "decrementPercentage"); + break; + case "sensorMultilevel": + appliance.actions.push("getTargetTemperature", "getTemperatureReading"); + break; + case "toggleButton": + appliance.actions.push("turnOn"); + var scene = _.find(instances, function(inst) { + return inst.id == vDev.get("creatorId") && inst.moduleId == "LightScene"; + }); + if(typeof scene !== 'undefined') {appliance.applianceTypes.push("SCENE_TRIGGER");} + break; + case "thermostat": + appliance.actions.push("setTargetTemperature", "incrementTargetTemperature", "decrementTargetTemperature", "getTargetTemperature", "getTemperatureReading"); + appliance.applianceTypes.push("THERMOSTAT"); + break; + case "doorlock": + appliance.actions.push("getLockState", "setLockState"); + appliance.applianceTypes.push("SMARTLOCK"); + break; + case "switchRGBW": + appliance.actions.push("setColor", "turnOff", "turnOn"); + appliance.applianceTypes.push("LIGHT"); + break; + } + + appliance.applianceId = vDev.id.replace(/[^\w_\-=#;:?@&]/g, '_'); // replace not allowed characters + appliance.friendlyDescription; + + var pos = active_devices.indexOf(vDev.id), + locationId = vDev.get("location"); + if(pos !== -1) { + var deviceName = self.config.devices[pos].callName; + } else { + // fallback if no call name set + var deviceName = vDev.get("metrics:title") == "" ? "Unknow device" : vDev.get("metrics:title"); + } + + if(locationId !== 0 && self.config.assign_room) { + var location = _.find(locations, function(location) { + return location.id === locationId; + }); + var room = location.title; + friendlyName = deviceName + " " + room; + appliance.friendlyDescription = deviceName + " " + room + " connected via Z-Way"; + } else { + friendlyName = deviceName; + appliance.friendlyDescription = deviceName + " connected via Z-Way"; + } + + appliance.friendlyName = friendlyName; + appliance.modelName = vDev.get("deviceType"); + appliance.additionalApplianceDetails = {"device": vDev.id}; + return appliance; + }, active_devices); + + return appliances; +}; + +Alexa.prototype.createMessageId = function() { + var d = new Date().getTime(); + + var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = (d + Math.random()*16)%16 | 0; + d = Math.floor(d/16); + return (c=='x' ? r : (r&0x3|0x8)).toString(16); + }); + + return uuid; +}; + +Alexa.prototype.ISODateString = function (d) { + function pad(n) {return n<10 ? '0'+n : n} + return d.getUTCFullYear()+'-' + + pad(d.getUTCMonth()+1)+'-' + + pad(d.getUTCDate())+'T' + + pad(d.getUTCHours())+':' + + pad(d.getUTCMinutes())+':' + + pad(d.getUTCSeconds())+'Z'; +}; + +// --------------- Public HTTP API ------------------- + + +Alexa.prototype.externalAPIAllow = function (name) { + var _name = !!name ? ("Alexa." + name) : "AlexaAPI"; + + ws.allowExternalAccess(_name, this.controller.auth.ROLE.USER); + ws.allowExternalAccess(_name + ".callActions", this.controller.auth.ROLE.USER); +}; + +Alexa.prototype.externalAPIRevoke = function (name) { + var _name = !!name ? ("Alexa." + name) : "AlexaAPI"; + + ws.revokeExternalAccess(_name); + ws.revokeExternalAccess(_name + ".callActions"); +}; + +Alexa.prototype.defineHandlers = function () { + var self = this; + + this.AlexaAPI = function () { + return {status: 400, body: "Bad AlexaAPI request "}; + }; + + this.AlexaAPI.callActions = function (url, request) { + console.log("Received data from Alexa Skill"); + //console.log("request:", JSON.stringify(request, null , 4)); + if (request.method === "POST" && request.body) { + reqObj = typeof request.body === "string" ? JSON.parse(request.body) : request.body; + + var requestedNamespace = reqObj.header.namespace; + + switch(requestedNamespace) { + case self.NAMESPACE_DISCOVERY: + response = self.handleDiscovery(reqObj); + break; + case self.NAMESPACE_CONTROL: + response = self.handleControl(reqObj); + break; + case self.NAMESPACE_QUERY: + response = self.handleQuery(reqObj); + break; + default: + console.log("Error: ", "Unsupported namespace: " + requestedNamespace); + self.handleUnexpectedInfo(requestedNamespace); + break; + } + console.log("Return Response to Alexa Skill"); + //console.log("response:", JSON.stringify(response, null, 4)); + return response; + } + }; +}; + +/** +* HSV/HSB to RGB color conversion +* +* H runs from 0 to 360 degrees +* S and V run from 0 to 100 +* +*/ +function hsvToRgb(h, s, v) { + var r, g, b; + var i; + var f, p, q, t; + + // Make sure our arguments stay in-range + h = Math.max(0, Math.min(360, h)); + s = Math.max(0, Math.min(100, s)); + v = Math.max(0, Math.min(100, v)); + // We accept saturation and value arguments from 0 to 100 because that's + if(s == 0) { + // Achromatic (grey) + r = g = b = v; + return { + r:Math.round(r * 255), + g:Math.round(g * 255), + b:Math.round(b * 255) + }; + } + + h /= 60; // sector 0 to 5 + i = Math.floor(h); + f = h - i; // factorial part of h + p = v * (1 - s); + q = v * (1 - s * f); + t = v * (1 - s * (1 - f)); + + switch(i) { + case 0: + r = v; + g = t; + b = p; + break; + + case 1: + r = q; + g = v; + b = p; + break; + + case 2: + r = p; + g = v; + b = t; + break; + + case 3: + r = p; + g = q; + b = v; + break; + + case 4: + r = t; + g = p; + b = v; + break; + + default: // case 5: + r = v; + g = p; + b = q; + } + + return { + r:Math.round(r * 255), + g:Math.round(g * 255), + b:Math.round(b * 255) + }; +} \ No newline at end of file diff --git a/modules/Alexa/lang/de.json b/modules/Alexa/lang/de.json new file mode 100644 index 000000000..c681153de --- /dev/null +++ b/modules/Alexa/lang/de.json @@ -0,0 +1,4 @@ +{ + "m_title":"Alexa", + "m_descr":"Mit dieser App können Sie ihre SmartHome Geräte und Szenen mit Amazons Echo oder Dot und Alexa per Sprache steuern.

1. Modul 'Alexa' herunterladen.
2. Bei alexa.amazon.de anmelden.
3. Unter 'Skills' den Skill 'SmartHome Popp' suchen und aktivieren.
4. In der anschließenden Eingabemaske einfach mit Ihren 'Remote'-Daten anmelden (ID/User + PW).
5. Nach erfolgreicher Bestätigung kann das Fenster geschlossen werden und Alexa bietet Ihnen eine automatische Gerätesuche an. Falls dies nicht erscheint, können Sie mit dem Sprachbefehl: 'Alexa – suche Geräte' die suche selbst starten.
6. Nachdem Alexa Ihre Geräte gefunden hat, können Sie diese ab sofort mit dem Sprachbefehl: 'Alexa – #Gerät# einschalten/ausschalten/auf #Wert# dimmen/auf #Wert# setzen' usw. steuern." +} \ No newline at end of file diff --git a/modules/Alexa/lang/en.json b/modules/Alexa/lang/en.json new file mode 100644 index 000000000..fed161930 --- /dev/null +++ b/modules/Alexa/lang/en.json @@ -0,0 +1,4 @@ +{ + "m_title":"Alexa", + "m_descr":"Gives possibility to control your SmartHome devcies and scene with your voice using Amazon's Echo or Dot and Alexa.

1. Download App 'Alexa'
2. Sign in to alexa.amazon.com
3. Under 'Skills' find and activate the Skill 'SmartHome Popp'
4. In the subsequent input mask simply register with your 'Remote' data (Remote-ID / User + PW)
5. After successful confirmation the window can be closed and Alexa offers you an automatic device search. If this does not appear, you can use the language command: 'Alexa - search devices' to start the search.
6. After Alexa has found your devices, they can now be controlled with voice commands, for example: 'Alexa - # device # switch on / off / to # value # dimming / set to # value #'." +} \ No newline at end of file diff --git a/modules/Alexa/module.json b/modules/Alexa/module.json new file mode 100644 index 000000000..bd1778d5b --- /dev/null +++ b/modules/Alexa/module.json @@ -0,0 +1,24 @@ +{ + "dependencies": [], + "singleton": true, + "category": "support_external_dev", + "author": "Z-Wave.Me", + "homepage": "http://razberry.z-wave.me", + "icon": "icon.png", + "moduleName":"Alexa", + "version": "1.1.0", + "maturity": "stable", + "repository": { + "type": "git", + "source": "https://github.com/Z-Wave-Me/home-automation" + }, + "defaults": { + "title" : "__m_title__", + "description": "__m_descr__", + "assign_room": false, + "devices": [] + }, + "schema": {}, + "options": {} +} + diff --git a/modules/Alexa/patchnotes.txt b/modules/Alexa/patchnotes.txt new file mode 100644 index 000000000..9387c56a2 --- /dev/null +++ b/modules/Alexa/patchnotes.txt @@ -0,0 +1,8 @@ +v1.0.3 +- improve error handling +- add device typ switchRGBW to support RGB bulbs (change color) +v1.0.2 +- add device type thermostat (set and get temperarture) and doorlock (request only the lockstate) +v1.0.1 +- Alexa discover only devices with the device typ sensorMultilevel (and probeType temperature), switchBinary, toggleButton and switchMultilevel +- add support to request temperature of the specific device diff --git a/modules/AutoLock/index.js b/modules/AutoLock/index.js index 3866ed05b..c1f7d4dbf 100644 --- a/modules/AutoLock/index.js +++ b/modules/AutoLock/index.js @@ -1,7 +1,7 @@ /*** AutoLock Z-Way Home Automation module ************************************* - Version: 1.1.1 - (c) Z-Wave.Me, 2014 + Version: 1.2 + (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Yurkin Vitaliy @@ -35,22 +35,29 @@ AutoLock.prototype.init = function (config) { // handler - it is a name of your function this.handler = function (vDev) { var nowSensorStatus = vDev.get("metrics:level"); - - console.log("----------------------------- AutoLock", self.config.BinarySensor, "=", nowSensorStatus); - // Clear delay if door opened if (nowSensorStatus === "on") { - console.log("Clear delay"); clearTimeout(self.timer); } // Close lock if sensor false if (nowSensorStatus === "off") { // Start Timer - console.log("Start delay"); self.timer = setTimeout(function () { - // Close lock - self.controller.devices.get(self.config.DoorLock).performCommand("close"); + var vDevDoorLock = self.controller.devices.get(self.config.DoorLock); + var doorLockState = vDevDoorLock.get("metrics:level"); + var doorLockDeviceType = vDevDoorLock.get("deviceType"); + + // Check lock, if already closed don't send command + if (!(self.config.doNotSendCommand && (doorLockState == "close" || doorLockState == "on"))) { + // Close lock + if (doorLockDeviceType == "doorlock") { + self.controller.devices.get(self.config.DoorLock).performCommand("close"); + } + if (doorLockDeviceType == "switchBinary") { + self.controller.devices.get(self.config.DoorLock).performCommand("on"); + } + } // And clearing out this.timer variable self.timer = null; }, self.config.delay*1000); diff --git a/modules/AutoLock/lang/de.json b/modules/AutoLock/lang/de.json index 405e776b2..0f07c63cd 100644 --- a/modules/AutoLock/lang/de.json +++ b/modules/AutoLock/lang/de.json @@ -3,5 +3,6 @@ "m_descr":"Elektronische Türschlösser treiben zumeist Riegelschaltkontakte an. Befindet sich dieser Riegelschaltkontakt in der Standardposition kann die Tür per Türgriff geöffnet und geschlossen werden. Diese App verriegelt automatisch die Tür (d. h. treibt den Riegelschaltkontakt an) wenn eine geöffnete Tür sich schließt. Das Schließen der Tür wird durch einen Türsensor erkannt. Sobald dieser Sensor signalisiert, dass eine Tür schließt, wird das Türschloss nach einer bestimmten Zeit gedreht.

Einstellungen:
", "l_dw_sensor":"Tür-/Fenstersensor", "l_doorlock":"Türverriegelung", - "l_delay_sec":"Verzögerung in Sekunden" + "l_delay_sec":"Verzögerung in Sekunden", + "rl_doNotSendCommand":"Don't send Lock command if doorlock already closed" } \ No newline at end of file diff --git a/modules/AutoLock/lang/en.json b/modules/AutoLock/lang/en.json index 52a19c51a..c534847d4 100644 --- a/modules/AutoLock/lang/en.json +++ b/modules/AutoLock/lang/en.json @@ -3,5 +3,6 @@ "m_descr":"Electronic Doors typically move the dead bold of the door. If the dead bold is in the default position the door can be opened and closed using the door handle. This app will automatically lock the door (means move the dead bold) when an opened door closes. The closing of the door is detected using a door sensor. Once this sensor signals a closing door the door lock is turned after a defined delay.

Settings:
", "l_dw_sensor":"Door/Window Sensor", "l_doorlock":"Door Lock", - "l_delay_sec":"Delay in seconds" + "l_delay_sec":"Delay in seconds", + "rl_doNotSendCommand":"Don't send Lock command if doorlock already closed" } \ No newline at end of file diff --git a/modules/AutoLock/lang/ru.json b/modules/AutoLock/lang/ru.json index bb3a66d15..bc89bd27e 100644 --- a/modules/AutoLock/lang/ru.json +++ b/modules/AutoLock/lang/ru.json @@ -3,5 +3,6 @@ "m_descr":"Датчик открытия двери автоматически закрывает замок при закрытии двери.", "l_dw_sensor":"Датчик открытия двери", "l_doorlock":"Замок", - "l_delay_sec":"Задержка на закрытие в секундах" + "l_delay_sec":"Задержка на закрытие в секундах", + "rl_doNotSendCommand":"Не отправлять команду закрытия, если замок уже закрыт" } \ No newline at end of file diff --git a/modules/AutoLock/module.json b/modules/AutoLock/module.json index 228fa56b2..3afb2651b 100644 --- a/modules/AutoLock/module.json +++ b/modules/AutoLock/module.json @@ -6,7 +6,7 @@ "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "AutoLock", - "version": "1.1.1", + "version": "1.2", "maturity": "stable", "repository": { "type": "git", @@ -16,7 +16,9 @@ "title": "__m_title__", "description": "__m_descr__", "BinarySensor": null, - "DoorLock": null + "DoorLock": null, + "delay": 5, + "doNotSendCommand": false }, "schema": { "type": "object", @@ -30,12 +32,17 @@ "DoorLock": { "field": "enum", "datasource": "namespaces", - "enum": "namespaces:devices_doorlock:deviceId", + "enum": "namespaces:devices_doorlock:deviceId,namespaces:devices_switchBinary:deviceId", "required": true }, "delay": { "type": "integer", - "minimum": 1 + "minimum": 1, + "required": true + }, + "doNotSendCommand": { + "type": "boolean", + "required": true } }, "required": true @@ -47,7 +54,7 @@ "type": "select", "datasource": "namespaces", "field": "optionLabels", - "optionLabels": "namespaces:devices_sensorBinary:deviceName" + "optionLabels": "namespaces:devices_sensorBinary:deviceName,namespaces:devices_switchBinary:deviceName" }, "DoorLock": { "label": "__l_doorlock__", @@ -59,6 +66,10 @@ "delay": { "label": "__l_delay_sec__", "type": "integer" + }, + "doNotSendCommand": { + "type": "checkbox", + "rightLabel": "__rl_doNotSendCommand__" } } } diff --git a/modules/BatteryPolling/htdocs/js/postRender.js b/modules/BatteryPolling/htdocs/js/postRender.js new file mode 100644 index 000000000..693c8989a --- /dev/null +++ b/modules/BatteryPolling/htdocs/js/postRender.js @@ -0,0 +1,76 @@ +function modulePostRender(control) { + var id = 0; + var mail_exists = false; + + $(document).find(".not_message").children().prop('disabled', true); + + $.ajax("/ZAutomation/api/v1/instances/MailNotifier") + .done(function () { + mail_exists = true; + $.ajax("/ZAutomation/api/v1/profiles") + .done(function (profilesResponse) { + var moduleId = parseInt(window.location.href.substring(window.location.href.lastIndexOf("/") + 1)); + if (!isNaN(moduleId)) { + $.ajax("/ZAutomation/api/v1/instances/" + moduleId) + .done(function (response) { + if(typeof response.data.params === 'undefined') { + fillDropDown(profilesResponse.data, false, id); + } else if (typeof response.data.params.notification.target !== 'undefined') { + fillDropDown(profilesResponse.data, response.data.params.notification.target, id); + $(document).find(".not_message").children().prop('disabled', false); + } + }); + } else { + fillDropDown(profilesResponse.data, false, id); + } + }) + .fail(function () { + showInputField(); + }); + }) + .fail(function () { + var info = $(document).find(".mail_input"); + $(info[id]).parent().append('
'+control.data.mail_helper+'

'); + $(".mail_input").hide(); + }); + + $(document).on("click", ".install_mail", function() { + alertify.confirm(control.data.mail_confirm, function () { + window.location.replace("/smarthome/#/module/post/MailNotifier"); + }); + }); + + $(document).on("change", "select[name=notification_target]", function () { + $(this).parent().parent().parent().find(".not_message").children().prop('disabled', ($(this).val() === '')); + }); +} + +function fillDropDown(profiles, selectedMail, notificationID) { + var select = $(".target_select").find('select'); + var added = false; + + profiles.forEach(function (singleProfile) { + if ((typeof singleProfile.email !== 'undefined') && (singleProfile.email !== "")) { + if (singleProfile.email === selectedMail) { + $(select[notificationID]).append($('').val(singleProfile.email).html(singleProfile.email)); + } else { + $(select[notificationID]).append($('').val(singleProfile.email).html(singleProfile.email)); + } + added = true; + } + }); + + if (!added){ + showInputField(); + } else { + if (selectedMail) { + $(select[notificationID]).val(selectedMail); + } + + $(".mail_input").hide(); + } +} + +function showInputField() { + $(".mail_input").show(); +} diff --git a/modules/BatteryPolling/index.js b/modules/BatteryPolling/index.js index 2fe0f039d..8058387a0 100644 --- a/modules/BatteryPolling/index.js +++ b/modules/BatteryPolling/index.js @@ -1,9 +1,10 @@ /*** BatteryPolling Z-Way HA module ******************************************* -Version: 2.1.0 +Version: 2.2.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- -Author: Gregory Sitnin nad Serguei Poltorak +Author: Gregory Sitnin nad Serguei Poltorak , +Karsten Reichel Description: This module periodically requests all battery devices for battery level report @@ -31,6 +32,9 @@ BatteryPolling.prototype.init = function (config) { var self = this; + // timestamp of last triggered notification + this.lastTriggered = []; + // polling function this.onPoll = function () { self.controller.devices.filter(function (el) { @@ -66,11 +70,25 @@ BatteryPolling.prototype.init = function (config) { } self.vDev.set("metrics:level", self.minimalBatteryValue()); - if (vDev.get("metrics:level") <= self.config.warningLevel) { + var now = Math.round(new Date().getTime()/1000); + if (vDev.get("metrics:level") <= self.config.warningLevel && (typeof self.lastTriggered[vDev.id] === 'undefined' || self.lastTriggered[vDev.id] <= (now-43200))) { var values = vDev.get("metrics:title"), langFile = self.loadModuleLang(); - - self.controller.addNotification("warning", langFile.warning + values, "battery", self.vDev.get(id)); + + // add notification + if(typeof self.config.notification.target !== 'undefined' || typeof self.config.notification.mail_to_input !== 'undefined') { + var mail; + if(self.config.notification.target.search('@') > 0 || (mail = typeof self.config.notification.mail_to_input !== 'undefined')) { + self.addNotification('mail.notification', typeof self.config.notification.message === 'undefined' ? langFile.warning + values : self.config.notification.message.replace('__device__',values), mail ? self.config.notification.mail_to_input : self.config.notification.target); + } else { + self.addNotification('push.notification', typeof self.config.notification.message === 'undefined' ? langFile.warning + values : self.config.notification.message.replace('__device__',values), self.config.notification.target); + } + } + else + { + self.addNotification("warning", langFile.warning + values, "battery", self.vDev.id); + } + self.lastTriggered[vDev.id] = now; } }; @@ -85,8 +103,8 @@ BatteryPolling.prototype.init = function (config) { if (this.config.launchWeekDay == everyDay) { // add cron schedule every day this.controller.emit("cron.addTask", "batteryPolling.poll", { - minute: null, - hour: 0, + minute: 0, + hour: 12, weekDay: null, day: null, month: null @@ -96,7 +114,7 @@ BatteryPolling.prototype.init = function (config) { // add cron schedule every week this.controller.emit("cron.addTask", "batteryPolling.poll", { minute: 0, - hour: 0, + hour: 12, weekDay: this.config.launchWeekDay, day: null, month: null diff --git a/modules/BatteryPolling/lang/de.json b/modules/BatteryPolling/lang/de.json index 8757d97d8..9acc17f78 100644 --- a/modules/BatteryPolling/lang/de.json +++ b/modules/BatteryPolling/lang/de.json @@ -12,5 +12,13 @@ "sun":"Sonntag", "l_warning_lvl":"Benachrichtigungslevel", "h_warning_lvl":"Das Modul sendet eine Benachrichtigung, wenn der Batteriestatus eines Gerätes unterhalb dieses Levels fällt.", - "warning":"Achtung! Das Batterielevel des Gerätes beträgt nur noch >:> " + "warning":"Achtung! Das Batterielevel des Gerätes beträgt nur noch >:> ", + "l_choose_target":"--- Ziel wählen ---", + "l_notification":"Benachrichtigung", + "l_notification_mail":"Email", + "l_notification_message": "Nachricht", + "h_notification_message": "Inhalt der Benachrichtigung. Über __device__ können Sie den Gerätenamen in der Nachricht ausgeben lassen.", + "h_nomail":"Aktuell ist keine Benachrichtigung über Email möglich, da die EMailMe-App nicht installiert/aktiviert ist.", + "b_createmail": "Aktiviere EMailMe", + "m_createmail": "ACHTUNG!
Beim Verlassen des Konfigurationsfensters gehen alle bisherigen Einstellungen verloren.

HINWEIS:
Sie können mit 'Abbrechen' ohne die Konfiguration der EMailMe-App fortfahren, diese im Nachgang anlegen und anschließend in der IfThen-Konfiguration hinzufügen." } \ No newline at end of file diff --git a/modules/BatteryPolling/lang/en.json b/modules/BatteryPolling/lang/en.json index 6a5e66635..52903f984 100644 --- a/modules/BatteryPolling/lang/en.json +++ b/modules/BatteryPolling/lang/en.json @@ -12,5 +12,13 @@ "sun":"Sunday", "l_warning_lvl":"Warning Level", "h_warning_lvl":"Warn if device's battery is below this level", - "warning":"Attention! Device is low battery >:> " + "warning":"Attention! Device is low battery >:> ", + "l_choose_target":"--- Choose a target ---", + "l_notification":"Message", + "l_notification_mail":"Email", + "l_notification_message": "Message", + "h_notification_message": "Content of the notification. You can use __device__ to display the device name in the message.", + "h_nomail":"Currently, no email notification is possible because the EMailMe app is not installed/activated.", + "b_createmail": "Activate EMailMe", + "m_createmail": "ATTENTION!
When leaving the current configuration, all previous settings are lost.

NOTE:
You can continue with 'Cancel' without configuring the EMailMe app, create it afterwards, and add it then in the If -> Then configuration." } \ No newline at end of file diff --git a/modules/BatteryPolling/lang/ru.json b/modules/BatteryPolling/lang/ru.json index e32b7c13e..3db86f65b 100644 --- a/modules/BatteryPolling/lang/ru.json +++ b/modules/BatteryPolling/lang/ru.json @@ -12,5 +12,13 @@ "sun":"Воскресенье", "l_warning_lvl":"Низкий уровень", "h_warning_lvl":"Оповестить если заряд любой батареи опустится ниже этого уровня", - "warning":"Внимание! Батарея разряжена >:> " + "warning":"Внимание! Батарея разряжена >:> ", + "l_choose_target":"--- Choose a target ---", + "l_notification":"Message", + "l_notification_mail":"Email", + "l_notification_message": "Message", + "h_notification_message": "Content of the notification. You can use __device__ to display the device name in the message.", + "h_nomail":"Currently, no email notification is possible because the EMailMe app is not installed/activated.", + "b_createmail": "Activate EMailMe", + "m_createmail": "ATTENTION!
When leaving the current configuration, all previous settings are lost.

NOTE:
You can continue with 'Cancel' without configuring the EMailMe app, create it afterwards, and add it then in the If -> Then configuration." } \ No newline at end of file diff --git a/modules/BatteryPolling/module.json b/modules/BatteryPolling/module.json index fcc946789..6abc60dd2 100644 --- a/modules/BatteryPolling/module.json +++ b/modules/BatteryPolling/module.json @@ -6,7 +6,7 @@ "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "BatteryPolling", - "version": "2.1.0", + "version": "2.2.0", "maturity": "stable", "repository": { "type": "git", @@ -16,7 +16,11 @@ "title": "__m_title__", "description": "__m_descr__", "launchWeekDay": 0, - "warningLevel": 20 + "warningLevel": 20, + "notification": {}, + "mail_helper": "__h_nomail__", + "mail_button": "__b_createmail__", + "mail_confirm": "__m_createmail__" }, "schema": { "type": "object", @@ -30,7 +34,24 @@ "type": "select", "required": true, "enum": [5, 10, 15, 20] - } + }, + "notification": { + "type": "object", + "properties": { + "target": { + "type": "string", + "datasource": "namespaces", + "enum": "namespaces:devices_toggleButton:notification:push:deviceId", + "required": false + }, + "mail_to_input": { + "required": false + }, + "message": { + "required": false + } + } + } }, "required": false }, @@ -44,7 +65,32 @@ "label": "__l_warning_lvl__", "helper": "__h_warning_lvl__", "optionLabels": ["5%", "10%", "15%", "20%"] - } + }, + "notification": { + "fields": { + "target": { + "type": "select", + "removeDefaultNone": false, + "noneLabel": "__l_choose_target__", + "label": "__l_notification__", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_toggleButton:notification:push:deviceId", + "fieldClass": "target_select" + }, + "mail_to_input": { + "label": "__l_notification_mail__", + "fieldClass": "mail_input" + }, + "message": { + "type": "textarea", + "label": "__l_notification_message__", + "helper": "__h_notification_message__", + "fieldClass": "not_message" + } + } + } } - } -} + }, + "postRender": "loadFunction:postRender.js" +} \ No newline at end of file diff --git a/modules/BindDevices/index.js b/modules/BindDevices/index.js index a49d1aaff..156f1ce4d 100644 --- a/modules/BindDevices/index.js +++ b/modules/BindDevices/index.js @@ -1,7 +1,7 @@ /*** BindDevices Z-Way HA module ******************************************* -Version: 1.0.1 -(c) Z-Wave.Me, 2014 +Version: 1.0.3 +(c) Z-Wave.Me, 2018 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: @@ -53,6 +53,10 @@ BindDevices.prototype.init = function (config) { vDev.performCommand(actionBinary); } else if ((vDev.get("deviceType") === "switchMultilevel") || (vDev.get("deviceType") === "thermostat")) { vDev.performCommand("exact", { level: actionMultilevel }); + } else if (vDev.get("deviceType") === "sensorMultilevel"){ + vDev.set("metrics:level", actionMultilevel); + } else if (vDev.get("deviceType") === "sensorBinary"){ + vDev.set("metrics:level", actionBinary); } } }); @@ -65,7 +69,7 @@ BindDevices.prototype.init = function (config) { var vDev = self.controller.devices.get(el); if (vDev) { - if ((vDev.get("deviceType") === "switchMultilevel") || (vDev.get("deviceType") === "thermostat")) { + if ((vDev.get("deviceType") === "switchMultilevel") || (vDev.get("deviceType") === "thermostat")) { vDev.performCommand(action); } } diff --git a/modules/BindDevices/module.json b/modules/BindDevices/module.json index b843d4690..1eee19236 100644 --- a/modules/BindDevices/module.json +++ b/modules/BindDevices/module.json @@ -6,7 +6,7 @@ "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "BindDevices", - "version": "1.0.1", + "version": "1.0.3", "maturity": "stable", "repository": { "type": "git", @@ -26,7 +26,7 @@ "items": { "field": "enum", "datasource": "namespaces", - "enum": "namespaces:devices_toggleButton:deviceId,namespaces:devices_switchControl:deviceId,namespaces:devices_switchBinary:deviceId,namespaces:devices_switchMultilevel:deviceId,namespaces:devices_thermostat:deviceId,namespaces:devices_sensorBinary:deviceId", + "enum": "namespaces:devices_toggleButton:deviceId,namespaces:devices_switchControl:deviceId,namespaces:devices_switchBinary:deviceId,namespaces:devices_switchMultilevel:deviceId,namespaces:devices_thermostat:deviceId,namespaces:devices_sensorBinary:deviceId,namespaces:devices_sensorMultilevel:deviceId", "required": true } }, @@ -35,7 +35,7 @@ "items": { "field": "enum", "datasource": "namespaces", - "enum": "namespaces:devices_switchBinary:deviceId,namespaces:devices_switchMultilevel:deviceId,namespaces:devices_thermostat:deviceId,namespaces:devices_doorlock:deviceId", + "enum": "namespaces:devices_switchBinary:deviceId,namespaces:devices_switchMultilevel:deviceId,namespaces:devices_thermostat:deviceId,namespaces:devices_doorlock:deviceId,namespaces:devices_sensorBinary:deviceId,namespaces:devices_sensorMultilevel:deviceId", "required": true } } @@ -51,7 +51,7 @@ "type": "select", "datasource": "namespaces", "field": "optionLabels", - "optionLabels": "namespaces:devices_toggleButton:deviceName,namespaces:devices_switchControl:deviceName,namespaces:devices_switchBinary:deviceName,namespaces:devices_switchMultilevel:deviceName,namespaces:devices_thermostat:deviceName,namespaces:devices_sensorBinary:deviceName" + "optionLabels": "namespaces:devices_toggleButton:deviceName,namespaces:devices_switchControl:deviceName,namespaces:devices_switchBinary:deviceName,namespaces:devices_switchMultilevel:deviceName,namespaces:devices_thermostat:deviceName,namespaces:devices_sensorBinary:deviceName,namespaces:devices_sensorMultilevel:deviceName" } } }, @@ -62,7 +62,7 @@ "type": "select", "datasource": "namespaces", "field": "optionLabels", - "optionLabels": "namespaces:devices_switchBinary:deviceName,namespaces:devices_switchMultilevel:deviceName,namespaces:devices_thermostat:deviceName,namespaces:devices_doorlock:deviceName" + "optionLabels": "namespaces:devices_switchBinary:deviceName,namespaces:devices_switchMultilevel:deviceName,namespaces:devices_thermostat:deviceName,namespaces:devices_doorlock:deviceName,namespaces:devices_sensorBinary:deviceName,namespaces:devices_sensorMultilevel:deviceName" } } } diff --git a/modules/DeviceHistory/index.js b/modules/DeviceHistory/index.js index 8aae366b2..6047be92f 100644 --- a/modules/DeviceHistory/index.js +++ b/modules/DeviceHistory/index.js @@ -1,6 +1,6 @@ /*** DeviceHistory Z-Way HA module ******************************************* -Version: 1.3.0 +Version: 2.0.0 (c) Z-Wave.Me, 2015 ----------------------------------------------------------------------------- Author: Niels Roche @@ -15,6 +15,20 @@ Description: function DeviceHistory (id, controller) { // Call superconstructor first (AutomationModule) DeviceHistory.super_.call(this, id, controller); + + // define excluded device types + this.devTypes = ['sensorMultilevel'], + this.langFile = this.loadModuleLang(); + + this.history = {} + this.allDevices = []; + this.initial = true; + this.registeredVDevIds = []; + this.storedDevHistories = []; + + this.exclSensors = this.controller.instances.filter(function (instance){ + return instance.moduleId === 'SensorsPolling' && instance.active === 'true'; + }); } inherits(DeviceHistory, AutomationModule); @@ -28,206 +42,375 @@ _module = DeviceHistory; _.extend(DeviceHistory.prototype, { init: function (config) { DeviceHistory.super_.prototype.init.call(this, config); - - // add cron schedule every 5 minutes - this.controller.emit("cron.addTask", "historyPolling.poll", { - minute: [0, 59, 5], - hour: null, - weekDay: null, - day: null, - month: null - }); - this.controller.emit("cron.addTask", "saveHistory.poll", { - minute: 0, - hour: [0, 23, 1], - weekDay: null, - day: null, - month: null - }); + var self = this; - var self = this, - // define excluded device types - exclDevTypes = ['battery','text','camera','switchRGBW','sensorMultiline'], - langFile = this.loadModuleLang(); + this.defineHandlers(); + this.externalAPIAllow(); + global["HistoryAPI"] = this.HistoryAPI; - this.history = this.controller.setHistory(); - this.allDevices = []; - this.initial = true; + // set history of excluded devices to false + this.config.devices.forEach(function (devId) { + var vDevd = self.controller.devices.get(devId); - this.exclSensors = this.controller.instances.filter(function (instance){ - return instance.moduleId === 'SensorsPolling' && instance.active === 'true'; + if (vDevd && vDevd.get('hasHistory') === true) { + vDevd.set('hasHistory', false, {silent: true}); + } }); - config.devices.forEach(function (devId) { - var d = self.controller.devices.get(devId); + this.initializeDevices(); - if (d && d.get('hasHistory') === true) { - d.set('hasHistory', false, {silent: true}); + // try to restore old histories + oldHistory = loadObject('history'); + + if(!!oldHistory || (_.isArray(oldHistory) && oldHistory.length > 0)) { + oldHistory.forEach(function(history){ + if (self.registeredVDevIds.indexOf(history.id) > -1 && self.devTypes.indexOf(history.dT) > -1) { + self.history[history.id].set(history.mH); + } + }); + + saveObject('history',null); + oldHistory = undefined; + } else { + oldHistory = undefined; + } + + this.assignDeviceListeners(); + + // cleanup storage content list after 30 secs + setTimeout(function(){ + // run first time to setting up histories + self.setupHistories(); + }, 30000); + }, + stop: function () { + var self = this; + + // remove eventhandlers + this.config.allRegisteredDevices.forEach(function(vDevId) { + vDev = self.controller.devices.get(vDevId); + + if(vDev && vDev.get("hasHistory") === true){ + vDev.set("hasHistory", false,{ silent: true }); } + + self.removeHistory(vDev); }); - this.updateDevList = function () { - return self.controller.devices.filter(function(dev){ - return dev.get('permanently_hidden') === false && // only none permanently_hidden devices - (!dev.get('metrics:removed') || dev.get('metrics:removed') === false) && // only none removed devices - _.unique(config.devices).indexOf(dev.id) === -1 && //in module excluded devices - exclDevTypes.indexOf(dev.get('deviceType')) === -1 && //excluded device types - self.exclSensors.indexOf(dev.id) === -1; //excluded sensors - }); + this.controller.devices.off('created', self.assignHistory); + this.controller.devices.off('removed', this.removeHistory); + + this.externalAPIRevoke(); + delete global["HistoryAPI"]; + + + DeviceHistory.super_.prototype.stop.call(this); + }, + // ---------------------------------------------------------------------------- + // --- Module methods + // ---------------------------------------------------------------------------- + setHistory: function (vdevId) { + + this.history[vdevId] = new LimitedArray( + loadObject('history_' + vdevId) || [], + function (arr) { + saveObject('history_' + vdevId, arr); + }, + 5, // check it every 10 entries + 1000, // save up to 1000 entries + function (devHistory){ + var now = Math.floor((new Date()).getTime() / 1000); + return devHistory.id >= (now - 86400); + } + ); + }, + initializeDevices: function (){ + var self = this; + + this.config.allRegisteredDevices = this.updateDevList(); + + //save into config + this.saveConfig(); + + // store whole history data on storage + this.storeData = function(dev) { + try { + var change = { + id: Math.floor((new Date()).getTime() / 1000), + l: parseInt(dev.get("metrics:level"),10) + }; + + self.history[dev.id].push(change); + + } catch(e) { + self.addNotification('error', self.langFile.err_store_history + dev.get('metrics:title') + " ERROR: " + e.toString(), 'module'); + } }; - this.allDevices = this.updateDevList(); - // setting up device histories - this.setupHistories = function(){ + _.forEach(this.config.allRegisteredDevices, function(vdevId){ + var vDev = self.controller.devices.get(vdevId); + + // create new LimitedArray for + self.setHistory(vdevId); - // cleanup first after all virtual devices are created - if(self.initial === true){ - self.initial = false; - } else { - if(self.allDevices.length > 0 && self.allDevices.length < self.history.length) { - var cleanedUpHistory = [], - devices = []; + // set hasHistory true + if(vDev && vDev.get("hasHistory") === false){ + vDev.set("hasHistory", true,{ silent: true }); + } - devices = self.allDevices.map(function (dev){ - return dev.get('id'); - }); - - cleanedUpHistory = self.history.filter(function (devHist) { - return devices.indexOf(devHist.id) > -1; - }); + self.controller.devices.off(vdevId, 'change:metrics:level', self.storeData); + self.controller.devices.on(vdevId, 'change:metrics:level', self.storeData); + }); + }, + assignDeviceListeners: function () { + var self = this; - if(cleanedUpHistory.length === self.allDevices.length){ - console.log("--- ", "clean up histories"); - self.history = cleanedUpHistory; - } + this.assignHistory = function (vDev) { + if (vDev && vDev.id && + vDev.get('permanently_hidden') === false && // only none permanently_hidden devices + (!vDev.get('metrics:removed') || vDev.get('metrics:removed') === false) && // only none removed devices + _.unique(self.config.devices).indexOf(vDev.id) === -1 && // in module excluded devices + self.devTypes.indexOf(vDev.get('deviceType')) > -1 && // allowed device types + self.exclSensors.indexOf(vDev.id) === -1) { // excluded sensors + + // add to registered vDev list + self.config.allRegisteredDevices.push(vDev.id); + + //save into config + self.saveConfig(); + + // set LimitedArray for history + self.setHistory(vDev.id); + + self.addNotification('info', self.langFile.info_add_history + vDev.get('metrics:title'), 'module'); + + //assign level listener + self.controller.devices.off(vDev.id, 'change:metrics:level', self.storeData); + self.controller.devices.on(vDev.id, 'change:metrics:level', self.storeData); + + if (vDev && vDev.get('hasHistory') === false) { + vDev.set('hasHistory', true, {silent: true}); } } + }; + + this.removeHistory = function (vDev) { + + if (vDev && self.config.allRegisteredDevices.indexOf(vDev.id) > -1) { + // remove history array + if (self.history[vDev.id]) { + self.history[vDev.id].finalize(); + saveObject("history_"+vDev.id, null); + delete self.history[vDev.id]; + } + + self.addNotification('info', self.langFile.info_remove_history + vDev.get('metrics:title'), 'module'); + + self.controller.devices.off(vDev.id, 'change:metrics:level', self.storeData); - if (self.allDevices.length === 0) { - self.allDevices = self.updateDevList(); + // remove from registry + self.config.allRegisteredDevices = self.config.allRegisteredDevices.filter(function(devId){ + return devId !== vDev.id; + }); + + // unassign history + if (vDev && vDev.get('hasHistory') === true) { + vDev.set('hasHistory', false, {silent: true}); + } } + }; - // Setup histories - self.allDevices.forEach(function(dev) { - var id = dev.id, - devType = dev.get('deviceType'), - lvl; + // assign listener to new vdev + this.controller.devices.on('created', this.assignHistory); + this.controller.devices.on('removed', this.removeHistory); + }, + // return list of registered vDev IDs + updateDevList: function () { + var self = this; - if(dev.get("hasHistory") === false){ - dev.set("hasHistory", true, { silent: true }); - } - - switch(devType){ - case 'sensorMultilevel': - case 'switchMultilevel': - case 'thermostat': - lvl = dev.get("metrics:level"); - self.storeData(dev, lvl); - break; - default: //'fan', - break; + return this.controller.devices.filter(function(dev){ + return dev.get('permanently_hidden') === false && // only none permanently_hidden devices + (!dev.get('metrics:removed') || dev.get('metrics:removed') === false) && // only none removed devices + _.unique(self.config.devices).indexOf(dev.id) === -1 && // in module excluded devices + self.devTypes.indexOf(dev.get('deviceType')) > -1 && // allowed device types + self.exclSensors.indexOf(dev.id) === -1; // excluded sensors + }).map(function(vdev) { + return vdev.id; + }); + }, + // setting up device histories + setupHistories: function(){ + var self = this, + storedDevHistories = []; + + // cleanup first after all virtual devices are created + if(__storageContent) { + + _.forEach(__storageContent, function (name) { + if (name.indexOf('history_') > -1) { + storedDevHistories.push(name.substring(8)); } }); - console.log("--- ", "histories polled"); - }; - // collect metrics changes from binary sensors or switches + storedDevHistories.forEach(function(historyFileName){ + if (!self.history[historyFileName]) { + self.addNotification('info', historyFileName + self.langFile.info_transformation, 'module'); + saveObject('history_' + historyFileName, null); + } + }); + } + }, + // --------------- Public HTTP API ------------------- + externalAPIAllow: function () { + ws.allowExternalAccess('HistoryAPI', this.controller.auth.ROLE.USER); + ws.allowExternalAccess('HistoryAPI.Get', this.controller.auth.ROLE.USER); + ws.allowExternalAccess('HistoryAPI.Delete', this.controller.auth.ROLE.USER); + }, + externalAPIRevoke: function () { + ws.revokeExternalAccess('HistoryAPI'); + ws.revokeExternalAccess('HistoryAPI.Get'); + ws.revokeExternalAccess('HistoryAPI.Delete'); + }, + defineHandlers: function () { + var self = this; - // store whole history data on storage - this.storeData = function(dev, lvl) { - try { - var devId = dev.id, - index = null, - history, change, histMetr, item; + this.HistoryAPI = function() { + return { status: 400, body: "Bad HistoryAPI request " }; + }; - change = self.setChangeObject(lvl); - - // find dev history and set index - _.find(self.history, function (data, idx) { - if(data.id === devId){ - index = idx; - return; + this.HistoryAPI.Get = function(url, request) { + var q = request.query || null, + since = parseInt(url.substring(1), 10) || 0, + items = q && q.hasOwnProperty('show')? parseInt(q.show,10) : 0, + devId = q && q.hasOwnProperty('id')? q.id : null, + averageEntries = [], + entries = [], + now = Math.floor(new Date().getTime() / 1000), + l = 0, + cnt = 0, + metric = {}, + sec = 0, + body = { + updateTime: now + }; + + if (devId && self.history[devId]) { + hist = self.history[devId].get(); + + // create output with n (= show) values - 1440, 288, 96, 48, 24, 12, 6 + if(items > 0 && items <= 1440){ + sec = 86400 / items; // calculate seconds of range + + // calculate averaged value of all meta values between 'sec' range + for (i = 0; i < items; i++){ + from = Math.floor(now - sec*(items - i)); + to = Math.floor(now - sec*(items - (i+1))); + + // filter values between from and to + range = hist.filter(function (metric){ + return metric.id >= from && metric.id <= to; + }); + + cnt = range.length; + + // calculate level + if(cnt > 0){ + + for(j=0; j < cnt; j++){ + l += parseInt(range[j]['l'],10); + } + + l = l /cnt; + + if(l === +l && l !== (l|0)) { // round to one position after '.' + l = l.toFixed(1); + } + } else { + l = null; + } + + // push new averaged entry to + metric = { + id: to, + l: parseFloat(l) + } + + averageEntries.push(metric); + + // cleanup variables + l = 0; + metric = {}; } - }); - - // create new device entry if necessary - if(self.history.length < 1 || index === null){ - item = { - id: dev.id, - dT: dev.get("deviceType"), - mH: [] - }; - - self.history.push(item); - index = self.history.length - 1; - } - - // push only changes during the last 24 hrs - histMetr = self.history[index].mH.filter(function (ch){ - return ch.id >= (change.id - 86400); - }); - - if(histMetr.length < 288){ // 86400 s (24 h) = 288 * 300 s (5 min) - histMetr.push(change); + + entries = averageEntries; } else { - histMetr.shift(); - histMetr.push(change); + entries = hist; } - self.history[index].mH = histMetr; + // filter meta entries by since + body.history = since > 0? entries.filter(function (metric) { + return metric.id >= since; + }) : entries; - self.controller.history = self.history; - - } catch(e){ - console.log("Cannot store history of device '" + dev.get('metrics:title') + "' because:", e.toString()); - self.addNotification('error', langFile.err_store_history + dev.get('metrics:title') + " ERROR: " + e.toString(), 'module', 'DeviceHistory'); + body.code = 200; + + } else if (devId && !self.history[devId]) { + body.code = 404; + body.message = 'Not Found.'; + + } else { + body.histories = {}; + body.code = 200; + + self.config.allRegisteredDevices.forEach(function(vDevId){ + body.histories[vDevId] = _.filter(self.history[vDevId].get(), function(entry){ + return entry.id >= since; + }); + }); } - }; - // polling function - this.saveHistory = function () { - saveObject("history", self.history); - }; + result = self.prepareHTTPResponse(body); - this.controller.on("historyPolling.poll", self.setupHistories); - this.controller.on("saveHistory.poll", self.saveHistory); + return result; + } - // run first time to setting up histories - this.setupHistories(); - this.saveHistory(); - }, - stop: function () { - var self = this; + this.HistoryAPI.Delete = function(url, request) { + var q = request.query || null, + vDevId = q && q.hasOwnProperty('id')? q.id : null, + body = { + updateTime: Math.floor(new Date().getTime() / 1000) + }; - // remove eventhandler + if (vDevId && self.history[vDevId]) { + self.history[vDevId].set([]); - this.allDevices.forEach(function(vDev) { - if(vDev.get("hasHistory") === true){ - vDev.set("hasHistory", false,{ silent: true }); - } - }); - - this.controller.emit("cron.removeTask", "historyPolling.poll"); - this.controller.off("historyPolling.poll", self.setupHistories); + vdev = self.controller.devices.get(vDevId); - this.controller.emit("cron.removeTask", "saveHistory.poll"); - this.controller.off("saveHistory.poll", self.saveHistory); + self.addNotification('info', self.langFile.info_clear_id_history + vdev? vdev.get('metrics:title') : vDevId, 'module'); - saveObject("history", null); + body.code = 201; - DeviceHistory.super_.prototype.stop.call(this); - }, - // ---------------------------------------------------------------------------- - // --- Module methods - // ---------------------------------------------------------------------------- - setChangeObject: function (lvl) { - var date = new Date(), - change = { - id: Math.floor(date.getTime() / 1000), - l: lvl - }; - - return change; + } else if (vDevId && !self.history[vDevId]) { + body.code = 404; + body.message = 'Not Found.'; + + } else { + + self.addNotification('info', self.langFile.info_clear_all_histories, 'module'); + + self.config.allRegisteredDevices.forEach(function(devId){ + if(self.history[devId]) { + self.history[devId].set([]); + } + }); + + body.code = 201; + } + + result = self.prepareHTTPResponse(body) + + return result; + } } }); \ No newline at end of file diff --git a/modules/DeviceHistory/lang/de.json b/modules/DeviceHistory/lang/de.json index 18a65300a..af7f3b10f 100644 --- a/modules/DeviceHistory/lang/de.json +++ b/modules/DeviceHistory/lang/de.json @@ -3,5 +3,10 @@ "m_descr":"Diese App fügt jedem Sensor und Aktor ein kleines Icon bei. Klickt man auf dieses Icon, öffnet sich ein Splash-Fenster mit einer Graphik, die den Status der vergangenen 24 Stunden aufzeigt. Bitte beachten Sie, dass diese Anzeige nur der Schnellinformation dient. Es werden lediglich Durchschnittswerte angezeigt. Ein kurzes Einschalten des Lichtes könnte möglicherweise in dieser Chronik nicht wider gespiegelt werden. Trotz alledem bietet es eine schöne und schnelle Möglichkeit, sich über den Sensorstatus der letzten 24 Stunden zu informieren. Wenn Sie sich die Daten Ihres Smart Home präzisier aufzeigen lassen wollen, sollten Sie die App „Sensor logging“ zusammen mit einem Service oder einer Software durch dritte Dienstleister anwenden.

Einstellungen: Sie können eine Geräteliste bestimmen, die nicht erfasst ist. Bitte beachten Sie, dass ein Gerät, welches bereits vom Sendeabruf der Sensor Polling App ausgeschlossen ist, ohnehin nicht aufgezeichnet werden kann.", "l_devices":"Speichere keine History für folgende Geräte:", "h_devices":"Bei gleichzeitiger Verwendung der App 'Periodische Sensorabfrage' ist folgendes zu beachten: Ein dort von der Abfrage ausgeschlossenes Gerät kann keine Historie erzeugen.", - "err_store_history":"Fehler beim Speichern der Historie von Gerät: " + "err_store_history":"Fehler beim Speichern der Historie von Gerät: ", + "info_add_history":"Historie zum Gerät hinzugefügt: ", + "info_remove_history":"Historie vom Gerät entfernt: ", + "info_clear_id_history":"Historieneinträge vom Gerät gelöscht: ", + "info_clear_all_histories":"Historieneinträge aller Geräte gelöscht.", + "info_transformation":" keine Historie gefunden. Historie wird gelöscht ..." } \ No newline at end of file diff --git a/modules/DeviceHistory/lang/en.json b/modules/DeviceHistory/lang/en.json index 72925d635..9afff34a5 100644 --- a/modules/DeviceHistory/lang/en.json +++ b/modules/DeviceHistory/lang/en.json @@ -3,5 +3,10 @@ "m_descr":"This app adds a little icon to every sensor and actor. Clicking on this icon opens a splash window with a graph showing the status of the last 24 hours. Please note that this is display is for quick information only. Only average values are shown so that a short turning on of a light may not even reflected in this history. Nevertheless it’s a nice and quick way to get an idea about the status of a sensor for the last 24 hours. If you like to record data about your smart home more precisely you should use the App “Sensor logging” together with some third party service or software.

Settings: You can define a list of devices that are not recorded. Please note that a device already excluded from polling in the Sensor Polling App can not be recorded anyway.", "l_devices":"Do not track devices marked below:", "h_devices":"With simultaneous use of the app 'Periodical Sensor Polling', note the following: One there from the polling excluded device cannot create a history.", - "err_store_history":"Cannot store history of device: " + "err_store_history":"Cannot store history of device: ", + "info_add_history":"History added to device: ", + "info_remove_history":"History removed for device: ", + "info_clear_id_history":"History entries cleared of device: ", + "info_clear_all_histories":"History entries of all devices cleared.", + "info_transformation":" not assigned to history. History will be removed ..." } \ No newline at end of file diff --git a/modules/DeviceHistory/module.json b/modules/DeviceHistory/module.json index 2eee97807..a99149f27 100644 --- a/modules/DeviceHistory/module.json +++ b/modules/DeviceHistory/module.json @@ -6,7 +6,7 @@ "homepage" : "http://razberry.z-wave.me", "icon" : "icon.png", "moduleName":"DeviceHistory", - "version" : "1.3.0", + "version" : "2.0.0", "maturity" : "stable", "repository" : { "type" : "git", @@ -15,7 +15,8 @@ "defaults" : { "title" : "__m_title__", "description" : "__m_descr__", - "devices": [] + "devices": [], + "allRegisteredDevices": [] }, "schema": { "properties": { @@ -24,7 +25,7 @@ "items": { "field":"enum", "datasource": "namespaces", - "enum": "namespaces:devices_switchMultilevel:deviceId,namespaces:devices_sensorMultilevel:deviceId,namespaces:devices_sensorMultiline:deviceId,namespaces:devices_fan:deviceId,namespaces:devices_thermostat:deviceId", + "enum": "namespaces:devices_sensorMultilevel:deviceId", "required": false } } @@ -39,7 +40,7 @@ "type": "select", "datasource": "namespaces", "field": "optionLabels", - "optionLabels": "namespaces:devices_switchMultilevel:deviceName,namespaces:devices_sensorMultilevel:deviceName,namespaces:devices_sensorMultiline:deviceName,namespaces:devices_fan:deviceName,namespaces:devices_thermostat:deviceName" + "optionLabels": "namespaces:devices_sensorMultilevel:deviceName" } }, "helper": "__h_devices__" diff --git a/modules/EdimaxSP1101/htdocs/icon.png b/modules/EdimaxSP1101/htdocs/icon.png new file mode 100644 index 000000000..1f1daa0ad Binary files /dev/null and b/modules/EdimaxSP1101/htdocs/icon.png differ diff --git a/modules/EdimaxSP1101/index.js b/modules/EdimaxSP1101/index.js new file mode 100644 index 000000000..6bf7332bc --- /dev/null +++ b/modules/EdimaxSP1101/index.js @@ -0,0 +1,84 @@ +/*** EdimaxSP1101 Z-Way HA module ******************************************* + +Version: 1.0.0 +(c) Z-Wave.Me, 2017 +----------------------------------------------------------------------------- +Author: Karsten Reichel +Description: + This module allows to switch the Edimax SP-1101 plug. + +******************************************************************************/ + +// ---------------------------------------------------------------------------- +// --- Class definition, inheritance and setup +// ---------------------------------------------------------------------------- + + +function EdimaxSP1101 (id, controller) { + // Call superconstructor first (AutomationModule) + EdimaxSP1101.super_.call(this, id, controller); +} + +inherits(EdimaxSP1101, AutomationModule); + +_module = EdimaxSP1101; + +// ---------------------------------------------------------------------------- +// --- Module instance initialized +// ---------------------------------------------------------------------------- + +EdimaxSP1101.prototype.init = function (config) { + EdimaxSP1101.super_.prototype.init.call(this, config); + + vDevId = 'EdimaxSP1101_' + this.id; + + this.url = 'http://admin:1234@' + config.url + ':10000/smartplug.cgi'; + + this.xml = '${state}'; + + var self = this; + + this.vDev = this.controller.devices.create({ + deviceId: vDevId, + defaults: { + deviceType: 'switchBinary', + customIcons: {}, + metrics: { + icon: 'switch', + level: 'off', + title: self.getInstanceTitle() + }, + }, + overlay: { + deviceType: 'switchBinary' + }, + handler: function(command) { + if (command != 'update') { + var data = self.xml.replace('${state}',command.toUpperCase()); + http.request({ + method: 'POST', + url: self.url, + async: true, + data: data, + success: function(response) { + self.vDev.set('metrics:level', command); + }, + error: function(response) { + console.log('EdimaxSP1101 - ERROR: ' + response.statusText); + } + }); + } + }, + moduleId: this.id + }); +}; + +EdimaxSP1101.prototype.stop = function () { + + if (this.vDev) { + this.controller.devices.remove(this.vDev.id); + this.vDev = null; + } + + EdimaxSP1101.super_.prototype.stop.call(this); +}; diff --git a/modules/EdimaxSP1101/lang/de.json b/modules/EdimaxSP1101/lang/de.json new file mode 100644 index 000000000..6136751f1 --- /dev/null +++ b/modules/EdimaxSP1101/lang/de.json @@ -0,0 +1,6 @@ +{ + "m_title":"Edimax SP-1101", + "m_descr":"Diese App ermöglicht Ihnen die Steuerung Ihrer Edimax SP-1101 WLAN Steckdose. Geben Sie dazu einfach die IP-Adresse der komplett eingerichteten WLAN Steckdose an.", + "ip":"IP-Adresse des Schalters", + "h_ip":"Beispielformat: '192.168.0.50'" +} \ No newline at end of file diff --git a/modules/EdimaxSP1101/lang/en.json b/modules/EdimaxSP1101/lang/en.json new file mode 100644 index 000000000..89246daf9 --- /dev/null +++ b/modules/EdimaxSP1101/lang/en.json @@ -0,0 +1,6 @@ +{ + "m_title":"Edimax SP-1101", + "m_descr":"This app allows you to control your Edimax SP-1101 WLAN plugs. Simply enter the IP address of the completely configured WLAN plug.", + "ip":"Plug IP URL", + "h_ip":"in the format '192.168.0.50'" +} \ No newline at end of file diff --git a/modules/EdimaxSP1101/module.json b/modules/EdimaxSP1101/module.json new file mode 100644 index 000000000..30842cd92 --- /dev/null +++ b/modules/EdimaxSP1101/module.json @@ -0,0 +1,39 @@ +{ + "singleton": false, + "dependencies": [], + "category": "wifiplug", + "author": "Z-Wave.Me", + "homepage": "http://www.edimax.com/", + "state": null, + "icon": "icon.png", + "moduleName":"EdimaxSP1101", + "version": "1.0.0", + "maturity": "stable", + "repository": { + "type": "git", + "source": "https://github.com/Z-Wave-Me/home-automation" + }, + "defaults": { + "title": "__m_title__", + "description": "__m_descr__", + "url": "" + }, + "schema": { + "type": "object", + "properties": { + "url": { + "required": true + } + }, + "required": false + }, + "options": { + "fields": { + "url": { + "label": "__ip__", + "helper": "__h_ip__", + "required": true + } + } + } +} \ No newline at end of file diff --git a/modules/EdimaxSP2101/htdocs/icon.png b/modules/EdimaxSP2101/htdocs/icon.png new file mode 100644 index 000000000..1f1daa0ad Binary files /dev/null and b/modules/EdimaxSP2101/htdocs/icon.png differ diff --git a/modules/EdimaxSP2101/index.js b/modules/EdimaxSP2101/index.js new file mode 100644 index 000000000..6802868c0 --- /dev/null +++ b/modules/EdimaxSP2101/index.js @@ -0,0 +1,84 @@ +/*** EdimaxSP2101 Z-Way HA module ******************************************* + +Version: 1.0.0 +(c) Z-Wave.Me, 2017 +----------------------------------------------------------------------------- +Author: Karsten Reichel +Description: + This module allows to switch the Edimax SP-2101 plug. + +******************************************************************************/ + +// ---------------------------------------------------------------------------- +// --- Class definition, inheritance and setup +// ---------------------------------------------------------------------------- + + +function EdimaxSP2101 (id, controller) { + // Call superconstructor first (AutomationModule) + EdimaxSP2101.super_.call(this, id, controller); +} + +inherits(EdimaxSP2101, AutomationModule); + +_module = EdimaxSP2101; + +// ---------------------------------------------------------------------------- +// --- Module instance initialized +// ---------------------------------------------------------------------------- + +EdimaxSP2101.prototype.init = function (config) { + EdimaxSP2101.super_.prototype.init.call(this, config); + + vDevId = 'EdimaxSP2101_' + this.id; + + this.url = 'http://admin:1234@' + config.url + ':10000/smartplug.cgi'; + + this.xml = '${state}'; + + var self = this; + + this.vDev = this.controller.devices.create({ + deviceId: vDevId, + defaults: { + deviceType: 'switchBinary', + customIcons: {}, + metrics: { + icon: 'switch', + level: 'off', + title: self.getInstanceTitle() + }, + }, + overlay: { + deviceType: 'switchBinary' + }, + handler: function(command) { + if (command != 'update') { + var data = self.xml.replace('${state}',command.toUpperCase()); + http.request({ + method: 'POST', + url: self.url, + async: true, + data: data, + success: function(response) { + self.vDev.set('metrics:level', command); + }, + error: function(response) { + console.log('EdimaxSP2101 - ERROR: ' + response.statusText); + } + }); + } + }, + moduleId: this.id + }); +}; + +EdimaxSP2101.prototype.stop = function () { + + if (this.vDev) { + this.controller.devices.remove(this.vDev.id); + this.vDev = null; + } + + EdimaxSP2101.super_.prototype.stop.call(this); +}; diff --git a/modules/EdimaxSP2101/lang/de.json b/modules/EdimaxSP2101/lang/de.json new file mode 100644 index 000000000..0ed1f6631 --- /dev/null +++ b/modules/EdimaxSP2101/lang/de.json @@ -0,0 +1,6 @@ +{ + "m_title":"Edimax SP-2101", + "m_descr":"Diese App ermöglicht Ihnen die Steuerung Ihrer Edimax SP-2101 WLAN Steckdose. Geben Sie dazu einfach die IP-Adresse der komplett eingerichteten WLAN Steckdose an.", + "ip":"IP-Adresse des Schalters", + "h_ip":"Beispielformat: '192.168.0.50'" +} \ No newline at end of file diff --git a/modules/EdimaxSP2101/lang/en.json b/modules/EdimaxSP2101/lang/en.json new file mode 100644 index 000000000..97199a8c9 --- /dev/null +++ b/modules/EdimaxSP2101/lang/en.json @@ -0,0 +1,6 @@ +{ + "m_title":"Edimax SP-2101", + "m_descr":"This app allows you to control your Edimax SP-2101 WLAN plugs. Simply enter the IP address of the completely configured WLAN plug.", + "ip":"Plug IP URL", + "h_ip":"in the format '192.168.0.50'" +} \ No newline at end of file diff --git a/modules/EdimaxSP2101/module.json b/modules/EdimaxSP2101/module.json new file mode 100644 index 000000000..fa0e0f5e3 --- /dev/null +++ b/modules/EdimaxSP2101/module.json @@ -0,0 +1,39 @@ +{ + "singleton": false, + "dependencies": [], + "category": "wifiplug", + "author": "Z-Wave.Me", + "homepage": "http://www.edimax.com/", + "state": null, + "icon": "icon.png", + "moduleName":"EdimaxSP2101", + "version": "1.0.0", + "maturity": "stable", + "repository": { + "type": "git", + "source": "https://github.com/Z-Wave-Me/home-automation" + }, + "defaults": { + "title": "__m_title__", + "description": "__m_descr__", + "url": "" + }, + "schema": { + "type": "object", + "properties": { + "url": { + "required": true + } + }, + "required": false + }, + "options": { + "fields": { + "url": { + "label": "__ip__", + "helper": "__h_ip__", + "required": true + } + } + } +} \ No newline at end of file diff --git a/modules/GoogleHome/htdocs/icon.png b/modules/GoogleHome/htdocs/icon.png new file mode 100644 index 000000000..ef411aade Binary files /dev/null and b/modules/GoogleHome/htdocs/icon.png differ diff --git a/modules/GoogleHome/index.js b/modules/GoogleHome/index.js new file mode 100644 index 000000000..54ce23826 --- /dev/null +++ b/modules/GoogleHome/index.js @@ -0,0 +1,853 @@ +/*** GoogleHome Z-Way HA module ******************************************* + + Version: 0.0.9 beta + (c) Z-Wave.Me, 2017 + ----------------------------------------------------------------------------- + Author: Michael Hensche + Description: + + ******************************************************************************/ + +function GoogleHome (id, controller) { + // Call superconstructor first (AutomationModule) + GoogleHome.super_.call(this, id, controller); + + this.HomeGraphAPIKey = "AIzaSyADXWi1oA0o8_hzzzPK0tjuzL7_BAqYakk"; + this.SYNC_URL = "https://homegraph.googleapis.com/v1/devices:requestSync?key=" + + // namespaces + this.NAMESPACE_SYNC = "action.devices.SYNC"; + this.NAMESPACE_QUERY = "action.devices.QUERY"; + this.NAMESPACE_EXECUTE = "action.devices.EXECUTE"; + + // supportted device types + this.THERMOSTAT = "action.devices.types.THERMOSTAT"; + this.LIGHT = "action.devices.types.LIGHT"; + this.OUTLET = "action.devices.types.OUTLET"; + this.SWITCH = "action.devices.types.SWITCH"; + this.SCENE = "action.devices.types.SCENE"; + + //actions + this.ONOFF = "action.devices.traits.OnOff"; + this.BRIGHTNESS = "action.devices.traits.Brightness"; + this.COLORSPECTRUM = "action.devices.traits.ColorSpectrum"; + this.COLORTEMPERATURE = "action.devices.traits.ColorTemperature"; + this.SCENE_ACTION = "action.devices.traits.Scene"; + this.TEMPERATURE_SETTING = "action.devices.traits.TemperatureSetting"; + + //commands + this.HANDLE_ONOF = "action.devices.commands.OnOff"; + this.HANDLE_BRIGHTNESS_ABSOLUTE = "action.devices.commands.BrightnessAbsolute"; + this.HANDLE_COLOR_ABSOLUTE = "action.devices.commands.ColorAbsolute"; + this.HANDLE_THERMOSTAT_TEMPERATURE_SETPOINT = "action.devices.commands.ThermostatTemperatureSetpoint"; + this.HANDLE_THERMOSTAT_TEMPERATURE_SET_RANGE = "action.devices.commands.ThermostatTemperatureSetRange"; + this.HANDLE_THERMOSTAT_SET_MODE = "action.devices.commands.ThermostatSetMode"; + this.HANDLE_ACTIVATE_SCENE = "action.devices.commands.ActivateScene"; + +} + +inherits(GoogleHome, AutomationModule); + +_module = GoogleHome; + +GoogleHome.prototype.init = function(config) { + var self = this; + + GoogleHome.super_.prototype.init.call(this, config); + + this.remoteID = self.controller.getRemoteId(); + + this.requestSync = function(device) { + var data = JSON.stringify({'agentUserId': self.remoteID.toString()}); + + http.request({ + method: 'POST', + url: self.SYNC_URL + self.HomeGraphAPIKey, + async: true, + data: data, + headers: { + "Content-Type": "application/json" + }, + success: function(response) { + console.log("SUCCESS: ", JSON.stringify(response, undefined, 4)); + }, + error: function(response) { + console.log("ERROR: ",JSON.stringify(response, undefined, 4)); + } + }); + } + + if(self.config.devices.length > 0) { + self.requestSync(); + } + + this.defineHandlers(); + this.externalAPIAllow(); + global["GoogleHomeAPI"] = this.GoogleHomeAPI; +}; + + /** + * + * @param event + * { + * "uid": "213456", + * "auth": "bearer xxx", + * "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf" + * } + * @param response + * @return {{}} + * { + * "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", + * "payload": { + * "devices": [{ + * "id": "123", + * "type": "action.devices.types.Outlet", + * "traits": [ + * "action.devices.traits.OnOff" + * ], + * "name": { + * "defaultNames": ["TP-Link Outlet C110"], + * "name": "Homer Simpson Light", + * "nicknames": ["wall plug"] + * }, + * "willReportState: false, + * "attributes": { + * // None defined for these traits yet. + * }, + * "roomHint": "living room", + * "config": { + * "manufacturer": "tplink", + * "model": "c110", + * "hwVersion": "3.2", + * "swVersion": "11.4" + * }, + * "customData": { + * "fooValue": 74, + * "barValue": true, + * "bazValue": "sheepdip" + * } + * }, { + * "id": "456", + * "type": "action.devices.types.Light", + * "traits": [ + * "action.devices.traits.OnOff", + * "action.devices.traits.Brightness", + * "action.devices.traits.ColorTemperature", + * "action.devices.traits.ColorSpectrum" + * ], + * "name": { + * "defaultNames": ["OSRAM bulb A19 color hyperglow"], + * "name": "lamp1", + * "nicknames": ["reading lamp"] + * }, + * "willReportState: false, + * "attributes": { + * "TemperatureMinK": 2000, + * "TemperatureMaxK": 6500 + * }, + * "roomHint": "living room", + * "config": { + * "manufacturer": "osram", + * "model": "hg11", + * "hwVersion": "1.2", + * "swVersion": "5.4" + * }, + * "customData": { + * "fooValue": 12, + * "barValue": false, + * "bazValue": "dancing alpaca" + * } + * }, { + * "id": "234" + * // ... + * }] + * } + * } + */ +GoogleHome.prototype.handleSync = function(event) { + var self = this, + devices = self.buildDevicesList(), + payload = { + "agentUserId": self.remoteID.toString(), + "devices": devices + }; + + return self.createResponse(event.requestId, payload); +}; + + /** + * + * @param event + * { + * "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", + * "uid": "213456", + * "auth": "bearer xxx", + * "devices": [{ + * "id": "123", + * "customData": { + * "fooValue": 12, + * "barValue": true, + * "bazValue": "alpaca sauce" + * } + * }, { + * "id": "234" + * }] + * } + * @param response + * @return {{}} + * { + * "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", + * "payload": { + * "devices": { + * "123": { + * "on": true , + * "online": true + * }, + * "456": { + * "on": true, + * "online": true, + * "brightness": 80, + * "color": { + * "name": "cerulian", + * "spectrumRGB": 31655 + * } + * }, + * ... + * } + * } + * } + */ +GoogleHome.prototype.handleQuery = function(event) { + var self = this, + devices = event.inputs[0].payload.devices, + payload = { + "devices": {} + }; + + for(dev in devices) { + var vDev = self.controller.devices.get(devices[dev].id), + state = {}; + switch(vDev.get("deviceType")) { + case "switchBinary": + state = { + "online": true, + "on": vDev.get("metrics:level") == "on" ? true : false + }; + break; + case "switchMultilevel": + state = { + "online": true, + "on": vDev.get("metrics:level") > 0 ? true : false, + "brightness": parseInt(vDev.get("metrics:level")) + }; + break; + case "toggleButton": + state = { + "online": true, + "on": vDev.get("metrics:level") == "on" ? true : false + }; + break; + case "sensorMultilevel": + case "thermostat": + state = { + "online": true, + "thermostatMode": "heat", + "thermostatTemperatureSetpoint": parseInt(vDev.get("metrics:level"))//, + /*"thermostatTemperatureAmbient": 25.1 + "thermostatHumidityAmbient": 45.3*/ + }; + break; + case "switchRGBW": { + var color = vdev.get("metrics:color"), + spectrumInt = parseInt(RGBToHex(color.r, color.g, color.b), 16); + + state = { + "online": true, + "color": { + "name": "", + "spectrumRGB": spectrumInt + } + }; + } + }; + payload.devices[devices[dev].id] = state; + } + + return self.createResponse(event.requestId, payload); +}; + + /** + * @param event: + * { + * "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", + * "uid": "213456", + * "auth": "bearer xxx", + * "commands": [{ + * "devices": [{ + * "id": "123", + * "customData": { + * "fooValue": 74, + * "barValue": false + * } + * }, { + * "id": "456", + * "customData": { + * "fooValue": 12, + * "barValue": true + * } + * }, { + * "id": "987", + * "customData": { + * "fooValue": 35, + * "barValue": false, + * "bazValue": "sheep dip" + * } + * }], + * "execution": [{ + * "command": "action.devices.commands.OnOff", + * "params": { + * "on": true + * } + * }] + * } + * + * @param response + * @return {{}} + * { + * "requestId": "ff36a3cc-ec34-11e6-b1a0-64510650abcf", + * "payload": { + * "commands": [{ + * "ids": ["123"], + * "status": "SUCCESS" + * "states": { + * "on": true, + * "online": true + * } + * }, { + * "ids": ["456"], + * "status": "SUCCESS" + * "states": { + * "on": true, + * "online": true + * } + * }, { + * "ids": ["987"], + * "status": "OFFLINE", + * "states": { + * "online": false + * } + * }] + * } + * } + */ +GoogleHome.prototype.handleExecute = function(event) { + var self = this, + response = null, + commands = event.inputs[0].payload.commands, + payload = { + commands: [] + }; + + for(var i = 0; i < commands.length; i++) { + var curCommand = commands.commands[i]; + for(var j = 0; j < curCommand.execution.length; j++) { + var curExec = curCommand.execution[j], + devices = curCommand.devices; + for(var k = 0; k < devices.length; k++) { + switch(curExec.command) { + case self.HANDLE_ONOF : + response = self.handleOnOff(curExec, devices[k]); + break; + case self.HANDLE_BRIGHTNESS_ABSOLUTE : + response = self.handleBrightness(curExec, devices[k]); + break; + case self.HANDLE_COLOR_ABSOLUTE : + response = self.handleColorAbsolute(curExec, devices[k]); + break + case self.HANDLE_THERMOSTAT_TEMPERATURE_SETPOINT: + response = self.handleTemperatrueSetPoint(curExec, devices[k]); + break; + default: + console.log("Error", "Unsupported operation" + curExec.command); + break; + } + payload.commands.push(response); + } + } + } + + return self.createResponse(event.requestId, payload); +}; + +/** + * + * @param command + * { + * "command": "action.devices.commands.OnOff", + * "params": { + * "on": true + * } + * } + * @param device + * { + * "id": "123", + * "customData": { + * "fooValue": 74, + * "barValue": false + * } + * } + * @return {{}} + * { + * "ids": ["123"], + * "status": "SUCCESS" + * "states": { + * "on": true, + * "online": true + * } + * } + */ +GoogleHome.prototype.handleOnOff = function(command, device) { + var self = this, + vDev = self.controller.devices.get(device.id), + commnad = {}; + + if(!vDev) { + command = { + "ids": [dev.id], + "status": "ERROR", + "errorCode": "deviceNotFound" + } + } else if(vDev.get("metrics:isFailed")) { + command = { + "ids": [dev.id], + "status": "ERROR", + "errorCode": "deviceOffline" + } + } else { + if(exe.params.on) { + vDev.performCommand("on"); + } else { + vDev.performCommand("off"); + } + command = { + "ids": [dev.id], + "status": "SUCCESS", + "states": { + "on": command.params.on, + "online": true + } + } + } + + return command; +}; + +/** + * + * @param command + * { + * "command": "action.devices.commands.BrightnessAbsolute", + * "params": { + * "brightness": 65 + * } + * } + * @param device + * { + * "id": "123", + * "customData": { + * "fooValue": 74, + * "barValue": false + * } + * } + * @return {} + * { + * "ids": ["123"], + * "status": "SUCCESS" + * "states": { + * "brightness": 65, + * "online": true + * } + * } + */ +GoogleHome.prototype.handleBrightness = function(command, device) { + var self = this, + command = {}, + vdev = self.controller.devices.get(device.id); + + if(!vDev) { + command = { + "ids": [device.id], + "status": "ERROR", + "errorCode": "deviceNotFound" + } + } else if(vDev.get("metrics:isFailed")) { + command = { + "ids": [dev.id], + "status": "ERROR", + "errorCode": "deviceOffline" + } + } else { + if(exe.params.brightness ) { + vdev.performCommand("exact",{level: exe.params.brightness}); + } + command = { + "ids": [dev.id], + "status": "SUCCESS", + "states": { + "brightness": exe.params.brightness, + "online": true + } + } + } + return command; +}; + +/** + * + * @param command + * { + * "command": "action.devices.commands.ColorAbsolute", + * "params": { + * "color": { + * "name": "red", + * "spectrumRGB": 16711680 + * } + * } + * } + * @param device + * { + * "id": "123", + * "customData": { + * "fooValue": 74, + * "barValue": false + * } + * } + * @return {} + * { + * "ids": ["123"], + * "status": "SUCCESS" + * "states": { + * "color": { + * "name": "red", + * "spectrumRGB": 16711680 + * }, + * "online": true + * } + * } + */ +GoogleHome.prototype.handleColorAbsolute = function(command, device) { + var self = this, + spectrumInt = command.params.color.spectrumRGB, + rgb = {}, + spectrumHex = "#"+spectrumInt.toString(16); + rgb = HexToRGB(spectrumHex), + vDev = self.controller.devices.get(device.id), + command = {}; + + if(!vDev) { + command = { + "ids": [device.id], + "status": "ERROR", + "errorCode": "deviceNotFound" + } + } else if(vDev.get("metrics:isFailed")) { + command = { + "ids": [device.id], + "status": "ERROR", + "errorCode": "deviceOffline" + } + } else { + command = { + "ids": [device.id], + "status": "SUCCESS", + "states": { + "color": { + "name": command.params.color.name, + "spectrumRGB": command.params.color.spectrumRGB + }, + "online": true + } + } + vDev.performCommand("exact",{red: rgb.r, green: rgb.g, blue: rgb.b}); + } + + return command; +}; + +/** + * + * @param command + * { + * "command": "action.devices.commands.ThermostatTemperatureSetpoint", + * "params": { + * "thermostatTemperatureSetpoint": 22.0 + * } + * } + * @param device + * { + * "id": "123", + * "customData": { + * "fooValue": 74, + * "barValue": false + * } + * } + * @return {} + * { + * "ids": ["123"], + * "status": "SUCCESS" + * "states": { + * "thermostatMode": "cool", + * "thermostatTemperatureSetpoint": 22.0, + * "thermostatTemperatureAmbient": 25.1, + * "thermostatHumidityAmbient": 43.2 + * "online": true + * } + * } + */ +GoogleHome.prototype.handleTemperatrueSetPoint = function(command, device) { + var self = this, + temperature = command.params.thermostatTemperatureSetpoint, + vDev = self.controller.devices.get(device.id), + command = {}; + + if(!vDev) { + command = { + "ids": [device.id], + "status": "ERROR", + "errorCode": "deviceNotFound" + } + } else if(vDev.get("metrics:isFailed")) { + command = { + "ids": [device.id], + "status": "ERROR", + "errorCode": "deviceOffline" + } + } else { + var minTemp = vDev.get("metrics:min"), + maxTemp = vDev.get("metrics:max"); + + if(temperature < minTemp || temperature > maxTemp){ + command = { + "ids": [device.id], + "status": "ERROR", + "errorCode": "valueOutOfRange" + } + } else { + command = { + "ids": [device.id], + "status": "SUCCESS", + "thermostatTemperatureSetpoint": temperature, + "online": true + } + vDev.performCommand("exact", {"level": parseInt(temperature)}); + } + } + + return command; +} + /** + * + * @param requestId + * @param payload + * { + * "devices": { + * "123": { + * "online": true, + * "brightness": 65 + * } + * } + * } + * OR + * { + * "commands": [{ + * "ids": ["123"], + * "status": "SUCCESS", + * "states": { + * "brightness": 65 + * } + * }] + * } + * @return {{}} + * { + * "requestId": "[requestId]", + * "payload": { + * "[payload]" + * } + * } + */ +GoogleHome.prototype.createResponse = function(requestId, payload) { + return { + "requestId": requestId, + "payload": payload + }; +}; + +GoogleHome.prototype.buildDevicesList = function() { + var self = this, + devices = self.controller.devices, + locations = self.controller.locations, + instances = self.controller.instances, + active_devices = self.config.devices.map(function(dev) {return dev.id}); + + var devicesList = devices.filter(function(device) { + var vDev = self.controller.devices.get(device.id), + pos = active_devices.indexOf(device.id); + + if(pos != -1) { + return vDev; + } + }).map(function(vDev) { + var dev = { + "id": "", //Required + "type": "", //Required + "traits": [], //Required + "name": {}, //Required + "willReportState": false, //Required + //"roomHint": "", //Optional will remove is no location set for device + //"structureHint": "", //Optional + //"deviceInfo": {}, //Optional + //"attributes": {}, //Optional + "customData": {} //Optional + }; + + switch(vDev.get("deviceType")) { + case "switchBinary": + dev.type = self.SWITCH; + dev.traits.push(self.ONOFF); + break; + case "switchMultilevel": + dev.type = self.SWITCH; + dev.traits.push(self.ONOFF, self.BRIGHTNESS); + break; + case "switchRGBW": + dev.type = self.LIGHT; + dev.traits.push(self.ONOFF, self.COLORSPECTRUM); + dev["attributes"] = {colorModel: "RGB"}; + break; + case "toggleButton": + dev.traits.push(self.ONOFF); + dev.type = self.SWITCH; + var scene = _.find(instances, function(inst) { + return inst.id == vDev.get("creatorId") && inst.moduleId == "LightScene"; + }); + if(typeof scene !== 'undefined') { + dev.traits[self.SCENE_ACTION]; + dev.type = self.SCENE; + dev.customData = {type: 'SCENE'}; + } + break; + case "sensorMultilevel": + case "thermostat": + dev.traits.push(self.TEMPERATURE_SETTING); + dev.type = self.THERMOSTAT; + dev.attributes = { + "availableThermostatModes": "heat", + "thermostatTemperatureUnit": "C" + }; + break; + }; + + dev.id = vDev.get("id"); + var pos = active_devices.indexOf(vDev.id), + locationId = vDev.get("location"); + + if(pos !== -1) { + var deviceName = self.config.devices[pos].callName; + } else { + // fallback if no call name set + var deviceName = vDev.get("metrics:title") == "" ? "Unknow device" : vDev.get("metrics:title"); + } + + var name = { + "defaultNames": [dev.id], //Optional + "name": deviceName //, //Optional + //"nicknames": [] //Optional + }; + + dev.name = name; + if(locationId !== 0) { + var location = _.find(locations, function(location) { + return location.id === locationId; + }); + var room = location.title; + dev.roomHint = room; + } + return dev; + }, active_devices); + + return devicesList; +}; + + +GoogleHome.prototype.stop = function() { + var self = this; + delete global["GoogleHomeAPI"]; + + GoogleHome.super_.prototype.stop.call(this); +}; + +// --------------- Public HTTP API ------------------- + + +GoogleHome.prototype.externalAPIAllow = function (name) { + var _name = !!name ? ("GoogleHome." + name) : "GoogleHomeAPI"; + + ws.allowExternalAccess(_name, this.controller.auth.ROLE.USER); + ws.allowExternalAccess(_name + ".callAction", this.controller.auth.ROLE.USER); +}; + +GoogleHome.prototype.externalAPIRevoke = function (name) { + var _name = !!name ? ("GoogleHome." + name) : "GoogleHomeAPI"; + + ws.revokeExternalAccess(_name); + ws.revokeExternalAccess(_name + ".callAction"); +}; + +GoogleHome.prototype.defineHandlers = function () { + var self = this; + + this.GoogleHomeAPI = function () { + return {status: 400, body: "Bad GoogleHomeAPI request "}; + }; + + this.GoogleHomeAPI.callAction = function (url, request) { + console.log("Received data from Google Home"); + console.log("request:", JSON.stringify(request)); + + if (request.method === "POST" && request.body) { + reqObj = typeof request.body === "string" ? JSON.parse(request.body) : request.body; + + var requestedNamespace = reqObj.inputs[0].intent; + switch(requestedNamespace) { + case self.NAMESPACE_SYNC: + response = self.handleSync(reqObj); + break; + case self.NAMESPACE_QUERY: + response = self.handleQuery(reqObj); + break; + case self.NAMESPACE_EXECUTE: + response = self.handleExecute(reqObj); + break; + default: + console.log("Error: ", "Unsupported namespace: " + requestedNamespace); + break; + } + console.log("Return Response to Google Home"); + console.log("response:", JSON.stringify(response)); + return response; + } + }; +}; + +function HexToRGB(hex) { + var c = hex.substring(1).split(''); + if(c.length == 3){ + c = [c[0], c[0], c[1], c[1], c[2], c[2]]; + } + c = '0x'+c.join(''); + return {r: (c>>16)&255, g: (c>>8)&255, b: c&255}; +} + +function RGBToHex(r, g, b) { + return ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); +} \ No newline at end of file diff --git a/modules/GoogleHome/lang/de.json b/modules/GoogleHome/lang/de.json new file mode 100644 index 000000000..19f0a7f06 --- /dev/null +++ b/modules/GoogleHome/lang/de.json @@ -0,0 +1,4 @@ +{ + "m_title":"Google Home", + "m_descr":"Mit diesem Modul können Sie Ihre Z-Way Geräte über den Google-Assistenten steuern." +} \ No newline at end of file diff --git a/modules/GoogleHome/lang/en.json b/modules/GoogleHome/lang/en.json new file mode 100644 index 000000000..09fe0bdeb --- /dev/null +++ b/modules/GoogleHome/lang/en.json @@ -0,0 +1,4 @@ +{ + "m_title":"Google Home", + "m_descr":"This module let you control your Z-Way devices through the Google Assistant." +} \ No newline at end of file diff --git a/modules/GoogleHome/module.json b/modules/GoogleHome/module.json new file mode 100644 index 000000000..24e7ecd64 --- /dev/null +++ b/modules/GoogleHome/module.json @@ -0,0 +1,19 @@ +{ + "dependencies": [], + "singleton": true, + "category": "support_external_dev", + "author": "Z-Wave.Me", + "homepage": "http://razberry.z-wave.me", + "icon": "icon.png", + "moduleName":"GoogleHome", + "version": "0.0.9", + "maturity": "beta", + "repository": { + "type": "git", + "source": "https://github.com/Z-Wave-Me/home-automation" + }, + "defaults": { + "title" : "__m_title__", + "description": "__m_descr__" + } +} diff --git a/modules/HTTPDevice/index.js b/modules/HTTPDevice/index.js index 2d3c2db2c..b4f1b1f47 100644 --- a/modules/HTTPDevice/index.js +++ b/modules/HTTPDevice/index.js @@ -1,9 +1,9 @@ /*** HTTPDevice Z-Way HA module ******************************************* -Version: 2.1.0 -(c) Z-Wave.Me, 2017 +Version: 2.2.0 +(c) Z-Wave.Me, 2018 ----------------------------------------------------------------------------- -Author: Poltorak Serguei +Author: Poltorak Serguei , Yurkin Vitaliy Description: Implements virtual device based on HTTP object ******************************************************************************/ @@ -131,7 +131,6 @@ HTTPDevice.prototype.update = function (vDev) { if (url) { var req = { url: url, - method: this.config.method, async: true, success: function(response) { var data = null; @@ -164,12 +163,31 @@ HTTPDevice.prototype.update = function (vDev) { console.log("Can not make request: " + response.statusText); // don't add it to notifications, since it will fill all the notifcations on error } }; - // With Content type - req.headers = { "Content-Type": self.config.contentType } - // With Data - if (self.config.data) { - req.data = self.config.data; + // With Method + if (self.config.methodForGetValue && (deviceType === "switchMultilevel" || deviceType === "switchBinary")) { // compatibility with 2.1 + req.method = self.config.methodForGetValue; + } + else { + req.method = self.config.method // compatibility with 2.1 + } + + if (req.method === "POST") { + // With Content type + if (self.config.contentTypeForGetValue && (deviceType === "switchMultilevel" || deviceType === "switchBinary")) { // compatibility with 2.1 + req.headers = { "Content-Type": self.config.contentTypeForGetValue} + } + else if (self.config.contentType) { + req.headers = { "Content-Type": self.config.contentType} // compatibility with 2.1 + } + + // With Data + if (self.config.dataForGetValue && (deviceType === "switchMultilevel" || deviceType === "switchBinary")) { // compatibility with 2.1 + req.data = self.config.dataForGetValue; + } + else if (self.config.data) { + req.data = self.config.data; // compatibility with 2.1 + } } // With Authorization @@ -201,12 +219,17 @@ HTTPDevice.prototype.act = function (vDev, action, subst, selfValue) { self.addNotification("error", langFile.err_req + response.statusText, "module"); } }; - // With Content type - req.headers = { "Content-Type": self.config.contentType } - // With Data - if (self.config.data) { - req.data = self.config.data.replace(/\$\$/g, subst); + if (req.method === "POST") { + // With Content type + if (self.config.contentType) { + req.headers = { "Content-Type": self.config.contentType } + } + + // With Data + if (self.config.data) { + req.data = self.config.data.replace(/\$\$/g, subst); + } } // With authorization if (self.config.login && self.config.password) { diff --git a/modules/HTTPDevice/lang/de.json b/modules/HTTPDevice/lang/de.json index 8f824e7dd..62774cf12 100644 --- a/modules/HTTPDevice/lang/de.json +++ b/modules/HTTPDevice/lang/de.json @@ -14,7 +14,7 @@ "l_getterPollInterval":"Abfrageintervall in Sekunden", "h_getterPollInterval":"Bei keiner Eingabe oder 0 wird kein Abfrageintervall instanziiert (Explizite update Kommandos funktionieren auch weiterhin)", "l_getterParser_Multilevel":"Inline Javascript für das Einlesen eingehender Daten als Zahlen", - "h_getterParser_Multilevel":"Bei keiner Eingabe wird parseFloat() verwendet. Bsp.: $$$$.split(':'')[1] oder parseInt($$$$, 16) oder $$$$.data.metrics.level oder parseFloat($$$$.findOne('/A/B[C='123']/D/text()'))", + "h_getterParser_Multilevel":"Bei keiner Eingabe wird parseFloat() verwendet. Bsp.: $$$$.split(':'')[1] oder parseInt($$$$, 16) oder $$$$.data.metrics.level oder parseFloat($$$$.findOne(\\\"/A/B[@C='123']/D/text()\\\"))", "l_setterLevel":"Führe eine Aktion über die URL aus", "p_setterLevel":"http://192.168.0.100/action/set/$$$$", "h_setterLevel":"$$$$ wird benutzt um das Level in der URL zu symbolisieren. Bsp.: http://host/dimmer/level/$$$$/set", @@ -24,6 +24,11 @@ "h_contentType":"Can be application/json, application/xml, text/html and another MIME types.", "l_data":"Data", "h_data":"Data to send with POST request. As example string to set value in virtual device in Fibaro HC2: {\\\"args\\\":[\\\"ui.Label1.value\\\",\\\"$$$$\\\"]}", + "l_methodForGetValue":"HTTP method to get value", + "l_contentTypeForGetValue":"Content type to get value", + "h_contentTypeForGetValue":"Can be application/json, application/xml, text/html and another MIME types.", + "l_dataForGetValue":"Data to get value", + "h_dataForGetValue":"Data to send with POST request. As example string to set value in virtual device in Fibaro HC2: {\\\"args\\\":[\\\"ui.Label1.value\\\",\\\"$$$$\\\"]}", "err_req":"Es konnte kein Request gesendet werden: ", "rl_updateOnAction": "Update value on action", "h_updateOnAction": "On actions (on/off/dimming) update value accordingly before it is polled", diff --git a/modules/HTTPDevice/lang/en.json b/modules/HTTPDevice/lang/en.json index 542676f44..389025a65 100644 --- a/modules/HTTPDevice/lang/en.json +++ b/modules/HTTPDevice/lang/en.json @@ -14,16 +14,21 @@ "l_getterPollInterval":"Interval in seconds between polling requests", "h_getterPollInterval":"Empty or 0 to disable periodical requests (explicit update command will still initiate request process)", "l_getterParser_Multilevel":"Inline Javascript to parse incoming data to number", - "h_getterParser_Multilevel":"Can be empty to use parseFloat() function. Example: $$$$.split(':')[1] or parseInt($$$$, 16) or $$$$.data.metrics.level or parseFloat($$$$.findOne('/A/B[C='123']/D/text()'))", + "h_getterParser_Multilevel":"Can be empty to use parseFloat() function. Example: $$$$.split(':')[1] or parseInt($$$$, 16) or $$$$.data.metrics.level or parseFloat($$$$.findOne(\\\"/A/B[@C='123']/D/text()\\\"))", "l_setterLevel":"URL for action", "p_setterLevel":"http://192.168.0.100/action/set/$$$$", "h_setterLevel":"$$$$ is used to represent level in the URL. For example: http://host/dimmer/level/$$$$/set", "l_scale":"Sensor scale", - "l_method":"HTTP Method", + "l_method":"HTTP method", "l_contentType":"Content type", "h_contentType":"Can be application/json, application/xml, text/html and another MIME types.", "l_data":"Data", "h_data":"Data to send with POST request. As example string to set value in virtual device in Fibaro HC2: {\\\"args\\\":[\\\"ui.Label1.value\\\",\\\"$$$$\\\"]}", + "l_methodForGetValue":"HTTP method to get value", + "l_contentTypeForGetValue":"Content type to get value", + "h_contentTypeForGetValue":"Can be application/json, application/xml, text/html and another MIME types.", + "l_dataForGetValue":"Data to get value", + "h_dataForGetValue":"Data to send with POST request. As example string to set value in virtual device in Fibaro HC2: {\\\"args\\\":[\\\"ui.Label1.value\\\",\\\"$$$$\\\"]}", "err_req":"Can not make request: ", "rl_updateOnAction": "Update value on action", "h_updateOnAction": "On actions (on/off/dimming) update value accordingly before it is polled", diff --git a/modules/HTTPDevice/lang/ru.json b/modules/HTTPDevice/lang/ru.json index 6471dfddf..86ef02640 100644 --- a/modules/HTTPDevice/lang/ru.json +++ b/modules/HTTPDevice/lang/ru.json @@ -14,16 +14,21 @@ "l_getterPollInterval":"Интервал в секундах опроса значения", "h_getterPollInterval":"Пусто или 0 отключают периодический опрос (запросить значение можно вручную командой update)", "l_getterParser_Multilevel":"JavaScript код для преобразования полученного значения в число", - "h_getterParser_Multilevel":"Поле может быть пустым, тогда будет использована функция parseFloat(). Пример преобразования: $$$$.split(':')[1] или parseInt($$$$, 16) или $$$$.data.metrics.level или parseFloat($$$$.findOne('/A/B[C='123']/D/text()'))", + "h_getterParser_Multilevel":"Поле может быть пустым, тогда будет использована функция parseFloat(). Пример преобразования: $$$$.split(':')[1] или parseInt($$$$, 16) или $$$$.data.metrics.level или parseFloat($$$$.findOne(\\\"/A/B[@C='123']/D/text()\\\"))", "l_setterLevel":"URL для действия", "p_setterLevel":"http://192.168.0.100/action/set/$$$$", "h_setterLevel":"Символы $$$$ содержат значение уровня диммирования от 0 до 99 в URL. Например: http://host/dimmer/level/$$$$/set", "l_scale":"Единица измерения", - "l_method":"HTTP Метод", + "l_method":"HTTP метод", "l_contentType":"Content type", "h_contentType":"Может быть application/json, application/xml, text/html и др. MIME типы.", "l_data":"Данные", "h_data":"Данные отправляемые в POST запросе. Например строка для передачи текущего значения датчика в виртуальное устройство на Fibaro HC2: {\\\"args\\\":[\\\"ui.Label1.value\\\",\\\"$$$$\\\"]}", + "l_method":"HTTP метод для получения значения", + "l_contentType":"Content type для получения значения", + "h_contentType":"Может быть application/json, application/xml, text/html и др. MIME типы.", + "l_data":"Data для получения значения", + "h_data":"Данные отправляемые в POST запросе. Например строка для передачи текущего значения датчика в виртуальное устройство на Fibaro HC2: {\\\"args\\\":[\\\"ui.Label1.value\\\",\\\"$$$$\\\"]}", "err_req":"Невозможно выполнить запрос: ", "rl_updateOnAction": "Менять состояние при действиях", "h_updateOnAction": "При действиях с устройством (включение/выключение/диммирование), менять состояние на соответствующее действию, не дожидаясь получения опроса значения.", diff --git a/modules/HTTPDevice/module.json b/modules/HTTPDevice/module.json index 11af71602..70f568eaf 100644 --- a/modules/HTTPDevice/module.json +++ b/modules/HTTPDevice/module.json @@ -6,7 +6,7 @@ "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "HTTPDevice", - "version": "2.1.0", + "version": "2.2.0", "maturity": "stable", "repository": { "type": "git", @@ -38,6 +38,9 @@ "method": "GET", "contentType": "application/x-www-form-urlencoded", "data":"", + "methodForGetValue": "GET", + "contentTypeForGetValue": "application/x-www-form-urlencoded", + "dataForGetValue":"", "updateOnAction": false, "skipEventIfSameValue": false, "login":"", @@ -157,11 +160,29 @@ }, "contentType": { "type": "string", - "required": true + "required": true, + "dependencies": "method" }, "data":{ "type": "string", - "required": false + "required": true, + "dependencies": "method" + }, + "methodForGetValue": { + "type": "string", + "required": true, + "enum": ["GET", "POST"], + "dependencies": "deviceType" + }, + "contentTypeForGetValue": { + "type": "string", + "required": true, + "dependencies": "methodForGetValue" + }, + "dataForGetValue":{ + "type": "string", + "required": true, + "dependencies": "methodForGetValue" }, "updateOnAction": { "type": "boolean", @@ -328,11 +349,38 @@ }, "contentType": { "label": "__l_contentType__", - "helper": "__h_contentType__" + "helper": "__h_contentType__", + "dependencies": { + "method": "POST" + } }, "data":{ "label": "__l_data__", - "helper": "__h_data__" + "helper": "__h_data__", + "dependencies": { + "method": "POST" + } + }, + "methodForGetValue": { + "label": "__l_methodForGetValue__", + "type": "select", + "dependencies": { + "deviceType": ["switchBinary", "switchMultilevel"] + } + }, + "contentTypeForGetValue": { + "label": "__l_contentTypeForGetValue__", + "helper": "__h_contentTypeForGetValue__", + "dependencies": { + "methodForGetValue": "POST" + } + }, + "dataForGetValue":{ + "label": "__l_dataForGetValue__", + "helper": "__h_dataForGetValue__", + "dependencies": { + "methodForGetValue": "POST" + } }, "updateOnAction": { "type": "checkbox", diff --git a/modules/HazardNotification/htdocs/fire_alarm.png b/modules/HazardNotification/htdocs/fire_alarm.png new file mode 100644 index 000000000..f0c46b761 Binary files /dev/null and b/modules/HazardNotification/htdocs/fire_alarm.png differ diff --git a/modules/HazardNotification/htdocs/fire_icon.png b/modules/HazardNotification/htdocs/fire_icon.png new file mode 100644 index 000000000..8d381355b Binary files /dev/null and b/modules/HazardNotification/htdocs/fire_icon.png differ diff --git a/modules/HazardNotification/htdocs/fire_ok.png b/modules/HazardNotification/htdocs/fire_ok.png new file mode 100644 index 000000000..d688497a8 Binary files /dev/null and b/modules/HazardNotification/htdocs/fire_ok.png differ diff --git a/modules/HazardNotification/htdocs/fire_warning.png b/modules/HazardNotification/htdocs/fire_warning.png new file mode 100644 index 000000000..921831025 Binary files /dev/null and b/modules/HazardNotification/htdocs/fire_warning.png differ diff --git a/modules/HazardNotification/htdocs/leakage_alarm.png b/modules/HazardNotification/htdocs/leakage_alarm.png new file mode 100644 index 000000000..b85d50674 Binary files /dev/null and b/modules/HazardNotification/htdocs/leakage_alarm.png differ diff --git a/modules/HazardNotification/htdocs/leakage_icon.png b/modules/HazardNotification/htdocs/leakage_icon.png new file mode 100644 index 000000000..9fc06c222 Binary files /dev/null and b/modules/HazardNotification/htdocs/leakage_icon.png differ diff --git a/modules/HazardNotification/htdocs/leakage_ok.png b/modules/HazardNotification/htdocs/leakage_ok.png new file mode 100644 index 000000000..06f96c882 Binary files /dev/null and b/modules/HazardNotification/htdocs/leakage_ok.png differ diff --git a/modules/HazardNotification/htdocs/leakage_warning.png b/modules/HazardNotification/htdocs/leakage_warning.png new file mode 100644 index 000000000..6223b65a5 Binary files /dev/null and b/modules/HazardNotification/htdocs/leakage_warning.png differ diff --git a/modules/HazardNotification/index.js b/modules/HazardNotification/index.js new file mode 100644 index 000000000..be93a6183 --- /dev/null +++ b/modules/HazardNotification/index.js @@ -0,0 +1,505 @@ +/*** HazardNotification Z-Way HA module ******************************************* + +Version: 1.0.0 +(c) Z-Wave.Me, 2015 +----------------------------------------------------------------------------- +Author: Niels Roche + Karsten Reichel + Michael Hensche +Description: + Filters all water sensors, fire sensors and creates a virtual device to monitor and control them together. +******************************************************************************/ +// ---------------------------------------------------------------------------- +// --- Class definition, inheritance and setup +// ---------------------------------------------------------------------------- +function HazardNotification(id, controller) { + // Call superconstructor first (AutomationModule) + HazardNotification.super_.call(this, id, controller); +} + +inherits(HazardNotification, AutomationModule); + +_module = HazardNotification; + +// ---------------------------------------------------------------------------- +// --- Module instance initialized +// ---------------------------------------------------------------------------- + +HazardNotification.prototype.init = function(config) { + HazardNotification.super_.prototype.init.call(this, config); + + var self = this, + filteredSensorsFromDevices = [], + hazardSensorMetrics = [], + item = {}; + + this.vDev = null; + + if (self.config.triggerEvent.length > 0 && !self.config.transformed) { + var new_triggerEvents = []; + + /* transform each single entry to the new format: switches, thermostats, dimmers, locks, scenes + { + deviceId: '', + deviceType: '', + level: '', // color: { r: 0, g: 0, b: 0}, on, off, open, close, color + sendAction: true || false >> don't do this if level is already triggered + } + */ + self.config.triggerEvent.forEach(function(entry) { + vDev = self.controller.devices.get(entry.deviceId); + + new_triggerEvents.push({ + deviceId: entry.deviceId, + deviceType: vDev ? vDev.get('deviceType') : '', + level: entry.status && entry.status != 'level' ? entry.status : (entry.status === 'level' && entry.level ? entry.level : 0), + sendAction: entry.sendAction || false + }); + }); + + // overwrite config devices list + self.config.triggerEvent = _.uniq(new_triggerEvents); + self.config.transformed = true; + + //save into config + self.saveConfig(); + } + + // update vDev attributes (water and fire sensors) + self.updateAttributes = function(dev) { + var hazardSensors = [], + sensor = [], + indx = null; + + hazardSensors = self.vDev.get('metrics:sensors'); + + sensor = hazardSensors.filter(function(sensor) { + return sensor.id === dev.get('id'); + }); + + if (sensor[0]) { + // update sensor metrics + sensor[0].metrics = dev.get('metrics'); + } + }; + + // add sensors to vDev (after server startup) + self.updateIfHazardSensorsAreCreated = function(dev) { + var indx = self.config.sensors.indexOf(dev.id); + + if (indx > -1 && hazardSensorMetrics.map(function(e) { + return e.id; + }).indexOf(dev.id) === -1) { + + if (filteredSensorsFromDevices.indexOf(dev) === -1) { + filteredSensorsFromDevices.push(dev); + } + + item = { + id: dev.id, + deviceType: dev.get('deviceType'), + metrics: dev.get('metrics'), + hasHistory: dev.get('hasHistory'), + updateTime: dev.get('updateTime') + }; + + hazardSensorMetrics.push(item); + + // listen to sensor changes + self.controller.devices.on(dev.id, 'change:[object Object]', self.updateAttributes); + + // setup arm mode (reinitialize vDev) + if (self.vDev.get('metrics:state') === 'armed') { + if (dev.get('metrics:level') === 'on') { + self.setAlert(); + } + + // listen to sensor changes + self.controller.devices.on(dev.id, 'change:metrics:level', self.throwAlert); + } + } + }; + + // activate armed modus + self.setupArmed = function() { + // if it is still alert set alert mode + if (self.getSensorLevels().length > 0 && self.getSensorLevels().indexOf('on') !== -1) { + self.setAlert(); + } else { + self.vDev.set('metrics:level', 'OK'); + var icon = config.hazardType == "fire" ? "fire_ok.png" : "leakage_ok.png"; + self.vDev.set('metrics:icon', '/ZAutomation/api/v1/load/modulemedia/HazardNotification/'+icon); + } + + // listen to sensor changes + filteredSensorsFromDevices.forEach(function(dev) { + self.controller.devices.on(dev.id, 'change:metrics:level', self.throwAlert); + }); + }; + + // listener - what to do if sensors state has changed + self.throwAlert = function(dev) { + + //set alert mode + if (dev.get('metrics:level') === 'on' && self.vDev.get('metrics:level') !== 'ALERT') { + self.setAlert(); + } + + //set back armed mode if alert is OK and device is not disarmed + if (dev.get('metrics:level') === 'off' && + self.vDev.get('metrics:level') === 'ALERT' && + self.getSensorLevels().length > 0 && + self.getSensorLevels().indexOf('on') === -1 && + self.vDev.get('metrics:state') !== 'disarmed') { + + self.vDev.set('metrics:level', 'OK!'); + var icon = config.hazardType == "fire" ? "fire_warning.png" : "leakage_warning.png"; + self.vDev.set('metrics:icon', '/ZAutomation/api/v1/load/modulemedia/HazardNotification/' + icon); + + //send notification: OK + //console.log('Leakage Protection state is "OK". Still armed ...'); + //stop sending notifications + //console.log('Stop sending notifications ...'); + + self.triggerNotification('revert'); + + if (self.sendInterval) { + //console.log('Stop - Clear send ...'); + clearInterval(self.sendInterval); + self.sendInterval = undefined; + } + } + }; + + // set vDev to alert mode + self.setAlert = function() { + var icon = config.hazardType == "fire" ? "fire_alarm.png" : "leakage_alarm.png"; + self.vDev.set('metrics:level', 'ALERT'); + self.vDev.set('metrics:icon', '/ZAutomation/api/v1/load/modulemedia/HazardNotification/' + icon); + + // trigger reaction + self.triggerEvents(); + + //start sending notifications + //console.log('Alert detected. Start sending notifications ...'); + + if (!self.sendInterval) { + self.triggerNotification('alarm'); + self.sendInterval = setInterval(function() { + //console.log('Send ...'); + self.triggerNotification('alarm'); + }, parseInt(config.notificationsInterval, 10) * 1000); + } + }; + + self.triggerNotification = function(type) { + _.forEach(config.sendNotifications, function(notification) { + if (type == notification.firedOn) { + var notificationType = '', + notificationMessage = ''; + + if (notification.target && notification.target !== '') { + notificationType = notification.target.search('@') > -1 ? 'mail.notification' : 'push.notification'; + notificationMessage = !notification.message ? fallbackMessage : notification.message; + + self.addNotification(notificationType, notificationMessage, notification.target); + } + } + }); + }; + + self.triggerEvents = function() { + _.forEach(config.triggerEvent, function(event) { + /*var vDev = self.controller.devices.get(event.deviceId), + lvl = event.status == "lvl" ? event.level : event.status, + set = event.sendAction ? self.executeActions(event.sendAction, vDev, lvl) : true; + if (vDev && set) { + self.setNewDeviceState(vDev, lvl); + }*/ + + self.shiftDevice(event); + }); + }; + + // listener - set vDev level back to OK if device is disarmed and sensor levels are all 'off' + self.onPoll = function() { + if (self.getSensorLevels().indexOf('on') === -1 && self.vDev) { + self.vDev.set('metrics:level', 'OK'); + var icon = config.hazardType == "fire" ? "fire_ok.png" : "leakage_ok.png"; + self.vDev.set('metrics:icon', '/ZAutomation/api/v1/load/modulemedia/HazardNotification/'+icon); + self.removePolling(); + } + }; + + // remove polling after disarm and sensor levels are ok + self.removePolling = function() { + + if (self.timer) { + //console.log('Clear check ...'); + clearInterval(self.timer); + self.timer = undefined; + } + + if (self.sendInterval) { + //console.log('Clear send ...'); + clearInterval(self.sendInterval); + self.sendInterval = undefined; + } + }; + + self.checkState = function() { + if (!self.timer) { + self.timer = setInterval(function() { + //console.log('Do check ...'); + self.onPoll(); + }, 10 * 1000); + } + }; + + // get sensors from devices + filteredSensorsFromDevices = self.controller.devices.filter(function(dev) { + return self.config.sensors.indexOf(dev.id) > -1; + }); + + // create vDev metrics with sensor values + filteredSensorsFromDevices.forEach(function(dev) { + + item = { + id: dev.id, + deviceType: dev.get('deviceType'), + metrics: dev.get('metrics'), + hasHistory: dev.get('hasHistory'), + updateTime: dev.get('updateTime') + }; + + hazardSensorMetrics.push(item); + + // listen to sensor changes + self.controller.devices.on(dev.id, 'change:[object Object]', self.updateAttributes); + }); + + var metr = self.controller.vdevInfo["HazardNotification_" + self.id] && self.controller.vdevInfo["HazardNotification_" + self.id].metrics ? self.controller.vdevInfo["HazardNotification_" + self.id].metrics : null, + icon = config.hazardType == "fire" ? "fire_ok.png" : "leakage_ok.png"; + + // create vDev + self.vDev = self.controller.devices.create({ + deviceId: "HazardNotification_" + self.id, + defaults: { + deviceType: 'sensorMultiline', + metrics: { + multilineType: 'protection', + title: self.getInstanceTitle(), + icon: '/ZAutomation/api/v1/load/modulemedia/HazardNotification/' + icon, + level: !!metr && metr.level ? metr.level : 'OK', + state: !!metr && metr.state ? metr.state : 'disarmed' + } + }, + overlay: { + metrics: { + title: self.getInstanceTitle(), + sensors: hazardSensorMetrics + } + }, + handler: function(command) { + var cutDevId = [], + cutIdNumbers = [], + nodId = []; + // arm + if (command === 'arm' && hazardSensorMetrics.length > 0) { + // set vDev state to armed + this.set('metrics:state', 'armed'); + + // remove polling + self.removePolling(); + + // set up arm mode + self.setupArmed(); + + self.triggerNotification('on'); + } + // disarm + if (command === 'disarm' && hazardSensorMetrics.length > 0) { + // set vDev state to disarmed + this.set('metrics:state', 'disarmed'); + + // set up cron handler checking for alert + if (self.getSensorLevels().indexOf('on') !== -1) { + + self.checkState(); + } else { + this.set('metrics:level', 'OK'); + this.set('metrics:icon', '/ZAutomation/api/v1/load/modulemedia/HazardNotification/' + icon); + } + + //stop sending notifications + //console.log('Disarmed. Stop sending notifications ...'); + + self.triggerNotification('off'); + + if (self.sendInterval) { + //console.log('Disarmed - Clear send ...'); + clearInterval(self.sendInterval); + self.sendInterval = undefined; + } + + // remove listener of sensor changes + filteredSensorsFromDevices.forEach(function(dev) { + self.controller.devices.off(dev.id, 'change:metrics:level', self.throwAlert); + }); + + //if ALERT send basic off to each water anf fire detector + if (this.get('metrics:level') === 'ALERT') { + //get correct node id + self.config.sensors.forEach(function(id) { + cutDevId = id.split('_'); + cutIdNumbers = cutDevId[2].split('-'); + + if (nodId.indexOf(cutIdNumbers[0]) === -1) { + nodId.push(cutIdNumbers[0]); + } + }); + + nodId.forEach(function(node) { + // send via z-way api + if (zway.devices[node].instances[0].commandClasses[32]) { + zway.devices[node].instances[0].commandClasses[32].Set(0); + } + }); + } + } + + //update + if (command === 'update' && hazardSensorMetrics.length > 0) { + filteredSensorsFromDevices.forEach(function(sensor) { + try { + sensor.performCommand('update'); + } catch (e) { + self.controller.addNotification('device-info', 'Update has failed. Error:' + e, 'device-status', sensor.id); + } + }); + } + }, + moduleId: self.id + }); + + // setup arm mode (reinitialize vDev) + if (self.vDev.get('metrics:state') === 'armed') { + self.setupArmed(); + } + + // refresh/create virtual device if sensors are created (after restart) + self.controller.devices.on('created', self.updateIfHazardSensorsAreCreated); + +}; + +HazardNotification.prototype.stop = function() { + var self = this; + + if (this.vDev) { + this.controller.devices.remove(this.vDev.id); + this.vDev = null; + } + + this.controller.devices.filter(function(dev) { + return self.config.sensors.indexOf(dev.id) > -1; + }).forEach(function(dev) { + self.controller.devices.off(dev.id, 'change:[object Object]', self.updateAttributes); + self.controller.devices.off(dev.id, 'change:metrics:level', self.throwAlert); + }); + + this.controller.devices.off('created', self.updateIfHazardSensorsAreCreated); + + // remove polling + this.removePolling(); + + HazardNotification.super_.prototype.stop.call(this); +}; + +// ---------------------------------------------------------------------------- +// --- Module methods +// ---------------------------------------------------------------------------- + +HazardNotification.prototype.getSensorLevels = function() { + var self = this; + + return self.vDev.get('metrics:sensors').map(function(sensor) { + return sensor.metrics.level; + }); +}; + +/*// compare old and new level to avoid unnecessary updates +HazardNotification.prototype.newValueNotEqualsOldValue = function (vDev, valNew) { + if (vDev && !!vDev) { + var devType = vDev.get('deviceType'), + vO = ''; + vN = _.isNaN(parseFloat(valNew)) ? valNew : parseFloat(valNew); + + switch (devType) { + case 'switchRGBW': + vO = typeof vN !== 'string' ? vDev.get('metrics:color') : vDev.get('metrics:level'); + + if (valNew !== 'string') { + return _.isEqual(vO, vN); + } + case 'switchControl': + if (_.contains(['on', 'off'], vN) || _.isNumber(vN)) { + vO = vDev.get('metrics:level'); + } else { + vO = vDev.get('metrics:change'); + } + default: + vO = vDev.get('metrics:level'); + } + return vO !== vN; + } +}; + +HazardNotification.prototype.executeActions = function (compareLevelsFirst, vDev, targetValue) { + return (!compareLevelsFirst || (compareLevelsFirst && this.newValueNotEqualsOldValue(vDev, targetValue))); +}; + +HazardNotification.prototype.setNewDeviceState = function (vDev, type, new_level) { + if (vDev && !!vDev) { + switch (type) { + case 'doorlock': + case 'switchBinary': + vDev.performCommand(new_level); + break; + case 'switchMultilevel': + case 'thermostat': + _.contains(['on', 'off'], new_level) ? vDev.performCommand(new_level) : vDev.performCommand("exact", { + level: new_level + }); + break; + case 'switchRGBW': + if (_.contains(["on", "off"], new_level)) { + vDev.performCommand(new_level); + } else { + vDev.performCommand("exact", { + red: new_level.red, + green: new_level.green, + blue: new_level.blue + }); + } + break; + case 'switchControl': + if (_.contains(["on", "off"], new_level)) { + vDev.performCommand(new_level); + } else if (_.contains(["upstart", "upstop", "downstart", "downstop"], new_level)) { + vDev.performCommand("exact", { + change: new_level + }); + } else { + vDev.performCommand("exact", { + level: new_level + }); + } + break; + case 'toggleButton': + vDev.performCommand('on'); + break; + default: + vDev.performCommand(new_level); + } + } +};*/ \ No newline at end of file diff --git a/modules/HazardNotification/lang/de.json b/modules/HazardNotification/lang/de.json new file mode 100644 index 000000000..94eaa1e4b --- /dev/null +++ b/modules/HazardNotification/lang/de.json @@ -0,0 +1,4 @@ +{ + "m_title": "Gefahrenbenachrichtigung", + "m_descr": "" +} \ No newline at end of file diff --git a/modules/HazardNotification/lang/en.json b/modules/HazardNotification/lang/en.json new file mode 100644 index 000000000..94187d9a2 --- /dev/null +++ b/modules/HazardNotification/lang/en.json @@ -0,0 +1,4 @@ +{ + "m_title": "Hazard Notification", + "m_descr": "" +} \ No newline at end of file diff --git a/modules/HazardNotification/module.json b/modules/HazardNotification/module.json new file mode 100644 index 000000000..a408885e0 --- /dev/null +++ b/modules/HazardNotification/module.json @@ -0,0 +1,26 @@ +{ + "dependencies": [], + "singleton": false, + "category": "system", + "author": "Z-Wave.Me", + "homepage": "http://razberry.z-wave.me", + "icon": "icon.png", + "moduleName": "HazardNotification", + "version": "1.0.0", + "maturity": "stable", + "repository": { + "type": "git", + "source": "https://github.com/Z-Wave-Me/home-automation" + }, + "defaults": { + "title": "__m_title__", + "description": "__m_descr__", + "sensors": [], + "triggerEvent": [], + "sendNotifications": [], + "notificationsInterval": 0, + "hazardType": "" + }, + "schema": {}, + "options": {} +} \ No newline at end of file diff --git a/modules/Heating/htdocs/heating_custom.png b/modules/Heating/htdocs/heating_custom.png new file mode 100644 index 000000000..c55874689 Binary files /dev/null and b/modules/Heating/htdocs/heating_custom.png differ diff --git a/modules/Heating/htdocs/icon.png b/modules/Heating/htdocs/icon.png new file mode 100644 index 000000000..0cc531705 Binary files /dev/null and b/modules/Heating/htdocs/icon.png differ diff --git a/modules/Heating/htdocs/js/postRender.js b/modules/Heating/htdocs/js/postRender.js new file mode 100644 index 000000000..7c68969d9 --- /dev/null +++ b/modules/Heating/htdocs/js/postRender.js @@ -0,0 +1,160 @@ +function modulePostRender(control) { + + var $tableRoom = $('.climateControlRoomSettings').find('table'), + $tableRoomHead = $tableRoom.find('thead tr'), + $tableSheduler = $('.climateControlSchedule').find('table'), + $tableShedulerHead = $tableSheduler.find('thead tr'); + + $(document).off('click', 'button[data-alpaca-array-actionbar-action="clone"]').on('click', 'button[data-alpaca-array-actionbar-action="clone"]', function(e) { + e.preventDefault(); + var $cloneButton = $(this); + var $addButton = $cloneButton.parent().find('button[data-alpaca-array-actionbar-action="add"]'); + + $addButton.on('click',function() { + + var $oldRow = $(this).parent().parent().parent(); + setTimeout(function(){ + var $newRow = $oldRow.next('tr'); + + var $oldInputs = $oldRow.find('input:text,input:checkbox, select'); + var $newInputs = $newRow.find('input:text,input:checkbox, select'); + + $newInputs.each(function(i,v) { + if ($(v).attr('type') == 'checkbox') { + $(v).prop('checked', $($oldInputs[i]).is(':checked')); + } + else { + $(v).val($($oldInputs[i]).val()); + } + + }); + + $addButton.off('click'); + },300); + }); + + $addButton.click(); + }); + + + if($tableRoomHead.children('th').length > 6) { + var tHeadlength = $tableRoomHead.children('th').length, + label = $tableRoomHead.find('th:nth-child(2)').text().split('_')[0], + visiblelength = tHeadlength-4; + + for(var i = visiblelength; i > 2; i--) { + $tableRoomHead.find('th:nth-child(' + i + ')').remove(); + } + $tableRoomHead.find('th:nth-child(2)').text(label); + } + + $tableRoomHead.hide(); + $tableShedulerHead.hide(); + + if($tableRoom.find('tbody tr').length >= 1) { + $tableRoomHead.show(); + $tableRoom.find('tbody tr').each(function(i, v) { + buildRow($(v)); + }); + } + + if($tableSheduler.find('tbody tr').length >= 1) { + $tableShedulerHead.show(); + } + + $('.climateControlRoomSettings').on('click', 'button[data-alpaca-array-toolbar-action=add]', function(event) { + $tableRoomHead.show(); + var toolbarTimer = setInterval(function() { + if($tableRoom.find('tbody tr').length >= 1) { + buildRow($tableRoom.find('tbody tr')); + + if(toolbarTimer) { + clearInterval(toolbarTimer); + } + } + }, 100); + }); + + $('.climateControlSchedule').on('click', 'button[data-alpaca-array-toolbar-action=add]', function(event) { + $tableShedulerHead.show(); + }); + + function buildRow($tr) { + var currRow = $tr.attr('data-alpaca-field-name'), + hideTd = [], + elCnt = $tr.find('td[data-alpaca-container-item-name^=' + currRow + '_devicesByRoom_]').length; + + $tr.find('td[data-alpaca-container-item-name^=' + currRow + '_devicesByRoom_]').each(function(i, v) { + $formGroup = $(v).find('select').closest('div'); + + if($formGroup.css('display') === 'none') { + hideTd.push($(v)); + } + }); + + if(elCnt === hideTd.length) { + $.each(hideTd, function(i, td) { + if(i === 0) { + td.show(); + } else { + td.hide(); + } + }); + } else { + $.each(hideTd, function(i, td) { + td.hide(); + }); + } + } + + $tableRoom.on('click', 'button', function(event) { + var $this = $(this), + action = $this.attr('data-alpaca-array-actionbar-action'), + tableRows = $tableRoom.find('tbody tr').length, + $tr = $this.closest('tr'); + + switch(action) { + case 'add': + var addTimer = setInterval(function () { + + if ($tableRoom.find('tbody tr').length === tableRows +1 && $tr.next('tr')) { + $tableRoom.find('tbody tr').each(function(i, v) { + buildRow($(v)); + }); + if (addTimer) { + clearInterval(addTimer); + } + } + }, 500); + break; + case 'remove': + $tr.remove(); + if($tableRoom.find('tbody tr').length < 1) { + $tableRoomHead.hide(); + $('.climateControlRoomSettings').find('button[data-alpaca-array-toolbar-action=add]').closest('div').show(); + } + break; + } + }); + + $tableRoom.on('change', 'select', function(event) { + var $this = $(this), + $tr = $this.closest('tr'); + + if($this.attr('name').split('_')[3] === 'room') { + var currRow = $tr.attr('data-alpaca-field-name'); + var roomId = $this.find('option:selected').val(); + if(roomId != '') { + $tr.find('td[data-alpaca-container-item-name^=' + currRow + '_devicesByRoom_]').each(function (i, v) { + if ($(v).attr('data-alpaca-container-item-name') === (currRow + '_devicesByRoom_' + roomId)) { + $(v).show(); + } else { + $(v).hide(); + } + }); + } else { + buildRow($tr); + } + } + }); +}; diff --git a/modules/Heating/htdocs/modal_de.png b/modules/Heating/htdocs/modal_de.png new file mode 100644 index 000000000..084f57201 Binary files /dev/null and b/modules/Heating/htdocs/modal_de.png differ diff --git a/modules/Heating/htdocs/modal_en.png b/modules/Heating/htdocs/modal_en.png new file mode 100644 index 000000000..dce51d897 Binary files /dev/null and b/modules/Heating/htdocs/modal_en.png differ diff --git a/modules/Heating/htdocs/widget_custom_de.png b/modules/Heating/htdocs/widget_custom_de.png new file mode 100644 index 000000000..3044f5971 Binary files /dev/null and b/modules/Heating/htdocs/widget_custom_de.png differ diff --git a/modules/Heating/htdocs/widget_custom_en.png b/modules/Heating/htdocs/widget_custom_en.png new file mode 100644 index 000000000..f9ba97b0c Binary files /dev/null and b/modules/Heating/htdocs/widget_custom_en.png differ diff --git a/modules/Heating/htdocs/widget_de.png b/modules/Heating/htdocs/widget_de.png new file mode 100644 index 000000000..8c636c000 Binary files /dev/null and b/modules/Heating/htdocs/widget_de.png differ diff --git a/modules/Heating/htdocs/widget_en.png b/modules/Heating/htdocs/widget_en.png new file mode 100644 index 000000000..aecf3ab96 Binary files /dev/null and b/modules/Heating/htdocs/widget_en.png differ diff --git a/modules/Heating/index.js b/modules/Heating/index.js new file mode 100644 index 000000000..cedae13ad --- /dev/null +++ b/modules/Heating/index.js @@ -0,0 +1,950 @@ +/*** Heating Z-Way HA module ******************************************* + + Version: 1.2.0 stable + (c) Z-Wave.Me, 2017 + ----------------------------------------------------------------------------- + Author: Niels Roche , + Martin Petzold , + Michael Hensche , + Karsten Reichel + Description: + This module creates a central heat control that can control all thermostats of a room + by defining a temperature sensor and a target temperature. + + ******************************************************************************/ + +// ---------------------------------------------------------------------------- +// --- Class definition, inheritance and setup +// ---------------------------------------------------------------------------- + +function Heating(id, controller) { + // Call superconstructor first (AutomationModule) + Heating.super_.call(this, id, controller); +} + +inherits(Heating, AutomationModule); + +_module = Heating; + +// ---------------------------------------------------------------------------- +// --- Module instance initialized +// ---------------------------------------------------------------------------- + +Heating.prototype.prepareSchedule = function(rooms) { + var self = this, + schedule = []; + _.each(rooms, function(data, roomID) { + + _.each(data.schedule, function(sc, day) { + + _.each(sc, function(s) { + // is there already some schedule with same start && end && temp ? -> just add day to list + sched = _.filter(schedule, function(entry) { + return entry.Starttime == s.stime && entry.Endtime == s.etime && entry.Temperature == s.temp; + }); + + if (!_.isEmpty(sched)) { + sched[0].Weekday.push(day); + } else { + // if there is some prevoius schedule, we maybe need to add some gap between both + sched = _.filter(schedule, function(entry) { + return entry.Weekday.indexOf(day) && (entry.Endtime == s.stime || entry.Starttime == s.etime); + }); + + startTime = s.stime; + endTime = s.etime; + + if (sched) { + if (startTime === sched.Endtime) { + console.log("startTime", startTime); + hours = parseInt(startTime.substr(0, 2), 10); + minutes = parseInt(startTime.substr(3, 2), 10); + + // add one minute + minutes++; + + if (minutes > 59) { + minutes = 0; + hours++; + if (hours > 23) { + hours = 0; + } + } + + if (minutes < 10) { + minutes = "0" + minutes; + } + + if (hours < 10) { + hours = "0" + hours; + } + startTime = hours + ":" + minutes; + } + if (endTime === s.Starttime) { + hours = parseInt(endTime.substr(0, 2), 10); + minutes = parseInt(endTime.substr(3, 2), 10); + + // remove one minute + minutes--; + + if (minutes < 0) { + minutes = 59; + hours--; + if (hours < 0) { + hours = 23; + } + } + + if (minutes < 10) { + minutes = "0" + minutes; + } + + if (hours < 10) { + hours = "0" + hours; + } + + endTime = hours + ":" + minutes; + } + } + + // add schedule + schedule.push({ + "RoomID": roomID, + "Room": "", + "Weekday": [day], + "Starttime": startTime, + "Endtime": endTime, + "Temperature": s.temp + }); + } + }); + + }); + + + }); + + return schedule; +}; + + + +Heating.prototype.init = function(config) { + Heating.super_.prototype.init.call(this, config); + var self = this; + self.fallbackOverTime = []; + //"room": _.findIndex(self.config.roomSettings,function(obj) {return obj === sc}), + this.newRooms = _.map(self.config.roomSettings, function(sc, roomId) { + return { + "room": roomId, + "comfort": sc.comfortTemp, + "energySave": sc.energySaveTemp, + "fallback": sc.fallbackTemp, + "mainSensor": sc.sensorId + } + }); + this.vDev = null; + this.alreadyChangedThermostats = []; + this.registerdSchedules = {}; + this.alarmTimer = {}; + this.fallbackThermostatSettings = {}; + this.langFile = self.controller.loadModuleLang("Heating"); + this.schedule = self.prepareSchedule(self.config.roomSettings); + this.waitingTime = self.config.resetTime * 1000 * 60 * 60; // convert in hours + self.initFunctions(); + this.createHouseControl(); + this.initialCCTurnON = function() { + self.activateCC = setTimeout(function() { + self.vDev.performCommand(self.vDev.get('metrics:state')); + }, 20000); + }; + + this.pollReset = function() { + var now = (new Date()).getTime(); + + //self.log("self.fallbackOverTime:", self.fallbackOverTime, true); + + Object.keys(self.resetList).forEach(function(resetEntry) { + var resetvDev = self.controller.devices.get(resetEntry); + + if (!!resetvDev) { + //self.log("time diff:", now - self.resetList[resetEntry]); + if (self.resetList[resetEntry] <= now) { + // check if a fallback level exists + entryExists = _.filter(self.fallbackOverTime, function(entry) { + return entry.id === resetvDev.id; + }); + + // set new level if values are not equal + if (entryExists[0] && (parseFloat(entryExists[0].temperature) !== resetvDev.get("metrics:level"))) { + // add thermostat to the module trigger array or change list + if (self.alreadyChangedThermostats.indexOf(resetvDev.id) < 0) { + self.alreadyChangedThermostats.push(resetvDev.id); + } + resetvDev.performCommand("exact", { + level: entryExists[0].temperature + }); + + delete self.resetList[resetEntry]; + } + } + } + }); + } + + this.controller.on("HeatingReset_" + this.id + ".poll", this.pollReset); + this.controller.emit("cron.addTask", "HeatingReset_" + this.id + ".poll", { + minute: null, + hour: null, + weekDay: null, + day: null, + month: null + }); + + this.triggerControl = function(vdev) { + var isThermostat = vdev.get('deviceType') === 'thermostat', + roomId = vdev.get('location'); + + if (isThermostat && roomId > 0) { + + var entryExists = _.filter(self.fallbackOverTime, function(entry) { + return entry.id === vdev.id; + }) + + if (entryExists[0]) { + entryExists[0].temperature = vdev.get('metrics:level'); + } else { + self.fallbackOverTime.push({ + id: vdev.id, + temperature: vdev.get('metrics:level') + }); + } + + //deregister thermostat + self.controller.devices.off(vdev.id, "change:metrics:level", self.sensorFunction); + //register thermostat + self.controller.devices.on(vdev.id, "change:metrics:level", self.sensorFunction); + } + } + + this.controller.devices.on('created', this.triggerControl); + this.controller.devices.forEach(this.triggerControl); + + // remove restart listener and timeouts + this.removeInitListener = setTimeout(function() { + + self.controller.off('core.start', self.initialCCTurnON); + + if (self.activateCC) { + clearTimeout(self.activateCC); + self.activateCC = null; + } + if (self.removeInitListener) { + clearTimeout(self.removeInitListener); + self.removeInitListener = null; + } + + }, 60000); // wait a minute*/ + + // restart app after server restart + this.controller.on('core.start', this.initialCCTurnON); +}; + +Heating.prototype.stop = function() { + var self = this; + self.newRooms.forEach(function(room) { + self.regTH('off', room.room); + }); + + var devID = "Heating_" + self.id; + + self.vDev = null; + + self.controller.devices.remove(devID); + + for (var key in self.registerdSchedules) { + self.registerdSchedules[key].forEach(function(pollEntry) { + self.controller.emit("cron.removeTask", pollEntry); + //console.log('remove task ...', self.registerdSchedules[key]); + if (key === 'start') { + self.controller.off(pollEntry, self.pollByStart); + } else { + self.controller.off(pollEntry, self.pollByEnd); + } + }); + + // clean registry + self.registerdSchedules[key] = []; + } + + if (self.activateCC) { + clearTimeout(self.activateCC); + self.activateCC = null; + } + + if (self.removeInitListener) { + clearTimeout(self.removeInitListener); + self.removeInitListener = null; + } + + this.controller.emit("cron.removeTask", "HeatingReset_" + this.id + ".poll"); + this.controller.off("HeatingReset_" + this.id + ".poll", this.pollReset); + + this.controller.off('core.start', self.initialCCTurnON); + + Heating.super_.prototype.stop.call(this); +}; + +// ---------------------------------------------------------------------------- +// --- Module methods +// ---------------------------------------------------------------------------- + +Heating.prototype.createHouseControl = function() { + + // use vdevinfo if it exists + var self = this, + vdevEntry = this.controller.vdevInfo["Heating_" + this.id] && this.controller.vdevInfo["Heating_" + this.id].metrics ? this.controller.vdevInfo["Heating_" + this.id].metrics : undefined; + + this.pollByStart = function(filter) { + + var pollIdentifier = this.event || filter, + identifierArr = pollIdentifier.split('.'), + locId = parseInt(identifierArr[1], 10), + schedulePreset = null, + thermostats = [], + metrRooms = []; + + /* + * identifierArr[1] ... room name + * identifierArr[7] ... '-' separeated temperature + * + */ + + schedulePreset = identifierArr[7] !== 'poll' ? identifierArr[7].replace('-', '.') : null; + + //get thermostats + thermostats = self.controller.devices.filter(function(device) { + return device.get('deviceType') === 'thermostat' && + device.get('location') === locId; + }); + + if (thermostats.length > 0) { + + // create fallback values + self.createFallbackThermostatSettings(locId, thermostats); + + if (self.vDev === null) { + self.vDev = self.controller.devices.get('Heating_' + self.id); + } + + metrRooms = self.vDev.get('metrics:rooms'); + + // update value in widget + metrRooms.forEach(function(room) { + if (parseInt(room.room, 10) === locId) { + + // get modus temperature + if (!!schedulePreset) { + switch (schedulePreset) { + case 'F': + temp = 6; + break; + case 'E': + temp = parseFloat(room.energySave); + break; + case 'C': + temp = room.comfort; + break; + default: + temp = schedulePreset; + } + } + + room.targetTemp = 't ~ ' + temp; + } + }); + + self.vDev.set('metrics:rooms', metrRooms); + + thermostats.forEach(function(thermostat) { + self.performChangesOnThermostats(thermostat, temp); + }); + } + }; + + this.pollByEnd = function() { + //console.log('do by end ...'); + + var pollIdentifier = this.event, + identifierArr = pollIdentifier.split('.'), + locId = parseInt(identifierArr[1], 10), + thermostats = [], + metrRooms = []; + + + /* + * identifierArr[1] ... room name + * identifierArr[7] ... '-' separeated temperature + * + */ + + //schedulePreset = identifierArr[7] !== 'poll'? identifierArr[7].replace('-', '.') : null; + + //get thermostats + thermostats = self.controller.devices.filter(function(device) { + return device.get('deviceType') === 'thermostat' && + device.get('location') === locId; + }); + + if (thermostats.length > 0) { + + metrRooms = self.vDev.get('metrics:rooms'); + + // get all rooms controlled by Heating app, and update them + // update value in widget + metrRooms.forEach(function(room) { + if (parseInt(room.room, 10) === locId) { + if (room.fallback) { + temp = room.fallback == 'F' ? 6 : (room.fallback == 'C' ? room.comfort : parseFloat(room.energySave)); + } else { + temp = parseFloat(room.energySave); + } + + room.targetTemp = temp; + } + }); + + self.vDev.set('metrics:rooms', metrRooms); + + thermostats.forEach(function(thermostat) { + self.performChangesOnThermostats(thermostat, temp); + }); + } + }; + + this.configureSchedules = function(roomId) { + var subString = 'Heating.' + roomId + '.', + tempSet = false; + + if (typeof self.registerdSchedules['start'] === 'undefined' || typeof self.registerdSchedules['end'] === 'undefined') + return false; + + var scheduleFilter = self.registerdSchedules['start'].concat(self.registerdSchedules['end']); + + scheduleFilter = scheduleFilter.filter(function(schedule) { + return ~schedule.indexOf(subString); + }); + + + + _.forEach(scheduleFilter, function(scheduleEntry) { + var scheduleItems = scheduleEntry.split('.'), + now = new Date(), + startStop = scheduleItems[4], + m = parseInt(scheduleItems[6], 10), + h = parseInt(scheduleItems[5], 10), + d = parseInt(scheduleItems[3], 10); + + self.controller.emit("cron.addTask", scheduleEntry, { + minute: m, + hour: h, + weekDay: d, + day: null, + month: null + }); + + if (startStop === 'start') { + self.controller.on(scheduleEntry, self.pollByStart); + } else if (startStop === 'end') { + self.controller.on(scheduleEntry, self.pollByEnd); + } + + /* + * filter for start /end - by index of or substring 'Heating.1.0.2.' + * check if start <= n < end + * check logic + * + * */ + if (now.getDay() === d) { + if (startStop === 'start') { + var nowTs = now.getTime(), + midnight = (new Date()).setHours(23, 59), + startI = startStop === 'start' ? (new Date()).setHours(h, m) : undefined, + endI = startStop === 'end' ? (new Date()).setHours(h, m) : undefined, + compareString = scheduleItems[0] + '.' + scheduleItems[1] + '.' + scheduleItems[2] + '.' + d; + + if (startStop === 'start') { + endI = getTime(scheduleFilter, compareString + '.end'); + } else { + startI = getTime(scheduleFilter, compareString + '.start'); + } + + // check if end is next day + if (startStop === 'start' && ((!endI && startI) || (startI && endI && endI < startI))) { + nextDay = d === 6 ? 0 : d + 1; + newCS = scheduleItems[0] + '.' + scheduleItems[1] + '.' + scheduleItems[2] + '.' + nextDay; + + endI = getTime(scheduleFilter, compareString + '.end') + 86400000; // add 24h + } + + if ((!startI && endI && nowTs < endI) || + (!endI && startI && startI <= midnight) || // if now is between start and end AND if end is on new day + (startI && endI && startI <= nowTs && nowTs < endI)) { // if now is between start and end + self.pollByStart(scheduleEntry); + tempSet = true; + } + } + } + }); + + return tempSet; + } + + function getTime(filterArray, compareString) { + var time = 0, + filter = []; + + filterArray.forEach(function(entry) { + if (entry.indexOf(compareString) > -1) { + filter = entry.split('.'); + time = (new Date().setHours(parseInt(filter[5], 10), parseInt(filter[6], 10))); + } + }); + + return time; + } + + self.vDev = self.controller.devices.create({ + deviceId: "Heating_" + this.id, + defaults: { + deviceType: "sensorMultiline", + metrics: { + multilineType: "climateControl", + icon: "climatecontrol", + rooms: self.newRooms + } + }, + overlay: { + metrics: { + multilineType: "climateControl", + title: self.getInstanceTitle(), + icon: "climatecontrol", + state: vdevEntry && vdevEntry.state ? vdevEntry.state : 'energySave', + rooms: self.newRooms + } + }, + /* + * commands: + * comfort ... set comfort temperature + * energySave ... set energy saving temperature + * frostProtection ... set frost protection temperature + * schedule ... activate configured schedule + * custom ... (only on Heating vDev) activate custom configurations + */ + handler: function(command, args) { + var argRoom = !args || args.room === "null" ? null : parseInt(args.room, 10), + currTemp = null, + roomCmd = command; + + // do commands for each room entry + self.newRooms.forEach(function(room, index) { + + var roomId = parseInt(room.room, 10); + + if (argRoom === null || argRoom === roomId) { + + // set custom configs if configured + if (argRoom === null && command === 'custom') { + roomCmd = room.state; + } + + var thermostats = self.getThermostats(roomId), + // set the current temperature depending on performed command + setCurrTemp = function(cmd) { + switch (cmd) { + case 'comfort': + currTemp = parseFloat(room.comfort); + break; + case 'energySave': + currTemp = parseFloat(room.energySave); + break; + case 'frostProtection': + currTemp = 6; // tbf frost protection check for lowest value of thermostat + break; + default: + currTemp = null; + } + }; + + // set current temperature + if (roomCmd !== 'schedule') { + setCurrTemp(roomCmd); + } + + // set thermostat temperature + if (!!currTemp) { + thermostats.forEach(function(device) { + self.performChangesOnThermostats(device, currTemp); + }); + + // update room target temperature + room.targetTemp = currTemp; + } + + // activate schedule by room or if comfort mode for all rooms is choosen + if ((argRoom === null && command === 'schedule' && room.state !== 'schedule') || (argRoom === null && command === 'custom' && roomCmd === 'schedule') || (!!argRoom && roomCmd === 'schedule')) { + // activate schedule + self.checkEntry(thermostats, room); + + if (!self.configureSchedules(roomId)) { + currTemp = parseFloat(room.energySave); + thermostats.forEach(function(device) { + self.performChangesOnThermostats(device, currTemp); + }); + + // update room target temperature + room.targetTemp = currTemp; + } + } + + // clean up schedules after revoking it by room or if a none 'comfort' mode for all rooms is choosen + if ((argRoom === null && self.vDev.get('metrics:state') === 'schedule' && command !== 'schedule') || (!!argRoom && room.state === 'schedule' && roomCmd !== 'schedule')) { + var subString = 'Heating.' + roomId + '.'; + + // delete all thermostat fallback setting for this room + if (self.fallbackThermostatSettings[roomId]) { + delete self.fallbackThermostatSettings[roomId]; + } + + for (var startStop in self.registerdSchedules) { + var newScheduleRegistry = []; + + // search in registry for all regsitered schedules that schould be removed from Cron + newScheduleRegistry = _.filter(self.registerdSchedules[startStop], function(schedule) { + return ~schedule.indexOf(subString); + }); + + // clean up registered schedules + self.registerdSchedules[startStop] = _.filter(self.registerdSchedules[startStop], function(schedule) { + return !~schedule.indexOf(subString); + }); + + // remove schedules from Cron + _.forEach(newScheduleRegistry, function(scheduleEntry) { + self.controller.emit("cron.removeTask", scheduleEntry); + if (startStop === 'start') { + self.controller.off(scheduleEntry, self.pollByStart); + } else if (startStop === 'end') { + self.controller.off(scheduleEntry, self.pollByEnd); + } + }); + } + } + + room.state = roomCmd; + } + }); + + if (!!argRoom) { + // set state to custom + this.set('metrics:state', 'custom'); + this.set('metrics:icon', '/ZAutomation/api/v1/load/modulemedia/Heating/heating_custom.png'); + } else { + this.set('metrics:state', command); + this.set('metrics:icon', command === 'custom' ? '/ZAutomation/api/v1/load/modulemedia/Heating/heating_custom.png' : 'climatecontrol'); + } + + //this.set('metrics:state', command); + + this.set('metrics:rooms', self.newRooms); + }, + moduleId: this.id + }); + + // handle room settings on instances start + self.newRooms.forEach(function(room, i) { + var roomId = parseInt(room.room, 10); + + // check for the stored state + room.state = vdevEntry && vdevEntry.rooms[i] && vdevEntry.rooms[i].state ? vdevEntry.rooms[i].state : "energySave"; + room.energySave = parseFloat(room.energySave); + room.targetTemp = vdevEntry && vdevEntry.rooms[i] && vdevEntry.rooms[i].targetTemp ? parseFloat(vdevEntry.rooms[i].targetTemp) : parseFloat(room.comfort); + + // activate schedule if exists + if (self.schedule) { + room.hasSchedule = _.findWhere(self.schedule, { + 'RoomID': roomId + }) ? true : false; + } else { + room.hasSchedule = false; + } + + + if (room.state === 'schedule') { + // activate schedule + var thermostats = self.getThermostats(roomId); + + // prepare schedule entries + self.checkEntry(thermostats, room); + + // set and activate schedule entries for rooms + self.configureSchedules(roomId); + } + + //deregister reset + self.regTH('off', room.room); + //register reset + self.regTH('on', room.room); + }); + + self.vDev.performCommand(self.vDev.get('metrics:state')); +}; + +Heating.prototype.performChangesOnThermostats = function(thermostat, temp) { + var self = this; + var entryExists = _.filter(self.fallbackOverTime, function(entry) { + return entry.id === thermostat.id; + }) + if (entryExists[0]) { + entryExists[0].temperature = temp; + } else { + self.fallbackOverTime.push({ + id: thermostat.id, + temperature: temp + }); + } + + // add thermostat to the module trigger array or change list + if (self.alreadyChangedThermostats.indexOf(thermostat.id) < 0) { + self.alreadyChangedThermostats.push(thermostat.id); + } + + // perform command on thermostat + thermostat.performCommand("exact", { + "level": String(temp) + }); +}; + +Heating.prototype.initFunctions = function() { + var self = this; + self.resetList = {}; + + self.sensorFunction = function sensorFunction(idev) { + if (self.vDev) { + // ignore devices listed on reset list + if (self.alreadyChangedThermostats.indexOf(idev.id) < 0) { + self.resetList[idev.id] = (new Date()).getTime() + self.waitingTime; + //self.log("Set reset time:", self.resetList[idev.id]); + + // remove thermostat from the module trigger array and reset reset timer if existing + } else { + if (self.alreadyChangedThermostats.indexOf(idev.id) > 0 && self.resetList[idev.id]) { + delete self.resetList[idev.id]; + } + self.alreadyChangedThermostats = _.filter(self.alreadyChangedThermostats, function(id) { + return id !== idev.id; + }); + } + } + } +}; + +/* + * un/register thermostats for the reset + */ +Heating.prototype.regTH = function(action, roomId) { + var self = this; + var roomId = parseInt(roomId, 10); + var thermostatsGL = this.getThermostats(roomId); + + thermostatsGL.forEach(function(device) { + self.controller.devices[action](device.id, "change:metrics:level", self.sensorFunction); + }); +}; + +/* + * checks schedule data content + * handles weekdays and checks for their validation + */ +Heating.prototype.checkEntry = function(thermostats, room) { + var self = this; + + if (_.isArray(thermostats) && thermostats.length > 0 && + self.schedule && self.schedule.length > 0) { + + var roomSchedules = self.schedule.filter(function(entry) { + return parseInt(entry.RoomID, 10) === parseInt(room.room, 10); + }); + + // create listeners for each schedule of the destinated room + roomSchedules.forEach(function(rSc, index) { + // check if there is a '1-3' string and create schedules for each day + if (_.isArray(rSc.Weekday) && rSc.Weekday.length > 0) { + rSc.Weekday.forEach(function(day) { + self.initializeSchedules(day, rSc, index); + }); + } else { + self.controller.addNotification('error', self.langFile.err_wrong_date_format, 'module', 'Heating'); + } + }); + + } else if (self.schedule && _.isArray(thermostats) && thermostats.length < 1) { + var thisRoom = self.controller.getLocation(self.controller.locations, room.room); + var roomName = thisRoom ? thisRoom.title : room.room; + self.controller.addNotification('warning', self.langFile.err_no_thermostats + roomName, 'module', 'Heating'); + } else { + self.controller.addNotification('warning', self.langFile.err_parsing_schedule_data, 'module', 'Heating'); + } +}; + +/* + * checks validation of time input + * and creates schedule identifiers for registry + */ +Heating.prototype.initializeSchedules = function(day, rSc, index) { + var self = this, + transformHour = function(hour) { + return hour === 24 ? 0 : hour; + }, + startHour = transformHour(parseInt(rSc.Starttime.substring(0, 2), 10)), + startMinute = parseInt(rSc.Starttime.substring(3, 5), 10), + endHour = transformHour(parseInt(rSc.Endtime.substring(0, 2), 10)), + endMinute = parseInt(rSc.Endtime.substring(3, 5), 10), + start = 0, + end = 0, + newDay = day === 6 ? 0 : (day + 1), + setStart = null, + setEnd = null, + tempOrModus = rSc.Temperature; + + if (tempOrModus !== '') { + + if (_.isNumber(tempOrModus) && (tempOrModus < 5 || tempOrModus > 29)) { + self.controller.addNotification('warning', self.langFile.err_temp_out_of_range, 'module', 'Heating'); + } else { + setStart = tempOrModus.toString(); + setEnd = null; + } + } else { + self.controller.addNotification('warning', self.langFile.err_temp_entry, 'module', 'Heating'); + } + + if ((startHour >= 0 && startHour < 24) && (endHour >= 0 && endHour < 24) && // check 0 >= hours < 24 + (startMinute >= 0 && startMinute < 60) && (endMinute >= 0 && endMinute < 60)) { // check 0 >= min < 60 + // check first if second time is on next day + if (startHour > endHour || (startHour === endHour && startMinute > endMinute)) { + // add cron start + self.createSchedule('start', startMinute, startHour, setStart, day, rSc.RoomID, index); + + // add cron stop on next day + self.createSchedule('end', endMinute, endHour, setEnd, newDay, rSc.RoomID, index); + + } else { + // add cron start + self.createSchedule('start', startMinute, startHour, setStart, day, rSc.RoomID, index); + + // add cron end + self.createSchedule('end', endMinute, endHour, setEnd, day, rSc.RoomID, index); + } + } else if ((startHour < 0 && startHour > 24) && (endHour < 0 && endHour > 24) && + (startMinute < 0 && startMinute > 60) && (endMinute < 0 && endMinute > 60)) { + + self.controller.addNotification('warning', self.langFile.err_wrong_time_format, 'module', 'Heating'); + } else { + + self.controller.addNotification('warning', self.langFile.err_something_went_wrong, 'module', 'Heating'); + } +}; + +/* + * adds schedule identifier to registry + * + */ +Heating.prototype.createSchedule = function(startStop, min, hour, setTempOrModus, weekDay, roomId, scheduleIndex) { + var temperature = !!setTempOrModus ? "." + setTempOrModus.replace(/\.|\,/ig, '-') : '', + pollIdentifier = "Heating." + roomId + "." + scheduleIndex + "." + weekDay + "." + startStop + "." + hour + "." + min + temperature + ".poll"; + + if (!this.registerdSchedules[startStop]) { + this.registerdSchedules[startStop] = []; + } + + // add identifier + if (!~this.registerdSchedules[startStop].indexOf(pollIdentifier)) { + this.registerdSchedules[startStop].push(pollIdentifier); + } +}; + +/* + * necessary after ending schedule or interval + * to restore all thermostats state + */ +Heating.prototype.createFallbackThermostatSettings = function(roomId, thermostatArr) { + var self = this; + + + _.forEach(thermostatArr, function(thermostat) { + var thermostatEntry = { + id: thermostat.id, + level: thermostat.get('metrics:level') + }, + entryExists = []; + + // if empty create new + if (!self.fallbackThermostatSettings[roomId]) { + self.fallbackThermostatSettings[roomId] = []; + + self.fallbackThermostatSettings[roomId].push(thermostatEntry); + } + + // check if entry exists + entryExists = _.filter(self.fallbackThermostatSettings[roomId], function(entry) { + return entry.id === thermostatEntry.id; + }); + + if (entryExists < 1) { + // add new one + self.fallbackThermostatSettings[roomId].push(thermostatEntry); + } else if (entryExists[0]) { + // update thermostat level + if (!_.isEqual(entryExists[0]['level'], thermostatEntry['level'])) { + entryExists[0]['level'] = thermostatEntry['level']; + } + } + }); +}; + +/* + * filter for all thermostats + * if surrendered also by room id + */ +Heating.prototype.getThermostats = function(roomId) { + var self = this; + // tbf - rrom id should be always present, if not, there is some misconfiguration + if (roomId) { + return self.controller.devices.filter(function(device) { + return device.get("deviceType") === "thermostat" && + parseInt(device.get("location"), 10) === roomId; + }); + } else { + return self.controller.devices.filter(function(device) { + return device.get("deviceType") === "thermostat"; + }); + } +}; + +Heating.prototype.log = function(message, value, stringify) { + var self = this; + if (stringify) { + console.log('##################################'); + console.log('##'); + console.log('##', message, JSON.stringify(value, null, 1)); + console.log('##'); + console.log('##################################'); + } else { + console.log('##################################'); + console.log('##'); + console.log('##', message, value); + console.log('##'); + console.log('##################################'); + } +}; \ No newline at end of file diff --git a/modules/Heating/lang/de.json b/modules/Heating/lang/de.json new file mode 100644 index 000000000..ba7a17847 --- /dev/null +++ b/modules/Heating/lang/de.json @@ -0,0 +1,35 @@ +{ + "C": "Komfort", + "E": "Energiespar", + "F": "Frostschutz", + "etimes": "Endzeit", + "friday_short": "Fr", + "h_resetTime": "in Stunden", + "l_table": "Raumeinstellungen", + "l_temperature_id": "Temperatur", + "l_times_id": "Zeitplan", + "m_comfort": "Komforttemp.", + "m_descr": "Die Heizungssteuerung ermöglicht die raumindividuelle Temperierung Ihres Heims.

Konfiguration

Raum Einstellungen
  • Fügen Sie die Räume hinzu, die von der Heizungssteuerung verwaltet werden sollen.
  • Pro Raum können Sie optional einen Temperatursensor auswählen, der später in der Übersicht aufgelistet werden soll.
  • Wählen Sie eine Komfort-Temperatur, die im Bereich zwischen 14 und 27 Grad liegt.
  • Wählen Sie eine Energiespar-Temperatur, welche im Energiesparmodus verwendet werden soll und die im Bereich zwischen 14 und 27 Grad liegt.
  • Abschließend wählen Sie eine Fallback-Temperatur, die verwendet wird, wenn die Anwendung gemäß dem Zeitplan arbeitet, aber für den aktuellen Zeitpunkt kein Eintrag vorhanden ist.
  • Verwenden Sie die Schaltfäche und um einen weiteren Eintrag hinzuzufügen bzw. einen bestehenden Eintrag zu löschen.
Zeitplan Einstellungen
  • Wählen Sie einen der zuvor definierten Räume und selektieren Sie die Wochentage und die Zeitstpanne, die durch den Zeitplan abgedeckt werden soll.
  • Anschließend wählen Sie die gewünschte Temperatur, indem Sie einen Wert zwischen 14 und 27 Grad oder einen der Modi Frostschutz, Energiesparen oder Komfort wählen.
  • Verwenden Sie die Schaltfäche und um einen weiteren Eintrag hinzuzufügen bzw. einen bestehenden Eintrag zu löschen.
  • Über die Schaltfäche haben Sie die Möglichkeit den aktuellen Eintrag zu duplizieren.
  • Beachten Sie, dass eine fehlerhafte Konfiguration zu undefiniertem Verhalten führen kann.
Erweiterte Einstellungen
  • Setzen Sie weiterhin die automatische Rücksetzzeit, indem Sie einen ganzzahligen Stundenwert X eingeben. Werden im laufenden Betrieb Änderungen an einzelnen Thermostaten vorgenommen, so werden diese nach X Stunden automatisch rückgängig gemacht, so dass das System wieder konform mit der Steuerung durch diese Anwendung arbeitet.

Handhabung

Die Anwendung generiert nach erfolgreicher Konfiguration ein eigenständiges Widget, welches die Steuerung der Heizungsmodi über vier Schaltfächen ermöglicht.

Von links nach rechts haben die Schaltfächen die Funktionen Frostschutz|Energiesparen|Komfort|Zeitgesteuert.
Über die Schaltfäche 'Räume' können Sie das Konfigurationsfenster mit der Auflistung der einzelnen Räume öffnen.

Wenn für den Raum ein Temperatursensor festgelegt wurde, liefert Ihnen die Übersicht den aktuellen Temperaturwert. Weiterhin wird der aktuell eingestellte Temperaturwert gelistet. Über das Dropdown-Menü haben Sie die Möglichkeit einen der vier Modi pro Raum zu setzen. Das folgende Icon zeigt Ihnen, dass die benutzerspezifische Einstellung pro Raum aktiv ist:
", + "m_energySave": "Energiespar-Temp.", + "m_fallback": "Fallback Temp.", + "m_mainSens": "Sensor", + "m_resetTime": "Automatisierte Rücksetzzeit", + "m_rooms": "Räume", + "m_setup": "Raumeinstellungen", + "m_title": "Heizungssteuerung", + "monday_short": "Mo", + "saturday_short": "Sa", + "select_room": "--- Raum auswählen ---", + "stimes": "Startzeit", + "sunday_short": "So", + "thursday_short": "Do", + "tuesday_short": "Di", + "wednesday_short": "Mi", + "err_wrong_time_format": "Falsches Zeitformat", + "err_something_went_wrong": "Interner Fehler", + "err_wrong_date_format": "Falsches Datumsformat", + "err_no_thermostats": "Keine Thermostate in Raum ", + "err_parsing_schedule_data": "Interner Fehler während dem einlesen des Zeitplans", + "err_temp_out_of_range": "Temperatur außerhalb des gültigen Bereichs", + "err_temp_entry": "Fehlerhafter Temperatureintrag" +} \ No newline at end of file diff --git a/modules/Heating/lang/en.json b/modules/Heating/lang/en.json new file mode 100644 index 000000000..b0433f103 --- /dev/null +++ b/modules/Heating/lang/en.json @@ -0,0 +1,35 @@ +{ + "C": "Comfort", + "E": "Saving", + "F": "Frost Protect", + "etimes": "End Time", + "friday_short": "Fr", + "h_resetTime": "in hours", + "l_table": "Add Room Settings", + "l_temperature_id": "Temperature", + "l_times_id": "Schedule", + "m_comfort": "Comfort Temp.", + "m_descr": "This climate control allows controlling the temperature in the whole home based on individual room settings.

Configuarion

Room Settings
  • Add the rooms that should be managed by the heating control.
  • You can optionally select a temperature sensor for each room. It will be listed in the overview described later.
  • Choose a comfort temperature range between 14 and 27 degrees.
  • Optionally select a temperature to be used in energy saving mode. If you do not make an adjustment, the temperature is obtained from the already set global setting.
  • Finally, select a fallback temperature that is used when the application is working according to the schedule, but no entry exists for the current time.
  • Use the button and to add another entry or delete an existing entry.
Schedule Settings
  • Select one of the previously defined rooms and select the day of the week and the time period to be covered by the schedule.
  • Then select the desired temperature by selecting a value between 14 and 27 degrees or one of the modes frost protection, energy saving or comfort.
  • Use the button and to add another entry or delete an existing entry.
  • The button allows you to duplicate the current entry.
  • Note that an incorrect configuration can lead to undefined behavior.
Advanced Settings
  • Set the automatic reset time by entering an integer value X. If changes are made to individual thermostats during application runtime, these are automatically reversed after X hours.

Usage

After successful configuration the application generates a separate widget, which allows the control of the heating modes via four buttons.

From the left to the right the buttons have the functions Frost Protection | Energy saving | Comfort | Time controlled.
Use the 'Rooms' button to open the configuration window with the list of the individual rooms.

If a temperature sensor has been defined for the room, the overview gives you the current temperature value. The currently set temperature is also listed. From the drop-down menu, you have the option to set one of the four modes per room. The following icon will show you that the custom configuration per room is active:
", + "m_energySave": "Saving Temp.", + "m_fallback": "Fallback Temp.", + "m_mainSens": "Temp. Sensor", + "m_resetTime": "Automation Resettime", + "m_rooms": "Room", + "m_setup": "Rooms", + "m_title": "Heating Control", + "monday_short": "Mo", + "saturday_short": "Sa", + "select_room": "--- Choose Room ---", + "stimes": "Start Time", + "sunday_short": "Su", + "thursday_short": "Th", + "tuesday_short": "Tu", + "wednesday_short": "We", + "err_wrong_time_format": "Wrong timeformat", + "err_something_went_wrong": "Internal Error", + "err_wrong_date_format": "Wrong dateformat", + "err_no_thermostats": "No thermostats found in room ", + "err_parsing_schedule_data": "Internal Error while parsing schedule", + "err_temp_out_of_range": "Temperature out of range", + "err_temp_entry": "Invalid temperature entry" +} \ No newline at end of file diff --git a/modules/Heating/module.json b/modules/Heating/module.json new file mode 100644 index 000000000..f27f8275c --- /dev/null +++ b/modules/Heating/module.json @@ -0,0 +1,25 @@ +{ + "dependencies": [ + "Cron" + ], + "singleton": false, + "category": "system", + "author": "Z-Wave.Me", + "homepage": "http://razberry.z-wave.me", + "icon": "icon.png", + "moduleName": "Heating", + "version": "1.0.0", + "maturity": "stable", + "repository": { + "type": "git", + "source": "https://github.com/Z-Wave-Me/home-automation" + }, + "defaults": { + "title": "__m_title__", + "description": "__m_descr__", + "roomSettings": {}, + "resetTime": 2 + }, + "schema": {}, + "options": {} +} \ No newline at end of file diff --git a/modules/IfThen/index.js b/modules/IfThen/index.js index b5ce223fe..c7a459f57 100755 --- a/modules/IfThen/index.js +++ b/modules/IfThen/index.js @@ -1,10 +1,11 @@ /*** IfThen Z-Way HA module ******************************************* -Version: 2.5.0 +Version: 2.5.1 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Niels Roche Author: Hans-Christian Göckeritz +Author: Karsten Reichel Description: Bind actions on one device to other devices or scenes ******************************************************************************/ @@ -51,7 +52,11 @@ IfThen.prototype.init = function (config) { break; } } - + + // handle 0 and 99 for dimmers as off/on + if (value === 99 && ifLevel === "on") value = "on"; + if (value === 0 && ifLevel === "off") value = "off"; + if(check || value === ifLevel || sDev.get('deviceType') === 'toggleButton'){ self.config.targets.forEach(function(el) { diff --git a/modules/IfThen/module.json b/modules/IfThen/module.json index 5a8b00207..51568f5e3 100755 --- a/modules/IfThen/module.json +++ b/modules/IfThen/module.json @@ -6,7 +6,7 @@ "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "IfThen", - "version": "2.5.0", + "version": "2.5.1", "maturity": "stable", "repository": { "type": "git", @@ -61,7 +61,6 @@ }, "status": { "type": "integer", - "minimum": 0, "required": true } } diff --git a/modules/IfThen/patchnotes.txt b/modules/IfThen/patchnotes.txt index 54209a3ee..03dd91d00 100755 --- a/modules/IfThen/patchnotes.txt +++ b/modules/IfThen/patchnotes.txt @@ -1,3 +1,5 @@ +v2.5.1 +- allow negativ values for multilevel sensors v2.5.0 - notification as part of If/Then v2.4.5 diff --git a/modules/LightScene/htdocs/icon.png b/modules/LightScene/htdocs/icon.png index c287dc0d1..557d9e7f1 100644 Binary files a/modules/LightScene/htdocs/icon.png and b/modules/LightScene/htdocs/icon.png differ diff --git a/modules/MailNotifier/index.js b/modules/MailNotifier/index.js index 17f635894..db6741f8d 100644 --- a/modules/MailNotifier/index.js +++ b/modules/MailNotifier/index.js @@ -49,7 +49,7 @@ MailNotifier.prototype.init = function (config) { //console.log(this.mail_to); this.message = config.mail_message; - this.collectMessages = 0; + this.collectMessages = []; var self = this; @@ -68,21 +68,24 @@ MailNotifier.prototype.init = function (config) { probeType: 'notification_email' }, handler: function(command, args) { + var mailObject = {}; var send_mail = false; if (command !== 'update') { if (send_mail |= command === "on") { - self.collectMessages++; - self.message = self.config.mail_message; - self.mail_to = self.config.mail_to_input === '' ? self.config.mail_to_select : self.config.mail_to_input; + //self.collectMessages++; + mailObject.message = self.config.mail_message; + mailObject.mail_to = self.config.mail_to_input === '' ? self.config.mail_to_select : self.config.mail_to_input; + self.collectMessages.push(mailObject); } else if (send_mail |= command === "send") { - self.collectMessages++; - self.message = args.message; - self.mail_to = args.mail_to; + //self.collectMessages++; + mailObject.message = args.message; + mailObject.mail_to = args.mail_to; + self.collectMessages.push(mailObject); } // TODO check for mail address - if (!self.mail_to || self.mail_to === '') { - self.addNotification('error', 'Missing receiver e-mail address. Please check your configuration in the following app instance: ' + self.config.title, 'module', 'MailNotifier'); + if (!mailObject.mail_to || mailObject.mail_to === '') { + self.addNotification('error', 'Missing receiver e-mail address. Please check your configuration in the following app instance: ' + self.config.title, 'module'); return; } @@ -112,7 +115,7 @@ MailNotifier.prototype.init = function (config) { var new_vDev = self.controller.devices.get(instance[0].moduleId + '_' + instance[0].id); if (instance[0].id === self.vDev.get('creatorId')) { - new_vDev.performCommand('send', {mail_to: notification.type, message: notification.message, subject: 'Z-Way Notification'}); + new_vDev.performCommand('send', {mail_to: notification.type, message: notification.message}); } } else { var def_instance = self.controller.instances.filter(function (instance){ @@ -122,7 +125,7 @@ MailNotifier.prototype.init = function (config) { var def_vDev = self.controller.devices.get(def_instance[0].moduleId + '_' + def_instance[0].id); if (def_instance[0].id === self.vDev.get('creatorId')) { - def_vDev.performCommand('send', {mail_to: notification.type, message: notification.message, subject: 'Z-Way Notification'}); + def_vDev.performCommand('send', {mail_to: notification.type, message: notification.message}); } } } @@ -152,11 +155,13 @@ MailNotifier.prototype.stop = function () { // ---------------------------------------------------------------------------- MailNotifier.prototype.sendSendMessageWithDelay = function () { - var self = this; + var self = this, + mailObject = {}; this.timer = setInterval( function() { - if (self.collectMessages > 0) { + if (self.collectMessages.length > 0) { + mailObject = self.collectMessages.shift(); http.request({ method: "POST", @@ -164,20 +169,18 @@ MailNotifier.prototype.sendSendMessageWithDelay = function () { async: true, data: { remote_id: self.remote_id, - mail_to: self.mail_to, + mail_to: mailObject.mail_to, subject: self.subject, - message: self.vDev.get('metrics:message') !== ''? self.vDev.get('metrics:message') : self.message, + message: self.vDev.get('metrics:message') !== ''? self.vDev.get('metrics:message') : mailObject.message, language: self.controller.defaultLang }, headers: { "Content-Type": "application/x-www-form-urlencoded" }, error: function(response) { - console.log("MailNotifier-ERROR: " + typeof response !== 'string'? JSON.stringify(response) : response); + self.addNotification('error', 'MailNotifier-ERROR: ' + (typeof response !== 'string'? JSON.stringify(response) : response), 'module'); } }); - - self.collectMessages--; } else { if (self.timer) { clearInterval(self.timer); diff --git a/modules/MobileAppSupport/index.js b/modules/MobileAppSupport/index.js index a989c4d89..baec2b0bc 100644 --- a/modules/MobileAppSupport/index.js +++ b/modules/MobileAppSupport/index.js @@ -7,19 +7,19 @@ * Changed: Michael Hensche * Changed: Marcel Kermer */ -function MobileAppSupport (id, controller) { +function MobileAppSupport(id, controller) { MobileAppSupport.super_.call(this, id, controller); var self = this; - self.DB_NAME = "MobileAppSupport"; - self.DB_TABLE_APP = "MobileAppSupport_App_v100"; // filename + self.DB_NAME = "MobileAppSupport"; + self.DB_TABLE_APP = "MobileAppSupport_App_v100"; // filename self.ANDROID = "android"; self.IOS = "ios"; self.FCM_ANDROID_TOKEN = "AAAA8nWqXJU:APA91bFZwwNpcfQRjjsmU6LMv0LhEw4XoqCqOhyINwJQI2BOGCoFQAK1PcbVy-W9jtnqb4f0DoH6yGSi3Opc_v9T6uDinCL373CMjorKZk8mCkK8CknxCA2JC2T5YiuEWnkz-6Ng2IPD"; - self.FCM_IOS_TOKEN = "AAAA8nWqXJU:APA91bE8we7hPUylPcvG_WsPD5_akKUnhvYMXnxO35grpj5XC7he-TnZ4eJT38JDEXwEUbxd1ad-tsn50uSb48kT1_X-WJUEXIfiJscMp750kaU_DzbVQyRul2OsYfW_5cmET7p_6xhf"; + self.FCM_IOS_TOKEN = "AAAA8nWqXJU:APA91bHLng5bi0STgF3ojBVsu-vMjM2ut7ywHjR1LIYHZWRLzWPMh1Fm6tGXNis6v3z7PF0NI9ouUiJO4YmMhWWTwWFBsjtXFsiuDYWDTJA7N82d6Ger9bNtL7dzjmk_s4iy6Egt7Qjc"; // stores object references of callback functions for removing event listener self.deviceUpdatesCallbackWrapper = {}; @@ -30,7 +30,7 @@ inherits(MobileAppSupport, AutomationModule); _module = MobileAppSupport; -MobileAppSupport.prototype.init = function (config) { +MobileAppSupport.prototype.init = function(config) { MobileAppSupport.super_.prototype.init.call(this, config); var self = this; @@ -52,7 +52,7 @@ MobileAppSupport.prototype.init = function (config) { icon: "/ZAutomation/api/v1/load/modulemedia/MobileAppSupport/icon.png" } }, - handler: function (command, args) { + handler: function(command, args) { /* * 1 - OK * 2 - Missing parameters @@ -62,22 +62,31 @@ MobileAppSupport.prototype.init = function (config) { if (args.token && args.hubId && args.title && args.os) { var app = self.generateApp(args.token, args.hubId, args.title, args.os); var status = self.registerApp(app); - if(status === 1) { + if (status === 1) { console.log("(Mobile App Support) App registered: " + app.title); // create virtual device for phone self.createMobileAppSupportPhone(app.token, app.hubId, app.title, app.os); - return { 'code': 1, 'message': 'OK' } - } else if(status === 0) { + return { + 'code': 1, + 'message': 'OK' + } + } else if (status === 0) { // update title self.updateApp(app); console.log("(Mobile App Support) App updated: " + app.title); - return { 'code': 1, 'message': 'OK' } + return { + 'code': 1, + 'message': 'OK' + } } } else { - return { 'code': 2, 'message': 'Error - missing parameter' } + return { + 'code': 2, + 'message': 'Error - missing parameter' + } } } else if (command === "updateActiveState") { if (args.token && args.active) { @@ -88,28 +97,46 @@ MobileAppSupport.prototype.init = function (config) { self.updateApp(app); console.log("(Mobile App Support) App active state updated: " + app.title); - return { 'code': 1, 'message': 'OK' } + return { + 'code': 1, + 'message': 'OK' + } } else { console.log("(Mobile App Support) Update active state: app doesn't exist"); - return { 'code': 3, 'message': "Update active state: app doesn't exist" } + return { + 'code': 3, + 'message': "Update active state: app doesn't exist" + } } } else { - return { 'code': 2, 'message': 'Error - missing parameter' } + return { + 'code': 2, + 'message': 'Error - missing parameter' + } } - } else if(command === "removeApp") { - if(args.token) { + } else if (command === "removeApp") { + if (args.token) { var app = self.removeApp(args.token); - if(app) { + if (app) { console.log('(Mobile App Support) App removed: ' + app.title); - return { 'code': 1, 'message': 'OK' } + return { + 'code': 1, + 'message': 'OK' + } } else { console.log('(Mobile App Support) Remove app failed: app not found'); - return { 'code': 3, 'message': "Remove app failed: app doesn't exist" } + return { + 'code': 3, + 'message': "Remove app failed: app doesn't exist" + } } } else { - return { 'code': 2, 'message': 'Error - missing parameter' } + return { + 'code': 2, + 'message': 'Error - missing parameter' + } } - } else if(command === "state") { + } else if (command === "state") { var app = loadObject(self.DB_TABLE_APP); return { @@ -119,7 +146,7 @@ MobileAppSupport.prototype.init = function (config) { 'app': app } } - } else if(command === "clearAll") { + } else if (command === "clearAll") { var appData = self.getAllApp(); appData.forEach(function(it) { @@ -127,15 +154,16 @@ MobileAppSupport.prototype.init = function (config) { console.log('(Mobile App Support) Remove all: ' + self.toStringApp(it)); }); - } else if(command === "clearOne") { + } else if (command === "clearOne") { /* remove phoneApp data */ self.getAllApp().forEach(function(it) { - if(it.title === args.title) { + if (it.title === args.title) { self.removeApp(it.token); } }); /* remove phone vDev */ self.controller.devices.remove(args.id); + self.controller.devices.remove(args.id + '-presence'); /* remove from current configuration page */ self.config.phones.table = self.config.phones.table.filter(function(p) { return p.phones_title !== args.title @@ -145,23 +173,23 @@ MobileAppSupport.prototype.init = function (config) { 'code': 1, 'message': 'OK' } - } else if(command === "setConnection") { + } else if (command === "setConnection") { console.log("setConnection", JSON.stringify(args)); var vDevId = 'Phone-' + args.name + '-0-presence', ret = { 'code': 1, - 'message': 'OK' + 'message': 'OK' } - - vDev = self.controller.devices.get(vDevId); - if(vDev) { + + vDev = self.controller.devices.get(vDevId); + if (vDev) { vDev.set('metrics:currentScene', args.connection); vDev.set('metrics:level', args.connection); } else { ret.code = 2; ret.message = "Error - Device not found"; - } + } return ret; } }, @@ -211,16 +239,18 @@ MobileAppSupport.prototype.init = function (config) { // wrap method with a function this.notificationUpdatesCallbackWrapper = function(notification) { // conditions for external notifications - if(notification.level === 'push.notification'){ + if (notification.level === 'push.notification') { var vDev = self.controller.devices.get(notification.type); if (vDev !== null) { - vDev.performCommand('alarm', {message: notification.message}); + vDev.performCommand('alarm', { + message: notification.message + }); } } else { var appData = self.getAllApp(); // push notification to configured devices - appData.forEach(function (it) { + appData.forEach(function(it) { if (it.active === "1") { console.log("(Mobile App Support) Notify listener (NotificationUpdate)"); @@ -258,98 +288,98 @@ MobileAppSupport.prototype.init = function (config) { this.collectMessages = []; if (config.devices) { - config.devices.forEach(function(device){ + config.devices.forEach(function(device) { var deviceId, level, message, comparator; - if (typeof device.dev_toggleButton !== 'undefined'){ + if (typeof device.dev_toggleButton !== 'undefined') { deviceId = device.dev_toggleButton.dev_select; level = device.dev_toggleButton.dev_logLevel; message = device.dev_toggleButton.dev_message; if (typeof device.dev_toggleButton.dev_matchValue !== 'undefined' && device.dev_toggleButton.dev_matchValue !== 'all') - comparator = "=='"+device.dev_toggleButton.dev_matchValue + "'"; + comparator = "=='" + device.dev_toggleButton.dev_matchValue + "'"; else comparator = null; - } else if (typeof device.dev_switchControl !== 'undefined'){ + } else if (typeof device.dev_switchControl !== 'undefined') { deviceId = device.dev_switchControl.dev_select; level = device.dev_switchControl.dev_logLevel; message = device.dev_switchControl.dev_message; if (typeof device.dev_switchControl.dev_matchValue !== 'undefined') { - if ((typeof device.dev_switchControl.dev_matchValue.dev_matchValueOperation !== 'undefined')&& + if ((typeof device.dev_switchControl.dev_matchValue.dev_matchValueOperation !== 'undefined') && (typeof device.dev_switchControl.dev_matchValue.dev_matchValueOperand !== 'undefined')) comparator = device.dev_switchControl.dev_matchValue.dev_matchValueOperation + device.dev_switchControl.dev_matchValue.dev_matchValueOperand; } else comparator = null; - } else if (typeof device.dev_switchBinary !== 'undefined'){ + } else if (typeof device.dev_switchBinary !== 'undefined') { deviceId = device.dev_switchBinary.dev_select; level = device.dev_switchBinary.dev_logLevel; message = device.dev_switchBinary.dev_message; if (typeof device.dev_switchBinary.dev_matchValue !== 'undefined' && device.dev_switchBinary.dev_matchValue !== 'all') - comparator = "=='"+device.dev_switchBinary.dev_matchValue + "'"; + comparator = "=='" + device.dev_switchBinary.dev_matchValue + "'"; else comparator = null; - } else if (typeof device.dev_switchMultilevel !== 'undefined'){ + } else if (typeof device.dev_switchMultilevel !== 'undefined') { deviceId = device.dev_switchMultilevel.dev_select; level = device.dev_switchMultilevel.dev_logLevel; message = device.dev_switchMultilevel.dev_message; if (typeof device.dev_switchMultilevel.dev_matchValue !== 'undefined') { - if ((typeof device.dev_switchMultilevel.dev_matchValue.dev_matchValueOperation !== 'undefined')&& + if ((typeof device.dev_switchMultilevel.dev_matchValue.dev_matchValueOperation !== 'undefined') && (typeof device.dev_switchMultilevel.dev_matchValue.dev_matchValueOperand !== 'undefined')) comparator = device.dev_switchMultilevel.dev_matchValue.dev_matchValueOperation + device.dev_switchMultilevel.dev_matchValue.dev_matchValueOperand; } else comparator = null; - } else if (typeof device.dev_sensorBinary !== 'undefined'){ + } else if (typeof device.dev_sensorBinary !== 'undefined') { deviceId = device.dev_sensorBinary.dev_select; level = device.dev_sensorBinary.dev_logLevel; message = device.dev_sensorBinary.dev_message; if (typeof device.dev_sensorBinary.dev_matchValue !== 'undefined' && device.dev_sensorBinary.dev_matchValue !== 'all') - comparator = "=='"+device.dev_sensorBinary.dev_matchValue + "'"; + comparator = "=='" + device.dev_sensorBinary.dev_matchValue + "'"; else comparator = null; - } else if (typeof device.dev_sensorMultilevel !== 'undefined'){ + } else if (typeof device.dev_sensorMultilevel !== 'undefined') { deviceId = device.dev_sensorMultilevel.dev_select; level = device.dev_sensorMultilevel.dev_logLevel; message = device.dev_sensorMultilevel.dev_message; if (typeof device.dev_sensorMultilevel.dev_matchValue !== 'undefined') { - if ((typeof device.dev_sensorMultilevel.dev_matchValue.dev_matchValueOperation !== 'undefined')&& + if ((typeof device.dev_sensorMultilevel.dev_matchValue.dev_matchValueOperation !== 'undefined') && (typeof device.dev_sensorMultilevel.dev_matchValue.dev_matchValueOperand !== 'undefined')) comparator = device.dev_sensorMultilevel.dev_matchValue.dev_matchValueOperation + device.dev_sensorMultilevel.dev_matchValue.dev_matchValueOperand; } else comparator = null; - } else if (typeof device.dev_sensorMultiline !== 'undefined'){ + } else if (typeof device.dev_sensorMultiline !== 'undefined') { deviceId = device.dev_sensorMultiline.dev_select; level = device.dev_sensorMultiline.dev_logLevel; message = device.dev_sensorMultiline.dev_message; if (typeof device.dev_sensorMultiline.dev_matchValue !== 'undefined') { - if ((typeof device.dev_sensorMultiline.dev_matchValue.dev_matchValueOperation !== 'undefined')&& + if ((typeof device.dev_sensorMultiline.dev_matchValue.dev_matchValueOperation !== 'undefined') && (typeof device.dev_sensorMultiline.dev_matchValue.dev_matchValueOperand !== 'undefined')) comparator = device.dev_sensorMultiline.dev_matchValue.dev_matchValueOperation + device.dev_sensorMultiline.dev_matchValue.dev_matchValueOperand; } else comparator = null; - } else if (typeof device.dev_fan !== 'undefined'){ + } else if (typeof device.dev_fan !== 'undefined') { deviceId = device.dev_fan.dev_select; level = device.dev_fan.dev_logLevel; message = device.dev_fan.dev_message; if (typeof device.dev_fan.dev_matchValue !== 'undefined') { - if ((typeof device.dev_fan.dev_matchValue.dev_matchValueOperation !== 'undefined')&& + if ((typeof device.dev_fan.dev_matchValue.dev_matchValueOperation !== 'undefined') && (typeof device.dev_fan.dev_matchValue.dev_matchValueOperand !== 'undefined')) comparator = device.dev_fan.dev_matchValue.dev_matchValueOperation + device.dev_fan.dev_matchValue.dev_matchValueOperand; } else comparator = null; - } else if (typeof device.dev_doorLock !== 'undefined'){ + } else if (typeof device.dev_doorLock !== 'undefined') { deviceId = device.dev_doorLock.dev_select; level = device.dev_doorLock.dev_logLevel; message = device.dev_doorLock.dev_message; if (typeof device.dev_doorLock.dev_matchValue !== 'undefined') { - if ((typeof device.dev_doorLock.dev_matchValue.dev_matchValueOperation !== 'undefined')&& + if ((typeof device.dev_doorLock.dev_matchValue.dev_matchValueOperation !== 'undefined') && (typeof device.dev_doorLock.dev_matchValue.dev_matchValueOperand !== 'undefined')) comparator = device.dev_doorLock.dev_matchValue.dev_matchValueOperation + device.dev_doorLock.dev_matchValue.dev_matchValueOperand; } else comparator = null; - } else if (typeof device.dev_thermostat !== 'undefined'){ + } else if (typeof device.dev_thermostat !== 'undefined') { deviceId = device.dev_thermostat.dev_select; level = device.dev_thermostat.dev_logLevel; message = device.dev_thermostat.dev_message; if (typeof device.dev_thermostat.dev_matchValue !== 'undefined') { - if ((typeof device.dev_thermostat.dev_matchValue.dev_matchValueOperation !== 'undefined')&& + if ((typeof device.dev_thermostat.dev_matchValue.dev_matchValueOperation !== 'undefined') && (typeof device.dev_thermostat.dev_matchValue.dev_matchValueOperand !== 'undefined')) comparator = device.dev_thermostat.dev_matchValue.dev_matchValueOperation + device.dev_thermostat.dev_matchValueOperand; } else @@ -381,7 +411,7 @@ MobileAppSupport.prototype.createMobileAppSupportPhone = function(deviceToken, h self.controller.devices.forEach(function(vDev) { var metrics = vDev.get("metrics"); if (metrics && metrics.deviceToken) { - if (vDev.id.indexOf("MobileAppSupportPhone") !==-1 && metrics.deviceToken !== deviceToken) { // same title and different device tokens -> other installation + if (vDev.id.indexOf("MobileAppSupportPhone") !== -1 && metrics.deviceToken !== deviceToken) { // same title and different device tokens -> other installation counter++; } else if (metrics.deviceToken === deviceToken) { // different device tokens mobileAppSupportPhoneExist = true; @@ -416,7 +446,7 @@ MobileAppSupport.prototype.createMobileAppSupportPhone = function(deviceToken, h os: os } }, - handler: function (command, args) { + handler: function(command, args) { if (command === "alarm") { var alarmMessage = args.message; var deviceToken = this.get("metrics").deviceToken; @@ -495,12 +525,19 @@ MobileAppSupport.prototype.createMobileAppSupportPhone = function(deviceToken, h /* Add device ID to MobileAppSupport instance */ var known_phone = false; - self.config.phones.table.forEach(function(phones) { - known_phone |= phones.phones_dev === vDev.deviceId; - }); - if(!known_phone) { - console.log('Add device to instance: ', vDev.deviceId); - self.config.phones.table.push({"phones_dev": vDev.deviceId, "phones_title": title}) + + if (self.config.phones && self.config.phones.table) { + self.config.phones.table.forEach(function(phones) { + known_phone |= phones.phones_dev === vDev.deviceId; + }); + + if (!known_phone) { + console.log('Add device to instance: ', vDev.deviceId); + self.config.phones.table.push({ + "phones_dev": vDev.deviceId, + "phones_title": title + }) + } } }; @@ -510,7 +547,7 @@ MobileAppSupport.prototype.createPresenceMobilePhone = function(title, counter) // create virtual device var vDev = self.controller.devices.create({ - deviceId: 'Phone-' + title + "-" + counter +'-presence', + deviceId: 'Phone-' + title + "-" + counter + '-presence', defaults: { deviceType: 'sensorDiscrete', metrics: { @@ -522,22 +559,22 @@ MobileAppSupport.prototype.createPresenceMobilePhone = function(title, counter) } }, overlay: {}, - handler: function (command, args) {}, + handler: function(command, args) {}, moduleId: self.id }); - if(vDev) { + if (vDev) { self.controller.devices.on(vDev.id, "change:metrics:currentScene", function(vDev) { - console.log("cahnge currentScene"); + console.log("change currentScene"); var state = vDev.get("metrics:currentScene"); - if(state === "LOCAL") { + if (state === "LOCAL") { vDev.set("metrics:icon", "/ZAutomation/api/v1/load/modulemedia/MobileAppSupport/phone_local.png"); - } else if(state === "REMOTE") { + } else if (state === "REMOTE") { vDev.set("metrics:icon", "/ZAutomation/api/v1/load/modulemedia/MobileAppSupport/phone_remote.png"); - } + } }); } -}; +}; MobileAppSupport.prototype.notifyListener = function(message, os) { var self = this; @@ -555,8 +592,8 @@ MobileAppSupport.prototype.notifyListener = function(message, os) { url: "https://fcm.googleapis.com/fcm/send", method: "POST", headers: { - 'Authorization': 'key=' + fcmToken, - 'Content-Type': 'application/json' + 'Authorization': 'key=' + fcmToken, + 'Content-Type': 'application/json' }, async: true, data: JSON.stringify(message), // message contains also device token (to: ...) @@ -576,23 +613,23 @@ MobileAppSupport.prototype.notifyListener = function(message, os) { } }; -MobileAppSupport.prototype.removeCallbacks = function () { +MobileAppSupport.prototype.removeCallbacks = function() { var self = this; // remove device updates callback - if(typeof self.deviceUpdatesCallbackWrapper === "function") { + if (typeof self.deviceUpdatesCallbackWrapper === "function") { self.controller.devices.off("change:metrics:level", self.deviceUpdatesCallbackWrapper); self.deviceUpdatesCallbackWrapper = {}; } // remove notification callback - if(typeof self.notificationUpdatesCallbackWrapper === "function") { + if (typeof self.notificationUpdatesCallbackWrapper === "function") { self.controller.off("notifications.push", self.notificationUpdatesCallbackWrapper); self.notificationUpdatesCallbackWrapper = {}; } }; -MobileAppSupport.prototype.stop = function () { +MobileAppSupport.prototype.stop = function() { var self = this; // remove websocket callbacks @@ -617,11 +654,11 @@ MobileAppSupport.prototype.stop = function () { * @param {Object} app - app instance * @return {Number} status - 0 item allready exist - 1 new item inserted */ -MobileAppSupport.prototype.registerApp = function (app) { +MobileAppSupport.prototype.registerApp = function(app) { var self = this; // load db-file - var tableApp= loadObject(self.DB_TABLE_APP); + var tableApp = loadObject(self.DB_TABLE_APP); // create db-file, if neccessary if (!tableApp) { @@ -633,8 +670,10 @@ MobileAppSupport.prototype.registerApp = function (app) { } // if an entry of app exist returns null - var found = _.findWhere(tableApp.data, {token: app.token}); // _.findWhere returns single object or undefined - if(found) { + var found = _.findWhere(tableApp.data, { + token: app.token + }); // _.findWhere returns single object or undefined + if (found) { return 0; } else { // add new item and store db-file @@ -650,7 +689,7 @@ MobileAppSupport.prototype.registerApp = function (app) { * @param {String} token * @return {Object} app or null */ -MobileAppSupport.prototype.getApp = function (token) { +MobileAppSupport.prototype.getApp = function(token) { var self = this; // load db-file @@ -662,8 +701,10 @@ MobileAppSupport.prototype.getApp = function (token) { } // if an entry of app exist returns null - var app = _.findWhere(tableApp.data, {token: token}); // _.findWhere returns single object or undefined - if(!app) { + var app = _.findWhere(tableApp.data, { + token: token + }); // _.findWhere returns single object or undefined + if (!app) { return null; } else { return app; @@ -675,7 +716,7 @@ MobileAppSupport.prototype.getApp = function (token) { * @param {Object} app app instance * @return {Number} status -1 db not found / 0 item not found / 1 item updated */ -MobileAppSupport.prototype.updateApp = function (app) { +MobileAppSupport.prototype.updateApp = function(app) { var self = this; // load db-file @@ -687,8 +728,10 @@ MobileAppSupport.prototype.updateApp = function (app) { } // if an entry of app exist returns null - var oldApp = _.findWhere(tableApp.data, {token: app.token}); // _.findWhere returns single object or undefined - if(!oldApp) { + var oldApp = _.findWhere(tableApp.data, { + token: app.token + }); // _.findWhere returns single object or undefined + if (!oldApp) { return 0; } else { // remove old item from array @@ -710,7 +753,7 @@ MobileAppSupport.prototype.updateApp = function (app) { * @param {String} token - token addresses the app * @return {Object} removed object or null if an error occours */ -MobileAppSupport.prototype.removeApp = function (token) { +MobileAppSupport.prototype.removeApp = function(token) { var self = this; // load db-file @@ -718,8 +761,10 @@ MobileAppSupport.prototype.removeApp = function (token) { // create db-file, if neccessary if (tableApp) { - var app = _.findWhere(tableApp.data, {token: token}); - if(app) { + var app = _.findWhere(tableApp.data, { + token: token + }); + if (app) { // remove from array tableApp.data = _.without(tableApp.data, _.findWhere(tableApp.data, app)); // save new array @@ -738,7 +783,7 @@ MobileAppSupport.prototype.removeApp = function (token) { * Returns an array of app from db-file * @return {Array} pure data, without db-structure */ -MobileAppSupport.prototype.getAllApp = function () { +MobileAppSupport.prototype.getAllApp = function() { var self = this; // load db-file @@ -760,19 +805,19 @@ MobileAppSupport.prototype.getAllApp = function () { * @param {Number} active - indicator for sending notifications * @return {Object} app */ -MobileAppSupport.prototype.generateApp = function (token, hubId, title, os) { +MobileAppSupport.prototype.generateApp = function(token, hubId, title, os) { var self = this; return { - 'token': token, - 'hubId': hubId, - 'title': title, - 'os': os, - 'active': "0", + 'token': token, + 'hubId': hubId, + 'title': title, + 'os': os, + 'active': "0", 'lastNotification': new Date(), - 'lastStatus': 'Unknown', - 'created': new Date(), - 'modified': new Date() + 'lastStatus': 'Unknown', + 'created': new Date(), + 'modified': new Date() } }; @@ -781,47 +826,49 @@ MobileAppSupport.prototype.generateApp = function (token, hubId, title, os) { * @param {Object} app - app instance * @return {String} string representation for app */ -MobileAppSupport.prototype.toStringApp = function (app) { +MobileAppSupport.prototype.toStringApp = function(app) { var self = this; - return "MobileAppSupport App" - + ":" + app.token - + ":" + app.hubId - + ":" + app.title - + ":" + app.active - + ":" + app.os - + ":" + app.lastNotification - + ":" + app.lastStatus - + ":" + app.created - + ":" + app.modified; + return "MobileAppSupport App" + + ":" + app.token + + ":" + app.hubId + + ":" + app.title + + ":" + app.active + + ":" + app.os + + ":" + app.lastNotification + + ":" + app.lastStatus + + ":" + app.created + + ":" + app.modified; }; /* * event forwarding */ -MobileAppSupport.prototype.onNotificationHandler = function () { +MobileAppSupport.prototype.onNotificationHandler = function() { var self = this; return function(notice) { - var sendMessage = false, deviceMessage = "", value; + var sendMessage = false, + deviceMessage = "", + value; if (self.logLevel.length > 0) { - self.logLevel.forEach(function (level) { - if (((level === "errors")&&((notice.level === "critical")||(notice.level === "error")))|| - ((level === "notifications")&&((notice.level === "notification")||(notice.level === "device-info")))|| - ((level === "warnings")&&(notice.level === "warning"))) { + self.logLevel.forEach(function(level) { + if (((level === "errors") && ((notice.level === "critical") || (notice.level === "error"))) || + ((level === "notifications") && ((notice.level === "notification") || (notice.level === "device-info"))) || + ((level === "warnings") && (notice.level === "warning"))) { sendMessage = true; } }); } - if ((!sendMessage)&&(self.devices.length > 0)) { - self.devices.forEach(function (device) { + if ((!sendMessage) && (self.devices.length > 0)) { + self.devices.forEach(function(device) { // additional condition: if device registered multiple with different comparators // only one matches ... if (notice.source === device.id && sendMessage === false) { - if (((device.level === "errors")&&((notice.level === "critical")||(notice.level === "error")))|| - ((device.level === "notifications")&&((notice.level === "notification")||(notice.level === "device-info")))|| - ((device.level === "warnings")&&(notice.level === "warning"))) { + if (((device.level === "errors") && ((notice.level === "critical") || (notice.level === "error"))) || + ((device.level === "notifications") && ((notice.level === "notification") || (notice.level === "device-info"))) || + ((device.level === "warnings") && (notice.level === "warning"))) { sendMessage = true; deviceMessage = device.message; if (device.comparator !== null) { @@ -831,7 +878,7 @@ MobileAppSupport.prototype.onNotificationHandler = function () { sendMessage = false; } } else { - if (eval(value+device.comparator) === false) { + if (eval(value + device.comparator) === false) { sendMessage = false; } } @@ -859,7 +906,7 @@ MobileAppSupport.prototype.onNotificationHandler = function () { }; }; -MobileAppSupport.prototype.sendPushMessage = function (notification) { +MobileAppSupport.prototype.sendPushMessage = function(notification) { var self = this; var appData = self.getAllApp(); @@ -894,15 +941,15 @@ MobileAppSupport.prototype.sendPushMessage = function (notification) { }); }; -MobileAppSupport.prototype.sendPushMessageWithDelay = function () { +MobileAppSupport.prototype.sendPushMessageWithDelay = function() { var self = this; - this.timer = setInterval( function() { + this.timer = setInterval(function() { if (self.collectMessages.length > 0) { var appData = self.getAllApp(); - var collectMessage = self.collectMessages.shift(); + var collectMessage = self.collectMessages.shift(); console.log("(Mobile App Support) Notify listener (EventForwarding): " + JSON.stringify(collectMessage)); appData.forEach(function(it) { @@ -939,4 +986,4 @@ MobileAppSupport.prototype.sendPushMessageWithDelay = function () { } } }, 500); -}; +}; \ No newline at end of file diff --git a/modules/MobileAppSupport/lang/de.json b/modules/MobileAppSupport/lang/de.json index 85070f3a1..8c22b474c 100644 --- a/modules/MobileAppSupport/lang/de.json +++ b/modules/MobileAppSupport/lang/de.json @@ -1,12 +1,16 @@ { "m_title":"Handy App Unterstützung", "m_descr":"Dieses Modul wird von der Handy App (Android, iOS) benötigt. Statusaktualisierungen aller Geräte und Ereignisse werden zur Laufzeit zum Handy gesendet. Zusätzliche Benachrichtigungen können konfiguriert werden, die im Hintergrund gesendet werden. Derzeit wird eine Event-Weiterleitung unterstützt (die gleiche Funktionalität wie bei dem Pushbullet Modul).", + "unselected": "--- Bitte auswählen ---", "errors": "Alle Fehlermeldungen", "warnings": "Alle Warnmeldungen", + "notifications": "Alle Benachrichtigungen", + "l_logLevelContainer": "Benachrichtigungsklassen für die Event-Weiterleitung", "l_logLevel": "Die Einstellungen weiter unten werden deaktiviert, sobald diese Optionen aktiviert werden. Also entweder alle Benachrichtigungen oder eine benutzerdefinierte Auswahl!", "h_logLevel": "Wählen Sie aus, über welche Art von Benachrichtigungen Sie informiert werden wollen.", + "l_devices": "Zusätzliche Geräte", "h_devices": "Wählen Sie hier weitere Geräte und jeweils die Benachrichtigungsklasse aus.", "d_devices": "Gerät", @@ -20,7 +24,7 @@ "h_dev_matchValueOperation": "Wählen Sie hier einen Operator, der beim Abgleich zwischen Geräte-Status und dem folgenden Vergleichswert verwendet werden soll.", "l_dev_matchValueOperand": "Wert", "h_dev_matchValueOperand": "Geben Sie hier einen numerischen Wert ein, mit dem der Geräte-Status verglichen werden soll. Eine Benachrichtigung wird nur bei einem erfolgreichen Vergleich gesendet.", - "filter_devices_toggleButton": "Schaltknöpfe", + "filter_devices_toggleButton": "Szenen", "filter_devices_switchControl": "Schaltersteuerungen", "filter_devices_switchBinary": "Binäre-Schalter", "filter_devices_switchMultilevel": "Multilevel-Schalter", @@ -30,7 +34,7 @@ "filter_devices_fan": "Ventilatoren", "filter_devices_doorLock": "Türschlösser", "filter_devices_thermostat": "Thermostate", - "l_ToggleButton": "Schaltknöpfe", + "l_ToggleButton": "Szenen", "l_switchControl": "Schaltersteuerungen", "l_switchBinary": "Binäre-Schalter", "l_switchMultilevel": "Multilevel-Schalter", @@ -40,7 +44,9 @@ "l_fan": "Ventilatoren", "l_doorLock": "Türschlösser", "l_thermostat": "Thermostate", + "l_phones": "Mobilgeräte", - "l_found_phones": "vorhandene Geräte", + "l_phones_id": "Geräte ID", + "l_phones_title": "Geräte Titel", "m_del_phone": "Wollen Sie das ausgewählte Mobilgerät wirklich aus Ihrer Konfiguration löschen?" } \ No newline at end of file diff --git a/modules/MobileAppSupport/lang/en.json b/modules/MobileAppSupport/lang/en.json index b6018582f..da69e62f6 100644 --- a/modules/MobileAppSupport/lang/en.json +++ b/modules/MobileAppSupport/lang/en.json @@ -1,13 +1,16 @@ { "m_title":"Mobile App Support", "m_descr":"This module is required by the mobile phone app (Android, iOS). State updates of all devices and events are send at runtime to the mobile phone. Additional notifications can be configured that will be send in background. Currently event forwarding is supported (the same logic as for pushbullet module).", + "unselected": "--- please select ---", "errors": "all error messages", "warnings": "all warnings", "notifications": "all notifications", + "l_logLevelContainer": "Notification classes for event forwarding", "l_logLevel": "The granular selection of the devices below is disabled when the corresponding option is activated. So either all devices or a detailed selection!", "h_logLevel": "Select, which kind of notifications you would be informed.", + "l_devices": "Additional devices for event forwarding", "h_devices": "Select additional devices and a notification-class for each of them.", "d_devices": "Device", @@ -21,7 +24,7 @@ "h_dev_matchValueOperation": "Select an operator that should be used to compare the device state with the following value.", "l_dev_matchValueOperand": "Value", "h_dev_matchValueOperand": "Enter a numeric value that should be compared to the device state. A notification is send if the comparison return true.", - "filter_devices_toggleButton": "Toggle Button", + "filter_devices_toggleButton": "Scenes", "filter_devices_switchControl": "Switch Control", "filter_devices_switchBinary": "Switch Binary", "filter_devices_switchMultilevel": "Switch Multilevel", @@ -31,7 +34,7 @@ "filter_devices_fan": "Fan", "filter_devices_doorLock": "Doorlock", "filter_devices_thermostat": "Thermostat", - "l_ToggleButton": "Toggle Button", + "l_ToggleButton": "Scenes", "l_switchControl": "Switch Control", "l_switchBinary": "Switch Binary", "l_switchMultilevel": "Switch Multilevel", @@ -41,7 +44,9 @@ "l_fan": "Fan", "l_doorLock": "Doorlock", "l_thermostat": "Thermostat", + "l_phones": "Mobile Devices", - "l_found_phones": "available mobile devices", + "l_phones_id": "Devices ID", + "l_phones_title": "Devices Title", "m_del_phone": "Do you really want to delete this mobile device from your current configuration?" -} +} \ No newline at end of file diff --git a/modules/MobileAppSupport/module.json b/modules/MobileAppSupport/module.json index bb87c42cb..883290cc7 100644 --- a/modules/MobileAppSupport/module.json +++ b/modules/MobileAppSupport/module.json @@ -1,1089 +1,1109 @@ { - "singleton": true, - "dependencies": [], - "category": "support_external_ui", - "author": "Marcel Kermer", - "homepage": "http://z-wave.me", - "icon": "icon.png", - "moduleName": "MobileAppSupport", - "version": "1.2.3", - "maturity": "stable", - "defaults": { - "title": "__m_title__", - "description": "__m_descr__", - "delete_phone":"__m_del_phone__", - "devices": [] - }, - "schema": { + "singleton": true, + "dependencies": [], + "category": "support_external_ui", + "author": "Marcel Kermer", + "homepage": "http://z-wave.me", + "icon": "icon.png", + "moduleName": "MobileAppSupport", + "version": "1.2.7", + "maturity": "stable", + "defaults": { + "title": "__m_title__", + "description": "__m_descr__", + "delete_phone":"__m_del_phone__", + "devices": [] + }, + "schema": { + "type": "object", + "properties": { + "logLevelContainer": { "type": "object", "properties": { - "logLevelContainer": { - "type": "object", - "properties": { - "logLevel": { - "type": "string", - "enum": [ - "errors", - "warnings" - ] + "logLevel": { + "type": "string", + "enum": [ + "errors", + "warnings" + ] + } + } + }, + "phones": { + "type": "object", + "$ref": "#/definitions/phoneList" + }, + "devices": { + "type": "array", + "items": { + "type": "object", + "properties": { + "dev_filter": { + "type": "string", + "enum": [ + "unselected", + "filter_devices_toggleButton", + "filter_devices_switchControl", + "filter_devices_switchBinary", + "filter_devices_switchMultilevel", + "filter_devices_sensorBinary", + "filter_devices_sensorMultilevel", + "filter_devices_sensorMultiline", + "filter_devices_fan", + "filter_devices_doorLock", + "filter_devices_thermostat" + ], + "required": true, + "title": "__d_namespaces__" + }, + "dev_toggleButton": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_toggleButton:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "dependencies": "dev_logLevel", + "type": "string", + "enum": [ + "all", + "on", + "off" + ], + "title": "__l_dev_matchValue__", + "required": true + } + } + }, + "dev_switchControl": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_switchControl:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings", + "notifications" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "type": "object", + "dependencies": "dev_logLevel", + "properties": { + "dev_matchValueOperation": { + "type": "string", + "enum": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "required": false, + "title": "__l_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "pattern": "[0-9]*", + "required": false + } + } + } + } + }, + "dev_switchBinary": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_switchBinary:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings", + "notifications" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "dependencies": "dev_logLevel", + "type": "string", + "enum": [ + "all", + "on", + "off" + ], + "title": "__l_dev_matchValue__", + "required": true + } + } + }, + "dev_switchMultilevel": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_switchMultilevel:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings", + "notifications" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "type": "object", + "dependencies": "dev_logLevel", + "properties": { + "dev_matchValueOperation": { + "type": "string", + "enum": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "required": false, + "title": "__l_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "pattern": "[0-9]*", + "required": false } + } } + } }, - "phones": { - "type": "object", - "$ref": "#/definitions/phoneList" + "dev_sensorBinary": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_sensorBinary:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings", + "notifications" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "dependencies": "dev_logLevel", + "type": "string", + "enum": [ + "all", + "on", + "off" + ], + "title": "__l_dev_matchValue__", + "required": true + } + } }, - "devices": { - "type": "array", - "items": { - "type": "object", - "properties": { - "dev_filter": { - "type": "string", - "enum": [ - "unselected", - "filter_devices_toggleButton", - "filter_devices_switchControl", - "filter_devices_switchBinary", - "filter_devices_switchMultilevel", - "filter_devices_sensorBinary", - "filter_devices_sensorMultilevel", - "filter_devices_sensorMultiline", - "filter_devices_fan", - "filter_devices_doorLock", - "filter_devices_thermostat" - ], - "required": true, - "title": "__d_namespaces__" - }, - "dev_toggleButton": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_toggleButton:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "dependencies": "dev_logLevel", - "type": "string", - "enum": [ - "all", - "on", - "off" - ], - "title": "__l_dev_matchValue__", - "required": true - } - } - }, - "dev_switchControl": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_switchControl:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "type": "object", - "dependencies": "dev_logLevel", - "properties": { - "dev_matchValueOperation": { - "type": "string", - "enum": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "required": false, - "title": "__l_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "pattern": "[0-9]*", - "required": false - } - } - } - } - }, - "dev_switchBinary": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_switchBinary:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "dependencies": "dev_logLevel", - "type": "string", - "enum": [ - "all", - "on", - "off" - ], - "title": "__l_dev_matchValue__", - "required": true - } - } - }, - "dev_switchMultilevel": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_switchMultilevel:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "type": "object", - "dependencies": "dev_logLevel", - "properties": { - "dev_matchValueOperation": { - "type": "string", - "enum": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "required": false, - "title": "__l_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "pattern": "[0-9]*", - "required": false - } - } - } - } - }, - "dev_sensorBinary": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_sensorBinary:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "dependencies": "dev_logLevel", - "type": "string", - "enum": [ - "all", - "on", - "off" - ], - "title": "__l_dev_matchValue__", - "required": true - } - } - }, - "dev_sensorMultilevel": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_sensorMultilevel:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "type": "object", - "dependencies": "dev_logLevel", - "properties": { - "dev_matchValueOperation": { - "type": "string", - "enum": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "required": false, - "title": "__l_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "pattern": "[0-9]*", - "required": false - } - } - } - } - }, - "dev_sensorMultiline": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_sensorMultiline:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "type": "object", - "dependencies": "dev_logLevel", - "properties": { - "dev_matchValueOperation": { - "type": "string", - "enum": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "required": false, - "title": "__l_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "pattern": "[0-9]*", - "required": false - } - } - } - } - }, - "dev_fan": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_fan:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "type": "object", - "dependencies": "dev_logLevel", - "properties": { - "dev_matchValueOperation": { - "type": "string", - "enum": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "required": false, - "title": "__l_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "pattern": "[0-9]*", - "required": false - } - } - } - } - }, - "dev_doorLock": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_doorlock:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "type": "object", - "dependencies": "dev_logLevel", - "properties": { - "dev_matchValueOperation": { - "type": "string", - "enum": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "required": false, - "title": "__l_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "pattern": "[0-9]*", - "required": false - } - } - } - } - }, - "dev_thermostat": { - "type": "object", - "dependencies": "dev_filter", - "properties": { - "dev_select": { - "field": "enum", - "datasource": "namespaces", - "enum": "namespaces:devices_thermostat:deviceId", - "required": true, - "title": "__d_devices__" - }, - "dev_logLevel": { - "type": "string", - "enum": [ - "unselected", - "errors", - "warnings" - ], - "required": true, - "title": "__d_devicesLogLevel__" - }, - "dev_message": { - "required": false - }, - "dev_matchValue": { - "type": "object", - "dependencies": "dev_logLevel", - "properties": { - "dev_matchValueOperation": { - "type": "string", - "enum": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "required": false, - "title": "__l_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "pattern": "[0-9]*", - "required": false - } - } - } - } - } + "dev_sensorMultilevel": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_sensorMultilevel:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings", + "notifications" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "type": "object", + "dependencies": "dev_logLevel", + "properties": { + "dev_matchValueOperation": { + "type": "string", + "enum": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "required": false, + "title": "__l_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "pattern": "[0-9]*", + "required": false } + } } - } - }, - "required": false, - "definitions": { - "phoneList": { - "type": "object", - "properties": { - "table": { - "type": "array", - "items": { - "type": "object", - "properties": { - "phones_dev": { - "type": "string", - "title": "__l_found_phones__", - "readonly": true - }, - "phones_title": { - "type": "string", - "readonly": true - } - } - } + } + }, + "dev_sensorMultiline": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_sensorMultiline:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings", + "notifications" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "type": "object", + "dependencies": "dev_logLevel", + "properties": { + "dev_matchValueOperation": { + "type": "string", + "enum": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "required": false, + "title": "__l_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "pattern": "[0-9]*", + "required": false } + } } + } + }, + "dev_fan": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_fan:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings", + "notifications" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "type": "object", + "dependencies": "dev_logLevel", + "properties": { + "dev_matchValueOperation": { + "type": "string", + "enum": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "required": false, + "title": "__l_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "pattern": "[0-9]*", + "required": false + } + } + } + } + }, + "dev_doorLock": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_doorlock:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings", + "notifications" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "type": "object", + "dependencies": "dev_logLevel", + "properties": { + "dev_matchValueOperation": { + "type": "string", + "enum": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "required": false, + "title": "__l_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "pattern": "[0-9]*", + "required": false + } + } + } + } + }, + "dev_thermostat": { + "type": "object", + "dependencies": "dev_filter", + "properties": { + "dev_select": { + "field": "enum", + "datasource": "namespaces", + "enum": "namespaces:devices_thermostat:deviceId", + "required": true, + "title": "__d_devices__" + }, + "dev_logLevel": { + "type": "string", + "enum": [ + "unselected", + "errors", + "warnings", + "notifications" + ], + "required": true, + "title": "__d_devicesLogLevel__" + }, + "dev_message": { + "required": false + }, + "dev_matchValue": { + "type": "object", + "dependencies": "dev_logLevel", + "properties": { + "dev_matchValueOperation": { + "type": "string", + "enum": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "required": false, + "title": "__l_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "pattern": "[0-9]*", + "required": false + } + } + } + } } + } } + } }, - "options": { + "required": false, + "definitions": { + "phoneList": { + "type": "object", + "properties": { + "table": { + "type": "array", + "items": { + "type": "object", + "properties": { + "phones_dev": { + "type": "string", + "title": "__l_phones_id__", + "readonly": true + }, + "phones_title": { + "type": "string", + "title": "__l_phones_title__", + "readonly": true + } + } + } + } + } + } + } + }, + "options": { + "fields": { + "logLevelContainer": { + "label": "__l_logLevelContainer__", "fields": { - "logLevelContainer": { - "label": "__l_logLevelContainer__", - "fields": { - "logLevel": { - "type": "checkbox", - "optionLabels": [ - "__errors__", - "__warnings__" - ], - "label": "__l_logLevel__", - "helper": "__h_logLevel__" + "logLevel": { + "type": "checkbox", + "optionLabels": [ + "__errors__", + "__warnings__" + ], + "label": "__l_logLevel__", + "helper": "__h_logLevel__" + } + } + }, + "phones": { + "label": "__l_phones__" + }, + "devices": { + "label": "__l_devices__", + "helper": "__h_devices__", + "items": { + "fields": { + "dev_filter": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__filter_devices_toggleButton__", + "__filter_devices_switchControl__", + "__filter_devices_switchBinary__", + "__filter_devices_switchMultilevel__", + "__filter_devices_sensorBinary__", + "__filter_devices_sensorMultilevel__", + "__filter_devices_sensorMultiline__", + "__filter_devices_fan__", + "__filter_devices_doorLock__", + "__filter_devices_thermostat__" + ] + }, + "dev_toggleButton": { + "label": "__l_ToggleButton__", + "dependencies": { + "dev_filter": "filter_devices_toggleButton" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_toggleButton:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "helper": "__h_dev_matchValue__", + "type": "select", + "optionLabels": [ + "All", + "On", + "Off" + ] + } + } + }, + "dev_switchControl": { + "label": "__l_switchControl__", + "dependencies": { + "dev_filter": "filter_devices_switchControl" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_switchControl:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "label": "__l_dev_matchValue__", + "fields": { + "dev_matchValueOperation": { + "type": "select", + "optionLabels": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "label": "__l_dev_matchValueOperation__", + "helper": "__h_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "label": "__l_dev_matchValueOperand__", + "helper": "__h_dev_matchValueOperand__" } + } } + } }, - "phones": { - "label": "__l_phones__" + "dev_switchBinary": { + "label": "__l_switchBinary__", + "dependencies": { + "dev_filter": "filter_devices_switchBinary" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_switchBinary:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "helper": "__h_dev_matchValue__", + "type": "select", + "optionLabels": [ + "All", + "On", + "Off" + ] + } + } }, - "devices": { - "label": "__l_devices__", - "helper": "__h_devices__", - "items": { - "fields": { - "dev_filter": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__filter_devices_toggleButton__", - "__filter_devices_switchControl__", - "__filter_devices_switchBinary__", - "__filter_devices_switchMultilevel__", - "__filter_devices_sensorBinary__", - "__filter_devices_sensorMultilevel__", - "__filter_devices_sensorMultiline__", - "__filter_devices_fan__", - "__filter_devices_doorLock__", - "__filter_devices_thermostat__" - ] - }, - "dev_toggleButton": { - "label": "__l_ToggleButton__", - "dependencies": { - "dev_filter": "filter_devices_toggleButton" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_toggleButton:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "helper": "__h_dev_matchValue__", - "type": "select", - "optionLabels": [ - "All", - "On", - "Off" - ] - } - } - }, - "dev_switchControl": { - "label": "__l_switchControl__", - "dependencies": { - "dev_filter": "filter_devices_switchControl" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_switchControl:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "label": "__l_dev_matchValue__", - "fields": { - "dev_matchValueOperation": { - "type": "select", - "optionLabels": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "label": "__l_dev_matchValueOperation__", - "helper": "__h_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "label": "__l_dev_matchValueOperand__", - "helper": "__h_dev_matchValueOperand__" - } - } - } - } - }, - "dev_switchBinary": { - "label": "__l_switchBinary__", - "dependencies": { - "dev_filter": "filter_devices_switchBinary" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_switchBinary:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "helper": "__h_dev_matchValue__", - "type": "select", - "optionLabels": [ - "All", - "On", - "Off" - ] - } - } - }, - "dev_switchMultilevel": { - "label": "__l_switchMultilevel__", - "dependencies": { - "dev_filter": "filter_devices_switchMultilevel" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_switchMultilevel:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "label": "__l_dev_matchValue__", - "fields": { - "dev_matchValueOperation": { - "type": "select", - "optionLabels": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "label": "__l_dev_matchValueOperation__", - "helper": "__h_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "label": "__l_dev_matchValueOperand__", - "helper": "__h_dev_matchValueOperand__" - } - } - } - } - }, - "dev_sensorBinary": { - "label": "__l_sensorBinary__", - "dependencies": { - "dev_filter": "filter_devices_sensorBinary" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_sensorBinary:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "helper": "__h_dev_matchValue__", - "type": "select", - "optionLabels": [ - "All", - "On", - "Off" - ] - } - } - }, - "dev_sensorMultilevel": { - "label": "__l_sensorMultilevel__", - "dependencies": { - "dev_filter": "filter_devices_sensorMultilevel" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_sensorMultilevel:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "label": "__l_dev_matchValue__", - "fields": { - "dev_matchValueOperation": { - "type": "select", - "optionLabels": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "label": "__l_dev_matchValueOperation__", - "helper": "__h_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "label": "__l_dev_matchValueOperand__", - "helper": "__h_dev_matchValueOperand__" - } - } - } - } - }, - "dev_sensorMultiline": { - "label": "__l_sensorMultiline__", - "dependencies": { - "dev_filter": "filter_devices_sensorMultiline" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_sensorMultiline:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "label": "__l_dev_matchValue__", - "fields": { - "dev_matchValueOperation": { - "type": "select", - "optionLabels": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "label": "__l_dev_matchValueOperation__", - "helper": "__h_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "label": "__l_dev_matchValueOperand__", - "helper": "__h_dev_matchValueOperand__" - } - } - } - } - }, - "dev_fan": { - "label": "__l_fan__", - "dependencies": { - "dev_filter": "filter_devices_fan" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_fan:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "label": "__l_dev_matchValue__", - "fields": { - "dev_matchValueOperation": { - "type": "select", - "optionLabels": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "label": "__l_dev_matchValueOperation__", - "helper": "__h_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "label": "__l_dev_matchValueOperand__", - "helper": "__h_dev_matchValueOperand__" - } - } - } - } - }, - "dev_doorLock": { - "label": "__l_doorLock__", - "dependencies": { - "dev_filter": "filter_devices_doorLock" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_doorlock:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "label": "__l_dev_matchValue__", - "fields": { - "dev_matchValueOperation": { - "type": "select", - "optionLabels": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "label": "__l_dev_matchValueOperation__", - "helper": "__h_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "label": "__l_dev_matchValueOperand__", - "helper": "__h_dev_matchValueOperand__" - } - } - } - } - }, - "dev_thermostat": { - "label": "__l_thermostat__", - "dependencies": { - "dev_filter": "filter_devices_thermostat" - }, - "fields": { - "dev_select": { - "type": "select", - "datasource": "namespaces", - "field": "optionLabels", - "optionLabels": "namespaces:devices_thermostat:deviceName" - }, - "dev_logLevel": { - "type": "select", - "optionLabels": [ - "__unselected__", - "__errors__", - "__warnings__" - ] - }, - "dev_message": { - "label": "__l_dev_message__", - "helper": "__h_dev_message__" - }, - "dev_matchValue": { - "dependencies": { - "dev_logLevel": "notifications" - }, - "label": "__l_dev_matchValue__", - "fields": { - "dev_matchValueOperation": { - "type": "select", - "optionLabels": [ - "<", - ">", - "=", - "<=", - ">=" - ], - "label": "__l_dev_matchValueOperation__", - "helper": "__h_dev_matchValueOperation__" - }, - "dev_matchValueOperand": { - "label": "__l_dev_matchValueOperand__", - "helper": "__h_dev_matchValueOperand__" - } - } - } - } - } + "dev_switchMultilevel": { + "label": "__l_switchMultilevel__", + "dependencies": { + "dev_filter": "filter_devices_switchMultilevel" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_switchMultilevel:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "label": "__l_dev_matchValue__", + "fields": { + "dev_matchValueOperation": { + "type": "select", + "optionLabels": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "label": "__l_dev_matchValueOperation__", + "helper": "__h_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "label": "__l_dev_matchValueOperand__", + "helper": "__h_dev_matchValueOperand__" } + } } - } - }, - "definitions": { - "phoneList": { - "fields": { - "table": { - "type": "table", - "fieldClass": "phoneTable", - "actionbar": { - "actions": [ - { - "action": "add", - "enabled": false - }, - { - "action": "remove", - "enabled": false - }, - { - "action": "delete", - "iconClass": "fa fa-trash-o", - "enabled": true - }, - { - "action": "up", - "enabled": false - }, - { - "action": "down", - "enabled": false - } - ] - }, - "items": { - "fields": { - "phones_dev": { - }, - "phones_title": { - } - } - } + } + }, + "dev_sensorBinary": { + "label": "__l_sensorBinary__", + "dependencies": { + "dev_filter": "filter_devices_sensorBinary" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_sensorBinary:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "helper": "__h_dev_matchValue__", + "type": "select", + "optionLabels": [ + "All", + "On", + "Off" + ] + } + } + }, + "dev_sensorMultilevel": { + "label": "__l_sensorMultilevel__", + "dependencies": { + "dev_filter": "filter_devices_sensorMultilevel" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_sensorMultilevel:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "label": "__l_dev_matchValue__", + "fields": { + "dev_matchValueOperation": { + "type": "select", + "optionLabels": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "label": "__l_dev_matchValueOperation__", + "helper": "__h_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "label": "__l_dev_matchValueOperand__", + "helper": "__h_dev_matchValueOperand__" } + } } + } + }, + "dev_sensorMultiline": { + "label": "__l_sensorMultiline__", + "dependencies": { + "dev_filter": "filter_devices_sensorMultiline" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_sensorMultiline:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "label": "__l_dev_matchValue__", + "fields": { + "dev_matchValueOperation": { + "type": "select", + "optionLabels": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "label": "__l_dev_matchValueOperation__", + "helper": "__h_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "label": "__l_dev_matchValueOperand__", + "helper": "__h_dev_matchValueOperand__" + } + } + } + } + }, + "dev_fan": { + "label": "__l_fan__", + "dependencies": { + "dev_filter": "filter_devices_fan" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_fan:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "label": "__l_dev_matchValue__", + "fields": { + "dev_matchValueOperation": { + "type": "select", + "optionLabels": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "label": "__l_dev_matchValueOperation__", + "helper": "__h_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "label": "__l_dev_matchValueOperand__", + "helper": "__h_dev_matchValueOperand__" + } + } + } + } + }, + "dev_doorLock": { + "label": "__l_doorLock__", + "dependencies": { + "dev_filter": "filter_devices_doorLock" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_doorlock:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "label": "__l_dev_matchValue__", + "fields": { + "dev_matchValueOperation": { + "type": "select", + "optionLabels": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "label": "__l_dev_matchValueOperation__", + "helper": "__h_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "label": "__l_dev_matchValueOperand__", + "helper": "__h_dev_matchValueOperand__" + } + } + } + } + }, + "dev_thermostat": { + "label": "__l_thermostat__", + "dependencies": { + "dev_filter": "filter_devices_thermostat" + }, + "fields": { + "dev_select": { + "type": "select", + "datasource": "namespaces", + "field": "optionLabels", + "optionLabels": "namespaces:devices_thermostat:deviceName" + }, + "dev_logLevel": { + "type": "select", + "optionLabels": [ + "__unselected__", + "__errors__", + "__warnings__", + "__notifications__" + ] + }, + "dev_message": { + "label": "__l_dev_message__", + "helper": "__h_dev_message__" + }, + "dev_matchValue": { + "dependencies": { + "dev_logLevel": "notifications" + }, + "label": "__l_dev_matchValue__", + "fields": { + "dev_matchValueOperation": { + "type": "select", + "optionLabels": [ + "<", + ">", + "=", + "<=", + ">=" + ], + "label": "__l_dev_matchValueOperation__", + "helper": "__h_dev_matchValueOperation__" + }, + "dev_matchValueOperand": { + "label": "__l_dev_matchValueOperand__", + "helper": "__h_dev_matchValueOperand__" + } + } + } + } } + } } + } }, - "postRender": "loadFunction:postRender.js" + "definitions": { + "phoneList": { + "fields": { + "table": { + "type": "table", + "fieldClass": "phoneTable", + "actionbar": { + "actions": [ + { + "action": "add", + "enabled": false + }, + { + "action": "remove", + "enabled": false + }, + { + "action": "delete", + "iconClass": "fa fa-trash-o", + "enabled": true + }, + { + "action": "up", + "enabled": false + }, + { + "action": "down", + "enabled": false + } + ] + }, + "items": { + "fields": { + "phones_dev": { + }, + "phones_title": { + } + } + } + } + } + } + } + }, + "postRender": "loadFunction:postRender.js" } diff --git a/modules/MobileAppSupport/patchnotes.txt b/modules/MobileAppSupport/patchnotes.txt index 219e47ed9..4aea58c15 100644 --- a/modules/MobileAppSupport/patchnotes.txt +++ b/modules/MobileAppSupport/patchnotes.txt @@ -1,3 +1,16 @@ +v1.2.7 +- configured devices for event forwarding now available to send "all notifications" + +v1.2.6 +- change table headings for "Mobile Devices" + +v1.2.5 +- iOS authentication token updated + +v1.2.4 +- bugfix catches undefined-error occurred during adding first mobile device +- bugfix removing mobile device deletes LOCAL widget + v1.2.2 - add presence widget for LOCAL(mobile phone WLAN) or REMOTE (mobile phone mobile) diff --git a/modules/NotificationSMSru/index.js b/modules/NotificationSMSru/index.js index 1edbfe4cf..8813b160f 100644 --- a/modules/NotificationSMSru/index.js +++ b/modules/NotificationSMSru/index.js @@ -1,11 +1,12 @@ /*** NotificationSMSru Z-Way HA module ******************************************* -Version: 1.1.0 +Version: 1.2.0 (c) Z-Wave.Me, 2014 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: This module allows to send notifications via SMS.ru proxy. + Also creates scene to send specific messages. ******************************************************************************/ @@ -29,6 +30,9 @@ _module = NotificationSMSru; NotificationSMSru.prototype.init = function (config) { NotificationSMSru.super_.prototype.init.call(this, config); + var self = this; + + // handle automatic notifications send this.handler = this.onNotificationHandler(config); this.api_key = config.api_key.toString(); @@ -36,12 +40,53 @@ NotificationSMSru.prototype.init = function (config) { this.prefix = config.prefix.toString(); this.controller.on('notifications.push', this.handler); + + // handle manual send + this.vDev = this.controller.devices.create({ + deviceId: "NotificationSMSru_" + this.id, + defaults: { + metrics: { + level: 'on', + title: self.getInstanceTitle(this.id), + icon: "/ZAutomation/api/v1/load/modulemedia/NotificationSMSru/icon.png", + message: "" + } + }, + overlay: { + deviceType: 'toggleButton' + }, + handler: function(command, args) { + if (command === 'on' && args.message) { + http.request({ + method: 'POST', + url: "http://sms.ru/sms/send", + data: { + api_id: self.api_key, + to: self.phone, + text: self.prefix + " " + args.message + } + }); + } + }, + moduleId: this.id + }); + + if (config.hide === true) { + this.vDev.set('visibility', false, { silent: true }); + } else { + this.vDev.set('visibility', true, { silent: true }); + } }; NotificationSMSru.prototype.stop = function () { NotificationSMSru.super_.prototype.stop.call(this); this.controller.off('notifications.push', this.handler); + + if (this.vDev) { + this.controller.devices.remove(this.vDev.id); + this.vDev = null; + } }; // ---------------------------------------------------------------------------- diff --git a/modules/NotificationSMSru/lang/de.json b/modules/NotificationSMSru/lang/de.json index 9a711f478..b97ad1b7b 100644 --- a/modules/NotificationSMSru/lang/de.json +++ b/modules/NotificationSMSru/lang/de.json @@ -7,6 +7,7 @@ "l_prefix":"Präfix für alle Benachrichtigungen:", "l_api_key":"API-Key von SMS.ru", "h_api_key":"Nach der Registrierung auf SMS.ru finden Sie auf der rechten Seite die benötigte 'api_id' (z.B. a43f6376-f57d-27e4-5981-ec48945d871f)", + "rl_hidden":"Blende das erzeugte Widget in der Elementübersicht aus.", "critical":"Critical", "error":"Error", "warning":"Warning", diff --git a/modules/NotificationSMSru/lang/en.json b/modules/NotificationSMSru/lang/en.json index 673b41980..c15546319 100644 --- a/modules/NotificationSMSru/lang/en.json +++ b/modules/NotificationSMSru/lang/en.json @@ -7,6 +7,7 @@ "l_prefix":"Prefix for all messages", "l_api_key":"API key from SMS.ru", "h_api_key":"After registration on the right you will see api_id (looks like a43f6376-f57d-27e4-5981-ec48945d871f)", + "rl_hidden":"Hide the created widget on elements view.", "critical":"Critical", "error":"Error", "warning":"Warning", diff --git a/modules/NotificationSMSru/lang/ru.json b/modules/NotificationSMSru/lang/ru.json index 270fa5190..45c53bfb7 100644 --- a/modules/NotificationSMSru/lang/ru.json +++ b/modules/NotificationSMSru/lang/ru.json @@ -7,6 +7,7 @@ "l_prefix":"Префикс для всех сообщений", "l_api_key":"API ключ с сайта SMS.ru", "h_api_key":"После регистрации телефона на SMS.ru в правой колонке появится ключ api_id (пример: a43f6376-f57d-27e4-5981-ec48945d871f)", + "rl_hidden":"Скрыть созданный виджет в списке устройств", "critical":"Critical", "error":"Error", "warning":"Warning", diff --git a/modules/NotificationSMSru/module.json b/modules/NotificationSMSru/module.json index 7678de264..d8e7be536 100644 --- a/modules/NotificationSMSru/module.json +++ b/modules/NotificationSMSru/module.json @@ -1,12 +1,12 @@ { "dependencies": [], "singleton": false, - "category": "support_external_dev", + "category": "notifications", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"NotificationSMSru", - "version": "1.1.0", + "version": "1.2.0", "maturity": "stable", "repository": { "type": "git", @@ -16,6 +16,7 @@ "title": "__m_title__", "description": "__m_descr__", "level": ["critical", "error", "warning", "info", "device-info", "notification"], + "hide": false, "api_key": "", "phone": "", "prefix": "__prefix__" @@ -25,10 +26,14 @@ "type": "object", "properties": { "level": { - "type": "array", - "enum": ["critical", "error", "warning", "info", "device-info", "notification"], + "type": "array", + "enum": ["critical", "error", "warning", "info", "device-info", "notification"], "required": true }, + "hide" : { + "type" : "boolean", + "required" : false + }, "phone": { "required": true, "pattern": "[1-9][0-9]*" @@ -48,6 +53,10 @@ "level": { "type": "checkbox", "optionLabels": ["__critical__", "__error__", "__warning__", "__info__", "__device-info__", "__notification__"] + }, + "hide": { + "type": "checkbox", + "rightLabel":"__rl_hidden__" }, "phone": { "label": "__l_phone__" diff --git a/modules/Rules/htdocs/icon.png b/modules/Rules/htdocs/icon.png new file mode 100644 index 000000000..0b58d89dd Binary files /dev/null and b/modules/Rules/htdocs/icon.png differ diff --git a/modules/Rules/index.js b/modules/Rules/index.js new file mode 100644 index 000000000..bd57fb950 --- /dev/null +++ b/modules/Rules/index.js @@ -0,0 +1,457 @@ +/*** Rules Z-Way HA module ******************************************* + +Version: 1.0.0 +(c) Z-Wave.Me, 2018 +----------------------------------------------------------------------------- +Author: Hans-Christian Göckeritz +Author: Niels Roche +Author: Karsten Reichel +Description: + Bind actions on one device to other devices or scenes +******************************************************************************/ + +// ---------------------------------------------------------------------------- +// --- Class definition, inheritance and setup +// ---------------------------------------------------------------------------- + +function Rules(id, controller) { + // Call superconstructor first (AutomationModule) + Rules.super_.call(this, id, controller); + + var self = this; + this.attachedList = []; + this.reversActivated = false; + + this._testRule = function() { // wrapper to correct this and parameters in testRule + self.testRule.call(self, null); + } +} + +inherits(Rules, AutomationModule); + +_module = Rules; + +/*"defaults": { + "title": "__m_title__", + "description": "__m_descr__", + "simple": { + "triggerEvent": {}, + "triggerDelay": 0, + "targetElements": [], + "sendNotifications": [], + "reverseDelay": 0 + }, + "advanced": { + "active": false, + "triggerScenes" : [], + "triggerDelay": 0, + "logicalOperator" : "and", + "tests" : [], + "targetElements": [], + "sendNotifications": [], + "reverseDelay": 0, + "triggerOnDevicesChange" : true + }, + "reverse": false + }*/ + +// ---------------------------------------------------------------------------- +// --- Module instance initialized +// ---------------------------------------------------------------------------- + +Rules.prototype.init = function(config) { + Rules.super_.prototype.init.call(this, config); + + var self = this, + ifElement = self.config.simple.triggerEvent, + /* + color als trigger?? + { + deviceId: '', + deviceType: '', + level: '', //on, off, open, close, color, level + operator: <, = , > ,'' + } + */ + doReverse = self.config.reverse, + advancedActive = self.config.advanced.active; + triggered = 0; + + this.handlerLevel = function(sDev) { + var operator = ifElement.operator || null, + ifLevel = ifElement.level, + ifType = ifElement.deviceType, + check = false, + value = sDev.get("metrics:level"), + simple = self.config.simple; + + // - IF-THEN-PART + if (!!operator && ifLevel) { + check = self.op(value, operator, ifLevel); + } else if (ifType === 'switchRGBW') { + check = _.isEqual(sDev.get("metrics:color"), ifLevel); + } + + var triggerTimeout = self.getConfigTimeout(simple.triggerDelay); + var reverseTimeout = self.getConfigTimeout(simple.reverseDelay); + + if (check || value === ifLevel || sDev.get('deviceType') === 'toggleButton') { + + this.simpleTriggerTimer = setTimeout(function() { + + /* + { + deviceId: '', + deviceType: '', + level: '', / color: { r: 0, g: 0, b: 0}, on, off, open, close, color + reverseLevel: '', + sendAction: true / false >> don't do this if level is already + } + */ + + // do action for all target devices + simple.targetElements.forEach(function(el) { + self.shiftDevice(el); + }); + + simple.sendNotifications.forEach(function(notification) { + self.sendNotification(notification, simple.triggerEvent, simple.targetElements); + }); + + self.reversActivated = true; + }, triggerTimeout); + } else if (doReverse && !check && self.reversActivated) { + this.simpleReverseTimer = setTimeout(function() { + self.performReverse(self.config.simple.targetElements); + }, reverseTimeout); + } + }; + + if (advancedActive) { // - LOGICAL-RULES-PART + this.expertTriggerEventRule(); + } + + // Setup metric update event listener + if (!advancedActive && ifElement && ifElement.deviceId) { + self.controller.devices.on(ifElement.deviceId, 'change:metrics:level', this.handlerLevel); + } +}; + +Rules.prototype.stop = function() { + var self = this; + + if (this.config.advanced.active) { + if (this.config.advanced.triggerScenes) { + this.config.advanced.triggerScenes.forEach(function(scene) { + self.attachDetach(scene, false); + }); + } + + // testType ... switchBinary, switchMultilevel, switchRGBW, doorlock, switchControl, time, sensorDiscrete, nested + // nested testType ... switchBinary, switchMultilevel, switchRGBW, doorlock, switchControl, time, sensorDiscrete + + this.config.advanced.tests.forEach(function(test) { + switch (test.type) { + case 'switchBinary': + case 'switchMultilevel': + case 'sensorBinary': + case 'sensorMultilevel': + case 'switchRGBW': + case 'doorlock': + case 'switchControl': + case 'toggleButton': + case 'sensorDiscrete': + case 'thermostat': + self.attachDetach(test, false); + break; + case 'nested': + test.tests.forEach(function(xtest) { + self.attachDetach(xtest, false); + }); + break; + case 'time': + break; + default: + break; + } + }); + + this.attachedList = []; + + } else { + // remove event IfElement listener + self.controller.devices.off(self.config.simple.triggerEvent.deviceId, 'change:metrics:level', self.handlerLevel); + } + + if (this.simpleReverseTimer) { + clearTimeout(this.simpleReverseTimer); + this.simpleReverseTimer = undefined; + } + + if (this.advancedReverseTimer) { + clearTimeout(this.advancedReverseTimer); + this.advancedReverseTimer = undefined; + } + + if (this.simpleTriggerTimer) { + clearTimeout(this.simpleTriggerTimer); + this.simpleTriggerTimer = undefined; + } + + if (this.advancedTriggerTimer) { + clearTimeout(this.advancedTriggerTimer); + this.advancedTriggerTimer = undefined; + } + + this.reversActivated = false; + + Rules.super_.prototype.stop.call(this); +}; + +// ---------------------------------------------------------------------------- +// --- Module methods +// ---------------------------------------------------------------------------- + +Rules.prototype.expertTriggerEventRule = function() { + var self = this; + + // testType ... switchBinary, switchMultilevel, switchRGBW, doorlock, switchControl, time, sensorDiscrete, nested + // nested testType ... switchBinary, switchMultilevel, switchRGBW, doorlock, switchControl, time, sensorDiscrete + + this.config.advanced.tests.forEach(function(test) { + switch (test.type) { + case 'switchBinary': + case 'switchMultilevel': + case 'sensorBinary': + case 'sensorMultilevel': + case 'switchRGBW': + case 'doorlock': + case 'switchControl': + case 'toggleButton': + case 'sensorDiscrete': + case 'thermostat': + /* + test = { + type: 'xxx', + deviceId: 'xxx', + level: 'xxx', + operator: '=', '!=', '<', '>', '<=', '>=' + }, + time = { + type: 'time', + level: 'xxx', + testOperator: '<=', '>=' + } + */ + self.attachDetach(test, true); + break; + case 'nested': + /* + nested = { + type: 'nested', + logicalOperator: 'and' // 'or' + tests: [{ + type: 'xxx' + deviceId: 'xxx' + level: 'xxx' + testOperator: '=', '!=', '<', '>', '<=', '>=' + },{ + type: 'aaa' + deviceId: 'aaa' + level: 'aaa' + testOperator: '=', '!=', '<', '>', '<=', '>=' + } + ] + } + */ + test.tests.forEach(function(xtest) { + self.attachDetach(xtest, true); + }); + break; + case 'time': + break; + default: + break; + } + }); + + // TODO add sensorDiscrete as trigger + this.config.advanced.triggerScenes.forEach(function(scene) { + self.attachDetach(scene, true); + }); +}; + +// LogicalRuleMethods +Rules.prototype.attachDetach = function(test, attachOrDetach) { + if (this.config.advanced.triggerOnDevicesChange === false) { // this condition is used to allow empty triggerOnDevicesChange if old LogicalRules is used + return; + } + + if (attachOrDetach) { + if (this.attachedList.indexOf(test.deviceId) === -1) { + this.attachedList.push(test.deviceId); + this.controller.devices.on(test.deviceId, "change:metrics:level", this._testRule); + this.controller.devices.on(test.deviceId, "change:metrics:change", this._testRule); //switchControl + } + } else { + this.controller.devices.off(test.deviceId, "change:metrics:level", this._testRule); + this.controller.devices.off(test.deviceId, "change:metrics:change", this._testRule); //switchControl + } +}; + +Rules.prototype.testRule = function(tree) { + var self = this, + res = null, + topLevel = !tree, + self = this, + langFile = this.loadModuleLang(), + doReverse = this.config.reverse || false, + triggerTimeout = this.getConfigTimeout(self.config.advanced.triggerDelay), + reverseTimeout = this.getConfigTimeout(self.config.advanced.reverseDelay); + + // if tests are false check if advanced is active + if (!!!tree) { + tree = this.config.advanced; + } + + // loop through all tests and proof conditions + if (_.contains(["and", "or"], tree.logicalOperator)) { + res = self.runTests[tree.logicalOperator].call(self, tree); + } + + if (topLevel && res) { + this.advancedTriggerTimer = setTimeout(function() { + tree.targetElements.forEach(function(el) { + self.shiftDevice(el); + }); + + tree.sendNotifications.forEach(function(notification) { + self.sendNotification(notification, tree.tests, tree.targetElements); + }); + + self.reversActivated = true; + + }, triggerTimeout); + } else if (doReverse && !res && self.reversActivated) { + this.advancedReverseTimer = setTimeout(function() { + self.performReverse(tree.targetElements); + }, reverseTimeout); + } + + return res; +}; + +Rules.prototype.runTests = { + "and": function(tree) { + var res = true, + self = this; + + tree.tests.forEach(function(test) { + var vDev = test.type !== 'nested' ? self.controller.devices.get(test.deviceId) : null, + level = !!vDev ? vDev.get("metrics:level") : undefined; + + switch (test.type) { + case 'doorlock': + case 'switchBinary': + case 'sensorBinary': + case 'sensorDiscrete': + res = res && (level === test.level); + break; + case 'thermostat': + case 'switchMultilevel': + case 'sensorMultilevel': + res = res && self.op(level, test.operator, test.level); + break; + case 'switchRGBW': + res = res && _.isEqual(vDev.get('metrics:color'), test.level); + break; + case 'toggleButton': + case 'switchControl': + res = res && self.compareSwitchControl(vDev, test.level); + break; + case 'time': + res = res && self.compareTime(test.level, test.operator); + break; + case 'nested': + res = res && self.testRule(test); + break; + default: + break; + } + }); + + return res; + }, + "or": function(tree) { + var res = false, + self = this; + + tree.tests.forEach(function(test) { + var vDev = test.type !== 'nested' ? self.controller.devices.get(test.deviceId) : null, + level = !!vDev ? vDev.get("metrics:level") : undefined; + + switch (test.type) { + case 'doorlock': + case 'switchBinary': + case 'sensorBinary': + case 'sensorDiscrete': + res = res || (level === test.level); + break; + case 'thermostat': + case 'switchMultilevel': + case 'sensorMultilevel': + res = res || self.op(level, test.operator, test.level); + break; + case 'switchRGBW': + res = res || _.isEqual(vDev.get('metrics:color'), test.level); + break; + case 'toggleButton': + case 'switchControl': + res = res || self.compareSwitchControl(vDev, test.level); + break; + case 'time': + res = res || self.compareTime(test.level, test.operator); + break; + case 'nested': + res = res || self.testRule(test); + break; + default: + break; + } + }); + + return res; + } +} + +Rules.prototype.performReverse = function(targetElements) { + var self = this; + + if (targetElements && targetElements.length > 0) { + targetElements.forEach(function(el) { + + if (el.reverseLevel !== null) { + self.shiftDevice(el, true); + + self.reversActivated = false; + } + }); + self.reversActivated = false; + } +}; + +Rules.prototype.sendNotification = function(notification, conditions, actions) { + var notificationType = '', + notificationMessage = ''; + + if (notification.target && notification.target !== '') { + notificationType = notification.target.search('@') > -1 ? 'mail.notification' : 'push.notification'; + notificationMessage = !notification.message ? 'Condition: ' + JSON.stringify(conditions) + ' Actions: ' + JSON.stringify(actions) : notification.message; + + this.addNotification(notificationType, notificationMessage, notification.target); + } +}; + +Rules.prototype.getConfigTimeout = function(configTimeout) { + return !!!configTimeout ? 0 : Math.floor(configTimeout * 1000); // !!! > proof for undefined and null +}; \ No newline at end of file diff --git a/modules/Rules/lang/de.json b/modules/Rules/lang/de.json new file mode 100644 index 000000000..10744c06a --- /dev/null +++ b/modules/Rules/lang/de.json @@ -0,0 +1,93 @@ +{ + "m_title":"Regeln", + "m_descr":"!!! SOLLTE EVTL AN NEUE FUNKTIONALITÄT ANGEPASST WERDEN... ODER MINDESTENS DEN NAMEN ÜBERALL ANPASSEN !!! Ein oder mehrere Geräte werden immer DANN geschaltet, WENN ein gewähltes Ereignis eingetreten ist. Dieses Ereignis kann das Auslösen eines Bewegungsmelders oder Türkontaktes sein aber auch das Umschalten eines anderen Schaltaktors.", + "l_event_sources":"Wenn", + "l_actors":"Dann", + "l_notification":"Benachrichtigung", + "l_delay":"Delay", + "l_reverse":"Umkehr", + "l_advanced":"Erweiterte Einstellungen", + "on":"An", + "off":"Aus", + "l_level":"Level", + "level": "Setze Level", + "open":"Öffnen", + "close":"Schließen", + "l_scene":"Szene", + "switchBinary":"Binärschalter", + "switchMultilevel":"Multilevelschalter", + "sensorMultilevel":"Multilevelsensor", + "doorlock":"Türschloss", + "thermostat":"Thermostat", + "l_filter":"Filtern nach Typ", + "l_target_device":"Gerät", + "l_action":"Aktion", + "toggleButton": "Schaltsteuerung / Szene (nur An)", + "switchControl": "Schaltsteuerung (An/Aus/Level)", + "sensorBinary": "Binärsensor", + "l_choose":"--- Gerätetyp wählen ---", + "l_choose_dev":"--- Gerät wählen ---", + "l_choose_controller_action":"--- Interaktion wählen ---", + "l_controller_action":"Interaktionen", + "h_controller_action":"Die Aktion wird als zweistelliger Wert angegeben: Die erste Zahl ist die Nummer des Tasters/der Szene, die zweite Zahl stellt die Aktion des Tasters/der Szene dar (0 = kurz gedrückt, 1 = losgelassen, 2 = gehalten, 3 = 2x kurz gedrückt, 4 = 3x kurz gedrückt, usw. Besispiele: Doppelklick Taste/Szene 2 => 23, Einfachklick Taste/Szene 1 = 10)", + "sensorDiscrete":"Szenensteuerung", + "switchRGBW":"Farbsteuerung", + "l_red":"Rot", + "l_green":"Grün", + "l_blue":"Blau", + "colors":"Farben", + "h_color":"Wählen Sie einen Wert von 0 - 255", + "l_notification_message":"Nachricht", + "h_notification_message":"Inhalt der Benachrichtigung.", + "l_notification_target":"Ziel", + "l_notification_device":"Gerät", + "l_notification_mail":"Email", + "l_choose_target":"--- Ziel wählen ---", + "h_nomail":"Aktuell ist keine Benachrichtigung über Email möglich, da die EMailMe-App nicht installiert/aktiviert ist.", + "b_createmail": "Aktiviere EMailMe", + "m_createmail": "ACHTUNG!
Beim Verlassen des Konfigurationsfensters gehen alle bisherigen Einstellungen verloren.

HINWEIS:
Sie können mit 'Abbrechen' ohne die Konfiguration der EMailMe-App fortfahren, diese im Nachgang anlegen und anschließend in der Rules-Konfiguration hinzufügen.", + "l_sendAction":"Sende kein 'An' Kommando, wenn das Gerät bereits aktiviert ist. Das Gleiche gilt für 'Aus'.", + "h_sendAction":"Verhindert überflüssige Befehle im Netzwerk.", + "h_delay":"Zeit in Sekunden bis das Then-Ereignis gestartet wird.", + "c_reverse":"Umkehrevent aktivieren", + "c_advanced":"Erweiterte Einstellungen Nutzen", + + "rl_options":"Prüfe die Regel bei jeder Zustandsänderung eines unter 'Bedingungen' gelisteten Gerätes", + "l_eventSource":"Führe eine Regelprüfung bei der Aktivierung folgender Szenen durch:", + "l_logicalOperator":"Logische Verknüpfung", + "h_logicalOperator":"Diese Option verknüpft alle in der Liste befindlichen Bedingungen mit einem logischen ODER (OR) oder UND (AND). Bei OR wird die Aktion ausgeführt, wenn mindestens eine Bedingung zutrifft. Bei AND wird die Aktion nur ausgeführt, wenn alle Bedingungen zutreffen.", + "l_tests":"Bedingungen", + "l_testBinary":"Binäre Schalter/Sensoren", + "l_testMultilevel":"Dimmer/Motorsteuerung/Analoge Sensoren/Batterien", + "l_testRemote":"Schaltsteuerung/Szenen", + "l_testTime":"Zeit", + "timeFormat":"hh:mm", + "l_testNested":"Verschachtelt", + "l_logical_action":"Aktionen:", + "l_switches":"Binäre Schalter:", + "l_thermostats":"Thermostats", + "upstart":"Aufwärts: start", + "upstop":"Aufwärts: stop", + "downstart":"Abwärts: start", + "downstop":"Abwärts: stop", + "l_dimmers":"Dimmer/Motorsteuerung:", + "l_status":"Level", + "l_locks":"Türschlösser", + "logical_close":"Verriegelt", + "logical_open":"Entriegelt", + "l_scenes":"Zu aktivierende Szenen/Aktionen:", + "or": "Mindestens eine Bedingung wird erfüllt (OR)", + "and": "Alle Bedingungen müssen erfüllt werden (AND)", + "l_testType": "Typ der Bedingung:", + "h_testNested": "Erzeugt weitere verschachtelte Bedingungen - logische Schreibweise: A AND B AND (C OR D). Die zusätzliche verschachtelte Bedingung ist (C OR D). Eine Aktion wird also nur dann ausgelöst, wenn die Bedingungen A und B sowie entweder die Bedingung C oder D gemeinsam erfüllt sind. Beispiel: Wenn außen der Lichtsensor A > 80% und der Temperatursensor B > 26°C und (Zeit < 12:00 Uhr oder Zeit > 14:00 Uhr) dann wird die Jalousie im Wohnzimmer auf 50% gesetzt.", + "l_logical_sendAction":"Sende kein 'An' Kommando, wenn das Gerät bereits aktiviert ist. Das Gleiche gilt für 'Aus'.", + "h_logical_sendAction":"Verhindert überflüssige Befehle im Netzwerk.", + "h_testTime":"Diese Bedingung überprüft, ob die aktuelle Zeit Kleiner-Gleich oder Größer-Gleich der eingegebenen ist. Z.B aktuelle Zeit: 09:00 und Bedingung: >= 10:00 würde ein 'false' zurückliefern und die Regel nicht auslösen. 24-h-Format - hh:mm", + "l_triggers": "Wie wird die Regel ausgelöst", + "l_conditions": "Bedingungen zum Auslösen", + "p_select": " Bitte auswählen", + "WrongOperator": "Wählen Sie einen gültigen Operator aus", + "h_triggerOnDevicesChange": "Standardmäßig achtet die logische Regel auf alle Änderungen von Geräten, die unter Bedingungen aufgelistet sind. Wird eine Änderung durch die logische Regel erkannt, wird geprüft, ob eine Aktion ausgelöst werden soll oder nicht. Deaktivieren Sie diese Option, wenn die logische Regel Bedingung nur bei ausgelösten Szenen geprüft werden soll. Es wird empfohlen, dieses Feld in Kombination mit der Option' Regelprüfung bei Aktivierung der folgenden Szenen auslösen' zu verwenden.", + "rl_expertSettings": "Erweiterte Einstellungen", + "h_expertSettings": "Hier finden Sie weitere Einstellungen zur Regelprüfung." +} \ No newline at end of file diff --git a/modules/Rules/lang/en.json b/modules/Rules/lang/en.json new file mode 100644 index 000000000..347d0527b --- /dev/null +++ b/modules/Rules/lang/en.json @@ -0,0 +1,94 @@ +{ + "m_title":"Rules", + "m_descr":"!!! SOLLTE EVTL AN NEUE FUNKTIONALITÄT ANGEPASST WERDEN... ODER MINDESTENS DEN NAMEN ÜBERALL ANPASSEN !!! The If->Then is the foundation of automation. A selected action (WHEN) is executed automatically at the moment when a certain event (IF) has happen. This app allows defining these relationships. The (IF) event must be a event that happens at one time. Examples for this are a door that is opened (the door sensor trips) or a button that is pressed. A simple switch is also a reasonable source for an event because this switch can be switched. A temperature sensor is NO event in this regard because this sensor will continuously send temperature and not create an event. The event need to have two defines status of ‘0’ and ‘1’ Every binary sensor or switch button has these states. Status=1 will send a Command Set(1) to the list of selected actuators, Status=0 will sen Set(0) to these devices.

Settings:
  • Pick the events that shall trigger. You can select multiple events and all of them will trigger the selected action (connected with Logical OR). If you pick a dimmer or a motor control the change to full 100 % will be considered as the event to trigger.
  • Pick the devices that shall be switches depending on the action. Please not that these devices must be able to receive and execute Set(1) and set(0) commands.
", + "l_event_sources":"If", + "l_actors":"Then", + "l_notification":"Notification", + "l_delay":"Delay", + "l_reverse":"Reverse", + "l_advanced":"Advanced settings", + "on":"On", + "off":"Off", + "l_level":"Level", + "level": "Set Level", + "open":"Open", + "close":"Close", + "logical_open":"Open", + "logical_close":"Close", + "l_scene":"Scene", + "l_filter":"Filter by Type", + "switchBinary":"Binary Switch", + "switchMultilevel":"Multilevel Switch", + "sensorMultilevel":"Multilevel Sensor", + "doorlock":"Doorlock", + "thermostat":"Thermostat", + "l_target_device":"Device", + "l_action":"Action", + "toggleButton": "Switch Control / Scene (On only)", + "switchControl": "Switch Control (On/Off/Level)", + "sensorBinary": "Binary Sensor", + "l_choose":"--- Choose a device type ---", + "l_choose_dev":"--- Choose a device ---", + "l_choose_controller_action":"--- Choose a controller action ---", + "l_controller_action":"Controller Actions", + "h_controller_action":"The action is described with a two-digit value: The first digit is the button number, the second digit points to the action of this button (0=short press, 1=release, 2=hold, 3=short press two times, 4 = short press three times, and so on. Examples: Double Click button 2 => 23, Single Click button 1 = 10)", + "sensorDiscrete":"Scene Controller", + "switchRGBW":"Color Switch", + "l_red":"Red", + "l_green":"Green", + "l_blue":"Blue", + "colors":"Colors", + "h_color":"Choose a value from 0 - 255", + "l_notification_message":"Message", + "h_notification_message":"Content of the notification.", + "l_notification_target":"Target", + "l_notification_device":"Device", + "l_notification_mail":"Email", + "l_choose_target":"--- Choose a target ---", + "h_nomail":"Currently, no email notification is possible because the EMailMe app is not installed/activated.", + "b_createmail": "Activate EMailMe", + "m_createmail": "ATTENTION!
When leaving the current configuration, all previous settings are lost.

NOTE:
You can continue with 'Cancel' without configuring the EMailMe app, create it afterwards, and add it then in the Rules configuration.", + "l_sendAction":"Don't send On command, if device is already turned On, similarly for Off", + "h_sendAction":"Need to avoid flooding the network.", + "h_delay":"Time in seconds when event starts after triggering event...", + "c_reverse":"activate reverse event", + "c_advanced":"use advanced settings", + + "rl_options":"Check the rule for every change of state of a device listed under 'Conditions'.", + "l_eventSource":"Trigger the rule check on activation of the following scenes:", + "l_logicalOperator":"Boolean operator", + "h_logicalOperator":"This option links all conditions in the list to a logical OR or AND. With OR, the action is executed if at least one condition applies. With AND, the action is only executed if all conditions apply.", + "l_tests":"Conditions", + "l_testBinary":"Binary condition", + "l_testMultilevel":"Multilevel condition", + "l_testRemote":"Remote condition", + "l_testTime":"Time condition", + "timeFormat":"hh:mm", + "l_testNested":"Nested conditions", + "l_logical_action":"Actions:", + "l_switches":"List of switches:", + "l_thermostats":"Thermostats", + "upstart":"Up start", + "upstop":"Up stop", + "downstart":"Down start", + "downstop":"Down stop", + "l_dimmers":"List of dimmers:", + "l_status":"Level", + "l_locks":"List of locks:", + "l_scenes":"List of scenes to activate:", + "or": "At least one condition is fulfilled (OR)", + "and": "All conditions must be met (AND)", + "l_testType": "Type of condition:", + "h_testNested": "Add a new nested condition - like in logical notation: A AND B AND (C OR D). The additional nested condition is (C OR D). An action is only triggered if the conditions A and B and either the condition C or D are met together. Example: If outside the light sensor A > 80% and the temperature sensor B > 26°C and (time < 12:00 am or time > 02:00 pm) then the jalousie in the living room is set to 50%.", + "l_logical_sendAction":"Don't send On command, if device is already turned On, similarly for Off. Need to avoid flooding the network.", + "h_logical_sendAction":"Prevents unnecessary network commands.", + "h_testTime":"This condition checks if the current time is 'less than or equal to' or 'greater than or equal to' the entered time. E.g. current time: 09:00 and condition: >= 10:00 will return 'false' and not pass the rule. 24-h-format - hh:mm", + "l_triggers": "How the Logical Rule is triggered", + "l_conditions": "What triggers the Logical Rule", + "p_select": " Please choose", + "WrongOperator": "Choose a valid operator", + "h_triggerOnDevicesChange": "By default the logical rule listens to all changes of devices listed in conditions. If a change is recognized by the logical rule it will check the condition wether to trigger an action or not. Deactivate this option if the logical rule condition should only be checked by triggered scenes. It is recommented to use this field in combination with 'Trigger the rule check on activation of the following scenes' option.", + "rl_expertSettings": "Advanced Settings", + "h_expertSettings": "Here you can find further settings for the rule check.", + "l_sensorDiscrete":"Scene Controller" +} \ No newline at end of file diff --git a/modules/Rules/lang/ru.json b/modules/Rules/lang/ru.json new file mode 100644 index 000000000..b7bb130e4 --- /dev/null +++ b/modules/Rules/lang/ru.json @@ -0,0 +1,47 @@ +{ + "m_title":"Если -> Тогда", + "m_descr":"Ассоциирование устройств с возможностью задать состояния устройств.", + "l_event_sources":"Если", + "l_actors":"Тогда", + "on":"Включить", + "off":"Выключить", + "l_level":"Уровень", + "level": "Set Level", + "open":"Открыть", + "close":"Закрыть", + "l_scene":"Сцена", + "l_filter":"Фильтр по типу", + "switchBinary":"Выключатель", + "switchMultilevel":"Диммер", + "sensorMultilevel":"Multilevel Sensor", + "doorlock":"Замок", + "thermostat":"Thermostat", + "l_target_device":"Устройство", + "l_action":"Действие", + "toggleButton": "Кнопка (только Включить)", + "switchControl": "Кнопка (Включить/Выключить/Level)", + "sensorBinary": "Бинарный датчик", + "l_choose":"--- Выберите тип устройства ---", + "l_choose_dev":"--- Выберите устройство ---", + "l_choose_controller_action":"--- Choose a controller action ---", + "l_controller_action":"Controller Actions", + "h_controller_action":"The action is described with a two-digit value: The first digit is the button number, the second digit points to the action of this button (0=short press, 1=release, 2=hold, 3=short press two times, 4 = short press three times, and so on. Examples: Double Click button 2 => 23, Single Click button 1 = 10)", + "sensorDiscrete":"Scene Controller", + "switchRGBW":"Color Switch", + "l_red":"Красный", + "l_green":"Зеленый", + "l_blue":"Синий", + "colors":"Цвет", + "h_color":"Выберите значение 0 - 255", + "l_sendAction":"Не отправлять команду ВКЛ, если устройство уже включено, аналогично для ВЫКЛ", + "h_sendAction":"Нужно чтобы не забивать сеть.", + "l_notification_message":"Message", + "h_notification_message":"Content of the notification.", + "l_notification_target":"Target", + "l_notification_device":"Device", + "l_notification_mail":"Email", + "l_choose_target":"--- Choose a target ---", + "h_nomail":"Currently, no email notification is possible because the EMailMe app is not installed/activated.", + "b_createmail": "Activate EMailMe", + "m_createmail": "ATTENTION!
When leaving the current configuration, all previous settings are lost.

NOTE:
You can continue with 'Cancel' without configuring the EMailMe app, create it afterwards, and add it then in the If -> Then configuration." +} \ No newline at end of file diff --git a/modules/Rules/module.json b/modules/Rules/module.json new file mode 100644 index 000000000..6f00a63ff --- /dev/null +++ b/modules/Rules/module.json @@ -0,0 +1,40 @@ +{ + "singleton": false, + "dependencies": [], + "category": "system", + "author": "Z-Wave.Me", + "homepage": "http://razberry.z-wave.me", + "icon": "icon.png", + "moduleName": "Rules", + "version": "1.0.0", + "maturity": "stable", + "repository": { + "type": "git", + "source": "https://github.com/Z-Wave-Me/home-automation" + }, + "defaults": { + "title": "__m_title__", + "description": "__m_descr__", + "simple": { + "triggerEvent": {}, + "triggerDelay": 0, + "targetElements": [], + "sendNotifications": [], + "reverseDelay": 0 + }, + "advanced": { + "active": false, + "triggerScenes" : [], + "triggerDelay": 0, + "logicalOperator" : "and", + "tests" : [], + "targetElements": [], + "sendNotifications": [], + "reverseDelay": 0, + "triggerOnDevicesChange" : true + }, + "reverse": false + }, + "schema": {}, + "options": {} +} diff --git a/modules/Rules/patchnotes.txt b/modules/Rules/patchnotes.txt new file mode 100644 index 000000000..ac3adabcc --- /dev/null +++ b/modules/Rules/patchnotes.txt @@ -0,0 +1,5 @@ +v1.0.0 +- delay for trigger +- logical rule features as "advanced" feature +- reverse trigger +- Basis: If-Then (v2.5.1), LogicalRules (v1.4.1) \ No newline at end of file diff --git a/modules/Scenes/htdocs/icon.png b/modules/Scenes/htdocs/icon.png new file mode 100644 index 000000000..557d9e7f1 Binary files /dev/null and b/modules/Scenes/htdocs/icon.png differ diff --git a/modules/Scenes/htdocs/js/postRender.js b/modules/Scenes/htdocs/js/postRender.js new file mode 100644 index 000000000..eac4980ed --- /dev/null +++ b/modules/Scenes/htdocs/js/postRender.js @@ -0,0 +1,127 @@ +function modulePostRender() { + var icons; + $(document).ready(function() { + + $('.alpaca-fileupload-well').hide(); + + $.ajax('/ZAutomation/api/v1/icons') + .done(function(iconsResponse) { + var moduleId = parseInt(window.location.href.substring(window.location.href.lastIndexOf("/") + 1)); + if (!isNaN(moduleId)) { + $.ajax('/ZAutomation/api/v1/instances/' + moduleId) + .done(function(response) { + if (typeof iconsResponse.data != 'undefined') + { + icons = iconsResponse.data; + $("select[name='icon-select']").each(function(i,select) { + $(iconsResponse.data).each(function(n, row){ + var id = $(select).attr('name').split('_')[2]; + if ($(select).attr('name').indexOf('customicon') != -1) + selected = row.file == response.data.params.customicon.table[id].icon; + + var option = $('