Repository: Z-Wave-Me/home-automation Branch: master Commit: ccbf5c355175 Files: 454 Total size: 3.1 MB Directory structure: gitextract_gic6ajyx/ ├── .jscsrc ├── .jshintrc ├── .syscommands ├── CHANGELOG.md ├── README.md ├── StorageProvider.js ├── Utils.js ├── Webserver.js ├── WebserverRequestRouter.js ├── ZAutomationAPIProvider.js ├── apiary.apib ├── classes/ │ ├── AuthController.js │ ├── AutomationController.js │ ├── AutomationModule.js │ ├── DevicesCollection.js │ └── VirtualDevice.js ├── defaultConfigs/ │ ├── README │ ├── config.json │ ├── config.json_WB │ ├── config.json_ttyACM0 │ ├── config.json_ttyACM0_ZBW-WD │ ├── config.json_ttyACM0_ZBW-no │ ├── config.json_ttyAMA0 │ ├── config.json_ttyAMA0_NonExpert │ ├── config.json_ttyS0 │ ├── config.json_ttyS0-JBox │ ├── config.json_ttyS0-ReHub │ ├── config.json_ttyS1 │ ├── config.json_ttyUSB0_ZBW-no_vDev-no │ └── config.json_windows ├── lang/ │ ├── cn.json │ ├── cz.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── fi.json │ ├── fr.json │ ├── it.json │ ├── pt.json │ ├── ru.json │ ├── se.json │ ├── sk.json │ └── sv.json ├── lib/ │ ├── BAOS_API_2011_01_29_001.js │ ├── IntelHex2bin.js │ ├── LimitedArray.js │ ├── base64.js │ ├── eventemitter2.js │ ├── underscore-umd-min.js │ └── underscore.js ├── main.js ├── modules/ │ ├── Alexa/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── AutoLock/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── AutoOff/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── BatteryPolling/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── BindDevices/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── Camera/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── CloudBackup/ │ │ ├── Readme.md │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── CodeDevice/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── CorrectValue/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── CounterTriggeringSensor/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── Cron/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── CustomUserCode/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── CustomUserCodeLoader/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── CustomUserCodeZWay/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── DecomposeRGB/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── DelayedScene/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── DeviceHistory/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── DummyDevice/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── EasyScripting/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ ├── compile.sh │ │ │ ├── postRender-with-comments.js │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── EdimaxSP1101/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── EdimaxSP2101/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── EnOcean/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── FosCam9805/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── FosCam9821/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── FosCam9826/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── FosCam9828/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── GlobalCache/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── GoogleHome/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── GroupDevices/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── HTTPDevice/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── HazardNotification/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── Heating/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── HomeKitGate/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── IfThen/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── ImportRemoteHA/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── InbandNotifications/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── InfoWidget/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── LightMotionRockerAutocontrol/ │ │ ├── index.js │ │ ├── lang/ │ │ │ └── en.json │ │ └── module.json │ ├── LightScene/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── LogicalRules/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── MQTTClient/ │ │ ├── index.js │ │ ├── lang/ │ │ │ └── en.json │ │ └── module.json │ ├── MatterGate/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── MobileAppSupport/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── MultilineSensor/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── NotificationChannelEmail/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ └── en.json │ │ └── module.json │ ├── NotificationFiltering/ │ │ ├── index.js │ │ ├── lang/ │ │ │ └── en.json │ │ └── module.json │ ├── NotificationSend/ │ │ ├── index.js │ │ ├── lang/ │ │ │ └── en.json │ │ └── module.json │ ├── OpenRemoteHelpers/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── OpenWeather/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── PhilioHW/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── PoppCam/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── RGB/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── RemoteAccess/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── RoundRobinScenes/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── Rules/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── Scenes/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── ScheduledScene/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── Schedules/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── Security/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── SecurityMode/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── SensorValueLogging/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── SensorsPolling/ │ │ ├── htdocs/ │ │ │ └── js/ │ │ │ └── postRender.js │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── SensorsPollingLogging/ │ │ ├── index.js │ │ └── module.json │ ├── SmartLight/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── Sonos/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── SwitchControlGenerator/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── SwitchPolling/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── TPLinkHS100/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── TPLinkHS110/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── TagOnOff/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── TamperAutoOff/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── TechnaxxTX65/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── TechnaxxTX66/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ └── module.json │ ├── TechnaxxTX67/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ └── en.json │ │ ├── module.json │ │ └── patchnotes.txt │ ├── ThermostatDevice/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── VistaCam/ │ │ ├── TVPIP320PIV1/ │ │ │ ├── index.js │ │ │ ├── lang/ │ │ │ │ ├── de.json │ │ │ │ └── en.json │ │ │ └── module.json │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── ZMEOpenWRT/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── ZMatter/ │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ └── module.json │ ├── ZWave/ │ │ ├── cmd_classes.json │ │ ├── index.js │ │ ├── lang/ │ │ │ ├── de.json │ │ │ ├── en.json │ │ │ └── ru.json │ │ ├── module.json │ │ └── postfix.json │ └── Zigbee/ │ ├── index.js │ ├── lang/ │ │ ├── de.json │ │ ├── en.json │ │ └── ru.json │ └── module.json ├── modulesCategories.json ├── release.sh ├── router.js └── updateBackendConfig.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .jscsrc ================================================ { "requireCurlyBraces": ["if", "else", "for", "while", "do", "try", "catch", "case"], "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], "requireSpaceBeforeBlockStatements": true, "requireParenthesesAroundIIFE": true, "requireSpacesInConditionalExpression": { "afterTest": true, "beforeConsequent": true, "afterConsequent": true, "beforeAlternate": true }, "requireSpacesInFunctionExpression": { "beforeOpeningRoundBrace": true, "beforeOpeningCurlyBrace": true }, "requireSpacesInFunctionDeclaration": { "beforeOpeningCurlyBrace": true }, "disallowSpacesInFunctionDeclaration": { "beforeOpeningRoundBrace": true }, "requireMultipleVarDecl": true, "requireBlocksOnNewline": true, "disallowPaddingNewlinesInBlocks": true, "disallowEmptyBlocks": true, "disallowSpaceAfterObjectKeys": true, "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], "requireSpaceBeforeBinaryOperators": ["=", "+", "-", "/", "*", "==", "===", "!=", "!=="], "requireSpaceAfterBinaryOperators": ["=", ",", "+", "-", "/", "*", "==", "===", "!=", "!=="], "disallowImplicitTypeConversion": ["numeric", "boolean", "binary", "string"], "disallowKeywords": ["with", "eval"], "disallowMultipleLineStrings": true, "disallowMultipleLineBreaks": true, "disallowMixedSpacesAndTabs": true, "disallowTrailingWhitespace": true, "disallowTrailingComma": true, "disallowKeywordsOnNewLine": ["else"], "requireCapitalizedConstructors": true, "safeContextKeyword": "self", "disallowYodaConditions": true, "validateJSDoc": { "checkParamNames": true, "requireParamTypes": true, "checkRedundantParams": true }, "excludeFiles": [ "node_modules/**", "lib/**", "storage/**", "system/*.json" ], "fileExtensions": [".js"] } ================================================ FILE: .jshintrc ================================================ { "nonew": true, "curly": true, "noarg": true, "forin": true, "noempty": true, "eqeqeq":true, "strict":true, "undef":true, "bitwise":true, "browser":true, "devel":true, "mocha": true, "node": true, "globals": { "describe": true, "it": true, "executeFile": true, "_": true, "Core": true, "global": true } } ================================================ FILE: .syscommands ================================================ cat /etc/z-way/box_type reboot cat /etc/timezone sh automation/lib/configAP.sh sh automation/lib/fetchLog.sh sh automation/lib/timezone.sh tail -1000 /var/log/z-way-server.log | tac ip a s dev eth0 | sed -n 's/.*inet \([0-9.]*\)\/.*/\1/p' | head -n 1 cat /sys/class/net/eth0/address ================================================ FILE: CHANGELOG.md ================================================ ## 10.10.2024 v5.0.1 New features: * Added externalUrl property in Camera app Fixes: * Fixed screenUrl property in Camera app * Fixed bug in Camera app with open/close * ZMatter Binding: Thread credentials fixed ## 22.07.2024 v5.0.0 New features: Improvements: * Matter support added * Zigbee: Meter cluster added (instanteneous and summation values) * Zigbee: WindowCovering added * Zigbee: isFailed added Fixes: * Fixed apps active state save problem ## 22.04.2024 v4.1.3 New features: * Zigbee: add support for CC Temperature-, Pressure-, RelativeHumidity- Measurement, OccupancySensor Improvements: * Cleanup vDevs in the profile * OpenWeather app * if wingust will not be reported the value will be set to windspeed to avoid an empty value * added "minutes" to label of refresh rate * app descriptions and https * added devices for using in automation * changed options to pulldown * added option for wind speed units * added option for refresh selection * added option for create temp/hum devices * added option for create wind devices Fixes: * Ignore ZBee in backup/restore * Update postfix.json (Philio PAN04, PAN06) ## 18.10.2023 v4.1.2 New features: * Support for Zigbee clusters OnOff, LevelControl, ColorControl, IasZone and OccupancySensing * Added Zigbee support to SwitchControlGenerator * Allow custom IP reported from localIP.json file Improvements: * Reworked technology/bindingName/nodeId attributes of vDevs in Z-Wave/Zigbee/EnOcean/ImportRemoteHA/SwitchControlGenerator * defaultConfigs updated, NotificationFiltering and NotificationEmail added * Add Battery notifications to inbound notification * Be more verbose in ImportHA Fixes: * Fixed double restart on zway.discover() exception * Fixes in Zigbee app * Fixed ZWave.prototype.applyPostfix error * Fixed firmware-fault report * Fixed wipeOut handler * Typo in getMACAddress * Update postfix.json ## 19.07.2023 v4.1.1 New features: * Zigbee DoorLock support Improvements: * Rework of SmartStart UI * HomeKit: Added Battery Service Fixes: * Fix missing flood sensor icon * Fix probeType for motion * Update postfix.json for FGS-213 * Fixed firmware update of Nth chip * DummyDevice parseInt fix * Set device isFailed based on lastCommunication value * Correct logging of Notification channel not assigned to a user * Fixed WB config to use Native module * Fixed thermostat getCurrentTemperature ## 02.05.2023 v4.1.0 New features: * Zigbee support added with Z-Wave.Me hardware Improvements: * MQTT: switchRGBW support * Add Battery notifications to inbound notification * Allow custom IP reported from localIP.json file Fixes: * Fixed double restart on zway.discover() exception * Fixed firmware-fault report * Fixed wipeOut handler * registerNotificationChannel parseInt(user) * Typo in getMACAddress * Be more verbose in ImportHA * Heating App: Don't set the temperature if it is not defined * Postfix for Thermostat fixed ## 22.12.2022 v4.0.2 New features: * Allow in Notification Filtering Improvements: * Add URL to the HTTP error message Fixes: * Security restore mode ## 24.11.2022 v4.0.1 Improvements: * Update postfix.json ## 01.11.2022 v4.0.0 New features: * Full rework of Notification (Alarm) CC widgets: state events produces widgets, stateless produce hidden toggleButton * Dummy device: * Added Door lock, Blinds, Siren and Water valve to Dummy device * User can generate access tokens now (for easy API integrations) * Added HTTP API /ZAutomation/api/v1/devices/%dev%/%property% and /ZAutomation/api/v1/devices/%dev%/metrics/%property% to cimplify integration with third part APIs * Postfix handling: * vDev creation based on Configuration parameter with configVDev keyword * vDev based on Notification Status * Window Tilt pre-creation * Added uuid, serial and mac fields to /system/info and /system/first-access API calls Improvements: * New API /encryptionKeys for S0/S2 key extraction (for Zniffer and PC Controller) * Notification: improved icons * Notification: Added support of eventParameter for Idle * Allow disable packet log for slow systems: allow track & save, track only, disable (for slow hardware) * Home name and Remote ID added to push messages * Better e-mail description on device trigger * HomeKitGate 2.2.2 * automaticaly add/remove Permanently Hidden devices * minor fixes * Added self.trigger in Easy Scripting Fixes: * HTTP API response for 0 and false fixed (before was null) * Firmware Upgrade for target > 0 with target 0 not upgradeable * AutoLock: minor * Security app: arming/disarming schedule and many minor fixes * Don't start paused instances after restore * New users can set night_mode correctly * Fixed profilesByDevice to also list devices in allows room * Fix duplicates of Door and AC Alarms * Fixed defered save (produced crashes on slow hardware) * Set timezone * Remove SoundSwitch Mute vDev on device deletion * Fix error on logout for local user * Fixed default .syscommands on Linux * Fix RGB color set * Fixed DeviceHistory check period * Added meter events in notifications ## 02.04.2022 v3.2.3 New features: * API for local token generation * Allow disable packet log for slow systems or big networks * Added uuid, serial and mac fields to /system/info and /system/first-access API calls * HomeKitGate setting to allow remove phantom devices Improvements: * HomeKitGate 2.2.2 * More commands available in EasyScripting * localGMT for getTime API * More events on Websoket API (profile and room changes/remove) Fixes: * Error on logout for local user * Security app fixes * Fixed default .syscommands on Linux * EasyScripting description fix * SwithcControlGenerator problem with Basic * Z-Wave binding stop abort on exception inside controller.device.remove * RGB color set * Remote Z-Way (ImportHA) RGBW control * Don't set Time to itself * Fixed DeviceHistory app * Postfixes: * Philio PAT13 Temperature Sensor postfix AutoOff on the Alarm button, set Alarm On * Tion 4S SwitchMultilevel.data.interviewDone ## 29.11.2021 v3.2.2 New features: * Websocket API improved. Now requires same token as for HTTP API * Full rework of Security module * Added Entrance group to allow delay for some sensors on armin and disarming * Added support for scenes and buttons for arm/disarm/clear by button * Added Arm condition and Arm failure action to allow checks for sensor state before arming * Added ClearPacketLog ZWaveAPI * Added duplicate field in packet log Improvements: * MQTTClient * Added door lock support * Added automatic re-connection * Thermostats are now float, not integer * Added Clock.Set() and TimeParameter.Set() daily at night to keep device at home synchronized Fixes: * Save defered saveObjects on Automation stop * Fixed homekit-skip being ignored after restart * Fixed bug with addTags not notifying subscribers * Fixed Secure thermostat and Vitrum switches support ## 15.09.2021 v3.2.1 Fixes: * Fixed missing third (immediate) parameter in saveObject * Fixed bug in HomeKit with wipeout * Fix local user devices list ## 24.05.2021 v3.2.0 New features: * New MQTTClient app for exporting all devices to MQTT * Added devices.wipedOut event * Added manufacturer, product and firmware fields to vDev * Added Appliance and Water Valve Notification types * Adopted to new WebServer API with WebSocket Auth handler and new ws.push parameter with list of IDs to be notified with the event Improvements: * Added lazy write in saveObject function to save SD card and improve engine speed * Full rework of applyPostfix to boost load process * HomeKit: * Full rework to fix rooms mix-up on Z-Wave app restart * Made PIN static acoss restarts * Added manufacturer, product and firmware information * Security app: * Disarm now works at any time, even if the sensor has triggered * Added listing of directory in modulemedia API * Improved speed with Date.now() instead of new Date().getTime() and new Date().valueOf() * Removed test ws.push for incomingPacket (improved engine speed) * Adopted the new lastExcludedDevice DH for proper cleanup on exclusion, zeno.unregister(). * Improved EnOcean.prototype.dataBind (like ZWave.prototype.dataBind). Fixes: * Fixed EasyScripting app UI slowdown * EnOcean devices do not re-appear with same name and in same room after being added back. * Fixed GetStatisticsData Z-Wave API and added ClearStatisticsData API. ## 24.03.2021 v3.1.4 New features: * New EnOcean profiles added * Added DecomposeRGB module Improvements: * EnOcean: periodical save of zddx, Made API non-public by default * Improved Security App * Improved Heating App * ThermostatDevice: Allow calling handler from create event * HomeKit support improved Fixes: * Fixed EnOcean duplicate devices * Fixed error in NotificationFiltering when Channel is absent ## 09.02.2021 v3.1.3 New features: * New EnOcean profiles added * Added update() to EasyScripting menu * Added SVG content-type to the list of loadable images * Added ZWAYSessionCookieIgnore header to the auth API * Added /devices/:v_dev_id/referenced API to show references to a vDev Improvements: * Logical Rules 1.5 * ThermostatDevice with float value * Improved InboundNotifications module to allow some duplicate events. General rework Fixes: * Heating App: Correct start after reboot, correct handling of subsequent time slots, correct handling of rooms deletion and device load/unload * Heating App: The schedule follows each other without reset to energy save, also when switching to another day. Without a schedule, energy saving is activated * Fixed Network Reorganization infinite loop * Removed SensorsPolling from default config - it blocks the queue and is not needed for most of customers anymore * Removed empty customIcons * Fixed a space before namespace that lead to this type of device not being displayed in the drop list * Fixed that toogleButtons names were not displayed * Sonos app cleanup ## 24.12.2020 v3.1.2 New features: * Added SimpleAV to SwitchControlGenerator * Added demultiplexer API call for HTTP GET request * Added lastSeen and IP to sessions in user profile * Added support for Window Tilt device * Added support for Alarm AC Reconnect/Disconnect widgets * FirmwareUpdate added support for 7th gen UZB/RaZberry * Added WiFiCli API for Z-Wave.Me Hub to select WiFi network * Added IP and RemoteID to first-access API * Added profileName (hub name) to MobileAppSupportAPI * HomeKit integration reworked and included in standard delivery Fixes: * Fixed problem with local URL in Camera settings * Fixed battery error in EnOcean * Added support for EnOCean GP * Fixed NotificationChannelEmail not to report failure on mail send error * Fixed HTTP API search to always match from the beginning * Time driven item enabled in Heating widget * Heating module. Update the list of rooms after deleting a room, frostProtection field * Fixed moduleId in SwitchControlGenerator * Do not logout permanent tokens * Added CORS Allow-Control-Expose-Headers * Added Authorization to Access-Control-Expose-Headers * Default config.json updated * Improved Alarm CC handler to fix error with undefined forEach * Fixed FirmwareUpdate for target > 0 * Fixed Factory Reset on a controller w/o Z-Wave running (no zway object) * Removed 'metrics:removed' * Reset vDev to globalRoom if location does not exist ## 16.09.2020 v3.1.1 Fixes: * Automation/Climate: current temperature always Comfort * Added probeType to Http and Code devices * Added probeType: "thermostat_set_point" ## 31.08.2020 v3.1.0 New features: * New notifications style in Automation * Added /notificationChannels and /notificationChannels/all API calls * Added namespaces:notificationChannels:channelNameEx * Added NotificationChannelEmail (replacement for MailNotifier), NotificationChannelSMSru (replacement for old NotificationSMSru) * Adopted IfThen, SecurityMode to the new notification style * Removed notifications from BatteryPolling - adopting new notifications style * Removed modules MailNotifier (replaced by NotificationChannelEmail), Notification (deprecated) and NotificationSMSru (replaced by NotificationChannelSMSru) * Added MobileAppSupport and NotificationFiltering. * Added Zniffer SetPromisc API * Improved Background RSSI measurement, made it more precise in time when polling Fixes: * Fixed EasyScripting Loop detected issue after an error * Fixed NotificationChannelEmail userId saved as integer and not as string * Temp fix of namespaces - to be reworked * Added NotificationChannelEmail and improved NotificationFiltering. * Fixed RSSI for sent packet for Zniffer * Fixed bug with two dongles * Cleanup. CIT code removed * Fixed FW update via URL download * Allow / in last argument in API router * Network reorg fix ## 28.04.2020 v3.0.6 New features: * EasyScripting app added ## 02.04.2020 v3.0.5 Fixes: * Postfix Association wrapped in lastIncludedDevice check * Full rework of NetworkReorganization * Fixed incoming packet in RouteMap * Fixed bug in ThemostatDevice * Allow restore of Dummy Device value * Added more error logging * Added probeType, icon valve and siren. * AutomationController.loadModuleMedia handles MIME types ## 20.01.2020 v3.0.4 New features: * Google Home integration * Added Sirent Notification/Alarm type for Goap Luxy * Added SoundSwitch CC support * Added debugPrintStack for easier debugging * EasyScripting app * Qubino Flush 1 Relay postfix Fixes: * Fixed Base64 bug with 0D 0A -> 0A. This issue resulted in corrupted images in rooms * Fixed bug with Thermostat Operation toggle missing after inclusion until reboot * Remove redirect_uri from profile and add uniq uuid to each profile * Delete full profile on DELETE ZAutomation/api/v1/profile if only one token exists or only the token if there are others * OAuth2 profiles metadata cleanup * Moved devicesByUser, deviceByUser, locationsByUser and profileByUser to AutomationController ## 06.09.2019 v3.0.2 Fixes: * location added to switchMultilevel and battery vDev types * Version updated * Fixed removal of old sessions * fix undefined location ## 18.07.2019 v3.0.0 New features: * Implemented Authentication Bearer for OAuth2.0. * Permanent tokens * Per device access for users added * Making automaticaly permanent tokens with Authorization Bearer * Add locationName attribure to vDev * Thermostat commands improved * HTTP redirect API added * RemoveToken API added. * Rework of profiles API * Made session permanent and stored in config.js * Added new bootloader to Z-Wave OTW Improvements: * EnOcean improvements * Updated encryption function for TP Link Wifi Plugs * Сapability to change level of sensorMultilevel and sensorBinary, useful for CodeDevice and HTTPDevice * Support of postfix application for all devices of the same manufacturer Fixes: * Do not return 404 if no rooms granted to the user. Return empty0 * Save vDev probeType in config.json * ThermostatSetPoint posfix fix * Removed HttpOnly from the ZWaySession cookie. * Duplicate ZWaySession in the header * Timezone fixed for Raspberry Pi * Fixed RGBW off oldColor save * Added missing set of metrics:level for toggleButton * Old tokens cleanup fixed. * Do not apply Anonymous if there was and attempt to login (Auth Basic, Bearer or SESSID) * Remove profile on OAuth2 failures * Added WWW-Authenticate header to HTTP 401 reply * Fixed bug in MobileAppSupport leading to error * SwitchControlGenerator. Do not create device for CentralScene as it is handled in ZWave module * Merge pull request #463 from RobertGebauer/master * Update check of level to set: must be a number * SHUI-546 Fix Info Widget * SHUI-543 do not save unneeded instanceId in instance * https://github.com/Z-Wave-Me/home-automation/issues/475 * update MobileAppSupport * SHUI-521 add notification to scenes * SHUI-534 fix silent and normal alarm * SHUI-519 Support to compare 2 devices * fix: getting new sid/session although the old session is still available, * add default checks to VirtualDevice function for params tags, order, location, creationTime * [SHUI-508] fix broken password reset * [SHUI-507] Rules: Fix reverse function problem with 0 * Update check of level to set: must be a number * Bugfix. Level "0" won't be set with previous check as !!level evaluates to "false" for level=0. As level "0" should be a valid level to set, the level is checked to be a number now. AutoOff: * Doorlock support for AutoOff module DeviceHistory: * externalAPIRevoke to DeviceHistory stop function added IfThen: * handling dimmer correctly in IfThen NotificationSMSru: * Huawei 3G/4G modems support ## 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 * Refactored PacketLog to make it work faster and take smaller RAM/FLASH * add location title in front of device names within module configuration dropdowns * Modified MobileAppSupport and LogicalRules apps to push user defined notifications * add rain and co sensor types * updated underscore.js to v1.8.3, * add auto installer for E-Mail Me instance (is added automatically after z-way f/w upgrade if e-mail under My Settings was set and no instance is already existing) * E-Mail Me instance is added automatically during initial setup - e-mail is set during initial login * add "zway_parsedPackets.json" to blacklists, to be ignored during backup / restore * add topology restore flag to ZAutomation restore api * add support for fan and siren (pull request #439 by maros) * add removed flag to vDevs * add vDevs of failed nodes will be marked as metrics.isFailed:true now * nodes with WakeUp CC - except portable remotes - will be checked once a day if they are failed (no wake up during a specific interval) * add filterByNode() and filterByCreatorId() to DeviceCollection.js * add modules to support Technaxx TX65, TX66, TX67 cams * add spanish translation * postfix.json updated (changed or added): * Popp 10 year smoke detector with siren * Fibaro Door/Window Sensor G5 * Popp Thermostat * Philio PST02-5B Motion Sensor * Philio PAT02-5C Flood Sensor * Foxx Project Range Extender * Foxx Project Smart Switch Gen5 * WiDom Universal Double Switch * MCO Home Fan Coil Thermostat (4-pipe) * MCO Home Fan Coil Thermostat (2-pipe) * TKB Plug Dimmer French * Fibaro Door/Window Sensor G3 * Fibaro Single Switch 2 * Fibaro Double Switch 2 * NodOn Micro Smart Plug * Aeotec Door/Window Sensor Gen5 * Popp Water Sensor * Qubino Dimmerinsert 0-10V * Aeotec Home Energy Meter - Gen5 * PAN16 Smart Energy Plug In Switch * Eurotronic Spirit * EVERSPRING ON/OFF Switch/Screw-In * Aeotec Smart Switch 6 * Philio Motionsensor * Fibaro Dimmer 2 * Aeotec Heavy Duty Switch * MCOHome Thermostat MH7-WH-EU * Aeotec WallMote Quad * POPP Plug Dimmer * Aeotec WallMote Dual * TKB Plug Dimmer German * Fibaro Keyfob * Fibaro Wall Plug FGWPx-102 * Fibaro Universalsensor with Binary Input * Popp Wall Plug Switch Outdoor IP44 * Popp Flow Stop * Qubino Relay Insert 1*2,3 kW * Vision Shock Sensor * Popp Radiator Thermostat * AEOTEC LED BULB * Qubino RGBW Dimmer * Vision DC/AC Power Siren * Danfoss Radiator Thermostat * Fibaro RGBW Module * Vision Security Siren G5 * Qubino Weatherstation * Domitech ZBulb * Z-Wave.Me Wall Remote * POPP Wall_C Forever - Wall Remote * MCOHome Thermostat MH7-EH-EU * Aeotec Nano Switch 1 Relay * Aeotec Home Energy Meter - Gen5 * Aeotec Nano Dimmer ZW111 * Popp Z-Rain * Aeotec Nano Switch * Z-Wave.Me Funkwandschalter * Fibaro CO Sensor * Zipato Bulb 2 * Devolo Door/Window Contact * Popp Solar Outdoor Siren * Qubino PWM Thermostat * WiDom Energy Driven Switch S/C * Secure Water Meter * Qubino Flush 1 Relay * Qubinio Flush2 Relays * TKB Home Wall Plug Switch * MCO Home MH-S314 EU (UK) * MCO Home CO2 Monitor * MCO Home MH-S412 * MCO Home MH-S411 * MCO Home MH-S412 UK * Philio 2-in-1 Sensor - Temperature and Humidity * Philio 3-in-1 Sensor - Flood, Temperature and Humidity * Qubino Roller Shutter Insert 2*1 kW with energy meter * Qubino Dimmerinsert with energy meter * Qubino Roller Shutter Insert 12-24V DC * Philio Relay Insert Blind * Aeotec LED Strip * EverSpring Motion Sensor SP816 * Fibaro Door/Window Sensor 2 * MCOHome Thermostat MH7H-XH-EU * TKB Home Power Meter Socket * Everspring Door/ Window Sensor * Philio Double Relay Insert 2*1.5 KW with Metering Function * Danfoss Hydronic Controller * Secure Z-Wave controlled Boiler Actuator (2 Channels) * Secure Electronic Room Thermostat with Temperature Sensor * Secure Temperature and Humidity Sensor * Popp 10 year smoke detector * Vision Door Lock with Handle * Vision Garage Door Sensor * TKB Digital Heating Thermostat * Remotec IR Extender ZXT-120 * Fakro Electric actuator * Everspring mini Plug * Aeonlabs Minimote * Aeonlabs Minimote * POPP Keypad Fixes: * fixed LimitedArray class * fix rssi output * fix endless timeout entries of reorganization * fix #442: Uncaught TypeError: Cannot read property 'filter' of undefined when getting notifications * ZWave: fix not working set of old multilevel value * ZWave: fixed that value and parameter not managed to 0 * fix not working Hide and hasHistory of elements is after z-way restart * fix deleting room sensors from location is vdev moved to new location * fix dublicated id's if instances are cloned * limit max size of notifications to 2500. Old limit (5000) allowed file size over 1MB that resulted in complete cleared file after z-way restart * fix d&d settings are lost after z-way reboot Modules: * DeviceHistory: * remove listeners on stop * fix notification bug in DeviceHistory app * remove binary support from DeviceHistory app - it's handles by UI now with a better readable event list * MobileAppSupport v1.2.2: * modified to push user defined notifications * add remote/local indentification * LogicalRules: * modified to push user defined notifications * Camera: * add screen URl for new camera view * ZWave: * add rain and co sensor types * add replace smoke image with burglar image for burglar events * fix not working set of old multilevel values * fixed that value and parameter not managed to 0 * add isFailed flag to zway vDevs * add SaveData to save once per hour * MailNotifier v1.2.0: * extend handler to send mails initialized by IfThen and LogicalRule * add checkbox to allow using an alternative email address next to the preconfigured addresses in user settings * LightScene v1.1.1: * change icon type gesture to new type scene * Sonos: * Added previous and next song functionality to sonos module (pull request #446 by vuza) * OpenWeather: * Added clouds info to metrics:zwaveOpenWeather (pull request #438 by RobertGebauer) * Added optional sunset / sunrise widget (inspired by pull request #229 from manyosit) * If/Then v2.5.0 * allow use of mail and push notifications if condition is triggered CIT: * fix missing encodeURI * send language keys for failed login feedback ## 28.07.2017 v2.3.5 Changes: * saving of notifications / z-way data refactored * now manged by LimitedArray Constructor * notifications file will be cleared on startup if > 1MB - this should avoid z-way-server startup problems * enable RSSI check only if implemented * ZWave * ZWave module specific files are now per instance * Replaced global vars by local * Better Z-Wave Binding restart code to try 10 times * save old color for switchRGBW in vdev, save old level for switchMultilevel (probeType: switchColor_soft_white, _cold_white, _red, _green, _blue) * load Cron as first module, to allow cronjob in ZWave module * Update IntelHex2bin.js * add app MobileAppSupport as system-app * add email.me as system-app * loadModuleLang refactored * extract unnecessary files from backup file .zab to reduce size * remove deprecated saveNotifications() function Features: * Support to show html page placed in folder MODULE_NAME/htdocs. You can open html page MODULE_NAME/htdocs/table.html from address: ZAutomation/api/v1/load/modulemedia/MODULE_NAME/table.html * BE support for background room images * Utils * internet check added * string to object parser added (and refactored in HA) * location added to device-info notifications * add periodically update of network statistics data * BE support for Drag and Drop * BE support for room main sensors (three sensors that can be assigned to room in UI) * Modules * Icons and Title name fix for modules which generate vDevs * New constructor function LimitedArray added (lib/LimitedArray.js) Bugfix: * memory problems caused by huge amount of data * fix zbw inquery #431 * UZB/RaZ upgrade fixes * Fix updates of manufacturerId = 0 (Sigma Designs) products * Fixed IntelHex converter * Remove QR code update on profile meta data update ZWaveAPI: * PacketLog API added ZAutomationAPI: * add api to control ntp service * certfxAuth api added * move api's zwaveDeviceInfoGet and zwaveDeviceInfoUpdate out of ZWave module into ZAutomation API * vendor db API added Modules: * HTTPDevice * Added fields Content type and Data * InbandNotifications * fix undefined scale unit of device info log * fix empty level in device-info notification * expand device events to include default custom icon information * refactored -> remove saving and polling * ZWave * inject zway config to ZWaveAPI * expertsettings expanded + API changed * zwaveDeviceInfoGet and zwaveDeviceInfoUpdate API's removed * SmartLight * add support for old Z-Wave.Me Dimmers * IfThen 2.5.0 * Don't send On command, if device is already turned On, similarly for Off CIT * authentication added * profile management added * system/info API adjusted * configuration added (ntp, wifi, CIT name, TZ) ## 18.04.2017 v2.3.4 Changes: * NetworkReorganization API refactored * widget CloudBackup instruction Features: * ZWave: * Added support of probeType GAS and WATER ## 27.03.2017 v2.3.1 Changes: * Added Z-Wave module restart after UZB/RaZberry upgrade * box reboot refactored * controller function - get instances by module name added * Factory reset: * add controller state check after SetDefault() * exclude 'default' from skin uninstaller * add null check to storage cleanup * add notification * saveObject(): * exclude null entries * updateBackendConfig: * add transformation that removes 'null' entries from storage content list * User passwords converted to salted hash (sha512) Bugfix: * Fixed crash on title compilation in system with multiple dongles * refactor error handling of broken or missing config.json / default-config.json * Fixed bug with door lock status not updated Features: * Z-Wave MeterPulse CC support added * QR code generation for login credentials added ZWaveAPI: * CallForALLNIF API added * TestNode API added * CheckAllLinks API added * RSSIGet API added * NetworkReorganization API added * GetRorganizationLog API added * sendZWayReport API added - sends a report including z-way data and last 1000 lines of z-way-server.log * ZWave Backup API enhancement - optional up to latest 20000 lines of z-way-server.log can be added to backup package Modules: * NotificationSMS.ru * Added filter for notification level * PhilioHW * improvements * Correct value * added ## 27.01.2017 v2.3.0 Changes: * Added password field to all modules which use password field * pull request #385 from pathec/patch-websocket * notification api refactored: * prepare redeem and delete of single or more notifications * add possibility to redeem or delete already redeemed notifications (via request params) * Postfix updated: * Philio PST02-5B added * Philio Vision PAT02-1A added * sensorDiscrete support updated for all CentralScene devices * fix debug.console * deactivated by default * JS/Run/controller.debug=true will activate console.debug output * lib file descriptions updated * more robust on config.json fault - will use default config.json instead Bugfix: * Do not update widgets if type is Invalidated Features: * add new device type 'sensorDiscrete' * n-state vDev for CentralScene CC * handles triggered scene in combination with their current key attribute * skins api for skins ui feature support: * update and install skins from https://developer.z-wave.me * delete and apply skins * reset skin * custom icons api for custom icons ui feature support: * update and install icon packages from https://developer.z-wave.me * upload single icons or custom icon packages locally * delete and apply icons * icons can be applied depending on device type and there different levels or states * cloud backup module for cloud backup ui feature support: * only adjustable in SHUI under Configuration > Management > Backup & Restore * needs deposited e-mail adress of user (Configuration > My Settings) * restricted for admin users only * could be triggered manually or automatically by configured schedule * limited to 3 backups per box (remote id) * a request for you cloud backups will verify your email against https://service.z-wave.me/cloudbackup/ and send you a response including accesses to your box backups * this feature is OPTIONAL, so you can still use the already existing backup (Configuration > Management > Backup & Restore > Download backup to your computer) * prepare set for timezone api * prepare ZWaveAPI (ZWaveDeviceInfoGet/ZWaveDeviceInfoUpdate) for DB update of device data * uploadModule.sh under automation/userModules added to allow upload of packed modules (tar.gz) directly from directory by ssh ZWaveAPI: * ZMEFirmwareUpgrade: * Added a way to flash ZMEFirmware from local file * ZMEBootloaderUpgrade: * Added a way to flash ZMEBootloader from local file * Added Access-Control-Allow-* headers * new: * ZWaveDeviceInfoGet ... GET * ZWaveDeviceInfoUpdate ... GET ZAutomation API: * new: * /notifications ... PUT/DELETE * /notifications/:notification_id ... PUT/DELETE * /skins/tokens ... GET/PUT/DELETE * /skins ... GET * /skins/install ... POST * /skins/update/:skin_id ... PUT * /skins/setToDefault ... GET * /skins/active ... GET (ANONYMOUS) * /skins/:skin_id ... GET/PUT/DELETE * /icons ... GET * /icons/:icon_id ... DELETE * /icons/upload ... POST * /icons/install ... PUT * /system/timezone ... PUT Modules: * ImportRemoteHA 2.0.3 * add functionality to tag all remote widgets * enhance url input to add ip adress only (with backward compatibility) * bugfix: vDevs siblings (pull request #393 from xibriz) * bugfix: inherit hidden or dectivated state * bugfix: missing probeType * IfThen 2.4.0 * add support for Color Switch (in targets) * add support for type 'sensorDiscrete' (in actions) * bugfix: doesn't decide if on/off was triggered - action is still fired * ZWave 2.3.0 * add new device type 'sensorDiscrete' * do not update vDev if type is Invalidated * InbandNotifications 1.1.0 * add support for type 'sensorDiscrete' * some refactorings * Cron 1.0.0 * bugfix: initialization * PhilioHW (POPP Hub 2) * no_breath option and WPS LED indication * breath off by default * CloudBackup 0.1.2 beta * added to automation/modules * OpenWeather 1.0.1 * update open weather url's * DummyDevice 1.0.1 * bugfix: NaN on switchMultilevel initialization ## 10.11.2016 v2.2.5 * some performance enhancements in CommunicationLogger and CommunicationHistory ## 21.10.2016 v2.2.4 Changes: * pull request #372 * pull request #342 * update main.js (//--- Load 3d-party dependencies) for HomeGear support * Postfix - (ZWave module): * a lot of changes in internal postfix logic * new configuration possibilities: * change device name (new) * change device icon (new) * change node name (new) * hide devices (new) * deactivate devices (new) * suppress device creation * change configuration * change CC data * app switch controller support * app major minor condition changed * bugfix for fibaro smoke sensor postfix * add postfix error messages, postfix.json updated * Postfix - (ZWaveAPI): * add expertconfig and api ExpertConfigGet + ExperConfigUpdate * api's Postfix, PostfixUpdate, PostfixGet, PostfixRemove, PostfixAdd added * ZAutomation API: * allow also req type object as post object in login * fix reload of initial getFirstLoginInfo call, add showWelcome entry - affecting (rebootBox, setLogin) Modules: * TamperAutoOff: * added, workaround for devices that don't deactivate tamper sensor * RoundRobinScenes: * new param added in config * LightMotionRockerAutocontrol: * some bug fixes and improvements * ZWave: * Timing statistics changed according to new IMA data * Added support for new bootloader and OTW to 6.70 SDK * new sensortypes, seismic, acceleration x, y and z added * IfThen * add Thermostat, SensorMultilevel support * BindDevices * add thermostat support * change name to Association * Notification * descriptions adjusted * AutoLock * pull request #319 Fixes: * Z-Wave-Me/zwave-smarthome #190 * ZWave module: * Fixed Communication statistics wrong timestamp * Fixed non-working blind stop command * fix for: Second Z-Wave module not generate a widgets #369 * bugfix: cannot read data of undefined / null (incomingPacket) *RemoteAccess: * bugfix: wrong ID after changing real ZBW ID. * ScheduledScene: * fixed bug after adding locks to the list of actions ## 12.07.2016 v2.2.3 Changes: * Allowing Basic Authentication for Ajax Requests * language keys updated * dependency / instance handling: * fetch undefined and failed instances to avoid error when they were adressed to global variable * rework loaded singleton handling - in-/activate instance will not influence that list * add new installed and added apps also to loadedModules list, to avoid there reinitialization * flags of dependency error messages changed * filtering in instantiateModules() changed * remove pushNamespaces() for emit 'destroy' * CHANGELOG, README, api doc updated New features: * Scene support for fibaro swipe added (4 scenes) * Support fixes added for Philio devices: PST02-5C Door Sensor, PST02-5B Motion Sensor, PAT02-5C Flood Sensor, PSG01 Smoke Sensor * /system/info api added * LightMotionRockerAutocontrol module added Fixes: * bugfix non working increase / decrease command in device api * bugfix non loaded modules - double load * bugfix 'cannot read property meta of undefined' in module initialization * minor refactoring of namespace generator Modules: * ScheduledScene: added locks support and send Action function * PhilioHW: vDev added, batery charge timer * ZWave: alarm probeTypes changed, tamper probe type added, renamed 'door' probe type into 'door-window' ## 11.04.2016 v2.2.2 New features: * Reset to factory default (load default config, clear storage and userModules, set z-Way controller back to factory default, logout if succesful). * Reset single device histories. * Backup / restore of userModules added (Internet connection necessary). * Welcome widget removed from default configs. Fixes: * CodeDevice typo. * Callback execution error after calling a non existing/registered api. * Added correct encoding in backup. * Updating user profiles. * Issues with set() of virtual devices. * Too many bindings problem fixed for Alarm CC. * More error messages internationalized. Command Classes: * Removed Basic CC from NIF, Secure NIF and Channel NIF to fit Z-Wave Plus (new in Z-Wave Plus specification). * MaxCmd in MultiCmd changed to 3 (thanks to buggy Danfoss RS). * Security Scheme Report value ignored according to spec change (new due to Z-Wave S2). * MultiChannelAssociation bit address fixed. * MultiChannelAssociation and Association are now limited by max together (sum of both groups) (clarified in Z-Wave specification). * Association Remove group=0 fixed according to spec (clarified in Z-Wave specification). New Command Classes: * SimpleAVControl Stability and security fixes: * Better error handling of broken instances. * Re-initialization of module refactored: * for better error handling * instances will be filtered and removed first * the module reloaded and all instances created again * user will get error output in events * instances will not be recreated, if something has broken * Version handling added, to check which installed App needs to be prefered (already preinstalled apps with higher version have priority). * Description and instances entries of default configs updated. * Don't backup/restore notifications. * instantiateModules() refactored* depending on their dependencies. * Refactoring creating user profiles. * Send device in sleep right after inclusion (to save battery). * Fix bug with keepAwake false on interview force. API changes: * Fixed Z-Way to start secure interview if Primary (no SIS) or if SIS itself. * zway.SetLearnMode and controller.SetLearnMode are back to prior to v2.2.0 syntax with true/false parameter. NWI is handled under the hood. * Object moduleCategories removed from config. * Reverted this feature "Request NIF for devices that do not have a valid NIF after loading" (introduced in v2.2.1) due to conflict with Security. * Added Function Class ExploreRequestInclusion (used internally in SetLearnMode). * Setting probeType refactored (moved completely in to ZWave module). * Remove old logic: Ignore SwitchBinary if SwitchMultilevel exists. Apps: * SecurityMode: event 'SecurityMode.alert' added by MarioGravel. * LightScene: Lock support added + minor enhancements. * RoundRobin: update on itself to allow catching own events. * RemoteAccess: refactored. * DeviceHistory: refactored (new handling for scenes, switchControl added). Debugging tools: * Debug function zway_fc_application_command_handler_inject (zway.InjectPacket) to inject packet from any device like incoming from Z-Wave. * Debug zway.SendDataSecure to allow sending commands using Security CC (like zway.SendData but with security forced). * Default config in automation/defaultConfigs/config.json for factory reset. Can be used to revert to factory default after screwing up. ## 18.02.2016 v2.2.1 UI: * Enhanced display on mobile devices and tablets. * Added the ability to customize the device from Devices/Manage. * Backup and restore. * Redirect to the APP detail after module instalation. * Rating, download counter and comments of the Apps. * Improved modal windows and dropdowns. * Remember me checkbox on the login page. * Sorting Elements by title, newest.... * Added icon to deveices list in Events. * Added stop button to blinds. Expert UI: * Network/Controller: Firmware Update page for RaZberry. * Size of UI optimized. New features: * Request NIF for devices that do not have a valid NIF after loading. Fixes: * Cannot add email address to user #90 * Climate Control widget displays the correct values. * List elements error #82. * Can not install user module from store #87. * Fixed redirect after post/put a local app. * Elements are completely refactored. * Replaced Bootstrap modal windows and dropdowns with Angular. * Fix zway.devices[X].instances.length problem Stability and security fixes: * Fixed SegFault on non-existing CC inside MultiChannel. * Fixed rare packet buffer corruption. * Fixed devices data loss on bad data from UZB/RaZberry. * Backup/restore problems fixed. * Mark CC in channel as secure if this CC on instance[0] is secure API changes: * Authentication process with Secure Login. * Replaced http protocol with https for external APIs. * Enable SIS on secondary controller after exclusion ## 12.12.2015 v2.2.0 UI: * Initial page forces to change default password and add email for password recovery. Password recovery by mail. * New design for RGB color picker. * Redesign of Dimmer Element. Has now 3 buttons for off, on/last state, on/full state. * Events can now be filtered on any device. * New design on Setup Menu, all Management functions are in single menu element shortening the menu. * New design for device management. EnOcean UI is only shown if Enocean is active, Setup and Management for all different technologies is unified now. * Z-Wave management now allows to remove Z-Wave device, either by Exclusion or by Removing a failed node. * New Info Page gives valuable data for support. * Dashboard Message improved if dashboard is still empty. * New design for all elements. They now show the room they are assigned to plus measured values are much larger. * Description of the bug report function added. * Menu icons for Elements and Rooms are twisted. * Whole new design of App store. * Now App store is open for third parties. * Allow update and delete of apps. * Apps are now grouped by theme. * Its possible to access private apps using token string. * New section "featured" for the most important apps. * Newly created elements are color marked to find them better. * New Icons for Thermostats and different other sensor values. * Elements are now ordered by name. * Plenty of changes to adapt to different mobile device screen sizes. * All devices now belong to a room. There is a wildcard room for devices not assigned yet. Expert UI: * Showing current license in Expert UI. * Description of different colors in routing table. * Complete redesign of Association Settings page. * Redirection to login page if accessed directly without login. New features: * Added support for WebSocket client and server (not on all platforms yet). * notification2ext modules added to save notifications on external flash. * Asynchronous DNS resolver for http and sockets not to block JS (not on all platforms yet). * Support SendData to broadcast (node 255). * Added JS functions for AssignReturnRoute, AssignSUCReturnRoute, DeleteReturnRoute. Fixes: * Restore functionality fixed. Make sure to update bootloader and firmware to 5.04 on UZB and RaZberry before running restore!!! * Conforms with latest Z-Wave Plus updated specification. * Fixed missing scales problem for Multilevel Sensors. * New scales added for Multilevel Sensors. * Fix incomplete read issue in system() API function. * Print module js file:line info when compilation error occurs. * Fixed SerialAPI AddNodeToNetwork and RemoveNodeFromNetwork callback mess. Need firmware 5.04 on UZB and RaZberry. * ReplaceFailedNode restarts full interview including Security interview. * ZMEFreqChange current frequency detection bug fixed. * Compatibility: allow setTimeout(fn, 0) which is used sometimes for deferred callbacks. * Fixed output of SDK version name of devices and RaZberry/UZB. * Remote Access rare problems fixed. * All device are now grouped by namespaces to allow easy selection in App settings. * OpenWeather now with API Key (according to changed of the service). New Command Classes: * MultiChannel v4, MultiChannelAssociation v3, Association v2 support * ThermostatSetPoint v3 Stability and security fixes: * More stable interview with Security on slow hardware * Few potential crash situations fixed API changes: * zway.SetLearnMode and controller.SetLearnMode parameter can now be 0, 1 and 2 to support NWI Learn Mode. See docs. * lastExcludedDevice is now updated AFTER device complete removal and is now also updated in RemoveFailed success callback. ## 04.09.2015 v2.1.1 Command Classes: * MCAv3 implementation added. * Updated CC implementation to the latest Z-Wave standards. New features: * Frequency request from Z-Wave.Me stick and RaZberry. * Authentication added to all HTTP requests. * Device specific fixes applied to device inclusion.to fix minor non-interoperability. * Add updateOnAction and skipEventIfSameValue flags to HTTPDevice and CodeDevice * SwitchMultilevel value is repeatedly requested every 2 seconds until value stops changing. Fixes: * loadObject problem fixed on Windows. * getsockname (detection of own IP) fixed. * Allow named access to command classes even without public functions (like CentralScene or Security). * Fix inf and nan problem in JSON* now they are null. * CentralScene added to controller default to allow catch them on controller. * zway_queue_inspect() call made public. * NoOperation now is now issuing an isFailed after undelivered packet and it is sent only once to battery devices (to mark as failed). * Many fixes in EnOcean. * Show only devices from allowed rooms (don't show unallocated devices), * Fixed authentication problems with find.z-wave.me and local user. * Removed status field from modules. * Fix devices update problem in ZAutomation API. * Warning (255) battery values mapped to 0 * Fixed Thermostat F scale problem * LogicalRules Switch on/off action fixed * Sonos, RGB, SecurityMode modules fixed UI: * Easy installation of new modules from online store. * Thermostats and A/C widgets fixed. * Many improvements in Smart Home UI and Expert UI. ## 28.06.2015 v2.0.1 Command Classes: * MultiCmd MaxNum changed to 6 New Command Classes: * MultiChannel v4 support Stability and security fixes: * Don't allow to call secure commands unsecurely. * Fix Security Scheme Inherit. * Fix timers issue when clock is adjusted. * Fix some non-blocking socket issues in sockets. * Check if device still exists after SendData callback. * Prevent segfault when unsubscribing data holder callback from within itself * Restore function on 6.51.03 works again New features: * Add remote peer info in WebServer. UI: * Remote access management added to Home Automation UI. * Removed UI selector page, access is possible via direct link or Info Widget ## 29.05.2015 v2.0.1-rc33 Command Classes: * Make Basic CC mandatory in Secure NIF too * MultCmd set maxNum in Defaults.xml. * Ignore supported reports for already interviewed * SensorBinary/SensorMultilevel command classes (to fix phantom sensors). * Add 10 sec grace period for SensorMultilevel v1-4 when new sensor types are still accepted. * Support rfStateCap == 0 in Protection CC to correctly handle devices with no RF protection. * Update event type parent DH for Alarm/AlarmSensor CC when receiving event reports. New Command Classes: * MultiChannel v4 fallback support (supported as v3). Fixes: * Fix timers and queue hangs issue when clock is adjusted too much. * Fix Security scheme inherit logic. * Fix Alarm v3 event type mask bug. * Make saveObject() atomic. * Fix Version CC segfault on version change * Workaround for devices not removing Security from NIF in usecure inclusion. Home Automation: * Notification CC (Alarm v3) renders vDev * Added Sonos and GlobalCache modules * Users management and authentication added New features: * Made queuing a bit faster: do not NACK job when received SOF while awaiting for ACK, CAN problem fixed. * JavaScript sockets module now supports non-blocking, asynchronous, multicast, broadcast and reusable sockets. * JavaScript XML module namespaces support added in findOne/findAll. * Added possibility to write own V8 extensions. Sample code here: http://razberry.z-wave.me/fileadmin/modsample.tgz * Added processPendingCallback() call in JS code (to keep callbacks working while handling slow code in JS). * Add UZB driver to Windows installer UI: * Removed Z-Way HA UI and Blue UI. * Made new Smart Home UI as default HA. ## 03.02.2015 v2.0.1-rc15 Z-Wave Plus certified for US (Certification Number ZC10-15010005). Support for UZB1 added. Require additional license from license.z-wave.me (not finished yet !!!!!!). From now Windows is maintained as well. Command Classes: * FirmwareUpdate progress added. * ThermostatSetPoint v3 thermostat modes added. * ManufacturerSpecific v2 implemented. New Command Classes: * MeterPulse * BarrierOperator * Hail (used to bypass Lutron patent in some old US devices). New features: * Added crypto module to JavaScript: crypto.sha256() and others (see docs). * Added sockets module to control third party devices via TCP and UDP (like Global Cache or Sonos). * Allow multi-level form serialization in http.request(). * Add support for HTTP compression of JS responses. * Serve first pre-gzipped static files if present. * Sort devices with equal probing score alphabetically (in GuessXML). Fixes: * Queueing made more stable. Sleeping secure devices managed better. * Interview made more stable: ** does not restart on Wakeup Notification from the device. ** continue interview on device wakeup. ** fixed problems with not all sensors rendered for SensorBinary V2. * Restore fixed on UZB1 and 5th gen RaZberry * Fixed issue with detached threads in http module. * Proxy can handle URLs with &. Used for some cameras. * Adopted latest changes in Apple HomeKit. * Encoding problems with Unicode in JS API. * Fixed problem with CRC16 and MultiCmd rendered due to corrupted packets from RF. * Fixed problem with wrong version of Command Class rendered during interview. * Fix problems caused by ether noise: ignore supported reports for already interviewed command classes. * Stability fixes. Home Automation: * Support for AlarmSensor (used in Fibaro devices and some others) in HA UI. * Logical Rules module can be triggered by scenes (not only by device change as previously). UI: * Localization added. You can translate Z-Way Homa Automation in your language in automation/lang/ and automation/modules/*/lang/. * RGB (SwitchColor CC) added to Z-Wave binding * Many small improvements in Expert UI * Small fixes in HA UI API changes: * SwitchColor CC now have new dataholder structure (same as other CCs with scales and types) * Added _ prefix to functions from z-commons library bytes_to_* and *_to_bytes to minimize risk of overload by user functions. * WebSocket API changed: event type added. Server can filter events based on this type before sending to clients. ## 21.11.2014 v2.0.0 Z-Wave Plus certified for EU (Certification Number ZC10-14110009). Z-Way can work as primary and as secondary controller (to work with other Z-Wave controllers). A lot of improvements and stability fixes. Command Classes: * SensorMultilevel will not create phantom sclaes anymore * Alarm v2 fixed * MultiCmd adopted to fix DLC 13 problem * Security implements inclusion timers for secure inclusion * SwitchBinary and SwitchMultilevel can now work as device * NodeNaming UTF16 fixed * MultiChannel Find Instance implemented * AGI bugs fixed * Association/MultiChannelAssociation autoconfig logic changed * PowerLevel support added New features: * Completely new software structure * HomeKit preliminary support. Waiting for apps in AppStore * Proxy implemented in the engine to pass thru WebCams and other content via find.z-wave.me * Websockets support * Syslog logging support added on Unix/Linux platforms * Add diagnostic messages in case of data access without a lock (for libzway users) * Support for new 5th gen UZB dongle and RaZberry (AutoFlashAutoProg, NMV, RFPowerLevel, SendTestFrame functions added) New Command Classes: * Proprietary for few devices on the market using it Minor changes in the API: * FirmwareUpdate dataholders renamed * New controller state Resetting assigned during reset to factory process (controller.data.controllerState) * Replace python-style DataHolders type names in ZDDX and JS: ** "NoneType" changed to "empty" ** "str" changed to "string" ** "str[]" changed to "string[]" * Data Holder JS property name is now hidden and is not returned in JS output neither in /ZWaveAPI/Data/ * Data Holders C API changes: all "zway_data_*(zway," should be changed to "zdata_*(" * Changed format of config.xml file and command line parameters. Now all Z-Wave related data moved into appropriate JS module * Functions loadJSON(), saveJSON() and similar moved to fs.loadJSON(), added fs.load() to load any file as string Fixes: * SUC/SIS handling enhanced * Works perfectly as secondary controller * PowerLevel full support on 5th gen dongles/RaZberries * AddNodeToNetwork and RemoveNodeFromNetwork cancel hang fixed * Max packet size handling enhanced * Answer-as-requested policy implemented to conform Z-Wave Plus (for Security/MultiCmd/CRC16) * zway.bind with EnumerateExisting fixed * RemoveFailedNode and ReplaceFailedNode timeout rised UI: * New Expert UI. Old Blue UI kept as second option. * Communication statistics added for more network debugging * New HA UI improved, more modules added About New Expert UI: * More installer friendly * Works perfectly on tablets * Multi Channel Associations can be set in a user friendly way like simple Associations * Communication Statistics for advanced analysis of network stability * Map removed from this UI * Firmware Upgrade support WebServer features: * It is possible to run many WebServers on different port with different API and handlers * Proxy support: webserver.proxify(url, target, [user, passwd]) creates transparent redirect * WebSockets clients can receive notification from WebServer via webserver.push(obj) method ## 26.07.2014 v1.7.2 Fixes and improvements: * Fix auth parameter in http.request() * Small Z-Way C core fixes and improvements * MeterTableMonitor historical data storage fix * Fix various minor issues in JS code * Fix DH deletion notification for array values * Fix memory leak in ZXmlDocument.findOne() and deviceTypeString * HTTP module: force adding content type for POST requests if not specified * Do not perform url encoding on http headers * Added optional timeout (ms) parameter to ZHttp * Correctly output JS functions as strings when returning from /JS/Run/func * Fix data holder value setter to work for both "dh.value=xxx" and "dh=xxx" New features: * New UI selector * Function classed for NVM operations for 5th gen dongles (for 6.5x SDK) Command Classes: * CentralScene: respond to supported get, return number of scenes from config * Time: respond with real time offset information, handle time offset report * ClimateControlSchedule: added Override Get/Set * Add UserCode SetRaw method for devices pretending user code to be binary. Minor change in the API: * givenName added to device dataholder to store names in Expert UI (for future use) * User Code data holder type now depends on actual code data (bnary or string) * Always read user code payload if present (even if status is 0) ## 18.06.2014 v1.7.1 Fixes and improvements: * Fix memory leak in http.request(). * Fix issues with too many threads created by many http.request(). * Make v8 debugger not to crash on ZXmlNode objects and on CC access. * Minor fix of UserCode clear. * Delivery statistics not recorder for encapsulated packets (only for physicaly sent). * z-cfg-update can now convert from very config starting from v1.2. * Improve experience with modern secure door locks inclusion. * Limited callbacks count on DataHolder from JS not to overkill the server from JS and to easily find bugs in JS code. * DataHolder callback with notifications for child events fixed. New features: * Experimental: v8 remote debugging! Debugger: The debugger is enabled by setting V8_DEBUG environment variable and uses port 8183 (base port + 100). Then you can connect either with d8 (./d8*-remote_debugger*-debugger_port=8183) or with node-inspector*-debug-port 8183. ## 23.05.2014 v1.7.0 Fixes and improvements * Check connectivity button fixed (NoOperation is not removed by Force Interview now). * Mark Failed Node and Remove Failed Node not always working fixed. * Blue UI fixes. * A lot small bugs fixed... New Command Classes: * PowerLevel * Version V2 * FirmwareUpdate * ZWave+ Info * AssociationGroupInformation * DeviceResetLocally * CRC16 * SwitchColor * CentralScene Minor change in the API: * Controlled only Command Classes are now also saved on server stop and loaded on starup. Allows to bind to dataholders on controlled Command Classes. * Added .supported datholder to all Command Classes data. * Battery level 255 is now mapped to 0 (so, 255 will be never seen from now). * Defaults.xml format changed!!! * xxxxxxxx-DevicesData.xml changed a bit. Added z-cfg-update utility to update xxxxxxxx-DevicesData.xml to the new format. * Timeouts rised for AddNodeToNetwork and RequestNodeNeighbourUpdate functions. New features: * Log JavaScript files and lines on exceptions for easier debug. * Log possible exceptions in HTTP. * Open serial port exclusively to prevent multiple Z-Way or some other software running in parallel. * Added secureInclusion for unsecure interview (set it to false to include device insecurely) * Keepalive enabled in Z-Way HTTP server * Z-Wave+ Associations made only to Life Line group #1 * SensorMultilevel new types implemented. * Blue UI ZDDX Create button. * Remote Access and 8084 port for maintainance added to the distribution. ## 13.02.2014 v1.5.0-rc1 New features: * XML parser in JavaScript with XPath (ZXmlDocument JS object). * HTTP network operations implemented in JavaScript (http JS object). * Basic, SwitchBinary and SwitchMultilevel Set events to controller now have srcInstance and srcNode to distinguish sender. Fixes and improvements: * Works now on new Raspbian based on 3.10.x kernels too. * Better network management for large networks. * Minimize packets flow with Secure nodes (might not work with ald Kwikset doorlock). * Better packet flow for sleeping secure devices. * Alarm and UserCode CC minor fixes. * Basic->SensorBinaryV1 mapping fixed. * Backup/Restore SDK names fixed. Minor change in the API: * bind() now returns just bound function instead of undefined * Basic dataholders lastset and mylevel removed. * SwitchBinary level dataholder made boolean. ## 23.10.2013 v1.4.1 New Command Classes are implemented: * Alarm CC V1-3 supported (no expert UI yet). * MeterTable CC V2 supported (no expert UI yet). Minor change in the API: * SwitchBinary Set value is now boolean. New features: * Communication statistics gathered in devices[N].data.lastPacketInfo Fixes: * Sensor Binary fixed to receive changes from devices * Some fixes in the blue UI * Minor bug fixes ## 24.09.2013 v1.4 All available UIs are shipped included now. Go to /index.html page to select a UI: * future UI (in development) * current UI (old blue for experts) * old jQuery mobile UI New Command Classes are implemented: * ApplicationStatus * DoorLockLogging * Indicator * Meter v3 * Protection v1 & v2 * ScheduleEntryLock * SensorBinary v2 * SensorConfiguration * ThermostatFanMode * ThermostatFanState * ThermostatOperatingState v2 * TimeParameters Home automation engine poject started (code in development, stored on https://github.com/Z-Wave-Me/home-automation) Important API changes: * loadJSON function added to API to allow load a file from program folder. * Command Class SensorBinary data tree has changed. Now it contains sensor types like SensorMultilevel. * SwitchMultilevel commands SetWithDuration and StartLevelChangeWithDuration are removed (Set and StartLevelChange should be used). * SwitchMultilevel Set/StartLevelChange are always with duration in C. * New ChildCreated event on dataholder added to trap new child node cration. * New JavaScript methods: fs.list(dir) and fs.stat(file). Fixes: * ThermostatSetPoint, SensorMultilevel, Meter UserCode improved, some minor problems fixed. * Full RF power during inclusion/exclusion is restored. * Thermostat temperature C/F conversion fixed (mostly for US products) Other features: * New V8 engine is used for better stability and performance. The code was reworked a lot to become faster and more stable. * Better logging with log levels (check config.xml). NB! You need to force interview for all devices with SensorBinary and SensorMultilevel Command Classes. Go to Device Configuration tab, toggle Expert mode (bottom right corner) and press Force Interview under Advanced actions cut link. NB! After backup & restore process it is recommended to re-install Z-Way (using same command as to install it). Otherwise it might not run next time after restore. (due to old config/Defaults.xml comming from old package). ## 19.05.2013 v1.3.1 New feature: * Backup/Restore implemented. You can restore config files only or full Z-Wave topology. ## 17.05.2013 v1.3 New features and improvements: * Full Security support with all certified locks (tested with Kwikset, Vision Security, Yale) * Communications with Danfoss living connect tuned to stop battery drain by the thermostat (full re-inclusion is required to heal Danfoss living connect!). * Better performance and memory footprint. We have made major refactor of our Z-Wave engine to make it faster and more memory efficient! * Better queue handling for failed nodes preventing them from blocking the full queue. Now even unpowered or failed devices in your network will not affect user experience anymore. * Added public method in C and JS API to check if command class is supported. * Release information stored in Z-Way library. New Command Classes: * MultiChannel v3 * SceneActivation * SceneControllerConf * SceneActuatorConf * Clock * Time Bug fixes: * fix of system() call in JavaScript * UserCode.Set() * crash on broadcast Basic.Set * potential problems with Add/RemoveNodeFromNetwork/ControllerChange/CreateNewPrimary/RemoveFailedNode Function Calls. Changes in frontend: * Translations to Spanish, Russia, Deutsch. We will appreciate your help to translate our UI to your language. * Thermostat UI fixed. Major changes in software design: * devices[x].data.ZDDXMLLang and devices[x].data.ZDDXML removed: client must load the file called devices[x].data.ZDDXMLFile.value from the backend and parse the language content. !NB Some devices might require interview force to show properly Expert Commands and configuration parameters! ## 29.03.2013 v1.2 New Command Classes: * SenorMultilevel V5 support (NB! Structure of SenorMultilevel data holder changed for all versions of this Command Class) New features: * /ZWaveAPI/Run/[...].data.[...] now returns dataholder tree in same structure as /ZWave/Data/[...] * shortcuts for CCs on device and instance in JS syntax: you can now write devices[5].Basic.Set(255) instead of devices[5].instances[0].commandClasses.Basic.Set(255)* both "commandClasses" and "instanes[0]" can be omitted Fixes: * Unnecessary Command Classes on devices removed * Queue handling after restart * Line endings in ZDDX that caused UI to stop working * More sanity checks to prevent crashes ## 04.03.2013 v1.1 New features: * Z-Wave secure door locks support New Command Classes: * Security * DoorLock * UserCode New methods in JavaScript to make more fun with JS. See documentation on documentation RaZberry page. Minor bug fixes. # Release 1.0.1 (in progress) * Removed `actions` and `metrics` properties from the `module.json` of all modules. * Module classes automatically loaded to the controller. Removed `modules` property from the `config.json`. * Introduced `skip` property to the `module.json` which instructs AutomationController to skip module class loading. * Introduced `autoloadPriority` property to the `module.json` which determines automatic module instantiation order (lower** the sooner). * Introduced `caps` property to the Virtual Device which allows to list device capabilities. * Widgets moved to the new CommonWidgets module. Creation and registering custom widgets made possible. * Module's templates and htdocs folders now resides in ./templates/ and ./htdocs/modules/ * `config.json` existence checking on startup * New configuration setting `vdevInfo` allows to set human-readable device names and tags * Introduced icon VDev metric * Introduced `VirtualDevice.deviceTitle` & `VirtualDevice.deviceIcon` methods * VirtualDevice now requires `init()` to be called after instantiation * Widget's htdocs and Module's templates moded outside of the module's folder (for all modules) * Introduced .caps vDev's property which contains device extended capabilities tags (take a look at BatterPolling module) * Introduced className property in the widget's meta-description which allows to set custom widget className (essential to custom widgets) * UI is now capable of showing widgets created and registered by the customer * Renamed AutomationController.widget* to AutomationController.widgetClass* and changed widget subsystem behaviour to resolve ambiguity * /ZAutomation/api/widgets/ now replies with widget classes definitions instead of widgets itself. Latter exists and creates only on the client-side, but exact widget's class names described in a vDevs. * UI Dashboard refactored according to widgets subsystem changes * Fixed bug with loading ZWaveDoorlockDevice. DoorlockWidget implemented. * Added `exact` command to the MultilevelSwitch * SwitchBinary & SwitchMultilevel now has equal deviceType (switch) and different deviceSubTypes * Added tags and locations subsystems * Added support multi-profiles * Added control widgets position * Refactored API (devices, locations) and added new methods (modules, profiles) # Release 1.0.0 (initial) ================================================ FILE: README.md ================================================ Z-Way Home Automation Engine ==================================== # Introduction # Join Chat on Gitter ## Available WebUI's: * Smart Home UI: https://github.com/Z-Wave-Me/zwave-smarthome * Smart Home Lite UI: https://github.com/Z-Wave-Me/z-wave-smarthome-lite/tree/mobile * Expert UI: https://github.com/Z-Wave-Me/ExpertUI ## Pull Requests 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. ## Complete Documentation: * [Z-Way Manual](https://z-wave.me/manual/z-way) ## Home Automation Documentation's: * https://github.com/Z-Wave-Me/home-automation/wiki ## API Documentation: #### ZAutomation: * http://docs.zwayhomeautomation.apiary.io/ (still in work) ## Issues, bugs and feature requests are welcome: * [Home Automation issues](https://github.com/Z-Wave-Me/home-automation/issues) * [Smart Home UI issues](https://github.com/Z-Wave-Me/zwave-smarthome/issues) * [Expert UI issues](https://github.com/Z-Wave-Me/ExpertUI/issues) * [Z-Way core issues](https://github.com/Z-Wave-Me/z-way-issues/issues) ## Available Z-Way Server Downloads: * 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 changelog](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. * https://developer.z-wave.me/ There are also some How To's about creating own apps for home automation and what to be considered during creating virtual Devices. * [Help and How To's](https://developer.z-wave.me/?uri=help) * also checkout the tab Upload-Process to see how it works You can also find a list of all [currently published apps](https://developer.z-wave.me/?uri=public#/web/apps) ## Discussion platforms: * https://forum.z-wave.me/ (discussions available in en/de/ru/fr/it/cn, use General or RaZberry forums) ================================================ FILE: StorageProvider.js ================================================ /*** Main Automation storage module ***************************************** Version: ------------------------------------------------------------------------------- Author: Stanislav Morozov Copyright: (c) ZWave.Me, 2014 ******************************************************************************/ // ---------------------------------------------------------------------------- // --- ZAutomationStorageWebRequest // ---------------------------------------------------------------------------- function ZAutomationStorageWebRequest () { ZAutomationStorageWebRequest.super_.call(this); this.allow_extensions = ['jpg', 'jpeg', 'png', 'gif', 'svg']; this.res = { status: 200, headers: { "Content-Type": "application/json; charset=utf-8" }, body: null } }; inherits(ZAutomationStorageWebRequest, ZAutomationWebRequest); ZAutomationStorageWebRequest.prototype.statusReport = function () { return function () { var reply = { error: null, data: "OK", code: 200 }; this.initResponse(reply); } } ZAutomationStorageWebRequest.prototype.uploadFileFunc = function () { var self = this; return function () { var reply = { error: 'Permission dendied', data: null, code: 403 }; if (req.role === controller.auth.ROLE.ADMIN) { var file = self.req.body.file, date = new Date(), extension = file.name.split('.').pop().toLowerCase(), fileName, type; reply = { error: 'Allow extensions ' + self.allow_extensions.join(','), data: null, code: 400 }; if (extension === 'jpg') { extension = 'jpeg'; } fileName = 'storage-' + (+date / 1000).toFixed(0) + '.' + extension; if (self.allow_extensions.indexOf(extension) !== -1) { type = 'image/' + extension; file.type = type; file.createdAt = date.toJSON(); saveObject(fileName, file, true); reply = { error: null, data: { uri: '/ZAutomation/storage/' + fileName, originalName: file.name, length: file.length, type: type }, code: 200 }; } } self.initResponse(reply) } }; ZAutomationStorageWebRequest.prototype.getFileFunc = function (fileId) { var self = this; return function () { var file = loadObject(fileId), ifNoneMatch = self.req.headers.hasOwnProperty('If-None-Match'); if (file && !ifNoneMatch) { self.res.headers = { 'Content-Type': file.type, ETag: 'W/' + fileId + file.createdAt, 'Cache-Control': 'public, max-age=31536000', 'Last-Modified': (new Date(file.createdAt)).toUTCString() }; self.res.body = file.content; self.res.code = 200; } else if (file && ifNoneMatch && self.req.headers['If-None-Match'] === 'W/' + fileId + file.createdAt) { self.res = { status: 304, headers: { "API-Version": "2.0.1", 'Content-Type': file.type }, body: '' }; } else { self.initResponse({ data: null, error: 'File isn\'t found', code: 404 }); } } }; ZAutomationStorageWebRequest.prototype.dispatchRequest = function (method, url) { // Default handler is NotFound var self = this, handlerFunc = this.NotFound; // ---------- Test exact URIs --------------------------------------------- if ("GET" === method && "/status" === url) { handlerFunc = self.statusReport(); } else if ("POST" === method) { handlerFunc = self.uploadFileFunc(); } // ---------- Test regexp URIs -------------------------------------------- var re, reTest, fileId; // --- Perform vDev command if (handlerFunc === this.NotFound) { re = /\/(.+)/; reTest = re.exec(url); if (!!reTest) { fileId = reTest[1]; if ("GET" === method && !!fileId) { handlerFunc = self.getFileFunc(fileId); } else { handlerFunc = self.uploadFileFunc(); } } } // --- Proceed to checkout =) return handlerFunc; }; ================================================ FILE: Utils.js ================================================ // Comon utilities and functions var console = { log: debugPrint, warn: debugPrint, error: debugPrint, debug: debugPrint, logJS: function () { var arr = [], pretty = undefined; for (var key in arguments) { if (key == 0 && arguments[key] === "pretty") { // unstrict key == 0 since it is a string pretty = " "; continue; } arr.push(JSON.stringify(arguments[key], undefined, pretty)); } debugPrint(arr); }, logJSP: function() { Array.prototype.unshift.call(arguments, "pretty"); console.logJS.apply(null, arguments); } }; function inherits(ctor, superCtor) { Object.defineProperty(ctor, "super_", { value: superCtor, enumerable: false, writable: false, configurable: false }); ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); } function in_array(array, value) { return -1 !== array.indexOf(value); } function is_function(func) { return !!(func && func.constructor && func.call && func.apply); } function has_key(obj, key) { return -1 !== Object.keys(obj).indexOf(key); } function get_values(obj) { var res = []; Object.keys(obj).forEach(function (key) { res.push(obj[key]); }); return res; } function byteArrayToString(data) { if (typeof ArrayBuffer !== 'undefined' && (data instanceof ArrayBuffer)) { data = new Uint8Array(data); } var output = ""; for (var i = 0; i < data.byteLength; i++) { output += String.fromCharCode(data[i]); } return output; } function byteArrayToHexString(data) { var output = ""; for (var i = 0; i < data.byteLength; i++) { output += ('0' + data[i].toString(16)).slice(-2); } return output; } function generateSalt() { return Base64.encode(byteArrayToString(crypto.random(64))); } function hashPassword(password, salt) { return Base64.encode(byteArrayToString(crypto.sha512(password + salt))); } function has_higher_version(newVersion, currVersion) { var isHigher = false, newVersion = newVersion && newVersion.toString() ? newVersion.toString().split('.') : null, currVersion = currVersion && currVersion.toString() ? currVersion.toString().split('.') : null; if (!!newVersion && !!currVersion) { for (var i = 0; i < currVersion.length; i++) { if ((parseInt(currVersion[i], 10) < parseInt(newVersion[i], 10)) || ((parseInt(currVersion[i], 10) <= parseInt(newVersion[i], 10)) && (!currVersion[i + 1] && newVersion[i + 1] && parseInt(newVersion[i + 1], 10) > 0))) { isHigher = true; break; } } } return isHigher; } /* * Iterate trough object, find the key and change its value * will change all keys that are equal to key */ function changeObjectValue(obj, key, value) { var objects = []; for (var i in obj) { if (!obj.hasOwnProperty(i)) { continue; } // arrays and objects are treated as objects if (!!obj[i] && typeof obj[i] === 'object') { objects = objects.concat(changeObjectValue(obj[i], key)); } else if (i === key) { obj[i] = value; } } return obj; } var Base64 = { _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", encode: function (e) { var t = ""; var n, r, i, s, o, u, a; var f = 0; e = Base64._utf8_encode(e); while (f < e.length) { n = e.charCodeAt(f++); r = e.charCodeAt(f++); i = e.charCodeAt(f++); s = n >> 2; o = (n & 3) << 4 | r >> 4; u = (r & 15) << 2 | i >> 6; a = i & 63; if (isNaN(r)) { u = a = 64 } else if (isNaN(i)) { a = 64 } t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a) } return t }, decode: function (e) { var t = ""; var n, r, i; var s, o, u, a; var f = 0; e = e.replace(/[^A-Za-z0-9\+\/\=]/g, ""); while (f < e.length) { s = this._keyStr.indexOf(e.charAt(f++)); o = this._keyStr.indexOf(e.charAt(f++)); u = this._keyStr.indexOf(e.charAt(f++)); a = this._keyStr.indexOf(e.charAt(f++)); n = s << 2 | o >> 4; r = (o & 15) << 4 | u >> 2; i = (u & 3) << 6 | a; t = t + String.fromCharCode(n); if (u != 64) { t = t + String.fromCharCode(r) } if (a != 64) { t = t + String.fromCharCode(i) } } t = Base64._utf8_decode(t); return t }, _utf8_encode: function (e) { var t = ""; for (var n = 0; n < e.length; n++) { var r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r) } else if (r > 127 && r < 2048) { t += String.fromCharCode(r >> 6 | 192); t += String.fromCharCode(r & 63 | 128) } else { t += String.fromCharCode(r >> 12 | 224); t += String.fromCharCode(r >> 6 & 63 | 128); t += String.fromCharCode(r & 63 | 128) } } return t }, _utf8_decode: function (e) { var t = ""; var n = 0; var r = c1 = c2 = 0; while (n < e.length) { r = e.charCodeAt(n); if (r < 128) { t += String.fromCharCode(r); n++ } else if (r > 191 && r < 224) { c2 = e.charCodeAt(n + 1); t += String.fromCharCode((r & 31) << 6 | c2 & 63); n += 2 } else { c2 = e.charCodeAt(n + 1); c3 = e.charCodeAt(n + 2); t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63); n += 3 } } return t } }; var formRequest = { _randomString: function () { var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; var rString = ''; for (var i = 0; i < 15; i++) { var rnum = Math.floor(Math.random() * chars.length); rString += chars.substring(rnum, rnum + 1); } return rString; }, send: function (formElements, method, url) { var boundary = "----WebKitFormBoundary" + formRequest._randomString(); var obj = {}; // set method obj.method = method; // set headers obj.headers = { "Content-Type": "multipart/form-data; boundary=" + boundary }; // set url obj.url = url; // init data obj.data = ""; // create form boundary for (var index in formElements) { obj.data += '--' + boundary + '\r\n'; obj.data += 'Content-Disposition: form-data; name="' + formElements[index].name + '"' + (formElements[index].filename ? ('; filename="' + formElements[index].filename) + '"' : '') + '\r\n'; obj.data += formElements[index].type ? ('Content-Type: ' + formElements[index].type + '\r\n\r\n') : '\r\n'; obj.data += formElements[index].value + '\r\n'; } ; // end of boundary obj.data += '\r\n--' + boundary + '--\r\n'; // return cloud server response return http.request(obj); } }; function getHRDateformat(now) { 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); return ts; }; function checkBoxtype(type) { var match = false; try { var bT = system('cat /etc/z-way/box_type'); bT.forEach(function (bType) { match = typeof bType === 'string' && (bType.indexOf(type) > -1 || bType === type) ? true : false; }); } catch (e) { } return match; }; function parseToObject(object) { return object && typeof object === "string" ? JSON.parse(object) : object; }; function checkInternetConnection(host_url) { var cn = false, response = 'in progress', d = Date.now() + 15000; // wait not more than 15 sec // try to reach google to check for internet connection http.request({ url: host_url, async: true, success: function (res) { response = 'done'; cn = true; }, error: function (res) { response = 'failed'; } }); // wait for response while (Date.now() < d && response === 'in progress') { processPendingCallbacks(); } if (response === 'in progress') { response = 'failed'; } return cn; }; /* * check for true / false * also if the variable is from type string e.g. 'true' / 'false' */ function retBoolean(boolean) { if (boolean === true || boolean === 'true') { return true; } else { 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; } /* * Dump object and fix circular references to output it * (for debug purposes) */ function dumpObject(obj, ancetors) { if (typeof obj !== "object") return obj; if (obj === null) return null; if (obj === undefined) return undefined; var result = Array.isArray(obj) ? [] : {} ; var keys = Object.keys(obj); if (!ancetors) ancetors = []; for (var i in keys) { var key = keys[i]; if (typeof obj[key] === "object") { var circular = false; for (var j in ancetors) { if (obj[key] === ancetors[j]) { circular = true; break; } } if (circular) { result[key] = "circular reference to ancetor (" + (ancetors.length - j) + " up)"; } else { var new_ancetors = ancetors.slice(); // copy array new_ancetors.push(obj); result[key] = dumpObject(obj[key], new_ancetors); } } else { result[key] = obj[key]; } } return result; } function debugPrintStack() { try { throw new Error("Printing stack trace"); } catch(e) { console.log(e.stack); } } ================================================ FILE: Webserver.js ================================================ /*** Initialize Webserver and Handlers ***************************************** Version: ------------------------------------------------------------------------------- Author: Serguei Poltorak Copyright: (c) ZWave.Me, 2015 ******************************************************************************/ ws = new WebServer(8083, function(req) { if (req.method === "OPTIONS") { return this.addHTTPHeaders({ status: 200 }); } var found = ws.find(req); if (found) { var auth = controller.auth.resolve(req, found.role); if (!auth) { return this.addHTTPHeaders({ status: 401, body: "Not logged in" }); } else if (controller.auth.isAuthorized(auth.role, found.role)) { return this.addHTTPHeaders(ws.execute(found.name, req, auth)); } else { return this.addHTTPHeaders({ status: 403, body: 'Permission denied' }); } } return null; }, function(req) { var auth = controller.auth.resolve(req, controller.auth.ROLE.USER); if (!auth) { return 0; // there is no profile with Id = 0 } else { return auth.user; } }, function(user, msg) { var obj = JSON.parse(msg); var profile = controller.getProfile(user); if (profile && obj.event === "httpEncapsulatedRequest") { var role = profile.role; // authentication data var auth = { user: user, role: role, token: "....." }; // extract query string var query = {}; var url = obj.data.url; var i = url.indexOf("?"); if (i > -1) { url.substring(i + 1).split("&").map(function(q) { var qq = q.split("="); query[qq[0]] = qq[1] }); url = url.substring(0, i); } // body var body = obj.data.body; var responseEvent = obj.responseEvent; var req = { method: obj.data.method ? obj.data.method.toUpperCase() : "GET", url: url, fullUrl: obj.data.url, query: query, body: (typeof body === "string" || typeof body === "undefined") ? body : JSON.stringify(body), peer: { address: "....", port: 0000 }, headers: {}, __authMethod: "....", user: auth.user, role: auth.role, authToken: auth.token }; var found = ws.find(req); var response; if (found && controller.auth.isAuthorized(role, found.role)) { response = ws.execute(found.name, req, auth); } else { response = { status: 404 }; } return { "ws-reply-type": responseEvent, "ws-reply-data": response }; } }, { document_root: "htdocs" }); ws.find = function(req) { var q = req.url.substring(1).replace(/\//g, '.'); if (!q) return null; var found = null; if (this.externalNames.some(function(ext) { found = ext; return (ext.name.length < q.length && q.slice(0, ext.name.length + 1) === ext.name + ".") || (ext.name === q); })) { return found; } else { return null; } }; ws.execute = function(name, req, auth) { var cache = this.evalCache || (this.evalCache = {}); var handler = cache[name] || (cache[name] = evalPath(name)); return handler(req.url.substring(name.length + 1), req, auth); }; ws.externalNames = []; // array of object {name, role} ws.allowExternalAccess = function(name, role) { // refresh cache anyways, even if adding duplicate name if (this.evalCache) delete this.evalCache[name]; var idx = this.externalNames.map(function (ext) { return ext.name}).indexOf(name); if (idx >= 0) return; this.externalNames.push({name: name, role: role || controller.auth.ROLE.ADMIN}); this.externalNames.sort(function(x, y) { return (y.name.length - x.name.length) || (x.name > y.name ? 1 : -1); }); }; ws.revokeExternalAccess = function(name) { // remove cached handler (if any) if (this.evalCache) delete this.evalCache[name]; var idx = this.externalNames.map(function (ext) { return ext.name}).indexOf(name); if (idx === -1) return; this.externalNames.splice(idx, 1); }; // Standard HTTP headers: HTTP and keep-laive ws.allow_headers = [ 'Authorization', 'Accept-Ranges', 'Content-Encoding', 'Content-Length', 'Content-Range', 'Content-Type', 'ETag', 'X-API-VERSION', 'Date', 'Cache-Control', 'If-None-Match', 'Content-Language', 'Accept-Language', 'X-ZBW-SESSID', 'ZWAYSession' ].join(', '); ws.addHTTPHeaders = function (ret) { if (!ret.headers) ret.headers = {}; ret.headers['Access-Control-Allow-Origin'] = '*'; ret.headers['Access-Control-Allow-Methods'] = 'GET, PUT, POST, DELETE, OPTIONS'; ret.headers['Access-Control-Allow-Headers'] = this.allow_headers; ret.headers['Access-Control-Expose-Headers'] = this.allow_headers; ret.headers['Connection'] = 'keep-alive'; ret.headers['Date'] = (new Date()).toUTCString(); return ret; }; ================================================ FILE: WebserverRequestRouter.js ================================================ /*** ZAutomationAPI Web Request Handler ***************************************** Version: ------------------------------------------------------------------------------- Author: Stanislav Morozov Copyright: (c) ZWave.Me, 2014 ******************************************************************************/ // ---------------------------------------------------------------------------- // --- ZAutomationWebRequest // ---------------------------------------------------------------------------- function ZAutomationWebRequest() { this.req = {}; this.res = { status: 501, headers: { "X-API-VERSION": "2.0.1", "Content-Type": "text/plain; charset=utf-8" }, body: null }; this.error = null; } ZAutomationWebRequest.prototype.handlerFunc = function () { var self = this; return function () { return self.handleRequest.apply(self, arguments); }; }; ZAutomationWebRequest.prototype.initResponse = function (response) { var that = this, reply, version = "2.0.1", fields, object = {}, data, mainKey = null, subPaths = ['notifications', 'devices'], tempData, pager = null, limit = that.req.query.hasOwnProperty('limit') ? parseInt(that.req.query['limit']) : 10, offset = that.req.query.hasOwnProperty('offset') ? parseInt(that.req.query['offset']) : 0, pagination = that.req.query.hasOwnProperty('pagination') ? that.req.query['pagination'] : false, query = that.req.query.hasOwnProperty('q') ? String(that.req.query['q']).toLowerCase() : null, httpCode = { 200: "200 OK", 201: "201 Created", 204: "204 No Content", 304: "304 Not Modified", 400: "400 Bad Request", 401: "401 Unauthorized", 403: "403 Forbidden", 404: "404 Not Found", 405: "405 Method Not Allowed", 501: "501 Not Implemented", 500: "500 Internal server error" }; response.data = response.data === undefined ? null : response.data; response.error = response.error || null; response.code = response.code || 200; response.contentType = response.contentType || "application/json; charset=utf-8"; response.message = response.message || null; tempData = response.data; data = response.data; if (data) { subPaths.forEach(function (path) { if (response.data.hasOwnProperty(path)) { mainKey = path; tempData = response.data[path]; data = response.data[path]; } }); } // filter fields if (!!tempData && response.code === 200 && that.req.query.hasOwnProperty('fields')) { fields = that.req.query['fields'].split(','); if (fields.length) { if (Array.isArray(tempData)) { data = []; tempData.forEach(function (model) { object = {}; Object.keys(model).forEach(function (key) { if (fields.indexOf(key) !== -1) { object[key] = model[key]; } }); data.push(object); }); } else { data = {}; Object.keys(response.data).forEach(function (key) { if (fields.indexOf(key) !== -1) { data[key] = response.data[key]; } }); } } } if (Array.isArray(tempData) && mainKey) { // search if (query && Array.isArray(tempData)) { tempData = []; if (mainKey === 'devices') { data.forEach(function (obj) { if (obj.metrics.title.toLowerCase().indexOf(query) !== -1) { tempData.push(obj); } }); } else if (mainKey === 'notifications') { data.forEach(function (obj) { if (obj.message.toLowerCase().indexOf(query) !== -1) { tempData.push(obj); } }); } data = tempData; } // pager if (limit > 0 && offset >= 0 && String(pagination) === 'true') { data = data.slice(offset, offset + limit); pager = { total: data.length, totalPage: Math.round(data.length / limit), limit: limit, offset: offset, page: (offset + limit) / limit }; } } // include if (!!mainKey) { response.data[mainKey] = data; data = response.data; } if (that.req.query.hasOwnProperty('suppress_response_codes') && String(that.req.query.suppress_response_code) === 'true') { response.code = '200'; } else { if (!!data) { response.code = response.code || 200; } else if (that.req.method === 'DELETE' || !data) { response.code = response.code || 204; response.data = null; response.message = '204 No Content'; } } if (String(pagination) === 'true') { _.extend(data, { pager: { offset: offset, limit: limit, page_total: response.data[mainKey].length } }); } reply = { data: data, code: response.code, message: httpCode[response.code], error: response.error }; if (pager) { reply.pager = pager; } var headers = { 'Content-Type': response.contentType, 'X-API-VERSION': version, }; if (response.code === 401 && !response.suppress401Auth) { _.extend(headers, {'WWW-Authenticate': 'Basic realm="Z-Way ZAutomation API"'}); } if (response.headers) { for (var hname in response.headers) { headers[hname] = response.headers[hname]; } } that.res = { status: response.code, body: JSON.stringify(reply), headers: headers }; return that.res; }; ZAutomationWebRequest.prototype.dispatchRequest = function (method, url) { return this.NotImplementedReply; }; ZAutomationWebRequest.prototype.handleRequest = function (url, request) { var self = this, requestProcessorFunc, bodyLength, response; response = { data: null, error: null, code: 200, message: null }; // Fill internal structures this.req.url = url; this.req.fullUrl = request.fullUrl || ""; this.req.method = request.method; this.req.query = request.query || {}; this.req.body = request.body || request.data; this.req.headers = request.headers || {}; this.req.peer = request.peer; this.req.user = request.user; this.req.role = request.role; this.req.authToken = request.authToken; var contentType = request.headers['Content-Type'] || ""; // set defaultLang if (self.req.query.hasOwnProperty('lang') || self.req.headers['Accept-Language']) { self.controller.setDefaultLang(self.req.query.lang || self.req.headers['Accept-Language']); } if (['PUT', 'POST'].indexOf(this.req.method) !== -1 && contentType.toLowerCase().indexOf('application/json') !== -1) { try { this.req.reqObj = JSON.parse(this.req.body); } catch (ex) { response.code = 500; response.error = 'JSON Parse Error [Syntax Error]'; } } if (response.error === null) { // Get and run request processor func requestProcessorFunc = this.dispatchRequest(request.method, url); response = requestProcessorFunc.call(this); } // Return to the z-way-http return response ? this.initResponse(response) : this.res; }; ZAutomationWebRequest.prototype.NotImplementedReply = function () { this.res = { status: 501, body: "Not implemented, yet", headers: { "Content-Type": "text/plain; charset=utf-8" } }; }; ZAutomationWebRequest.prototype.NotFound = function () { this.res = { status: 404, headers: { "Content-Type": "text/plain; charset=utf-8" }, body: "Not Found" }; }; ================================================ FILE: ZAutomationAPIProvider.js ================================================ /*** ZAutomationAPI Provider ************************************************** Version: ------------------------------------------------------------------------------- Author: Gregory Sitnin Copyright: (c) ZWave.Me, 2013 ******************************************************************************/ // ---------------------------------------------------------------------------- // --- ZAutomationAPIWebRequest // ---------------------------------------------------------------------------- executeFile("router.js"); function ZAutomationAPIWebRequest(controller) { ZAutomationAPIWebRequest.super_.call(this); this.ROLE = controller.auth.ROLE; this.router = new Router("/v1"); this.controller = controller; this.res = { status: 200, headers: { "Content-Type": "application/json; charset=utf-8" }, body: null }; this.registerRoutes(); }; var ZAutomationWebRequest = ZAutomationWebRequest || function() {}; inherits(ZAutomationAPIWebRequest, ZAutomationWebRequest); _.extend(ZAutomationAPIWebRequest.prototype, { registerRoutes: function() { this.router.get("/status", this.ROLE.USER, this.statusReport); this.router.get("/session", this.ROLE.ANONYMOUS, this.verifySession); this.router.post("/login", this.ROLE.ANONYMOUS, this.verifyLogin); this.router.get("/logout", this.ROLE.USER, this.doLogout); 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("/devices", this.ROLE.USER, this.listDevices); this.router.get("/restart", this.ROLE.ADMIN, this.restartController); this.router.get("/locations", this.ROLE.USER, this.listLocations); this.router.get("/profiles", this.ROLE.USER, this.listProfiles); this.router.get("/namespaces", this.ROLE.ADMIN, this.listNamespaces); this.router.post("/profiles", this.ROLE.ADMIN, this.createProfile); this.router.get("/locations/add", this.ROLE.ADMIN, this.addLocation); this.router.post("/locations", this.ROLE.ADMIN, this.addLocation); this.router.get("/locations/remove", this.ROLE.ADMIN, this.removeLocation); 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); // module tokens this.router.get("/modules/tokens", this.ROLE.ADMIN, this.getModuleTokens); this.router.put("/modules/tokens", this.ROLE.ADMIN, this.storeModuleToken); this.router.del("/modules/tokens", this.ROLE.ADMIN, this.deleteModuleToken); this.router.get("/instances", this.ROLE.ADMIN, this.listInstances); this.router.post("/instances", this.ROLE.ADMIN, this.createInstance); this.router.post("/upload/file", this.ROLE.ADMIN, this.uploadFile); // patterned routes, right now we are going to just send in the wrapper // function. We will let the handler consumer handle the application of // the parameters. this.router.get("/devices/:v_dev_id/command/:command_id", this.ROLE.USER, this.performVDevCommandFunc); this.router.get("/devices/:v_dev_id/referenced", this.ROLE.ADMIN, this.getDeviceReference); this.router.get('/devices/:dev_id/:param/:innerParam', this.ROLE.USER, this.getVDevParam); this.router.get('/devices/:dev_id/:param', this.ROLE.USER, this.getVDevParam); 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); this.router.get("/notifications/:notification_id", this.ROLE.USER, this.exposeNotifications, [parseInt]); 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/token/:token", this.ROLE.USER, this.removeToken, [parseInt, undefined]); this.router.put("/profiles/:profile_id/token/:token", this.ROLE.USER, this.permanentToken, [parseInt, undefined]); this.router.get("/profiles/token/local/:profile_id", this.ROLE.USER, this.generateLocalAccessToken, [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]); this.router.del("/profile", this.ROLE.USER, this.removeOwnProfile); this.router.post("/oauth2", this.ROLE.ADMIN, this.createOAuth2Profile); this.router.get("/notificationFiltering", this.ROLE.USER, this.notificationFilteringGet); this.router.put("/notificationFiltering", this.ROLE.USER, this.notificationFilteringSet); this.router.get("/notificationChannels", this.ROLE.USER, this.notificationChannelsGet); this.router.get("/notificationChannels/all", this.ROLE.ADMIN, this.notificationChannelsGetAll); this.router.post("/auth/forgotten", this.ROLE.ANONYMOUS, this.restorePassword); this.router.post("/auth/forgotten/:profile_id", this.ROLE.ANONYMOUS, this.restorePassword, [parseInt]); this.router.put("/auth/update/:profile_id", this.ROLE.ANONYMOUS, this.updateProfileAuth, [parseInt]); this.router.put("/devices/:dev_id", this.ROLE.USER, this.setVDevFunc); this.router.get("/devices/:dev_id", this.ROLE.USER, this.getVDevFunc); this.router.get("/instances/:instance_id", this.ROLE.ADMIN, this.getInstanceFunc); this.router.put("/instances/:instance_id", this.ROLE.ADMIN, this.reconfigureInstanceFunc, [parseInt]); this.router.del("/instances/:instance_id", this.ROLE.ADMIN, this.deleteInstanceFunc, [parseInt]); 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); 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); this.router.post("/restore", this.ROLE.ADMIN, this.restore); this.router.get("/resetToFactoryDefault", this.ROLE.ADMIN, this.resetToFactoryDefault); // skins tokens this.router.get("/skins/tokens", this.ROLE.ADMIN, this.getSkinTokens); this.router.put("/skins/tokens", this.ROLE.ADMIN, this.storeSkinToken); this.router.del("/skins/tokens", this.ROLE.ADMIN, this.deleteSkinToken); this.router.get("/skins", this.ROLE.ADMIN, this.getSkins); this.router.post("/skins/install", this.ROLE.ADMIN, this.addOrUpdateSkin); this.router.put("/skins/update/:skin_id", this.ROLE.ADMIN, this.addOrUpdateSkin); this.router.get("/skins/setToDefault", this.ROLE.ADMIN, this.setDefaultSkin); this.router.get("/skins/active", this.ROLE.ANONYMOUS, this.getActiveSkin); this.router.get("/skins/:skin_id", this.ROLE.ADMIN, this.getSkin); this.router.put("/skins/:skin_id", this.ROLE.ADMIN, this.activateOrDeactivateSkin); this.router.del("/skins/:skin_id", this.ROLE.ADMIN, this.deleteSkin); this.router.get("/icons", this.ROLE.ADMIN, this.getIcons); this.router.del("/icons/:icon_id", this.ROLE.ADMIN, this.deleteIcons); this.router.post("/icons/upload", this.ROLE.ADMIN, this.uploadIcon); this.router.post("/icons/install", this.ROLE.ADMIN, this.addOrUpdateIcons); this.router.get("/system/webif-access", this.ROLE.ADMIN, this.setWebifAccessTimout); this.router.get("/system/reboot", this.ROLE.ADMIN, this.rebootBox); this.router.get("/system/wifiCli/settings", this.ROLE.ADMIN, this.getWiFiCliSettings); this.router.post("/system/wifiCli/settings", this.ROLE.ADMIN, this.setWiFiCliSettings); this.router.get("/system/connectionType", this.ROLE.ADMIN, this.getConnectionType); this.router.post("/system/timezone", this.ROLE.ADMIN, this.setTimezone); this.router.get("/system/time/get", this.ROLE.ANONYMOUS, this.getTime); 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.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); this.router.get("/redirect", this.ROLE.ANONYMOUS, this.redirectURL); this.router.post("/redirect", this.ROLE.ANONYMOUS, this.redirectURL); this.router.get("/demultiplex/:paths", this.ROLE.ANONYMOUS, this.demultiplex); this.router.get('/expert/deviceDescription/:deviceId', this.ROLE.ADMIN, this.getDeviceDescription, [parseInt]); this.router.get('/encryptionKeys', this.ROLE.ADMIN, this.encryptionKeys) }, // Used by the android app to request server status statusReport: function() { var currentDateTime = new Date(), reply = { error: null, data: 'OK', code: 200 }; if (Boolean(this.error)) { reply.error = "Internal server error. Please fill in bug report with request_id='" + this.error + "'"; reply.data = null; reply.code = 503; reply.message = "Service Unavailable"; } return reply; }, setLogin: function(profile, req) { var sid, resProfile = {}; sid = this.controller.auth.checkIn(profile, req); resProfile = this.controller.safeProfile(profile, ["authTokens"]); resProfile.sid = sid; if (profile.password !== 'admin' && !this.controller.config.hasOwnProperty('firstaccess') || this.controller.config.firstaccess === true) { this.controller.config.firstaccess = false; } // if showWelcome flag is set in controller add showWelcome flag to profile and remove it from controller if (!this.controller.config.firstaccess && this.controller.config.showWelcome) { resProfile.showWelcome = true; delete this.controller.config.showWelcome; this.controller.saveConfig(true); } return { error: null, data: resProfile, code: 200, headers: { "ZWAYSession": resProfile.sid, "Set-Cookie": "ZWAYSession=" + sid + "; Path=/" // set cookie - it will duplicate header just in case client prefers cookies } }; }, // Method to return a 401 to the user denyLogin: function(error) { return { error: error, data: null, code: 401, suppress401Auth: true, // to suppress basic auth form from the browser headers: { "Set-Cookie": "ZWAYSession=deleted; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT" // clean cookie } } }, // Returns user session information for the smarthome UI verifySession: function() { var auth = controller.auth.resolve(this.req, controller.auth.ROLE.USER); if (!auth) { return this.denyLogin("No valid user session found"); } var profile = _.find(this.controller.profiles, function(profile) { return profile.id === auth.user; }); res = _.extend(this.controller.safeProfile(profile, ["authTokens"]), {sid: controller.auth.getSessionId(this.req)}); return { error: null, data: res, code: 200, headers: { "ZWAYSession": res.sid, "Set-Cookie": "ZWAYSession=" + res.sid + "; Path=/" // set cookie - it will duplicate header just in case client prefers cookies } }; }, // Check if login exists and password is correct verifyLogin: function() { var reqObj; try { reqObj = parseToObject(this.req.body); } catch (ex) { return { error: ex.message, data: null, code: 500, headers: null }; } var profile = _.find(this.controller.profiles, function(profile) { return profile.login === reqObj.login; }); if (profile) { // check if the pwd matches var pwd_check = reqObj.password ? (!profile.salt && profile.password === reqObj.password) || (profile.salt && profile.password === hashPassword(reqObj.password, profile.salt)) : false; if (pwd_check) { return this.setLogin(profile, this.req); } else { return this.denyLogin(); } } else { return this.denyLogin(); } }, doLogout: function() { var reply = { error: null, data: null, code: 400, headers: null }, self = this, session; reply.headers = { "Set-Cookie": "ZWAYSession=deleted; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT" // clean cookie }; reply.code = 200; var sessionId = this.controller.auth.getSessionId(this.req); if (sessionId) { var session = {}; var sessionProfile = _.find(this.controller.profiles, function(profile) { var sess = _.find(profile.authTokens, function(authToken) { return authToken.sid == sessionId; }); if (sess) session = sess; return sess; }); if (session.expire !== 0) { // do not logout from permanent tokens - they should be deleted explicitelly via remoteToken API call this.controller.removeToken(sessionProfile, session.sid); } } return reply; }, // Devices listDevices: function() { var nowTS = Math.floor(Date.now() / 1000), reply = { error: null, data: { structureChanged: false, updateTime: nowTS, devices: [] } }, since = this.req.query.hasOwnProperty("since") ? parseInt(this.req.query.since, 10) : 0; reply.data.structureChanged = this.controller.lastStructureChangeTime >= since && since? true : false; reply.data.devices = this.controller.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) { var reply = { error: null, data: null }, device = _.find(this.controller.devicesByUser(this.req.user), function(device) { return device.id === vDevId; }); if (device) { reply.code = 200; reply.data = device.toJSON(); } else { reply.code = 404; reply.error = "Device " + vDevId + " doesn't exist"; } return reply; }, getVDevParam: function (vDevId, param, innerParam) { var reply = { error: null, data: null }, device = _.find(this.controller.devicesByUser(this.req.user), function(device) { return device.id === vDevId; }); if (device) { device = device.toJSON(); if (device.hasOwnProperty(param)) { var field = device[param]; if (innerParam) { if (field.hasOwnProperty(innerParam)) { reply.code = 200; reply.data = field[innerParam]; } else { reply.code = 404; reply.error = 'Parameter ' + param + '.' + innerParam +' in device ' + vDevId + ' doesn\'t exist'; } } else { reply.code = 200; reply.data = field; } } else { reply.code = 404; reply.error = 'Parameter ' + param + ' in device ' + vDevId + ' doesn\'t exist'; } } else { reply.code = 404; reply.error = "Device " + vDevId + " doesn't exist"; } return reply; }, setVDevFunc: function(vDevId) { var reqObj, device = null, reply = { error: null, data: null, code: 500, }, result = false; try { reqObj = typeof this.req.body === 'string' ? JSON.parse(this.req.body) : this.req.body; } catch (ex) { reply.error = ex.message; return reply; } // check security hole here!!!! if (this.req.query.hasOwnProperty('icon')) { device = this.controller.devices.get(vDevId); if (device) { device.set('customIcons', reqObj.customicons, { silent: true }); reply.data = "OK"; result = true; } } else { device = this.controller.deviceByUser(vDevId, this.req.user); if (device) { reply.data = device.set(reqObj); result = true; } } if (result) { reply.code = 200; } else { reply.code = 404; reply.error = "Device " + vDevId + " doesn't exist"; } return reply; }, performVDevCommandFunc: function(vDevId, commandId) { var reply = { error: null, data: null, code: 200 }, result_execution_command, vDev = this.controller.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; } else { reply.data = null; reply.code = 404; reply.error = "Device " + vDevId + " doesn't exist"; } return reply; }, getDeviceReference: function(vDevId) { var reply = { error: null, data: this.controller.findModulesReferencingDeviceId(vDevId), code: 200 }; return reply; }, // Notifications exposeNotifications: function(notificationId) { var notifications, reply = { error: null, data: null, code: 200 }, timestamp = Date.now(), 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.controller.profileByUser(this.req.user), devices = this.controller.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_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 }; if (notificationId) { notification = this.controller.notifications.get().filter(function(notification) { return notification.id === notificationId && // filter by id test(notification); // check against 2nd filter }); if (notification.length > 0) { reply.data = notification[0]; } else { reply.code = 404; reply.error = 'Not found'; } } 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 }); reply.data = { updateTime: Math.floor(timestamp / 1000), notifications: notifications }; } if (Boolean(this.req.query.pagination)) { reply.data.total_count = notifications.length; // !!! fix pagination notifications = notifications.slice(); } return reply; }, // delete single notifications or all privious by a choosen timestamp deleteNotifications: function(notificationId) { var id = notificationId ? parseInt(notificationId) : 0, reply = { code: 500, data: null, error: "Something went wrong." }, before; before = this.req.query.hasOwnProperty("allPrevious") ? Boolean(this.req.query.allPrevious) : false; redeemed = this.req.query.hasOwnProperty("allRedeemed") ? Boolean(this.req.query.allRedeemed) : false; if (!redeemed) { this.controller.deleteNotifications(id, before, function(notice) { if (notice) { reply.code = 204; reply.data = null; reply.error = null; } else { reply.code = 404; reply.data = null; reply.error = "Notifications not found."; } }); } else { this.controller.deleteAllRedeemedNotifications(function(notice) { if (notice) { reply.code = 204; reply.data = null; reply.error = null; } }); } return reply; }, // redeem single or all notifications (true/false) redeemNotifications: function(notificationId) { var id = notificationId ? parseInt(notificationId) : 0, reply = { code: 500, data: null, 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; if (!all) { this.controller.redeemNotification(id, redeemed, function(notice) { if (notice) { reply.code = 204; reply.data = null; reply.error = null; } else { reply.code = 404; reply.data = null; reply.error = 'Notification not found.'; } }); } else { this.controller.redeemAllNotifications(redeemed, function(notice) { if (notice) { reply.code = 204; reply.data = null; reply.error = null; } }); } return reply; }, //locations listLocations: function() { var reply = { data: null, error: null }, locations = this.controller.locationsByUser(this.req.user), expLocations = []; // generate namespaces per location reply.code = 200; reply.data = locations; return reply; }, // get location getLocationFunc: function(locationId) { var reply = { data: null, error: null }, locations = this.controller.locationsByUser(this.req.user), _location = [], locationId = !isNaN(locationId) ? parseInt(locationId, 10) : locationId; _location = this.controller.getLocation(locations, locationId); // generate namespaces for location if (_location) { reply.data = _location; reply.code = 200; } else { reply.code = 404; reply.error = "Location " + locationId + " not found"; } return reply; }, //filter location namespaces getLocationNamespacesFunc: function(locationId, namespaceId) { var reply = { data: null, error: null }, locations = this.controller.locationsByUser(this.req.user), _location = [], locationId = !isNaN(locationId) ? parseInt(locationId, 10) : locationId; _location = this.controller.getLocation(locations, locationId); // generate namespaces for location and get its namespaces if (_location) { // get namespaces by path (namespaceId) if (!namespaceId) { getFilteredNspc = _location.namespaces; } else { getFilteredNspc = this.controller.getListNamespaces(namespaceId, _location.namespaces); } if (!getFilteredNspc || (_.isArray(getFilteredNspc) && getFilteredNspc.length < 1)) { reply.code = 404; reply.error = "Couldn't find namespaces entry with: '" + namespaceId + "'"; } else { reply.data = getFilteredNspc; reply.code = 200; } } else { reply.code = 404; reply.error = "Location " + locationId === 0 ? 'globalRoom' : locationId + " not found"; } return reply; }, addLocation: function() { var title, reply = { error: null, data: null }, reqObj, locProps = {}; if (this.req.method === 'GET') { reqObj = this.req.query; } else if (this.req.method === 'POST') { // POST try { reqObj = JSON.parse(this.req.body); } catch (ex) { reply.code = 500; reply.error = "Cannot parse POST request. ERROR:" + ex.message; } } for (var property in reqObj) { if (property !== 'id') { locProps[property] = reqObj[property] ? reqObj[property] : null; } } if (!!locProps.title) { this.controller.addLocation(locProps, function(data) { if (data) { reply.code = 201; reply.data = data; } else { reply.code = 500; reply.error = "Location doesn't created: Parsing the arguments has failed."; } }); } else { reply.code = 500; reply.error = "Argument 'title' is required."; } return reply; }, removeLocation: function(locationId) { var id, reqObj, reply = { error: null, data: null, code: 200 }; if (this.req.method === 'GET') { id = parseInt(this.req.query.id); } else if (this.req.method === 'DELETE' && locationId === undefined) { try { reqObj = JSON.parse(this.req.body); } catch (ex) { reply.error = ex.message; } id = reqObj.id; } else if (this.req.method === 'DELETE' && locationId !== undefined) { id = locationId; } if (!!id) { if (id !== 0) { this.controller.removeLocation(id, function(result) { if (result) { reply.code = 204; reply.data = null; } 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 is required"; } return reply; }, 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, true); 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, default_img, img_type, show_background, main_sensors, reply = { error: null, data: null, code: 200 }, reqObj; if (locationId !== 0) { if (this.req.method === 'GET') { id = parseInt(this.req.query.id); title = this.req.query.title; } else if (this.req.method === 'PUT') { try { reqObj = JSON.parse(this.req.body); } catch (ex) { reply.error = ex.message; } id = locationId || reqObj.id; title = reqObj.title; user_img = reqObj.user_img || ''; default_img = reqObj.default_img || ''; img_type = reqObj.img_type || ''; show_background = reqObj.show_background || false; main_sensors = reqObj.main_sensors || []; } if (!!title && title.length > 0) { this.controller.updateLocation(id, title, user_img, default_img, img_type, show_background, main_sensors, function(data) { if (data) { reply.data = data; } else { reply.code = 404; reply.error = "Location " + id + " doesn't exist"; } }); } else { reply.code = 400; reply.error = "Arguments id & title are required"; } } else { reply.code = 403; reply.error = "Permission denied."; } return reply; }, // modules listModules: function() { var reply = { error: null, data: [], code: 200 }, module = null; 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)) { module.hasReset = true; } else { module.hasReset = false; } if (module.singleton && _.any(this.controller.instances, function(instance) { return instance.moduleId === module.id; })) { module.created = true; } else { module.created = false; } reply.data.push(module); }); return reply; }, getModuleFunc: function(moduleId) { var reply = { error: null, data: null, code: null }, moduleData; if (!this.controller.modules.hasOwnProperty(moduleId)) { reply.code = 404; reply.error = 'Instance ' + moduleId + ' not found'; } else { // get module data moduleData = this.controller.getModuleData(moduleId); if (moduleData.location === ('userModules/' + moduleId) && fs.list('modules/' + moduleId)) { moduleData.hasReset = true; } else { moduleData.hasReset = false; } reply.code = 200; // replace namspace filters reply.data = this.controller.replaceNamespaceFilters(moduleData); } return reply; }, // modules categories listModulesCategories: function() { var reply = { error: null, data: null, code: 200 }; reply.data = this.controller.getListModulesCategories(); return reply; }, getModuleCategoryFunc: function(categoryId) { var reply = { error: null, data: null, code: 500 }; category = this.controller.getListModulesCategories(categoryId); if (!Boolean(category)) { reply.code = 404; reply.error = "Categories " + categoryId + " not found"; } else { reply.code = 200; reply.data = category; } 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() { var reply = { error: { key: null, errorMsg: null }, data: { key: null, appendix: null }, code: 500 }, moduleUrl = parseToObject(this.req.body).moduleUrl, result = "", 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) { reply.code = 201; reply.data.key = "app_installation_successful"; // send language key as response } else { reply.code = 201; reply.data.key = "app_installation_successful_but_restart_necessary"; // send language key as response } } else { reply.code = 500; reply.error.key = 'app_failed_to_install'; } } else { reply.code = 409; reply.error.key = 'app_from_url_already_exist'; } return reply; }, updateModule: function() { var reply = { error: { key: null, errorMsg: null }, data: { key: null, appendix: null }, code: 500 }, moduleUrl = parseToObject(this.req.body).moduleUrl, result = "", moduleId = moduleUrl.split(/[\/]+/).pop().split(/[.]+/).shift(); if (this.controller.modules[moduleId]) { // download and install/overwrite the module result = this.controller.installModule(moduleUrl, moduleId); if (result === "done") { loadSuccessfully = this.controller.reinitializeModule(moduleId, 'userModules/'); if (loadSuccessfully) { reply.code = 200; reply.data.key = "app_update_successful"; // send language key as response } else { reply.code = 200; reply.data.key = "app_update_successful_but_restart_necessary"; // send language key as response } } else { reply.code = 500; reply.error.key = 'app_failed_to_update'; } } else { reply.code = 404; reply.error.key = 'app_from_url_not_exist'; } return reply; }, deleteModule: function(moduleId) { var reply = { error: { key: null }, data: { key: null, appendix: null }, code: 500 }, uninstall = false; if (this.controller.modules[moduleId]) { uninstall = this.controller.uninstallModule(moduleId); if (uninstall) { reply.code = 200; reply.data.key = "app_delete_successful"; // send language key as response } 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) { 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)) { 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; } 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'; } } else { reply.code = 404; reply.error.key = 'app_not_exist'; } return reply; }, getModuleTokens: function() { var reply = { error: null, data: null, code: 500 }, tokenObj = { tokens: [] }, getTokens = function() { return loadObject('moduleTokens.json'); }; if (getTokens() === null) { saveObject('moduleTokens.json', tokenObj, true); } if (!!getTokens()) { reply.data = getTokens(); reply.code = 200; } else { reply.error = 'failed_to_load_tokens'; } return reply; }, storeModuleToken: function() { var reply = { error: null, data: null, code: 500 }, reqObj = parseToObject(this.req.body), tokenObj = loadObject('moduleTokens.json'); if (tokenObj === null) { saveObject('moduleTokens.json', tokenObj, true); // try to load it again tokenObj = loadObject('moduleTokens.json'); } if (reqObj && reqObj.token && !!tokenObj && tokenObj.tokens) { if (tokenObj.tokens.indexOf(reqObj.token) < 0) { // add new token id tokenObj.tokens.push(reqObj.token); // save tokens saveObject('moduleTokens.json', tokenObj, true); reply.data = tokenObj; reply.code = 201; } else { reply.code = 409; reply.error = 'token_not_unique'; } } else { reply.error = 'failed_to_load_tokens'; } return reply; }, deleteModuleToken: function() { var reply = { 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) { // add new token id tokenObj.tokens = _.filter(tokenObj.tokens, function(token) { return token !== reqObj.token; }); // save tokens saveObject('moduleTokens.json', tokenObj, true); reply.data = tokenObj; reply.code = 200; } else { reply.code = 404; reply.error = 'not_existing_token'; } } else { reply.error = 'failed_to_load_tokens'; } return reply; }, // reinitialize modules reinitializeModule: function(moduleId) { var reply = { error: null, data: null, code: 500 }, location = [], loadSuccessfully = 0; if (fs.list('modules/' + moduleId)) { location.push('modules/'); } if (fs.list('userModules/' + moduleId)) { location.push('userModules/'); } if (location.length > 0) { try { _.forEach(location, function(loc) { loadSuccessfully += this.controller.reinitializeModule(moduleId, loc); }); if (loadSuccessfully > 0) { reply.data = 'Reinitialization of app "' + moduleId + '" successfull.'; reply.code = 200; } } catch (e) { reply.error = e.toString(); } } else { reply.code = 404; reply.error = "App not found."; } return reply; }, // instances listInstances: function() { var reply = { error: null, data: null, code: 200 }, instances = this.controller.listInstances(); if (instances) { reply.data = instances; } else { reply.code = 500; reply.error = 'Could not list Instances.'; } return reply; }, createInstance: function() { var reply = { error: null, data: null, code: 500 }, reqObj = this.req.reqObj, instance; if (this.controller.modules.hasOwnProperty(reqObj.moduleId)) { instance = this.controller.createInstance(reqObj); if (!!instance && instance) { reply.code = 201; reply.data = instance; } else { reply.code = 500; reply.error = "Cannot instantiate module " + reqObj.moduleId; } } else { reply.code = 404; reply.error = "Module " + reqObj.moduleId + " doesn't exist"; } return reply; }, 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; }); } else { instance = _.find(this.controller.instances, function(i) { return parseInt(instanceId) === i.id; }); } if (!Boolean(instance) || instance.length === 0) { reply.code = 404; reply.error = "Instance " + instanceId + " is not found"; } else { reply.code = 200; reply.data = instance; } return reply; }, reconfigureInstanceFunc: function(instanceId) { var reply = { error: null, data: null }, reqObj = this.req.reqObj, instance; if (!_.any(this.controller.instances, function(instance) { return instanceId === instance.id; })) { reply.code = 404; reply.error = "Instance " + instanceId + " doesn't exist"; } else { instance = this.controller.reconfigureInstance(instanceId, reqObj); if (instance) { reply.code = 200; reply.data = instance; } else { reply.code = 500; reply.error = "Cannot reconfigure module " + instanceId + " config"; } } return reply; }, deleteInstanceFunc: function(instanceId) { var reply = { error: null, data: null, code: 200 }; if (!_.any(this.controller.instances, function(instance) { return instance.id === instanceId; })) { reply.code = 404; reply.error = "Instance " + instanceId + " not found"; } else { reply.code = 204; reply.data = null; this.controller.deleteInstance(instanceId); } return reply; }, // profiles listProfiles: function(profileId) { var reply = { error: null, data: null, code: 500 }, profiles, getProfile, excl = []; // list all profiles only if user has 'admin' permissions if (!_.isNumber(profileId)) { if (this.req.role === this.ROLE.ADMIN) { profiles = this.controller.getListProfiles(); } else { getProfile = this.controller.safeProfile(this.controller.getProfile(this.req.user), ["role", "authTokens"]); if (getProfile) { profiles = [getProfile]; } } if (!Array.isArray(profiles)) { reply.code = 500; reply.error = "Unknown error"; } else { reply.code = 200; reply.data = profiles; } } else { getProfile = this.controller.getProfile(profileId); 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 not admin) if (this.req.role === this.ROLE.ADMIN) { excl = []; } else { excl = ["role"]; } reply.code = 200; reply.data = this.controller.safeProfile(getProfile); } else { reply.code = 404; reply.error = "Profile not found."; } } return reply; }, createProfile: function() { var reply = { error: null, data: null, code: 500 }, reqObj, profile, uniqueEmail = [], uniqueLogin = []; try { reqObj = JSON.parse(this.req.body); } catch (ex) { reply.error = ex.message; return reply; } uniqueEmail = _.filter(this.controller.profiles, function(p) { return p.email !== '' && p.email === reqObj.email; }); uniqueLogin = _.filter(this.controller.profiles, function(p) { return p.login !== '' && p.login === reqObj.login; }); if (uniqueEmail.length > 0) { reply.code = 409; reply.error = 'nonunique_email'; } else if (uniqueLogin.length > 0) { reply.code = 409; reply.error = 'nonunique_user'; } else { _.defaults(reqObj, { role: null, name: 'User', email: '', lang: 'en', dashboard: [], interval: 2000, rooms: reqObj.role === this.ROLE.ADMIN ? [0] : [], devices: [], expert_view: false, hide_all_device_events: false, hide_system_events: false, hide_single_device_events: [], skin: '', night_mode: false, }); // skip OAuth2 and other metadata reqObj = _.omit(reqObj, 'passwordConfirm', 'client_id', 'response_type', 'redirect_uri'); profile = this.controller.createProfile(reqObj); if (profile !== undefined && profile.id !== undefined) { reply.data = this.controller.safeProfile(profile); reply.code = 201; } else { reply.code = 500; reply.error = "Profile creation error"; } } return reply; }, createOAuth2Profile: function() { var reply = { error: null, data: null, code: 500 }, reqObj, profile, profileReply, zbwToken, sid, oauthReply, authToken, clientId; // check that find.z-wave.me token is present if (this.req.headers['Cookie']) { var zbwCookie = this.req.headers['Cookie'].split(";").map(function(el) { return el.trim().split("="); }).filter(function(el) { return el[0] === "ZBW_SESSID" })[0]; if (zbwCookie) zbwToken = zbwCookie[1]; } if (!zbwToken) { reply.code = 405; reply.error = "This method must be called thru find.z-wave.me"; return reply; } try { reqObj = JSON.parse(this.req.body); clientId = reqObj.client_id; redirectUri = reqObj.redirect_uri; responseType = reqObj.response_type; } catch (ex) { reply.code = 500; reply.error = ex.message; return reply; } profileReply = this.createProfile(); if (profileReply.code !== 200 && profileReply.code !== 201) return profileReply; // profileReply.data is a safe copy, so get the original profile for checkIn profile = _.find(this.controller.profiles, function (_profile) { return _profile.id == profileReply.data.id; }); // create permanent auth token for this user sid = this.controller.auth.checkIn(profile, this.req, true); data = { access_token: zbwToken + "/" + sid, client_id: clientId, redirect_uri: redirectUri, response_type: responseType } oauthReply = http.request({ url: "https://oauth2.z-wave.me:5000/saveToken", method: "POST", async: false, headers: { 'Content-Type':'application/json' }, data: JSON.stringify(data) }); if (oauthReply.status != 200) { this.removeProfile(profile.id); // revert creation of the user reply.code = oauthReply.status; reply.error = oauthReply.statusText; return reply } authCode = oauthReply.data.auth_code; if (!authCode) { this.removeProfile(profile.id); // revert creation of the user reply.code = 500; reply.error = "OAuth2 auth token is empty"; return reply } reply.code = 200; reply.data = { auth_code: authCode }; return reply; }, updateProfile: function(profileId) { var reply = { error: null, data: null, code: 500 }, reqObj, profile = _.clone(this.controller.getProfile(profileId)), // clone to allow check changes in controller.updateProfile 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); if (profile.id === this.req.user && profile.role === this.ROLE.ADMIN && reqObj.role !== this.ROLE.ADMIN) { reply.code = 403; reply.error = "Revoking self Admin priviledge is not allowed."; } else { // check that e-mail is unique or empty uniqueProfProps = _.filter(this.controller.profiles, function(p) { return (p.email !== '' && p.email === reqObj.email) && p.id !== profileId; }); if (uniqueProfProps.length === 0) { // only Admin can change critical parameters 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 === this.ROLE.ADMIN ? reqObj.rooms.push(0) : reqObj.rooms; profile.devices = reqObj.devices || []; profile.expert_view = reqObj.expert_view; profile.beta = reqObj.beta; } // could be changed by user role profile.name = reqObj.name; // profile name profile.interval = reqObj.interval; // update interval from ui profile.hide_system_events = reqObj.hide_system_events; profile.hide_all_device_events = reqObj.hide_all_device_events; profile.lang = reqObj.lang; 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.controller.safeProfile(profile); reply.code = 200; } else { reply.code = 500; reply.error = "Profile was not created"; } } else { reply.code = 409; reply.error = 'nonunique_email'; } } } else { reply.code = 404; reply.error = "Profile not found."; } return reply; }, // different pipe for updating authentication values updateProfileAuth: function(profileId) { var self = this, reply = { error: null, data: null, code: 500 }, reqObj, profile = this.controller.getProfile(profileId), uniqueLogin = [], reqToken = this.req.reqObj.hasOwnProperty("token") ? this.req.reqObj.token : null, tokenObj = {}; reqObj = JSON.parse(this.req.body); 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) { 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 { return p.login !== '' && p.login === reqObj.login && p.id !== self.req.user; } }); if (uniqueLogin.length < 1) { profile = this.controller.updateProfileAuth(reqObj, profileId); if (!!profile && profile.id !== undefined) { reply.data = this.controller.safeProfile(profile); reply.code = 200; } else { reply.code = 500; reply.error = "Was not able to update password."; } } else { reply.code = 409; reply.error = 'nonunique_user'; } } 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; reply.error = "Was not able to update password."; } } else { reply.code = 404; reply.error = "Token not found."; } } else { reply.code = 403; reply.error = "Forbidden."; } return reply; }, 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, profile, emailExists = [], tokenObj; if (reqObj.email) { 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.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) { try { tokenObj = self.controller.auth.getForgottenPwdToken(reqToken); if (tokenObj && !!tokenObj) { profile = _.filter(self.controller.profiles, function(p) { return p.email === tokenObj.email; }); if (profile[0]) { reply.code = 200; reply.data = { userId: profile[0].id }; } else { reply.code = 404; reply.error = "User not found."; } } else { reply.code = 404; reply.error = "Token not found."; } } catch (e) { reply.code = 500; reply.error = "Internal server error."; } } else if (!!reqToken && emailExists.length < 1 && profileId) { profile = self.controller.updateProfileAuth(reqObj, profileId); if (!!profile && profile.id !== undefined) { reply.code = 200; } else { reply.code = 500; reply.error = "Wasn't able to update password."; } } else { reply.code = 404; reply.error = "Email not found."; } return reply; }, removeProfile: function(profileId) { var reply = { error: null, data: null, code: 500 }, profile = this.controller.getProfile(profileId); if (profile) { // It is not possible to delete own profile if (profile.id !== this.req.user) { this.controller.removeProfile(profileId); reply.data = null; reply.code = 204; } else { reply.code = 403; reply.error = "Deleting own profile is not allowed."; } } else { reply.code = 404; reply.error = "Profile not found"; } return reply; }, removeOwnProfile: function() { var reply = { error: null, data: null, code: 500 }, profile = this.controller.getProfile(this.req.user); if (profile) { // It is possible to delete own profile if the role is USER if (this.req.role === profile.role) { if (profile.authTokens.length == 1) { // remove full profile this.controller.removeProfile(this.req.user); reply.data = null; reply.code = 204; } else if (profile.authTokens.length > 1) { // remove single token this.controller.removeToken(profile, this.req.token) reply.data = null; reply.code = 204; } else { reply.code = 404; reply.error = "No tokens found - how have you logged in?"; } } else { reply.code = 403; reply.error = "Deleting is possible only for own user profile."; } } else { reply.code = 404; reply.error = "Profile not found"; } return reply; }, removeToken: function(profileId, token) { var reply = { error: null, data: null, code: 500 }, profile = this.controller.getProfile(profileId); if (profile) { // Manage own tokens for users or any token for admin if (profile.id === this.req.user && this.req.role === this.ROLE.USER || this.req.role === this.ROLE.ADMIN) { if (this.controller.removeToken(profile, token)) { reply.data = null; reply.code = 204; } else { reply.code = 404; reply.error = "Token not found"; } } else { reply.code = 403; reply.error = "Permission denied"; } } else { reply.code = 404; reply.error = "Profile not found"; } return reply; }, permanentToken: function(profileId, token) { var reply = { error: null, data: null, code: 500 }, profile = this.controller.getProfile(profileId); if (profile) { // Manage own tokens for users or any token for admin if (profile.id === this.req.user && this.req.role === this.ROLE.USER || this.req.role === this.ROLE.ADMIN) { if (this.controller.permanentToken(profile, token)) { reply.data = null; reply.code = 204; } else { reply.code = 404; reply.error = "Token not found"; } } else { reply.code = 403; reply.error = "Permission denied"; } } else { reply.code = 404; reply.error = "Profile not found"; } return reply; }, getQRCodeString: function(profileId) { var reply = { error: null, data: null, code: 500 }, profile = this.controller.getProfile(profileId); try { var reqObj = parseToObject(this.req.body); } catch (e) { return reply.error = e.message; } 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 (qrcode_str !== undefined) { reply.code = 200; reply.data = qrcode_str; } else { reply.code = 500; } } else { reply.error = "wrong_password"; reply.code = 500; } } else { reply.error = "Forbidden"; reply.code = 403; } } else { reply.code = 404; reply.error = "Profile not found"; } return reply; }, /* Generating a local access token for the user. */ generateLocalAccessToken: function (profileId) { var reply = { error: null, data: null, code: 500 }; var profile = _.find(this.controller.profiles, function (_profile) { return _profile.id === profileId; }); if (profile) { var sid = this.controller.auth.checkIn(profile, this.req, true); var remoteId = this.controller.getRemoteId(); reply.code = 200; reply.data = { token: sid, remoteId: remoteId } } else { reply.code = 404; reply.error = "Profile not found"; } return reply; }, notificationFilteringGet: function() { var reply = { error: null, data: null, code: 500 }, self = this; var userDevices = this.controller.devicesByUser(this.req.user).map(function(dev) { return dev.id; }); var nfInstance = this.controller.listInstances().filter(function(i) { return i.moduleId === "NotificationFiltering"})[0]; // dirty hack - think about exported function by NotificationFiltering app if (nfInstance) { var devsStruct = []; var arr = nfInstance.params.rules.filter(function(rule) { var channel = self.controller.getNotificationChannel(rule.channel); return false || (rule.recipient_type === "user" && rule.user == self.req.user) || // non-strict == because might be as string in module params (rule.recipient_type === "channel" && channel && channel.user == self.req.user); // non-strict == because might be as string in module params }).forEach(function(rule) { // flatten structure rule.devices.forEach(function(devStruct) { if (userDevices.indexOf(devStruct[devStruct["dev_filter"]]["dev_select"]) > -1) { // filter devices by allowed list var _devStruct = _.clone(devStruct); _devStruct.channel = rule.recipient_type === "channel" ? rule.channel : null; devsStruct.push(_devStruct); } }); }); reply.data = devsStruct; reply.code = 200; } else { reply.code = 501; reply.error = "Not implemented. Activate NotificationFilter app"; } return reply; }, notificationFilteringSet: function() { var reply = { error: null, data: null, code: 500 }, self = this; var userConfig = []; try { var reqObj = parseToObject(this.req.body); // Transform to NotificationFiltring format // Do sanity check not to let user break global NotificationFiltring config reqObj.forEach(function(rule) { var channel = rule.channel; rule.channel = undefined; if (!rule["dev_filter"] || !rule[rule["dev_filter"]]["dev_select"]) return; userConfig.push({ recipient_type: channel ? "channel" : "user", user: channel ? undefined : self.req.user, channel: channel ? channel : undefined, logLevel: "", devices: [rule] }); }); } catch (e) { return reply.error = e.message; } this.controller.emit('notificationFiltering.userConfigUpdate', this.req.user, userConfig); reply.code = 200; return reply; }, notificationChannelsGet: function(all) { var reply = { error: null, data: null, code: 500 }, self = this; var channels = this.controller.notificationChannels; reply.data = Object.keys(channels).map(function(ch) { var profile = self.controller.getProfile(channels[ch].user); return _.extend({id: ch, userName: profile ? profile.name : "-" }, channels[ch]); }).filter(function(ch) { return ch.user == self.req.user || (all && self.req.role === self.ROLE.ADMIN); }); reply.code = 200; return reply; }, notificationChannelsGetAll: function() { return this.notificationChannelsGet(true); }, // namespaces listNamespaces: function() { 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; }, // restart restartController: function(profileId) { var reply = { error: null, data: null, code: 200 }; this.controller.restart(); return reply; }, loadModuleMedia: function(moduleName, fileName) { var reply = { error: null, data: null, code: 200 }, obj, _obj; if ((moduleName !== '' || !!moduleName || moduleName) && (fileName !== '' || !!fileName || fileName)) { obj = this.controller.loadModuleMedia(moduleName, fileName); if (obj && obj.data === "") { // for folder we will get empty - try to open index.html _obj = this.controller.loadModuleMedia(moduleName, fileName + "/index.html"); if (_obj && !!_obj.data) { obj = _obj; } } if (!this.controller.modules[moduleName]) { reply.code = 404; 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 = { "Content-Type": obj.ct }; this.res.body = obj.data; return null; // let handleRequest take this.res as is } else { reply.code = 500; reply.error = "Failed to load file from module."; return reply; } } else { reply.code = 400; reply.error = "Incorrect app or file name"; return reply; } }, loadImage: function(imageName) { var reply = { error: null, data: null, code: 200 }, data; data = this.controller.loadImage(imageName); if (data !== null) { this.res.status = 200; this.res.headers = { "Content-Type": "image/*" }; this.res.body = data; return null; // let handleRequest take this.res as is } else { reply.code = 500; reply.error = "Failed to load file."; return reply; } }, uploadFile: function() { var reply = { error: null, data: null, code: 200 }, file; if (this.req.method === "POST" && this.req.body) { 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') { var csv = null; Papa.parse(file.content, { header: true, dynamicTyping: true, complete: function(results) { csv = results; } }); if (!!csv) { saveObject(file.name, csv, true); } } else { // Create Base64 Object saveObject(file.name, Base64.encode(file.content), true); } reply.code = 200; reply.data = file.name; } else { reply.code = 500; reply.error = "Failed to upload file"; } } else { reply.code = 400; reply.error = "Invalid request"; } return reply; }, backup: function() { var self = this, reply = { error: null, data: null, code: 500 }; var now = new Date(); // create a timestamp in format yyyy-MM-dd-HH-mm var ts = getHRDateformat(now); try { var backupJSON = self.controller.createBackup(); reply.headers = { "Content-Type": "application/octet-stream", // application/x-download octet-stream "Content-Disposition": "attachment; filename=z-way-backup-" + ts + ".zab" }; reply.code = 200; reply.data = Base64.encode(JSON.stringify(backupJSON)); } catch (e) { reply.code = 500; reply.error = e.toString(); } return reply; }, restore: function() { var self = this, reqObj, reply = { error: null, data: null, code: 500 }, result = "", langfile = this.controller.loadMainLang(), dontSave = this.controller.getIgnoredStorageFiles([ "__ZWay", "__ZBee", "__EnOcean", "__userModules", "__userSkins" ]); function waitForInstallation(allreadyInstalled, reqKey) { var d = Date.now() + 300000; // wait not more than 5 min while (Date.now() < d && allreadyInstalled.length <= reqObj.data[reqKey].length) { if (allreadyInstalled.length === reqObj.data[reqKey].length) { 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++) { chars[i] = bytes.charCodeAt(i); } return chars; } reqObj = parseToObject(this.req.body.backupFile.content); if (typeof reqObj.data === 'string') { // new .zab files are base64 encoded, while old are not decodeData = Base64.decode(reqObj.data); // to JSON reqObj.data = JSON.parse(decodeData); } // check if data is not empty if (!reqObj.data) { // missing file reply.code = 400; reply.error = "Bad Request. Please input a .zab backup file."; return reply; } // stop the controller this.controller.stop(); for (var obj in reqObj.data) { if (dontSave.indexOf(obj) === -1) { saveObject(obj, reqObj.data[obj], true); console.log('Restore', obj, '... done'); } } // start controller with reload flag to apply config.json this.controller.start(true); // restore Z-Wave, Zigbee and EnOcean !!reqObj.data["__ZWay"] && Object.keys(reqObj.data["__ZWay"]).forEach(function(zwayName) { var zwayData = utf8Decode(reqObj.data["__ZWay"][zwayName]); global.ZWave[zwayName] && global.ZWave[zwayName].zway.controller.Restore(zwayData, allowTopoRestore); }); /* TODO !!reqObj.data["__ZBee"] && Object.keys(reqObj.data["__ZBee"]).forEach(function(zbeeName) { var zbeeData = utf8Decode(reqObj.data["__ZBee"][zbeeName]); global.Zigbee[zbeeName] && global.Zigbee[zbeeName].zbee.controller.Restore(zbeeData, allowTopoRestore); }); */ /* TODO !!reqObj.data["__EnOcean"] && reqObj.data["__EnOcean"].forEach(function(zenoName) { // global.EnOcean[zenoName] && global.EnOcean[zenoName].zeno.Restore(reqObj.data["__EnOcean"][zenoName]); }); */ // install userModules if (reqObj.data["__userModules"]) { var installedModules = []; _.forEach(reqObj.data["__userModules"], function(entry) { http.request({ url: 'https://developer.z-wave.me/?uri=api-module-archive/' + entry.name, method: 'GET', async: true, success: function(res) { var archiv = [], item = { name: entry.name }, location = 'modules/' + entry.name, overwriteCoreModule = false; if (res.data.data && res.data.data.length > 0) { 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; // check if version of core module isn't higher than the restored one if (coreModule) { overwriteCoreModule = has_higher_version(entry.version, self.controller.modules[entry.name].meta.version); } // 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); 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"); } } else { 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); item.status = result; if (result === "done") { self.controller.reinitializeModule(entry.name, 'userModules/', false); self.controller.addNotification("warning", langfile.zaap_war_app_installed_corrupt_instance + ' :: ' + entry.name, "core", "AppInstaller"); } else { self.controller.addNotification("error", langfile.zaap_err_app_install + ' :: ' + entry.name, "core", "AppInstaller"); } } else { 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'; } installedModules.push(item); }, 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"); } // install userSkins if (reqObj.data["__userSkins"]) { var installedSkins = [], remoteSkins = []; http.request({ // get online list of all existing modules first url: 'https://developer.z-wave.me/?uri=api-skins', method: 'GET', async: true, success: function(res) { if (res.data.data) { remoteSkins = res.data.data; // download all skins that are online available _.forEach(reqObj.data["__userSkins"], function(entry) { var item = { name: entry.name, status: 'failed' }, // check if backed up skin is in online list remSkinObj = _.filter(remoteSkins, function(skin) { return skin.name === entry.name; }); if (remSkinObj[0]) { index = _.findIndex(self.controller.skins, function(skin) { return skin.name === entry.name; }); try { // install skin result = self.controller.installSkin(remSkinObj[0], entry.name, index); item.status = result; } catch (e) { self.controller.addNotification("error", langfile.zaap_err_no_archives + ' :: ' + entry.name, "core", "SkinInstaller"); } } installedSkins.push(item); }); } }, error: function(res) { self.controller.addNotification("error", langfile.zaap_err_server + ' :: ' + res.statusText, "core", "SkinInstaller"); } }); waitForInstallation(installedSkins, "__userSkins"); } // success reply.code = 200; reply.data = { userModules: installedModules, userSkins: installedSkins }; } catch (e) { reply.error = e.toString(); } return reply; }, resetToFactoryDefault: function() { var self = this, langFile = this.controller.loadMainLang(); reply = { error: null, data: null, code: 500 }; { var backupCfg = loadObject("backupConfig"), storageContentList = loadObject("__storageContent"), defaultConfigExists = fs.stat('defaultConfigs/config.json'), // will be added during build - build depending defaultConfig = {}, defaultSkins = [{ 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 }], now = new Date(); try { if (defaultConfigExists && defaultConfigExists.type !== 'dir' && defaultConfigExists.size > 0) { defaultConfig = fs.loadJSON('defaultConfigs/config.json'); } if (!!defaultConfig && !_.isEmpty(defaultConfig)) { 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'), true); // remove all active instances of moduleId this.controller.instances.forEach(function(instance) { if (instance.moduleId !== 'ZWave') { self.controller.deleteInstance(instance.id); } }); if (typeof zway !== "undefined" && zway) { // reset z-way controller console.log('Reset Controller ...'); var d = Date.now() + 15000; // wait not more than 15 sec zway.controller.SetDefault(); while (Date.now() < d && zway.controller.data.controllerState.value === 20) { processPendingCallbacks(); } // 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); }); } } 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(); // clean up storage for (var ind in storageContentList) { if (storageContentList[ind].indexOf('backupConfig') < 0 && !!storageContentList[ind]) { saveObject(storageContentList[ind], null, true); } } // clean up storageContent if (__storageContent.length > 0) { __saveObject("__storageContent", []); __storageContent = []; } // set back to default config saveObject('config.json', defaultConfig, true); saveObject('userSkins.json', defaultSkins, true); // start controller with reload flag to apply config.json this.controller.start(true); reply.code = 200; setTimeout(function() { self.doLogout(); }, 3000); } else { reply.code = 404; reply.error = 'No default configuration file found.'; } } catch (e) { reply.error = 'Something went wrong. Error: ' + e.toString(); } } return reply; }, getSkins: function() { var reply = { error: null, data: null, code: 500 }; if (this.controller.skins) { reply.data = this.controller.skins; reply.code = 200; } else { reply.error = 'failed_to_load_skins'; } return reply; }, getSkin: function(skinName) { var reply = { error: null, data: null, code: 500 }; if (this.controller.skins) { index = _.findIndex(this.controller.skins, function(skin) { return skin.name === skinName; }); if (index > -1) { reply.data = this.controller.skins[index]; reply.code = 200; } else { reply.code = 404; reply.error = 'skin_not_exists'; } } else { reply.error = 'failed_to_load_skins'; } return reply; }, getActiveSkin: function() { var reply = { error: null, data: null, code: 500 }; if (this.controller.skins) { index = _.findIndex(this.controller.skins, function(skin) { return skin.active === true; }); if (index > -1) { reply.data = this.controller.skins[index]; reply.code = 200; } else { reply.code = 404; reply.error = 'skin_not_exists'; } } else { reply.error = 'failed_to_load_skins'; } return reply; }, activateOrDeactivateSkin: function(skinName) { var reply = { error: null, data: null, code: 500 }, reqObj = parseToObject(this.req.body), skin = null; skin = this.controller.setSkinState(skinName, reqObj); try { if (!!skin) { reply.data = skin; reply.code = 200; } else { reply.code = 404; reply.error = 'skin_not_exists'; } } catch (e) { reply.error = 'failed_to_load_skins'; reply.message = e.message; } return reply; }, addOrUpdateSkin: function(skinName) { var reply = { error: 'skin_failed_to_install', data: null, code: 500 }, reqObj = parseToObject(this.req.body), result = "", skName = skinName || reqObj.name; if (skName !== 'default') { 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)) { // 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.error = null; } } else if (this.req.method === 'POST' && !skinName) { reply.code = 409; reply.error = 'skin_from_url_already_exists'; } else if (this.req.method === 'PUT' && skinName) { reply.code = 404; reply.error = 'skin_not_exists'; } } else { reply.code = 403; reply.error = 'No Permission'; } return reply; }, deleteSkin: function(skinName) { var reply = { error: 'skin_failed_to_delete', data: null, code: 500 }, uninstall = false; if (skinName !== 'default') { index = _.findIndex(this.controller.skins, function(skin) { return skin.name === skinName; }); if (index > -1) { uninstall = this.controller.uninstallSkin(skinName); if (uninstall) { 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'; } } else { reply.code = 403; reply.error = 'No Permission'; } return reply; }, 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; }) saveObject("userSkins.json", this.controller.skins, true); 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() { var reply = { error: null, data: null, code: 500 }, tokenObj = loadObject('skinTokens.json'); if (tokenObj === null) { tokenObj = { skinTokens: [] }; saveObject('skinTokens.json', tokenObj, true); } if (!!tokenObj) { reply.data = tokenObj; reply.code = 200; } else { reply.error = 'failed_to_load_skin_tokens'; } return reply; }, storeSkinToken: function() { var reply = { error: null, data: null, code: 500 }, reqObj = parseToObject(this.req.body), tokenObj = loadObject('skinTokens.json'); if (reqObj && reqObj.token) { if (tokenObj === null) { tokenObj = { skinTokens: [reqObj.token] } // save tokens saveObject('skinTokens.json', tokenObj, true); reply.data = tokenObj; reply.code = 201; } else if (!!tokenObj && tokenObj.skinTokens) { if (tokenObj.skinTokens.indexOf(reqObj.token) < 0) { // add new token id tokenObj.skinTokens.push(reqObj.token); // save tokens saveObject('skinTokens.json', tokenObj, true); reply.data = tokenObj; reply.code = 201; } else { reply.code = 409; reply.error = 'skin_token_not_unique'; } } } else { reply.error = 'failed_to_load_skin_tokens'; } return reply; }, deleteSkinToken: function() { var reply = { 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) { // add new token id tokenObj.skinTokens = _.filter(tokenObj.skinTokens, function(token) { return token !== reqObj.token; }); // save tokens saveObject('skinTokens.json', tokenObj, true); reply.data = tokenObj; reply.code = 200; } else { reply.code = 404; reply.error = 'not_existing_skin_token'; } } else { reply.error = 'failed_to_load_skin_tokens'; } return reply; }, getIcons: function() { var reply = { error: null, data: null, code: 500 }; if (this.controller.icons) { reply.data = this.controller.icons; reply.code = 200; } else { reply.error = 'failed_to_load_icons'; } return reply; }, uploadIcon: function() { var reply = { error: 'icon_failed_to_install', data: null, code: 500 }; for (prop in this.req.body) { if (this.req.body[prop]['content']) { file = this.req.body[prop]; } } function utf8Decode(bytes) { var chars = []; for (var i = 0; i < bytes.length; i++) { chars[i] = bytes.charCodeAt(i); } return chars; } function Uint8ToBase64(uint8) { var i, extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes output = "", temp, length; var lookup = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' ]; function tripletToBase64(num) { return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F]; }; // go through the array every three bytes, we'll deal with trailing stuff later for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]); output += tripletToBase64(temp); } // this prevents an ERR_INVALID_URL in Chrome (Firefox okay) switch (output.length % 4) { case 1: output += '='; break; case 2: output += '=='; break; default: break; } return output; } var data, bytes = new Uint8Array(utf8Decode(file.content)), re = /(?:\.([^.]+))?$/; ext = re.exec(file.name)[1]; if (ext === 'gz') { var gunzip = new Zlib.Gunzip(bytes); data = gunzip.decompress(); } else { data = bytes; } file.content = Uint8ToBase64(data); result = this.controller.installIcon('local', file, 'custom', 'icon'); if (result.message === "done") { reply.code = 200; reply.data = result.files; reply.error = null; } return reply }, addOrUpdateIcons: function(iconName) { var reply = { error: 'icon_failed_to_install', data: null, code: 500 }, reqObj = parseToObject(this.req.body), result = "", icName = iconName || reqObj.name, id = reqObj.id; index = _.findIndex(this.controller.icons, function(icon) { return icon.source === icName + "_" + id; }); if (index === -1) { // download and install the icon result = this.controller.installIcon('remote', reqObj, icName, reqObj.id); if (result.message === "done") { reply.code = 200; reply.data = result.files; reply.error = null; } } else { reply.code = 409; reply.error = 'icon_from_url_already_exists'; } return reply; }, deleteIcons: function(iconName) { var reply = { error: 'icon_failed_to_delete', data: null, code: 500 }, uninstall = false; var reqObj = typeof this.req.body === 'string' ? JSON.parse(this.req.body) : this.req.body; this.controller.deleteCustomicon(iconName); uninstall = this.controller.uninstallIcon(iconName); if (uninstall) { reply.code = 200; reply.data = "icon_delete_successful"; reply.error = null; } return reply; }, getTime: function() { var reply = { error: null, data: null, code: 500 }, tz = "", now = new Date(); try { var sys = system("sh automation/lib/timezone.sh getTZ"); sys.forEach(function(i) { if (typeof i === 'string') { tz = i.replace(/\n/g, ''); } }); } 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, localTimeZone: tz, localGMT: now.toString().match(/([-\+][0-9]+)\s/)[1] }; } else { reply.error = 'Cannot get current date and time.'; } return reply; }, setTimezone: function() { var self = this, langfile = this.controller.loadMainLang(); reply = { error: null, data: null, code: 500 }, data = { "act": "set", "tz": "" }; reqObj = parseToObject(this.req.body); data.tz = reqObj.timeZone; try { if (system("sh automation/lib/timezone.sh setTZ " + reqObj.timeZone)[0] !== 0) { throw "Failed to set timezone"; } else { reply.code = 200; // 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", "SetTimezone"); } }, 5000); } } catch (e) { reply.error = res.statusText + "; " + e.toString(); } return reply; }, getRemoteId: function() { var self = this, reply = { error: null, data: null, code: 500 }; 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; } } 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 setWebifAccessTimout: function() { var reply = { error: null, data: null, code: 500 }, allowAcc = 0, timeout = 900; // in s ~ 15 min allowAcc = this.req.query.hasOwnProperty("allow_access") ? parseInt(this.req.query.allow_access, 10) : 0; timeout = this.req.query.hasOwnProperty("timeout") ? parseInt(this.req.query.timeout, 10) : timeout; if (allowAcc === 1 && timeout > 0 && timeout <= 1200) { saveObject('8084AccessTimeout', timeout, true); reply.code = 200; reply.data = { timeout: timeout }; } else if (allowAcc === 0) { saveObject('8084AccessTimeout', null, true); reply.code = 200; reply.data = { timeout: null }; } else { reply.code = 400; reply.error = 'Invalid Request'; } return reply; }, getFirstLoginInfo: function() { var reply = { error: null, data: {}, code: 500 }, defaultProfile = [], setLogin = {}; try { defaultProfile = _.filter(this.controller.profiles, function(profile) { return profile.login === 'admin' && profile.password === 'admin'; }); if ((!this.controller.config.first_start_up && defaultProfile.length > 0) || (defaultProfile.length > 0 && (typeof this.controller.config.firstaccess === 'undefined' || this.controller.config.firstaccess)) || (defaultProfile.length > 0 && !this.controller.config.firstaccess)) { setLogin = this.setLogin(defaultProfile[0], this.req); reply.headers = setLogin.headers; // set '/' Z-Way-Session root cookie reply.data.defaultProfile = setLogin.data; // set login data of default profile reply.data.firstaccess = true; reply.data.defaultProfile.showWelcome = true; } else { reply.data.firstaccess = false; } reply.data.uuid = this.controller.getUUID(); reply.data.serial = this.controller.getSerial(); reply.data.mac = this.controller.getMACAddress(); reply.data.remote_id = this.controller.getRemoteId(); reply.data.ip_address = this.controller.getIPAddress(); reply.code = 200; } catch (e) { reply.data = null; reply.error = e.message; } return reply; }, getSystemInfo: function() { var reply = { error: null, data: {}, 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; 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(), uuid: this.controller.getUUID(), serial: this.controller.getSerial(), mac: this.controller.getMACAddress(), firstaccess: this.controller.config.hasOwnProperty('firstaccess') ? this.controller.config.firstaccess : true }; reply.code = 200; } catch (e) { reply.data = null; reply.error = e.message; } return reply; }, rebootBox: function() { var self = this, langfile = this.controller.loadMainLang(); 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(true); } // 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 ..."; return reply; }, getWiFiCliSettings: function() { // for Z-Wave.Me Hub try { var list = system("/lib/wifi-helper.sh LIST")[1].split("\n"); list.pop(); list.pop(); list.shift(); list.shift(); // remove header and footer var saved = system("/lib/wifi-helper.sh GETSAVEDCONNECTION")[1].split("\n"), savedEssid = saved[0].trim(), // LIST returns with spaces, so we trim in current too for uniform comparison savedSecurity = saved[1], savedEncryption = saved[2], savedChannel = parseInt(saved[3]), current = system("/lib/wifi-helper.sh GETCURRENTCONNECTION")[1].split("\n"), currentEssid = current[0].trim(), // LIST returns with spaces, so we trim in current too for uniform comparison currentChannel = parseInt(current[1]); // sprintf(msg+strlen(msg),"%-4s%-33s%-20s%-23s%-9s%-7s%-7s%-3s\n", // "Ch", "SSID", "BSSID", "Security", "Siganl(%)", "W-Mode", " ExtCH"," NT"); // sprintf(msg+strlen(msg)-1,"%-4s%-5s\n", " WPS", " DPID"); var ret = list.map(function(l) { var m = l.match(/(.{4})(.{33})(.{20})(.{23})(.{9})(.{7})/); var sec = m[4].trim().split("/"); return { saved: savedEssid === m[2].trim() && savedSecurity === sec[0] && savedEncryption === sec[1] && savedChannel === parseInt(m[1]), current: false, // to be filled later channel: parseInt(m[1]), essid: m[2].trim(), security: sec[0], encryption: sec[1], signal: parseInt(m[5]) }; // TODO!!! Parse WPA1PSKWPA2PSK and TKIPAES }).filter(function(entry) { return entry.essid; // skip entries with empty ESSID (can be as a result of error on weak signal) }); var savedEntry = ret.filter(function(entry) { return entry.saved; })[0]; if (savedEntry) { savedEntry.current = savedEntry.saved && currentEssid === savedEntry.essid && currentChannel === savedEntry.channel; } else { if (savedEssid && savedSecurity && savedEncryption && savedChannel) { ret.push({ saved: true, current: false, channel: savedChannel, essid: savedEssid, security: savedSecurity, encryption: savedEncryption, signal: 0 }); } } return { data: ret, code: 200 }; } catch (e) { console.log(e.toString()); return { error: 'Internal Server Error. Can not get the list of WiFi networks: ' + e.toString(), code: 500 } } }, setWiFiCliSettings: function() { // for Z-Wave.Me Hub try { var reqObj = parseToObject(this.req.body), essid = reqObj.essid, security = reqObj.security, encryption = reqObj.encryption, password = reqObj.password; if (essid && (!security || !encryption || !password)) throw "Missing mandatory options: essid, security, encryption, password"; if (essid) { system('/lib/wifi-helper.sh "' + essid + '" "' + security + '" "' + encryption + '" "' + password + '"'); } else { system('/lib/wifi-helper.sh DISCONNECT'); } return { data: { }, code: 200 }; } catch (e) { console.log(e.toString()); return { error: 'Internal Server Error. Can not set WiFi network: ' + e.toString(), code: 500 } } }, getConnectionType: function() { // for Z-Wave.Me Hub try { var currentConnection = system("route | grep default | awk '$5 == 0 { if ($8 == \"eth0.1\") print \"ethernet\"; else if ($8 == \"apcli0\") print \"wifi\"; else if ($8 == \"eth1\") print \"mobile\"; else print $8 }'")[1].trim(); var availableConnections = system("route | grep default | awk '$5 != 0 { if ($8 == \"eth0.1\") print \"ethernet\"; else if ($8 == \"apcli0\") print \"wifi\"; else if ($8 == \"eth1\") print \"mobile\"; else print $8 }'")[1].split("\n").filter(function(x) { return x != "" }); var possibleConnections = [ "ethernet", "wifi", "mobile" ]; // possible values return { data: { currentConnection: currentConnection, availableConnections: availableConnections, possibleConnections: possibleConnections }, code: 200 }; } catch (e) { console.log(e.toString()); return { error: 'Internal Server Error. Can not check current connection type: ' + e.toString(), code: 500 } } }, setWifiSettings: function() { var reply = { error: "Wifi setup failed!", data: null, code: 500 }, retPp = [], retSsid = [], retR = []; if (fs.stat('lib/configAP.sh')) { try { reqObj = parseToObject(this.req.body); 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."; return reply; } } else { retPp[1] = ""; } 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)) { retR = system("sh automation/lib/configAP.sh reload"); if (retR[1].indexOf("Done") !== -1) { reply.error = null; reply.data = "OK"; reply.code = 200; } } } catch (e) { console.log(e.toString()); reply.error = 'Internal Server Error. ' + e.toString(); } } else { reply.error = 'Not Implemented'; reply.code = 501; } return reply; }, getWifiSettings: function() { var reply = { error: null, data: null, code: 500 }; if (fs.stat('lib/configAP.sh')) { try { var retSsid = system("sh automation/lib/configAP.sh getSsid"); var ssid = retSsid[1].replace(' 0', '').replace(/\n/g, ''); reply.code = 200; reply.data = { "ssid": ssid }; } catch (e) { console.log(e.toString()); reply.error = 'Internal Server Error. ' + e.toString(); } } else { reply.error = 'Not Implemented'; reply.code = 501; } return reply; }, configNtp: function(action) { var reply = { error: "Internal Server Error", data: null, code: 500 }, 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')) { try { reqObj = parseToObject(this.req.query); 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 + "'" : "")); if (action === 'status' && res[1]) { reply.data = JSON.parse(res[1]); } else { reply.data = res[1] ? res[1] : res; } reply.code = 200; reply.error = null; } else { reply.error = 'Bad Request. Allowed are: ' + actions.toString() + '?dateTime=yyyy-mm-dd hh:mm'; reply.code = 400; } } catch (e) { console.log(e.toString()); reply.error = 'Internal Server Error. ' + e.toString(); } } else { reply.error = 'Not Implemented'; reply.code = 501; } return reply; }, zwaveDeviceInfoGet: function() { var reply = { error: null, code: 500, data: null }, l = ['en', 'de'], //this.controller.availableLang devInfo = {}, 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'; 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 if (devInfo === null) { reply.code = 404; reply.error = 'No list of Z-Wave devices found. Please try to download them first.'; } else { reply.data = devInfo; reply.code = 200; if (!!devID) { reply.data = _.find(devInfo.zwave_devices, function(dev) { return dev['Product_Code'] === devID; }); if (!reply.data) { reply.code = 404; reply.error = 'No Z-Wave device with ' + devID + ' found.'; reply.data = null; } } } } catch (e) { reply.error = 'Something went wrong:' + e.message; } return reply; }, zwaveDeviceInfoUpdate: function() { var self = this, result = [], l = ['en', 'de'], //this.controller.availableLang, reply = { error: null, code: 500, data: null }, delay = Date.now() + 10000; // wait not more than 10 seconds try { // update postfix JSON l.forEach(function(lang) { var obj = {}, list = { updateTime: '', zwave_devices: [] }; obj[lang] = false; http.request({ url: "http://manuals-backend.z-wave.info/make.php?lang=" + lang + "&mode=ui_devices", async: true, success: function(res) { if (res.data) { data = parseToObject(res.data); list.updateTime = Date.now(); for (index in data) { list.zwave_devices.push(data[index]); } saveObject(lang + '.devices.json', list, true); obj[lang] = true; } result.push(obj); }, error: function() { self.controller.addNotification('error', 'Z-Wave device list for lang:' + lang + ' not found.', 'core', 'ZAutomationAPI'); result.push(obj); } }); }); while (result.length < l.length && Date.now() < delay) { processPendingCallbacks(); } if (result) { reply.code = 200; reply.data = result; } } catch (e) { this.controller.addNotification('error', 'Error has occured during updating the Z-Wave devices list', 'core', 'ZAutomationAPI'); reply.error = 'Something went wrong:' + e.message; } return reply; }, zwaveVendorsInfoGet: function() { var reply = { error: null, code: 500, data: null }, devInfo = {}, reqObj = !this.req.query ? undefined : parseToObject(this.req.query); try { vendorID = reqObj && reqObj.id ? reqObj.id : null; devInfo = loadObject('zwave_vendors.json'); if (devInfo === null) { reply.code = 404; reply.error = 'No list of Z-Wave vendors found. Please try to download them first.'; } else { reply.data = devInfo; reply.code = 200; if (devInfo.zwave_vendors[vendorID]) { reply.data = devInfo.zwave_vendors[vendorID]; } else if (reqObj.id) { reply.code = 404; reply.error = 'No Z-Wave vendor with id "' + vendorID + '" found.'; reply.data = null; } } } catch (e) { reply.error = 'Something went wrong:' + e.message; } return reply; }, zwaveVendorsInfoUpdate: function() { var self = this, result = 'in progress', reply = { error: null, code: 500, data: null }, delay = Date.now() + 10000; // wait not more than 10 seconds try { // update postfix JSON var list = { updateTime: '', zwave_vendors: {} }; http.request({ url: "http://manuals-backend.z-wave.info/make.php?mode=brand", async: true, success: function(res) { if (res.data) { list.updateTime = Date.now(); list.zwave_vendors = parseToObject(res.data); saveObject('zwave_vendors.json', list, true); result = 'done'; reply.code = 200; reply.data = list; } }, error: function(e) { var msg = 'Z-Wave vendors list could not be updated. Error: ' + e.toString(); self.controller.addNotification('error', msg, 'core', 'ZAutomationAPI'); result = 'failed'; reply.code = e.status; reply.error = msg; reply.data = e.data; } }); while (result === 'in progress' && Date.now() < delay) { processPendingCallbacks(); } if (result === 'in progress') { result = 'failed'; } } catch (e) { this.controller.addNotification('error', 'Error has occured during updating the Z-Wave devices list', 'core', 'ZAutomationAPI'); reply.error = 'Something went wrong:' + e.message; } return reply; }, redirectURL: function() { var self = this; var params = Object.keys(this.req.query).filter(function(k) { return k != "to"; }).map(function(k) { return k + "=" + self.req.query[k]; }).join("&"); return { error: null, code: 302, headers: { "Location": this.req.query["to"] + (params ? "?" + params : "") } }; }, demultiplex: function(_path) { var self = this; var reply = { error: null, data: [], code: 200 }; var fullUrl = this.req.fullUrl, prefix = "/demultiplex/", ind; // A bit urgly logic to remove API prefix if (!fullUrl || (ind = fullUrl.indexOf(prefix + _path)) === -1) return { code: 500 }; fullUrlPrefix = fullUrl.slice(0, fullUrl.indexOf(this.req.url)); fullUrl = fullUrl.slice(ind + prefix.length); var prefix = _.first(self.req.url.split("/"), 2).join("/") + "/"; fullUrl.split(";").forEach(function(url) { var path = url, query = {}, qind = url.indexOf("?"); if (qind > -1) { var q = url.slice(qind + 1); path = url.slice(0, qind); q.split("&").forEach(function(kv) { var kv_arr = kv.split("="); query[kv_arr[0]] = kv_arr[1]; }); } self.req.fullUrl = fullUrlPrefix + prefix + url; self.req.url = prefix + path; self.req.query = query; var ret = self.handleRequest(self.req.url, self.req); var body; try { body = JSON.parse(ret.body); } catch(ex) { body = ret.body; } reply.data.push({ status: ret.status, body: body }); }); return reply; } }); ZAutomationAPIWebRequest.prototype.Unauthorized = function() { return { error: 'Not logged in', data: null, code: 401 }; } ZAutomationAPIWebRequest.prototype.Forbidden = function() { return { error: 'Permission denied', data: null, code: 403 }; } ZAutomationAPIWebRequest.prototype.dispatchRequest = function(method, url) { var self = this, handlerFunc = this.NotFound, // Default handler is NotFound validParams; var matched = this.router.dispatch(method, url); if (matched) { var auth = this.controller.auth.resolve(this.req, matched.role); if (!auth) { return this.Unauthorized; } else if (this.controller.auth.isAuthorized(auth.role, matched.role)) { if (matched.params.length) { validParams = _.every(matched.params), function(p) { return !!p; }; if (validParams) { handlerFunc = function() { return matched.handler.apply(this, matched.params); } } } else { handlerFunc = matched.handler ? matched.handler : handlerFunc; } // --- Proceed to checkout =) return handlerFunc; } else { return this.Forbidden; } } else { return handlerFunc; } }; ZAutomationAPIWebRequest.prototype.reorderDevices = function() { var self = this, reply = { error: "Internal Server Error", data: null, code: 500 }; var reqObj = typeof this.req.body !== 'object' ? JSON.parse(this.req.body) : this.req.body; var data = reqObj.data, // ordered list of devices action = reqObj.action; // Dashboard, Elements, Room(location) if (self.controller.reoderDevices(data, action)) { reply.error = ""; reply.data = "OK"; reply.code = 200; } // a = self.controller.order.elements.indexOf(reqObj[0].id); // b = self.controller.order.elements.indexOf(reqObj[1].id); // // Array.prototype.swapItems = function(a, b){ // this[a] = this.splice(b, 1, this[a])[0]; // return this; // } // // // self.controller.order.elements = self.controller.order.elements.swapItems(a,b); // return reply; } ================================================ FILE: apiary.apib ================================================ emFORMAT: 1A HOST: http://r3bfish.ddns.net:8083/ZAutomation/api # Z-Way API + Allowed HTTPs requests: + POST To create resource + PUT To update resource + GET Get a resource or list of resources + DELETE To delete resource + Allow get parameters + suppress_response_codes (optional, boolean) ... If this parameter is present, all responses will be returned with a 200 OK status code - even errors. This parameter exists to accommodate Flash and JavaScript applications running in browsers that intercept all non-200 responses. If used, it’s then the job of the client to determine error states by parsing the response body. Use with caution, as those error messages may change. + pagination (optional, boolean) ... If this parameter is present, all response of the lists (Devices, Notifications) will be included pagination + limit (optional, number) ... If this parameter is present, then set limit models per page, else set default 10 models per page + offset (options, number) ... If this parameter is present, then set start point models in page, else set default offset 0. + fields (options, string) ... example: nameField1,nameField2,... + Status codes, + 200: "200 OK", + 201: "201 Created", + 204: "204 No Content", + 304: "304 Not Modified", + 400: "400 Bad Request", + 401: "401 Unauthorized", + 403: "403 Forbidden", + 404: "404 Not Found", + 405: "405 Method Not Allowed", + 500: "500 Internal server error", + 501: "501 Not Implemented" + 503: "503 Service Unavailable" # Group Platform Availability It will return 503 with Error message (see the details below). You can also use **/v1/status** resource to check the Platform availability. ## /v1/status ### GET Resource for checking the Platform availability. + Request + Headers Accept: application/json + Response 200 + Headers Content-Type: application/json + Body { error: null, data: "OK", code: 200 } + Schema { "type": "object", "required": false, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 503 + Headers Content-Type: application/json + Body { error: "Internal server error. Please fill in bug report with request_id='" + this.error + "'", data: null, code: 503, message: "Service Unavailable" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } # Group Platform Restart You can use **/v1/restart** to restart the controller. This will reload all notifications, ZAutomation API and reinitialize (not reload) all apps. ## /v1/restart ### GET Platform Restart + Request + Headers Accept: application/json + Response 200 + Headers Content-Type: application/json + Body { error: null, data: "OK", code: 200 } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } # Group Namespaces Namespaces is a collection of internal registered names and ids - e.g. for zway or virtual devices. This API is mostly used in apps to create custom filters for virtual device selection. The data is build during z-way controller start up. Virtual device namespaces are influenced by the following listener emits: * **created** ... added to devices collection * **removed** ... removed from devices collection * **change:metrics:title** ... change name of virtual dev * **change:permanently_hide** ... deactivate virtual device - not hidden and usable The namespace structure of virtual devices depends on their device types an probe types. Please check chapter **[Devices](anchor-devices)** for more information. ###Methods - **/v1/namespaces** - read all namespaces. - **/v1/namespaces/zways** - zway namespaces. - **/v1/namespaces/devices_all** - array of all registered virtual devices that includes deviceId and deviceName. - **/v1/namespaces/{devices_DEVICETYPE}** - read namespaces of specific **device type** and show their **probe type** children. - **/v1/namespaces/{devices_DEVICETYPE}.{PATH}** - use path to read namespaces of child probe types from a specific device type. - **/v1/namespaces/{devices_DEVICETYPE}.{PATH}.deviceId** - get an array of all deviceId's that lies under path endpoint. - **/v1/namespaces/{devices_DEVICETYPE}.{PATH}.deviceName** - get an array of all deviceNames's lies under path endpoint. ## Namespaces Collection [/v1/namespaces] ### List all Namespaces [GET] + Response 200 (application/json) + Body { error: null, data: [ { id: "zways", params: [ { zwayName: "zway" } ] }, { id: "devices_battery", params: [ { deviceId: "ZWayVDev_zway_7-0-128", deviceName: "Fibar Group Battery (7.0)" } ] }, { id: "devices_all", params: [ { deviceId: "DummyDevice_12", deviceName: "Switch All On" }, { deviceId: "DummyDevice_13", deviceName: "Tischlampe" }, { deviceId: "DummyDevice_18", deviceName: "Deckenlampe" }, { deviceId: "ZWayVDev_zway_7-0-37", deviceName: "Smoke Sirene Switch" }, { deviceId: "ZWayVDev_zway_7-0-48-2", deviceName: "Smoke Sensor" }, { deviceId: "ZWayVDev_zway_7-0-48-8", deviceName: "Smoke Tamper Sensor" }, { deviceId: "ZWayVDev_zway_7-0-113-1-2-A", deviceName: "Smoke Alarm" }, { deviceId: "ZWayVDev_zway_7-0-113-7-3-A", deviceName: "Smoke Burglar Alarm" }, { deviceId: "ZWayVDev_zway_7-0-113-9-1-A", deviceName: "Smoke System Alarm" }, { deviceId: "ZWayVDev_zway_7-0-128", deviceName: "Fibar Group Battery (7.0)" }, { deviceId: "Sonos_Device_Play_192.168.10.205_21", deviceName: "Sonos Play Wohnzimmer" }, { deviceId: "Sonos_Device_Volume_192.168.10.205_21", deviceName: "Sonos Volume Wohnzimmer" } ] }, { id: "devices_switchBinary", params: [ { deviceId: "DummyDevice_12", deviceName: "Switch All On" }, { deviceId: "ZWayVDev_zway_7-0-37", deviceName: "Smoke Sirene Switch" }, { deviceId: "Sonos_Device_Play_192.168.10.205_21", deviceName: "Sonos Play Wohnzimmer" } ] }, { id: "devices_switchMultilevel", params: [ { deviceId: "DummyDevice_13", deviceName: "Tischlampe" }, { deviceId: "DummyDevice_18", deviceName: "Deckenlampe" }, { deviceId: "Sonos_Device_Volume_192.168.10.205_21", deviceName: "Sonos Volume Wohnzimmer" } ] }, { id: "devices_sensorBinary", params: { smoke: [ { deviceId: "ZWayVDev_zway_7-0-48-2", deviceName: "Smoke Sensor" } ], tamper: [ { deviceId: "ZWayVDev_zway_7-0-48-8", deviceName: "Smoke Tamper Sensor" } ], alarm: { smoke: [ { deviceId: "ZWayVDev_zway_7-0-113-1-2-A", deviceName: "Smoke Alarm" } ], burglar: [ { deviceId: "ZWayVDev_zway_7-0-113-7-3-A", deviceName: "Smoke Burglar Alarm" } ], system: [ { deviceId: "ZWayVDev_zway_7-0-113-9-1-A", deviceName: "Smoke System Alarm" } ] } } } ], code: 200, message: "200 OK" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "array", "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not Found", error: "Namespaces array is null" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string"], "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } ## Z-Way Namespaces Collection [/v1/namespaces/zways] ### List all Z-Way Namespaces [GET] + Response 200 (application/json) + Body { data: [ { "zwayName": "zway" }, { "zwayName": "zway_remote_1" }, { "zwayName": "zway_remote_2" } ], code: 200, message: "200 OK", error: null } + Schema { "type": "object", "required": true, "properties": { "data": { "type": "array", "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": "string", "required": true }, "error": { "type": ["string", "null"], "required": true } } } ## Virtual Device Namespace Model [/v1/namespaces/{devices_DEVICETYPE}.{PATH}.{deviceId,deviceName}] ### List specific namespaces [GET] + Parameters + `devices_DEVICETYPE` (optional, string, `devices_sensorBinary`) ... get namespaces of all devices `devices_all` or by device type - e.g. `devices_switchBinary`, `devices_sensorBinary`, `devices_switchControl` ... + PATH (optional, string, `alarm.smoke`) ... get namespaces of devices depending on their probe type - e.g. `smoke`, `alarm`, `alarm.smoke` + deviceId (optional, string, `deviceId`) ... get an array with device ID's of all virtual child devices. + deviceName (optional, string, `deviceName`) ... get an array with device names's of all virtual child devices. + Response 200 (application/json) + Body { data: { smoke: [ { deviceId: "ZWayVDev_zway_7-0-48-2", deviceName: "Smoke Sensor" } ], tamper: [ { deviceId: "ZWayVDev_zway_7-0-48-8", deviceName: "Smoke Tamper Sensor" } ], alarm: { smoke: [ { deviceId: "ZWayVDev_zway_7-0-113-1-2-A", deviceName: "Smoke Alarm" } ], burglar: [ { deviceId: "ZWayVDev_zway_7-0-113-7-3-A", deviceName: "Smoke Burglar Alarm" } ], system: [ { deviceId: "ZWayVDev_zway_7-0-113-9-1-A", deviceName: "Smoke System Alarm" } ] } }, code: 200, message: "200 OK", error: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "object", "properties": { "smoke": { "type": "array", "required": true }, "tamper" "type": "array", "required": true }, "alarm": { "type": "object", "properties": { "smoke": { "type": "array", "required": true }, "burglar" "type": "array", "required": true }, "system": { "type": "integer", "required": true } } }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not Found", error: "No namespaces found with this path: foobar" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string"], "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } # Group Modules This API prepares the whole meta data of all registered modules and their instances. ###Methods for: #### Module Meta Data - **/v1/modules** - read all module meta data. - **/v1/modules/{module-id}** - get meta data of specific module #### Module Handling - **/v1/modules/install** - download and install module (.tar.gz/ .zip) from the given address. - **/v1/modules/update** - download and overwrite module (.tar.gz/ .zip) from the given address. Reload JS and meta data of updated module. - **/v1/modules/reset/{module-id}** - resets an updated module back to its preinstalled version. Module needs to be preinstalled by on current firmware. - **/v1/modules/delete/{module-id}** - deletes a **non preinstalled** module and removes all its instances. Cannot be undone without doing backup before. - **/v1/modules/reinitialize/{module-id}** - stops all instances of module, reloads JS and meta data and starts instances again - useful for module development. #### Module Categories - **/v1/modules/categories** - show all module categories - **/v1/modules/categories/{category-id}** - list modules by category #### Access to Online Store Groups - **/v1/modules/tokens** - create/read/update/delete tokens for online store group access. ## Module attributes This is a list of all module attributes hand out by the api. Some of them are predefined by module.json and the rest is added by the controller after initialisation. | Module Attributes| Type | Comment | |---------------|-----------------------------|----------------------------------------------------| | `id` | _immutable_ (string, uniq) | class name as unique identifier | | `location` | _immutable_ (string) | path to file directory: /modules or /userModules | | `className` | _immutable_ (string) | reference to class name from index.js | | `hasReset` | (boolean, false) | indicator if module could be reseted | | `created` | (boolean, false) | indicator if an instance of a singleton module already exists | | `dependencies`| (array, []) | id's of modules that are necessary to initiate this module | | `singleton` | (boolean, false) | indicates if a module could have only one (singleton) or more running instances | | `category` | (string) | assign module to special category | | `author` | (string) | name of author | | `homepage` | (string, uri) | homepage (of author) | | `icon` | (string, uri) | custom icon field: looks in folder _/LOCATION/MODULENAME/htdocs/ICON-NAME.png_ | | `version` | (string or number) | current version of module, format MAJOR.FEATURES.PATCHES ``0.0.0`` | | `maturity` | (string) | shows maturity of a modules: _alpha, beta, gamma, ... or stable_ | | `repository` | (object) | information about the repository where to find the module e.g. ``{"type": "git","source": "https://github.com/Z-Wave-Me/home-automation"}`` | | `defaults` | (object) | defines default attributes (basic skeleton) of the module, also includes module title and description e.g. ``{"title": "AutoLock","description": "Electronic Doors typically move the dead bold of the door. [...]"`` } | | `schema` | (object) | defines formatting schema for UI transformation with Alpaca Forms | | `options` | (object) | defines view attributes of objects for UI transformation with Alpaca Forms | More information about 'How to build a module?' you will find [here in this tutorial on developer.z-wave.me](http://developer.z-wave.me/?uri=help). Please check out [http://www.alpacajs.org/](http://www.alpacajs.org/) to learn more about Alpaca Forms and how you could use it to make your own modules for home automation. ## Modules Collection [/v1/modules] ### List all Modules [GET] + Response 200 (application/json) + Body { error: null, data: [ { id: 'module-id', } ], code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": ["array"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } ## Module Model [/v1/modules/{module-id}] ### List Module Meta Data [GET] + Parameters + module-id (required, string, `BatteryPolling`) ... class name of module from which meta data should be requested + Response 200 (application/json) + Body { error: null, data: { "singleton": true, "dependencies": [ "Cron" ], "category": "basic_gateway_modules", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "BatteryPolling", "version": "2.0.1", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "Battery Polling", "description": "The battery polling will update the battery status by asking all battery-operated device once a week for a battery status update.. You can pick the day of the week and a warning level, when this module will send you a warning note.", "launchWeekDay": 0, "warningLevel": 20 }, "schema": { "type": "object", "properties": { "launchWeekDay": { "type": "number", "required": true, "enum": [ -1, 1, 2, 3, 4, 5, 6, 0 ] }, "warningLevel": { "type": "select", "required": true, "enum": [ 5, 10, 15, 20 ] } }, "required": false }, "options": { "fields": { "launchWeekDay": { "label": "Do battery polling on", "optionLabels": [ "Every Day", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday" ] }, "warningLevel": { "label": "Warning Level", "helper": "Warn if device's battery is below this level", "optionLabels": [ "5%", "10%", "15%", "20%" ] } } }, "id": "BatteryPolling", "location": "modules/BatteryPolling", "hasReset": false }, code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": ["array"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not Found", error: "Instance 'module-id' not found" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string"], "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } ## Install Module [/v1/modules/install{?moduleUrl}] ### INSTALL [POST] + Parameters + moduleUrl (required, string, `http://developer.z-wave.me/modules/ScheduledScene.tar.gz`) ... target download url + Request (application/json) + Body { error: null, data: { moduleUrl: "http://developer.z-wave.me/modules/ScheduledScene.tar.gz" }, code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "object", "required": true, "properties": { "moduleUrl": { "type": "string", "required": true }, } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 201 (application/json) + Body { error: null, data: { key: "The app was successfully installed.", appendix: null }, code: 201, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data"; { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "appendix" "type": ["string", "null"], "required": true } } }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 409 + Headers Content-Type: application/json + Body { data: null, code: 409, message: "409 conflict", error: { key: "The app is already installed.", errorMsg: null } } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "errorMsg" "type": ["string", "null"], "required": true } } }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } + Response 500 + Headers Content-Type: application/json + Body { data: null, code: 500, message: "500 Internal server error", error: { key: "Failed to install the app.", errorMsg: null } } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "errorMsg" "type": ["string", "null"], "required": true } } }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } ## Update Module [/v1/modules/update{?moduleUrl}] ### UPDATE [POST] + Parameters + moduleUrl (required, string, `http://developer.z-wave.me/modules/ScheduledScene.tar.gz`) ... target download url + Request (application/json) + Body { error: null, data: { moduleUrl: "http://developer.z-wave.me/modules/ScheduledScene.tar.gz" }, code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "object", "required": true, "properties": { "moduleUrl": { "type": "string", "required": true }, } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 200 (application/json) + Body { error: null, data: { key: "The app was successfully updated.", appendix: null }, code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": ["array"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not Found", error: "Cannot find the app." } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string"], "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } + Response 500 + Headers Content-Type: application/json + Body { data: null, code: 500, message: "500 Internal server error", error: { key: "Failed to update the app.", errorMsg: null } } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "errorMsg" "type": ["string", "null"], "required": true } } }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } ## Reset Module [/v1/modules/reset/{module-id}] ### RESET - works only if module was preinstalled by default [POST] + Parameters + module-id (required, string, `ScheduledScene`) ... class name of module that should be reseted + Response 200 (application/json) + Body { error: null, data: { key: "The app was successfully reseted.", appendix: "current_version" }, code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "appendix" "type": "string", "required": true } } }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not Found", error: { key: "App not found.", errorMsg: null } } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "errorMsg" "type": ["string", "null"], "required": true } } }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } + Response 412 + Headers Content-Type: application/json + Body { data: null, code: 412, message: "412 Precondition Failed", error: { key: "App is already reset.", errorMsg: null } } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "errorMsg" "type": ["string", "null"], "required": true } } }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } + Response 500 + Headers Content-Type: application/json + Body { data: null, code: 500, message: "500 Internal server error", error: { key: "There was an error during resetting the app 'module-id'. Maybe a server restart could solve this problem.", errorMsg: null } } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "errorMsg" "type": ["string", "null"], "required": true } } }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } ## Delete Module [/v1/delete/{module-id}] ### DELETE - works only with non preinstalled modules [DELETE] + Parameters + module-id (required, string, `MultiButton`) ... class name of module that should deleted + Response 200 (application/json) + Body { error: null, data: { key: "The app was successfully deleted", appendix: null }, code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "appendix" "type": ["string", "null"], "required": true } } }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not Found", error: { key: "App not found." } } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "errorMsg" "type": ["string", "null"], "required": true } } }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } + Response 500 + Headers Content-Type: application/json + Body { data: null, code: 500, message: "500 Internal server error", error: { key: "Failed to delete the app." } } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "object", "required": true, "properties": { "key": { "type": "string", "required": true }, "errorMsg" "type": ["string", "null"], "required": true } } }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } ## Reinitialize Module [/v1/reinitialize/{module-id}] ### REINITIALIZE [GET] + Parameters + module-id (required, string, `BatteryPolling`) ... class name of module that should be reinitialized + Response 200 (application/json) + Body { error: null, data: "Reinitialization of app 'module-id' successfull.", code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "string", "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not Found", error: "App not found." } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "string", "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string"], "required": true } } } ## Module Categories [/v1/modules/categories] - **/v1/modules/categories** - show all module categories ## Module Categories [/v1/modules/categories/{category-id}] - **/v1/modules/categories/{category-id}** - list modules by category ## Access to Online Store Groups [/v1/modules/tokens] - **/v1/modules/tokens** - create/read/update/delete tokens for online store group access. # Group Instances An instance is an object that is derived from a modules class. The module itself is a predefined mask and allows to create custom or different instances of it. For example using the module **If > Then**: 1. If ``Switch A`` is ``ON`` > Set ``Dimmer A`` to level ``99``, ``Radio`` to level ``12``, switch ``Plug Media````ON`` 2. If ``Switch A`` is ``OFF`` > Set ``Dimmer A`` to level ``0``, switch ``Radio````OFF``, switch ``Plug Media````ON`` Both instances are derived objects from the _IfThen()_ class of **If > Then** module. They have the same origin but different attributes and usage. This also means that one module could usually have one or more registered instances. #### Singleton Instances: If a module has the the attribute ``"singleton": true`` then it could only have one registered instance - called singleton. Singletons are useful to avoid unnecessary instantiations of one class, so for example the module **Z-Wave Network Access** is configured as singleton. This module is the interface between the z-way controller and the Smart Home UI. It makes no sence to add another instance of that module because the interface already exists. Also another instance could interfere with the existing one because their class isn't concipated to handle more than one. #### Instances - **/v1/instances** - get all instances or create new instance (POST). - **/v1/instances/{module-name}** - get an array of all instances of one module. - **/v1/instances/{instance-id}** - read/update/delete instances. ## Instances Collection [/v1/instances] ### List all Instances [GET] + Response 200 (application/json) + Body { data: [ { id: 2, moduleId: "Cron", params: {}, active: true, title: "System Clock (CRON)", description: "Scheduler used by other modules.", creationTime: 1459769281, state: null, module: "System Clock (CRON)" }, { id: 3, moduleId: "InbandNotifications", params: {}, active: true, title: "Inband Notifier", description: "Creates and records the presentation of events in the event list (Eventlog).", creationTime: 1459769281, state: null, module: "Inband Notifier" }, { id: 7, moduleId: "BatteryPolling", active: "true", title: "Battery Polling", description: "Set up an interval that regularly polls the battery status of a battery devices.", params: { launchWeekDay: 0, warningLevel: "20" }, creationTime: 1459769281 } ], code: 200, message: "200 OK", error: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "array", "required": true }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 500 + Headers Content-Type: application/json + Body { data: null, code: 500, message: "500 Internal server error", error: "Could not list Instances." } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "string", "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": "string", "required": true } } } ### CREATE [POST] + Request (application/json) + Body { error: null, data: { moduleId: "BatteryPolling", params: { launchWeekDay: 0, warningLevel: "20" } }, code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "object", "required": true, "properties": { "moduleId": { "type": "string", "required": true }, "params": { "type": "object", "required": true } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 201 (application/json) + Body { error: null, data: [ { moduleId: 'moduleId', } ], code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "id": { "type": "integer", "required": true }, "moduleId": { "type": "string", "required": true }, "params": { "type": "object", "required": true } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not found", error: "Module 'module-id' doesn't exist." } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "string", "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": "string", "required": true } } } + Response 500 + Headers Content-Type: application/json + Body { data: null, code: 500, message: "500 Internal server error", error: "Cannot instantiate module 'module-id'" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "string", "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": "string", "required": true } } } ## Instance Collection by module id [/v1/instances/{module-id}] List all instances that has the same module as origin. + Parameters + module-id (required, string, `IfThen`) ... module id (class name) of parent module ### READ [GET] + Response 200 (application/json) + Body { "data": [ { "instanceId": "0", "moduleId": "IfThen", "active": "true", "title": "All On", "params": { "sourceDevice": { "filterIf": "scene", "scene": { "device": "ZWayVDev_zway_Remote_39-0-0-1-S" } }, "targets": [ { "filterThen": "switchBinary", "switchBinary": { "target": "ZWayVDev_zway_13-0-37", "status": "on" } }, { "filterThen": "switchBinary", "switchBinary": { "target": "Sonos_Device_Play_192.168.10.205_21", "status": "on" } } ] }, "id": 24, "creationTime": 1461152581 }, { "instanceId": "0", "moduleId": "IfThen", "active": true, "title": "All Off", "params": { "sourceDevice": { "filterIf": "scene", "scene": { "device": "ZWayVDev_zway_Remote_39-0-0-2-S" } }, "targets": [ { "filterThen": "switchBinary", "switchBinary": { "target": "Sonos_Device_Play_192.168.10.205_21", "status": "off" } }, { "filterThen": "switchBinary", "switchBinary": { "target": "DummyDevice_12", "status": "off" } } ] }, "id": 25, "creationTime": 1461152774 } ], "code": 200, "message": "200 OK", "error": null } + Response 404 + Headers Content-Type: application/json + Body { "data": null, "code": 404, "message": "404 Not Found", "error": "Instance foobar is not found" } ## Instance Model [/v1/instances/{instance-id}] | Instance Attributes| Type | Comment | |---------------|-----------------------------|----------------------------------------------------| | `id` | _immutable_ (string, uniq) | id of instance | | `moduleId` | _immutable_ (string) | id of referenced module (classname) | | `active` | (boolean, true) | marks instance as in-/active | | `title` | (string) | title of instance | | `params` | (object, {}) | depends on module, includes all last configured parameters that could be entered through the module form | | `creationTime`| (integer) | timestamp of creation time | + Parameters + instance-id (required, string, `7`) ... id (number) of target instance ### READ [GET] + Response 200 (application/json) + Body { data: [ { id: 7, moduleId: "BatteryPolling", active: "true", title: "Battery Polling", description: "Set up an interval that regularly polls the battery status of a battery devices.", params: { launchWeekDay: 0, warningLevel: "20" }, creationTime: 1459769281 } ], code: 200, message: "200 OK", error: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "array", "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true }, } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not found", error: "Instance 'instance-id' is not found" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "string", "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": "string", "required": true } } } ### UPDATE [PUT] + Request (application/json) + Body { error: null, data: { moduleId: "BatteryPolling", title: "New name for BatteryPolling", params: { launchWeekDay: 5, warningLevel: "15" } }, code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data": { "type": "object", "required": true, "properties": { "moduleId": { "type": "string", "required": true }, "params": { "type": "object", "required": true } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true } } } + Response 200 (application/json) + Body { error: null, data: [ { id: 7, moduleId: "BatteryPolling", active: "true", title: "New name for BatteryPolling", description: "Set up an interval that regularly polls the battery status of a battery devices.", params: { launchWeekDay: 5, warningLevel: "15" }, creationTime: 1459769281 } ], code: 200, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" :{ "type": "array", "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true }, } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not found", error: "Instance 'instance-id' is not found" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "string", "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": "string", "required": true } } } + Response 500 + Headers Content-Type: application/json + Body { data: null, code: 500, message: "500 Internal server error", error: "Cannot reconfigure module 'instance-id' config." } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "string", "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": "string", "required": true } } } ### DELETE [DELETE] + Response 204 (application/json) + Body { error: null, data: null, code: 204, message: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" :{ "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": ["string", "null"], "required": true }, } } + Response 404 + Headers Content-Type: application/json + Body { data: null, code: 404, message: "404 Not found", error: "Instance 'instance-id' is not found" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": "string", "required": true }, "data": { "type": ["string", "null"], "required": true }, "code": { "type": "integer", "required": true }, "message": { "type": "string", "required": true } } } # Group Devices The **Devices API** handles all actions of virtual devices in the gateway related to their origin or node (included device). To recognize all this API could be fetched immediately. Also sending commands to change levels etc. is is done by this API. ## Devices Collection [/v1/devices{?since}] | Devices Data Attribute | Type | Comment | |-------------------|---------------|--------------------------| | `structureChanged`| (boolean) | indicates of structure of list has changed (virtual devices has been added or removed) | | `updateTime` | (integer) | timestamp of last change | ### List all Devices [GET] + Parameters + since (optional, integer, `1459939093`) ... `updateTime` of the devices list. + Response 200 (application/json) + Body { data: { structureChanged: true, updateTime: 1464761666, devices: [{ creationTime: 1459939093, creatorId: 7, deviceType: "battery", h: -592588978, hasHistory: false, id: "BatteryPolling_7", location: 0, metrics: { probeTitle: "Battery", scaleTitle: "%", title: "Battery digest 7", level: 47, modificationTime: 1464684791, lastLevel: 47 }, permanently_hidden: false, probeType: "", tags: [], visibility: true, updateTime: 1464684791 }, { creationTime: 1459947936, creatorId: 29, deviceType: "sensorBinary", h: 1303282175, hasHistory: false, id: "ZWayVDev_zway_2-0-48-1", location: 2, metrics: { probeTitle: "General purpose", scaleTitle: "", icon: "motion", level: "on", title: "Motion - Living Room", modificationTime: 1463116944, lastLevel: "on" }, permanently_hidden: false, probeType: "general_purpose", tags: [], visibility: true, updateTime: 1464684789 }, { creationTime: 1461244852, creatorId: 29, deviceType: "sensorMultilevel", h: 1303283136, hasHistory: false, id: "ZWayVDev_zway_2-0-49-1", location: 2, metrics: { probeTitle: "Temperature", scaleTitle: "°C", level: 24, icon: "temperature", title: "Temperature Door - Living Room", modificationTime: 1463128674, lastLevel: 24 }, permanently_hidden: false, probeType: "temperature", tags: [], visibility: true, updateTime: 1464684789 }, { creationTime: 1461244853, creatorId: 29, deviceType: "sensorMultilevel", h: 1303283138, hasHistory: false, id: "ZWayVDev_zway_2-0-49-3", location: 2, metrics: { probeTitle: "Luminiscence", scaleTitle: "Lux", level: 130, icon: "luminosity", title: "Luminiscence - Living Room", modificationTime: 1463128674, lastLevel: 130 }, permanently_hidden: false, probeType: "luminosity", tags: [], visibility: true, updateTime: 1464684790 }, { creationTime: 1459947935, creatorId: 29, deviceType: "battery", h: -927793024, hasHistory: false, id: "ZWayVDev_zway_2-0-128", location: 0, metrics: { probeTitle: "Battery", scaleTitle: "%", level: 100, icon: "battery", title: "Multisensor Battery - Living Room", modificationTime: 1463128674, lastLevel: 100 }, permanently_hidden: false, probeType: "", tags: [], visibility: true, updateTime: 1464684790 }, { creationTime: 1461244852, creatorId: 29, deviceType: "sensorBinary", h: 1129728498, hasHistory: false, id: "ZWayVDev_zway_2-0-156-0-A", location: 2, metrics: { icon: "alarm", level: "on", title: "Motion Alarm - Living Room", modificationTime: 1462367037, lastLevel: "on" }, permanently_hidden: false, probeType: "alarmSensor_general_purpose", tags: [], visibility: true, updateTime: 1464684790 }, { creationTime: 1463037018, creatorId: 29, deviceType: "sensorMultilevel", h: -1332622515, hasHistory: false, id: "ZWayVDev_zway_18-0-49-1", location: 2, metrics: { probeTitle: "Temperature", scaleTitle: "°C", level: 25, icon: "temperature", title: "Temperature - Living Room", modificationTime: 1463116879, lastLevel: 25 }, permanently_hidden: false, probeType: "temperature", tags: [], visibility: true, updateTime: 1464684791 }, { creationTime: 1463037018, creatorId: 29, deviceType: "switchBinary", h: -1503060958, hasHistory: false, id: "ZWayVDev_zway_18-0-64", location: 2, metrics: { icon: "thermostat", title: "Thermostat operation - Living Room", level: "on", modificationTime: 1463037070, lastLevel: "on" }, permanently_hidden: false, probeType: "thermostat_mode", tags: [], visibility: true, updateTime: 1464684791 }, { creationTime: 1463037019, creatorId: 29, deviceType: "thermostat", h: -1332564855, hasHistory: false, id: "ZWayVDev_zway_18-0-67-1", location: 2, metrics: { scaleTitle: "°C", level: 23.5, min: 5, max: 40, icon: "thermostat", title: "Thermostat - Living Room", modificationTime: 1463037973, lastLevel: 23.5 }, permanently_hidden: false, probeType: "thermostat_set_point", tags: [], visibility: true, updateTime: 1464684791 }, { creationTime: 1460715630, creatorId: 21, deviceType: "switchBinary", h: 1606285100, hasHistory: false, id: "Sonos_Device_Play_192.168.10.205_21", location: 2, metrics: { title: "Sonos Play - Living Room", icon: "", level: "off", modificationTime: 1464619055, lastLevel: "off" }, permanently_hidden: false, probeType: "", tags: [], visibility: true, updateTime: 1464760397 }, { creationTime: 1460715630, creatorId: 21, deviceType: "switchMultilevel", h: 1810681234, hasHistory: false, id: "Sonos_Device_Volume_192.168.10.205_21", location: 2, metrics: { title: "Sonos Volume - Living Room", icon: "", level: 9, modificationTime: 1463640278, lastLevel: 9 }, permanently_hidden: false, probeType: "", tags: [], visibility: true, updateTime: 1464760397 }] }, code: 200, message: "200 OK", error: null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "updateTime": { "type": "integer", "required": true }, "structureChanged": { "type": "boolean", "required": true }, "devices": { "type": "array", "required": true } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } ## Virtual Device [/v1/devices/{id}] A single virtual device object with all its details. ## Virtual Device Attributes | Virtual Device Attribute | Type | Comment | |-----------------------|-----------------------------|----------------------------------------------| | `id` | _immutable_ (string, uniq) | unique identifier | | `deviceType` | _immutable_ (string) | major categorization of virtual device types, example _switchBinary_, _thermostat_, _sensorMultilevel_ etc.| | `updateTime` | _immutable_ (integer) | timestamp of last update| | `metrics` | (object) | device parameters | | `metrics.probeTitle` | (string) | title of probe, example: _Temperature_ | | `metrics.scaleTitle` | (string) | title of scale, example: _%_, _LUX_, _°C_ etc. | | `metrics.level` | (string or integer) | level, example: _5_, _21.4_, _on/off_, _ALARM_ etc. | | `metrics.icon` | (string) | custom icon field, example: _uri, icon type_ | | `metrics.title` | (string) | current title, example: _Temperature Door - Living Room_ | | `creationTime` | (integer) | creation time timestamp | | `creatorId` | (integer) | id of instance that has created the virtual device (Z-Way devices usually will be created from ZWave instance) | | `hasHistory` | (boolean) | virtual device has a history | | `permanently_hidden` | (boolean) | virtual device is pemanently hidden (inactive) - not usable in apps etc. | | `probeType` | _immutable_ (string) | type of probe for deeper categorization, example: _temperature_ | | `visibility` | (boolean) | hide virtual device in view (e.g. elements view) | | `tags` | (array) | list of tags assigned to virtual device | ## Virtual Device Types | deviceType |Metrics | Commands | Examples | |-----------------------------------|----------------------|-----------------------------------------------------------------------------------------------|-----------------------------------------------| | battery | probeTitle,scaleTitle, level, icon, title | - |- | | doorlock | level, icon, title |**_open_** or **_close_** |apiURL/devices/:deviceId/command/open | | thermostat | scaleTitle, min, max, level, icon, title |**_exact_** with get-param **_level_** |apiURL/devices/:deviceId/command/exact?level=22.5| | switchBinary (Thermostat) | level, icon, title | **_on_**, **_off_** or **_update_** |apiURL/devices/:deviceId/command/on | | switchBinary | level, icon, title | **_on_**, **_off_** or **_update_** |apiURL/devices/:deviceId/command/on | | switchMultilevel | level, icon, title | **_on_** Set(255), **_off_** Set(0), **_min_** Set(10), **_max_** Set(99), **_increase_** Set(l+10), **_decrease_** Set(l-10), **_update_**, **_exact_** + get params **_level_** |apiURL/devices/:deviceId/command/exact?level=40| | switchMultilevel (Blinds) | level, icon, title | **_up_** Set(255), **_down_** Set(0), **_upMax_** Set(99), **_increase_** Set(l+10), **_decrease_** Set(l-10), **_startUp_** StartLevelChange(0), **_startDown_** StartLevelChange(1), **_stop_** StopLevelChange(), **_update_**, **_excactSmooth_** + get params **_level_** |apiURL/devices/:deviceId/command/stop| | sensorBinary | probeTitle, level, icon, title | **_update_** |apiURL/devices/:deviceId/command/update | | sensorMultilevel | probeTitle, scaleTitle, level, icon, title | **_update_** |apiURL/devices/:deviceId/command/update | | toggleButton | level, icon, title | **_on_** |apiURL/devices/:deviceId/command/on | | camera | icon, title | depends on installed camera - could be: **_zoomIn_**, **_zoomOut_**, **_up_**, **_down_**, **_left_**, **_right_**, **_close_**, **_open_** | apiURL/devices/:deviceId/zoomIn | | switchControl | level, icon, title, change | **_on_**, **_off_**, **_upstart_**, **_upstop_**, **_downstart_**, **_downstop_**, **_exact_** with get-param **_level_** | apiURL/devices/:deviceId/command/on | | text | title, text, icon | - | - | | sensorMultiline | multilineType, title, icon, level, (scaleTitle, ...) | depends on apps | apiURL/devices/:deviceId/command/:cmd | | switchRGB | icon, title, color: {r:255,g:255,b:255}, level | **_on_**, **_off_**, **_exact_** with get-params: **_red_**, **_green_** and **_blue_** | apiURL/devices/:deviceId/command/exact?red=20&green=240&blue=0 | ## Probe Types (Z-Way) | Command Class |Probe Types | Sensor ID | Parent deviceType | |-----------------------------------|---------------------------|-----------|---------------------------| | ThermostatSetPoint | `thermostat_set_point` | - | thermostat | | ThermostatMode | `thermostat_mode` | - | switchBinary | | SwitchMultilevel | `multiLevel`
`motor` | default
3,5,6,7 | switchMultilevel | | SwitchColor (virtual device with RGB panel) | `switchColor_rgb` | - | switchRGB | | SwitchColor | `switchColor_soft_white`
`switchColor_cold_white`
`switchColor_red`
`switchColor_green`
`switchColor_blue` | 0
1
2
3
4 | switchMultilevel | | SensorBinary | `general_purpose`
`smoke`
`co`
`flood`
`cooling`
`tamper`
`door-window`
`motion` | default
2
3,4
6
7
8
10
12 | sensorBinary | | SensorMultilevel | `temperature`
`luminosity`
`energy`
`humidity`
`barometer`
`ultraviolet`| 1
3
4,15,16
5
9
27 | sensorMultilevel | | Meter | `meterElectric_kilowatt_per_hour`
`meterElectric_watt`
`meterElectric_pulse_count`
`meterElectric_voltage`
`meterElectric_ampere`
`meterElectric_power_factor` | 0
2
3
4
5
6 | sensorMultilevel | | Alarm | `alarm_general_purpose`
`alarm_smoke`
`alarm_co`
`alarm_coo`
`alarm_heat`
`alarm_flood`
`alarm_door`
`alarm_burglar`
`alarm_power`
`alarm_system`
`alarm_emergency`
`alarm_clock` | 0
1
2
3
4
5
6
7
8
9
10
11 | sensorBinary | | AlarmSensor | `alarmSensor_general_purpose`
`alarmSensor_smoke`
`alarmSensor_co`
`alarmSensor_coo`
`alarmSensor_heat`
`alarmSensor_flood`
`alarmSensor_door`
`alarmSensor_burglar`
`alarmSensor_power`
`alarmSensor_system`
`alarmSensor_emergency`
`alarmSensor_clock` | 0
1
2
3
4
5
6
7
8
9
10
11 | sensorBinary | Probe Types are used to allow deeper categorizations in the Namespaces API. If a virtual device has no probeType then the parameter will be empty `''`. You can also use probeType in your apps to differentiate between your virtual devices generated by them. ## Icon Types **icon** attribute of **metrics** object may be: | Icon Attribute |Comment |--------------------------------------------------|--------------------------------------------| | `''` | none| | `url(uri)` | URL, example: http://image.com/image.png .| | `relative url string` | use home automation api to show pictures from app htdocs folder, example: `apiURL/modulemedia/:module_name/:file_name`| | `switch` | for **switchBinary** deviceType.| | `multilevel`, `blinds` | for **switchMultilevel** deviceType.| | `smoke`, `co`, `cooling`, `door`, `motion`, `flood`, `alarm`, `window` | for **sensorBinary** deviceType.| | `temperature`, `luminosity`, `energy`, `humidity`, `ultraviolet`, `barometer` | for **sensorMultilevel** deviceType.| | `battery` | for **battery** deviceType.| | `thermostat` | for **thermostat** deviceType.| | `meter` | for **meter** deviceType.| | `door` | for **doorlock** deviceType.| | `doorlockcontrol` | for **doorlock** deviceType.| | `camera` | for **camera** deviceType.| + Parameters + id (required, integer, `1`) ... Numeric `id` of the Device to perform action with. ### READ [GET] Retrieve a virtual device + Response 200 (application/json) + Body { "data": { "creationTime": 1464779507, "creatorId": 34, "deviceType": "sensorMultilevel", "h": 1303283138, "hasHistory": false, "id": "ZWayVDev_zway_2-0-49-3", "location": 0, "metrics": { "probeTitle": "Luminiscence", "scaleTitle": "Lux", "level": 130, "icon": "luminosity", "title": "Luminiscence - Living Room", "modificationTime": 1464779507, "lastLevel": 130 }, "permanently_hidden": false, "probeType": "luminosity", "tags": [], "visibility": true, "updateTime": 1464779507 }, "code": 200, "message": "200 OK", "error": null } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "creationTime": { "type": "integer", "required": true }, "creatorId": { "type": "integer", "required": true }, "deviceType": { "type": "string", "required": true }, "hasHistory": { "type": "bolean" "required": false }, "id": { "type": "integer", "required": true }, "location": { "type": "integer", "required": true }, "metrics": { "type": "object", "required": true }, "permanently_hidden": { "type": "boolean" "required": true }, "probeType": { "type": "integer", "required": false }, "tags": { "type": "array", "required": false }, "visibility": { "type": "boolean" "required": true }, "updateTime": { "type": "integer" "required": true } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } ### UPDATE [PUT] Update a virtual device + Parameters + id (required, integer, `1`) ... Numeric `id` of the Device to perform action with. + Request (application/json) + Body { "creationTime": 1464779507, "creatorId": 34, "deviceType": "sensorMultilevel", "h": 1303283138, "hasHistory": false, "id": "ZWayVDev_zway_2-0-49-3", "location": 0, "metrics": { "probeTitle": "Luminiscence", "scaleTitle": "Lux", "level": 130, "icon": "luminosity", "title": "Luminiscence - Living Room", "modificationTime": 1464779507, "lastLevel": 130 }, "permanently_hidden": false, "probeType": "luminosity", "tags": [], "visibility": true, "updateTime": 1464779507 } + Response 200 (application/json) + Body { "data": { "creationTime": 1464779507, "creatorId": 34, "deviceType": "sensorMultilevel", "h": 1303283138, "hasHistory": false, "id": "ZWayVDev_zway_2-0-49-3", "location": 0, "metrics": { "probeTitle": "Luminiscence", "scaleTitle": "Lux", "level": 130, "icon": "luminosity", "title": "Luminiscence - Living Room", "modificationTime": 1464779507, "lastLevel": 130 }, "permanently_hidden": false, "probeType": "luminosity", "tags": [], "visibility": true, "updateTime": 1464779507 }, "code": 200, "message": "200 OK", "error": null } + Response 404 (application/json) + Body { data: null, code: 404, message: "404 Not found", error: "Device :id doesn't exist" } ## Devices Commands [/v1/devices/{id}/command/{command}{?params}] ### Send Command to Device [GET] + Response 200 (application/json) + Body { "data": null, "code": 200, "message": "200 OK", "error": null } # Group Locations Locations related resources of the **Locations API** ## Locations Collection [/v1/locations] ### List all Locations [GET] + Response 200 (application/json) + Body { "data":[ {"id":6,"title":"Garage"}, {"id":7,"title":"Hall"}, {"id":8,"title":"Kids"}, {"id":9,"title":"Kitchen"}, {"id":10,"title":"Bedroom"}, {"id":11,"title":"name123"}, {"id":12,"title":"test"} ], "error": null, "code": "200 OK", "message" null, } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "array", "required": true }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } ### CREATE [POST] Create a Location + Request (application/json) + Body { "title": "Garage", "icon": "http://example.com/garage.png" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "title": { "type": "string", "required": true }, "icon": { "type": "string", "required": false } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } + Parameters + title (required, string, `Garage`) ... `title` of the location. + icon (optional, string, `http://example.com/garage.png`) ... `icon` of the location. + Response 201 (application/json) + Body { "id": 1, "title": "Garage", "icon": "http://example.com/garage.png" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "id": { "type": "integer", "required": true }, "title": { "type": "string", "required": true }, "icon": { "type": "string", "required": true } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } ## Location [/v1/locations/{id}] A single Location object with all its details + Parameters + id (required, integer, `1`) ... Numeric `id` of the Location to perform action with. ### READ [GET] Retrieve a Location + Response 200 (application/json) + Body { "id": 2, "title": "Pick-up posters from post-office" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "title": { "type": "string", "required": true }, "icon": { "type": "string", "required": false } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } ### UPDATE [PUT] Update a Location + Response 200 (application/json) + Body { "id": 2, "title": "Pick-up posters from post-office" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "title": { "type": "string", "required": true }, "icon": { "type": "string", "required": false } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } ### DELETE [DELETE] Remove a Location + Response 204 # Group Profiles Profiles related resources of the **Profiles API** ## Profiles Collection [/v1/profiles] ### List all Profiles [GET] + Response 200 (application/json) [{ "data":[ "{"id":1,"name":"Default","description":"This is default profile. Default profile created automatically.","widgets":[],"active":true}", ], "error": null, "message": null, "code": "200 OK" }] ### CREATE [POST] Create a Profile + Request (application/json) + Body { "name": "Default", "description": "descriptions", "active": false, "positions": []} + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "description": { "type": "string", "required": false, }, "title": { "type": "string", "required": true }, "positions": { "type": "array", "required": true }, "active": { "type": "boolean", "required": true } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } + Response 201 (application/json) + Body { "data":[ "{"id":1,"name":"Default","description":"This is default profile. Default profile created automatically.","widgets":[],"active":true}", ], "error": null, "message": null, "code": "200 OK" } ## Profile [/v1/profiles/{id}] A single Profile object with all its details + Parameters + id (required, integer, `1`) ... Numeric `id` of the Profile to perform action with. + name (required, string, `Default`) ... `name` of the profile. + description (optional, string, `description`) ... `description` of the profile. + active (optional, boolean, `false`) ... `profile is activated`. + positions (required, array, `[deviceId1, deviceId2]`) ... `show and sortable on dashboard`. ### READ [GET] Retrieve a Profile + Response 200 (application/json) + Body { "data":[ {"id":1,"name":"Default","description":"This is default profile. Default profile created automatically.","positions":[],"active":true}, ], "error": null, "message": null, "code": "200 OK" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "id": { "type": "integer", "required": true }, "title": { "type": "string", "required": true }, "positions": { "type": "array", "required": true }, "active": { "type": "boolean", "required": true } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } ### Update a Profile [PUT] + Parameters + id (required, integer, `1`) ... Numeric `id` of the Profile to perform action with. + name (required, string, `Default`) ... `name` of the profile. + description (optional, string, `description`) ... `description` of the profile. + active (optional, boolean, `false`) ... `profile is activated`. + positions (required, array, `[deviceId1, deviceId2]`) ... `show and sortable on dashboard`. + Response 200 (application/json) + Body { "data":[ "{"id":1,"name":"Default","description":"This is default profile. Default profile created automatically.","widgets":[],"active":true}", ], "error": null, "message": null, "code": "200 OK" } + Schema { "type": "object", "required": true, "properties": { "error": { "type": ["string", "null"], "required": true }, "data" "type": "object", "required": true, "properties": { "title": { "type": "string", "required": true }, "description": { "type": "string", "required": false }, "positions": { "type": "array", "required": true }, "active": { "type": "boolean", "required": true } } }, "code": { "type": ["integer", "null"], "required": true }, "message": { "type": ["string", "null"], "required": true }, } } ### Remove a Profile [DELETE] + Response 204 # Group Notifications Locations related resources of the **Notifications API** ## Notifications Collection [/v1/notifications{?since}] ### List all Notification [GET] + Parameters + redeemed = `false` (optional, boolean, `?redeemed=true`) ... if `redeemed` eq true - include redeemed notifications + since (optional, integer, `?since=1387881635`) ... `updateTime` of the Notifications. + Response 200 (application/json) + Body {"error":null, "data":{ "updateTime":1387884437, "notifications":[ {"id":"1387199352223","timestamp":"2013-12-16T13:09:12.223Z","level":"error","message":"Cannot remove location 54545 - doesn't exist","redeemed":true}, {"id":"1387200419730","timestamp":"2013-12-16T13:26:59.730Z","level":"error","message":"Cannot remove location 54545 - doesn't exist","redeemed":true} ] } } ## Notification [/v1/notifications/{id}] A single Notice object with all its details + Parameters + id (required, integer, `1`) ... Numeric `id` of the notifications to perform action with. ### Retrieve a Notification [GET] + Response 200 (application/json) + Body {"error":null,"data":{"id":1387202340396,"timestamp":"2013-12-16T13:59:00.396Z","level":"error","message":"Cannot remove location 54545 - doesn't exist","redeemed":true}} ### Update a Notification [PUT] + Parameters + id (required, integer, `1`) ... Numeric `id` of the Notification to perform action with. + Request (application/json) + Body {"id":1387202340396,"timestamp":"2013-12-16T13:59:00.396Z","level":"error","message":"Cannot remove location 54545 - doesn't exist","redeemed":true} + Response 200 (application/json) + Body {"error":null,"data":{"id":1387202340396,"timestamp":"2013-12-16T13:59:00.396Z","level":"error","message":"Cannot remove location 54545 - doesn't exist","redeemed":true}} # Group Device Histories Device related resources of the **History API** ## Histories Collection [/v1/history] ### List all Device Histories [GET] + Response 200 (application/json) + Body {"error":null, "data":{ "updateTime":1387884437, "history":[ { "id": "ZWayVDev_zway_3-0-48-1", "dT": "sensorBinary", "mH": [ { "id": 1424185598, "t": "2015-02-17T15:06:38.404Z", "l": "on" }, { "id": 1424185662, "t": "2015-02-17T15:07:42.382Z", "l": "off" } ] },{ "id": "ZWayVDev_zway_3-0-49-3", "dT": "sensorMultilevel", "mH": [ { "id": 1424172352, "t": "2015-02-17T11:25:52.723Z", "l": "21" }, { "id": 1424172382, "t": "2015-02-17T11:26:22.743Z", "l": "22" } ] } ] } } ## Device History [/v1/history/{id}{?since}] A single device history object with all its details + Parameters + id (optional, string, `/ZWayVDev_zway_3-0-48-1`) ... Device `id` of the device to perform action with. + since (optional, integer, `?since=1424186368`) ... `updateTime` of the metrics change. ### Retrieve a single Device History [GET] + Response 200 (application/json) + Body { "data": { "id": "ZWayVDev_zway_3-0-48-1", "since": 1424186368, "deviceHistory": [ { "id": 1424186368, "t": "2015-02-17T15:19:28.698Z", "l": "off" }, { "id": 1424186396, "t": "2015-02-17T15:19:56.806Z", "l": "on" }, { "id": 1424186427, "t": "2015-02-17T15:20:27.238Z", "l": "off" }, { "id": 1424187453, "t": "2015-02-17T15:37:33.410Z", "l": "on" } ] }, "code": 200, "message": "200 OK", "error": null } ================================================ FILE: classes/AuthController.js ================================================ /*** Automation Webserver Auth Controller ************************************* Version: ------------------------------------------------------------------------------- Author: Poltorak Serguei Copyright: (c) ZWave.Me, 2015 ******************************************************************************/ 'use strict'; function AuthController (controller) { // available roles this.ROLE = { ADMIN: 1, USER: 2, LOCAL: 3, ANONYMOUS: 4 }; this.forgottenPwdCollector = {}; // link to controller to get profiles this.controller = controller; } AuthController.prototype.isAuthorized = function(myRole, requiredRole) { if (!requiredRole) { // no role required, allow access return true; } return myRole <= requiredRole; } AuthController.prototype.getSessionId = function(request) { // check if session id is specified in cookie or header var profileSID; // first try Authorization Bearer var authHeader = request.headers['Authorization']; if (authHeader && authHeader.indexOf('Bearer ') === 0) { profileSID = authHeader.substr('Bearer '.length).split('/')[1]; if (profileSID) { request.__authMethod = 'Authorization Bearer'; return profileSID; } } // second try ZWAYSession profileSID = request.headers['ZWAYSession']; if (profileSID) { request.__authMethod = 'ZWAYSession'; return profileSID; } // third try cookie var cookiesHeader = request.headers['Cookie']; var cookiesIgnore = !!request.headers['ZWAYSessionCookieIgnore']; if (cookiesHeader && !cookiesIgnore) { var cookie = cookiesHeader.split(";").map(function(el) { return el.trim().split("="); }).filter(function(el) { return el[0] === "ZWAYSession" }); if (!!cookie && !!cookie[0]) { request.__authMethod = 'Cookie'; return cookie[0][1]; } } return ''; } AuthController.prototype.resolve = function(request, requestedRole) { var user, role, token, session, self = this; if (request.user && request.role && request.authToken) { user = request.user; role = request.role; token = request.authToken; if ((role === this.ROLE.LOCAL || role === this.ROLE.ANONYMOUS) && requestedRole === this.ROLE.USER) { role = this.ROLE.USER; } } else { var defaultProfile = _.filter(this.controller.profiles, function (profile) { return profile.login === 'admin' && profile.password === 'admin'; }); var noAnonymous = false; var reqSession = self.getSessionId(request); if (reqSession) { noAnonymous = true; var removedExpired = false; session = _.find(this.controller.profiles, function(profile) { var _profile, toRemove = []; _profile = _.find(profile.authTokens, function(authToken) { // remove expired tokens if (authToken.expire > 0 && authToken.expire < Date.now()) { toRemove.push(authToken.sid); removedExpired = true; } if (authToken.sid == reqSession) { // make the Authorization Bearer always permanent if (authToken.expire != 0 && request.__authMethod == 'Authorization Bearer') { self.controller.permanentToken(profile, authToken.sid); } // Update last seen and IP authToken.lastSeen = Date.now(); authToken.ip = self.getClientIP(request); return true; } }); toRemove.forEach(function(token) { self.controller.removeToken(profile, token, true); // skip save }); return _profile; }); if (removedExpired) this.controller.saveConfig(false); } if (!session) { // no session found or session expired // try Basic auth var authHeader = request.headers['Authorization']; if (authHeader && authHeader.substring(0, 6) === "Basic ") { authHeader = Base64.decode(authHeader.substring(6)); if (authHeader) { noAnonymous = true; var authInfo = authHeader.split(':'); if (authInfo.length === 2 && authInfo[0].length > 0) { var profile = _.find(this.controller.profiles, function (profile) { return profile.login === authInfo[0]; }); if (profile && (!profile.salt && profile.password === authInfo[1] || profile.salt && profile.password === hashPassword(authInfo[1], profile.salt))) { // auth successful, use selected profile session = profile; } } } } if (!session && requestedRole === this.ROLE.USER) { // try to find Local user account if (request.peer.address === "127.0.0.1" && defaultProfile.length < 1 && !this.controller.config.firstaccess) { // don't treat find.z-wave.me as local user (connection comes from local ssh server) if (!(request.headers['Cookie'] && request.headers['Cookie'].split(";").map(function(el) { return el.trim().split("="); }).filter(function(el) { return el[0] === "ZBW_SESSID" }))) { // TODO: cache this in future session = _.find(this.controller.profiles, function (profile) { return profile.role === self.ROLE.LOCAL; }); } } // try to find Anonymous user account if (!session) { // TODO: cache this in future session = _.find(this.controller.profiles, function (profile) { return profile.role === self.ROLE.ANONYMOUS; }); } } if (!session) { // no session found, but requested role is anonymous - use dummy session. if (requestedRole === this.ROLE.ANONYMOUS && !noAnonymous) { session = { id: -1, // non-existant ID role: this.ROLE.ANONYMOUS }; } else { return null; } } } // change role type, if we found matching local/anonymous user (real user, not dummy with -1) if (session.id !== -1 && (session.role === this.ROLE.LOCAL || session.role === this.ROLE.ANONYMOUS)) { role = this.ROLE.USER; } user = session.id; if (!role) { role = session.role; } token = reqSession.substr(0, 6); } // save in request fields for handling in API and to allow subsequent call of this function request.user = user; request.role = role; request.authToken = token; return {user: user, role: role, token: token}; }; AuthController.prototype.checkIn = function(profile, req, permanent) { var sid; // generate a new sid do { sid = crypto.guid(); // and make sure it is unique (first 6 letters can be used to show to the user and to delete sid) var found = _.find(this.controller.profiles, function(profile) { _.find(profile.authTokens, function(authToken) { authToken.sid.substr(0, 6) == sid.substr(0, 6); }); }); if (!found) break; } while(true); // save user agent var userAgent; if (req && req.headers && req.headers['User-Agent']) { userAgent = req.headers['User-Agent']; } // save auth token in the profile if (!profile.authTokens) { profile.authTokens = []; } var TTL = 7 * 24 * 60 * 60 * 1000; // 1 week in ms var d = Date.now(); profile.authTokens.push({ sid: sid, agent: userAgent, date: d, lastSeen: d, ip: this.getClientIP(req), expire: permanent ? 0 : (d + TTL) }); this.controller.saveConfig(false); return sid }; AuthController.prototype.forgottenPwd = function(email, token) { var self = this, success = true, setToken = function(){ self.forgottenPwdCollector[token] = { email: email, expTime: Math.floor(Date.now() / 1000) + 600 // 10 min }; }; if (Object.keys(this.forgottenPwdCollector).length > 0) { Object.keys(this.forgottenPwdCollector).forEach(function(t){ if (self.forgottenPwdCollector[t].email === email) { success = false; } }); if (success) { setToken(); } } else { setToken(); } if (!this.expireTokens) { this.expireTokens = setInterval(function() { var expirationTime = Math.floor(Date.now() / 1000); Object.keys(self.forgottenPwdCollector).forEach(function(tkn) { if (self.forgottenPwdCollector[tkn].expTime < expirationTime) { self.removeForgottenPwdEntry(tkn); } }); if (Object.keys(self.forgottenPwdCollector).length < 1 && self.expireTokens) { clearInterval(self.expireTokens); self.expireTokens = null; } }, 60 * 1000); // check each minute } return success; }; AuthController.prototype.removeForgottenPwdEntry = function(token) { if (this.forgottenPwdCollector[token]){ delete this.forgottenPwdCollector[token]; } }; AuthController.prototype.getForgottenPwdToken = function(token) { var result = null; if (this.forgottenPwdCollector[token]){ result = this.forgottenPwdCollector[token]; } return result; }; AuthController.prototype.getClientIP = function(request) { var ip = request.peer.address; if (request.peer.address === "127.0.0.1") { // don't treat find.z-wave.me as local user (connection comes from local ssh server) if (request.headers['Cookie'] && request.headers['Cookie'].split(";").map(function(el) { return el.trim().split("="); }).filter(function(el) { return el[0] === "ZBW_SESSID" }) && request.headers['X-Forwarded-For']) { ip = request.headers['X-Forwarded-For'].split(",")[0].trim(); } } return ip; }; ================================================ FILE: classes/AutomationController.js ================================================ /*** Z-Way HA Controller class module ***************************************** Version: 1.0.0 ------------------------------------------------------------------------------- Author: Gregory Sitnin Copyright: (c) ZWave.Me, 2013 ******************************************************************************/ function AutomationController() { AutomationController.super_.call(this); this.savePeriod = 60; // save objects and notifications period (in seconds): increase for slow SD cards this.config = config.controller || {}; this.debug = false; this.availableLang = ['en', 'ru', 'de', 'sk', 'cz', 'se', 'fr', 'es']; // will be updated by correct ISO language codes in future this.defaultLang = 'en'; this.profiles = config.profiles; this.instances = config.instances; this.locations = config.locations || []; this.vdevInfo = config.vdevInfo || {}; this.modules_categories = fs.loadJSON('modulesCategories.json') || []; this.namespaces = namespaces || []; this.registerInstances = {}; this.files = files || {}; this.modules = {}; this.devices = new DevicesCollection(this); this.notifications = []; this.lastStructureChangeTime = 0; this.notificationChannels = {}; this._loadedSingletons = []; this.auth = new AuthController(this); this.skins = loadObject('userSkins.json') || [{ name: "default", title: "Default", description: "Default skin", version: "1.0.0", icon: true, author: "Z-Wave.Me", homepage: "https://www.z-wave.me", active: true }]; this.icons = loadObject('userIcons.json') || []; } inherits(AutomationController, EventEmitter2); function wrap(self, func) { return function() { func.apply(self, arguments); }; } AutomationController.prototype.init = function() { var self = this; function pushNamespaces(device, locationNspcOnly) { self.generateNamespaces(function(namespaces) { ws.push('me.z-wave.namespaces.update', namespaces, self.profilesByRole(self.auth.ROLE.ADMIN)); }, device, locationNspcOnly); } self.loadModules(function() { self.emit("core.init"); // update device state self.devices.on('change:metrics:level', function(device) { ws.push("me.z-wave.devices.level", device.toJSON(), self.profilesByDevice(device.id)); }); // update namespaces if device title has changed self.devices.on('change:metrics:title', function(device) { ws.push("me.z-wave.devices.title_update", device.toJSON(), self.profilesByDevice(device.id)); pushNamespaces(device, false); }); // update only location namespaces if device location has changed self.devices.on('change:location', function(device) { ws.push("me.z-wave.devices.location_update", device.toJSON(), self.profilesByRole(self.auth.ROLE.ADMIN)); pushNamespaces(device, true); var id = device.get('id'); locationId = device.get('location'), order = device.get('order'), count = 0; self.devices.forEach(function(dev) { if (dev.get('location') == locationId) { count++; } }); 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 && 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"); } }); } device.set("locationName", self.locationName(locationId)); }); // update namespaces if device permanently_hidden status has changed self.devices.on('change:permanently_hidden', function(device) { ws.push("me.z-wave.devices.visibility_update", device.toJSON(), self.profilesByDevice(device.id)); pushNamespaces(device, false); }); // update namespaces if device removed status has changed self.devices.on('change:metrics:removed', function(device) { ws.push("me.z-wave.devices.visibility_update", device.toJSON(), self.profilesByDevice(device.id)); pushNamespaces(device, false); }); // update namespaces if structure of devices collection changed self.devices.on('created', function(device) { ws.push("me.z-wave.devices.add", device.toJSON(), self.profilesByDevice(device.id)); pushNamespaces(device); }); // update namespaces if structure of devices collection changed self.devices.on('wipedOut', function(device_id) { ws.push("me.z-wave.devices.wipe", device_id, self.profilesByDevice(device_id)); }); 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) { return location.id === locationId; }); 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) { if (!data) { console.log("Error location not exists"); } }); } } } } ws.push("me.z-wave.devices.remove", id, self.profilesByDevice(id)); }); self.on('location.added', function(location) { ws.push("me.z-wave.locations.add", location, self.profilesByLocation(location)); }); self.on('location.removed', function(location) { ws.push("me.z-wave.locations.remove", location, self.profilesByLocation(location)); }); self.on('location.updated', function(location) { ws.push("me.z-wave.locations.updated", location, self.profilesByLocation(location)); }); self.on('profile.updated', function(profile) { ws.push("me.z-wave.profile.updated", self.safeProfile(profile), self.profilesByRole(self.auth.ROLE.ADMIN).concat([profile.id])); }); self.on("notifications.push", function(notice) { ws.push("me.z-wave.notifications.add", notice, self.profilesByRole(self.auth.ROLE.ADMIN)); }); }); }; AutomationController.prototype.setDefaultLang = function(lang) { var self = this, oldLang = self.defaultLang; self.defaultLang = self.availableLang.indexOf(lang) === -1 ? 'en' : lang; if (self.defaultLang !== oldLang) { this.emit('language.changed', self.defaultLang); } }; AutomationController.prototype.saveConfig = function(immediate) { // do clean up of location namespaces cleanupLocations = function(locations) { var newLoc = []; locations.forEach(function(loc) { newLoc.push(_.omit(loc, 'namespaces')); }); return newLoc; }; var cfgObject = { "controller": this.config, "vdevInfo": this.vdevInfo, "locations": cleanupLocations(this.locations), "profiles": this.profiles, "instances": this.instances }; try { saveObject("config.json", cfgObject, immediate); } catch (e) { console.log("Error: can not write back config to storage: ", e); } }; AutomationController.prototype.saveFiles = function() { saveObject("files.json", this.files, true); }; AutomationController.prototype.start = function(reload) { var restore = restore || false; // if reload flag is true, overwrite config values first 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 }]; this.icons = loadObject('userIcons.json') || []; } // Restore persistent data this.loadNotifications(); // Run all modules console.log("Loading modules..."); this.instantiateModules(); ZAutomation = function() { return { status: 400, body: "Invalid ZAutomation request" }; }; ws.allowExternalAccess("ZAutomation", this.auth.ROLE.ANONYMOUS); // Run webserver console.log("Starting automation..."); ZAutomation.api = new ZAutomationAPIWebRequest(this).handlerFunc(); ws.allowExternalAccess("ZAutomation.api", this.auth.ROLE.ANONYMOUS); // there would be additional auth check for each request // Run storage console.log("Starting storage..."); ZAutomation.storage = new ZAutomationStorageWebRequest(this).handlerFunc(); ws.allowExternalAccess("ZAutomation.storage", this.auth.ROLE.ANONYMOUS); // there would be additional auth check for each request // Notify core this.emit("core.start"); }; AutomationController.prototype.reloadConfig = function() { var reloadedConfig = loadObject("config.json"); // overwrite variables with restored data this.config = reloadedConfig.controller || config.controller; this.profiles = reloadedConfig.profiles || config.profiles; this.instances = reloadedConfig.instances || config.instances; this.locations = reloadedConfig.locations || config.locations; this.vdevInfo = reloadedConfig.vdevInfo || config.vdevInfo; }; AutomationController.prototype.stop = function() { var self = this, modWithoutDep = []; // Remove API webserver console.log("Stopping automation..."); ZAutomation = null; ws.revokeExternalAccess("ZAutomation"); ws.revokeExternalAccess("ZAutomation.api"); ws.revokeExternalAccess("ZAutomation.storage"); // Clean instances console.log("Stopping instances with dependencies ..."); 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) { self.removeInstance(instance.id); } else if ((instance.active === true || instance.active === 'true')) { modWithoutDep.push(instance.id) } } }); // stop instances without dependencies at least if (modWithoutDep.length > 0) { console.log("Stopping all remaining instances ..."); modWithoutDep.forEach(function(instanceId) { self.removeInstance(instanceId); }) } this._loadedSingletons = []; // save notifications on automation shut down this.notifications.finalize(); this.notifications = null; // Notify core this.emit("core.stop"); }; AutomationController.prototype.restart = function() { this.stop(); this.start(); var langFile = this.loadMainLang(); this.addNotification("warning", langFile.ac_warn_restart, "core", "AutomationController"); }; AutomationController.prototype.loadModules = function(callback) { console.log("--- Loading ZAutomation classes"); var self = this; fs.list("modules/").forEach(function(moduleClassName) { self.loadModuleFromFolder(moduleClassName, "modules/"); }); (fs.list("userModules/") || []).forEach(function(moduleClassName) { self.loadModuleFromFolder(moduleClassName, "userModules/"); }); if (typeof callback === 'function') { callback(); } }; AutomationController.prototype.loadModuleFromFolder = function(moduleClassName, folder, ignoreVersion) { var self = this, langFile = self.loadMainLang(), values, addModule = false, ignoreVersion = ignoreVersion ? ignoreVersion : false; var moduleMetaFilename = folder + moduleClassName + "/module.json", _st; _st = fs.stat(folder + moduleClassName); if (_st && "file" === _st.type) { return; // skip files in modules folders } _st = fs.stat(moduleMetaFilename); if (!_st || "file" !== _st.type || 2 > _st.size) { console.log("ERROR: Cannot read module metadata from", moduleMetaFilename); return; } try { var moduleMeta = fs.loadJSON(moduleMetaFilename); } catch (e) { values = moduleMetaFilename + ": " + e.toString(); self.addNotification("error", langFile.ac_err_load_mod_json + values, "core", "AutomationController"); console.log(e.stack); return; // skip this modules } if (moduleMeta.hasOwnProperty("skip"), !!moduleMeta["skip"]) return; var moduleFilename = folder + moduleClassName + "/index.js"; _st = fs.stat(moduleFilename); if ("file" !== _st.type || 2 > _st.size) { console.log("ERROR: Cannot stat module", moduleFilename); return; } moduleMeta.id = 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) { var existingVersion = self.modules[moduleClassName].meta.version.toString(), currentVersion = moduleMeta.version.toString(); if (existingVersion.localeCompare(currentVersion) === 0 || ignoreVersion) { addModule = true; } else { addModule = has_higher_version(currentVersion, existingVersion); } } else { addModule = true; } if (addModule) { // Grab _module and clear it out self.modules[moduleClassName] = { meta: moduleMeta, location: folder + moduleClassName }; } else { console.log('Lower version detected, ignoring ...'); } return addModule; }; AutomationController.prototype.instantiateModule = function(instanceModel) { var self = this, module = _.find(self.modules, function(module) { return instanceModel.moduleId === module.meta.id; }), instance = null, langFile = self.loadMainLang(), values, cntExistInst = []; if (!module) { self.addNotification("error", langFile.ac_err_init_module_not_found, "core", "AutomationController"); return null; // not loaded } if (module.failed) { self.addNotification("error", langFile.ac_err_load_failure + (module.meta && module.meta.id ? ': ' + module.meta.id : ''), "core", "AutomationController"); return null; // not loaded } // add creation time if (!instanceModel.creationTime) { instanceModel.creationTime = Math.floor(Date.now() / 1000); } try { instance = new global[module.meta.id](instanceModel.id, self); } catch (e) { values = ((module && module.meta) ? module.meta.id : instanceModel.moduleId) + ": " + e.toString(); self.addNotification("error", langFile.ac_err_init_module + values, "core", "AutomationController"); console.log(e.stack); return null; // not loaded } console.log("Instantiating module", instanceModel.id, "from class", module.meta.id); cntExistInst = _.filter(self.instances, function(inst) { return module.meta.id === inst.moduleId; }); if (module.meta.singleton) { if (in_array(self._loadedSingletons, module.meta.id) && cntExistInst.length > 1) { console.log("WARNING: Module", instanceModel.id, "is a singleton and already has been instantiated. Skipping."); return null; // not loaded } self._loadedSingletons.push(module.meta.id); } instanceModel.active = instanceModel.active === 'true' || (typeof instanceModel.active == 'boolean' && instanceModel.active) ? true : false; if (typeof instanceModel.instanceId != 'undefined') { delete instanceModel.instanceId; } if (instanceModel.active) { try { instance.init(instanceModel.params); } catch (e) { // remove singleton entry of broken instance if (module.meta.singleton) { var index = self._loadedSingletons.indexOf(module.meta.id); if (index > -1) { self._loadedSingletons.splice(index, 1); } } values = ((module && module.meta) ? module.meta.id : instanceModel.moduleId) + ": " + e.toString(); self.addNotification("error", langFile.ac_err_init_module + values, "core", "AutomationController"); console.log(e.stack); return null; // not loaded } self.registerInstance(instance); } // add module to loaded modules if at least one instance exists if (this.loadedModules.indexOf(module) < 0) { this.loadedModules.push(module); } return instance; }; AutomationController.prototype.loadModule = function(module, rootModule, instancesCount) { var langFile = this.loadMainLang(), values; if (rootModule && rootModule === module) { console.log('Circular dependencies detected!'); return false; } if (module.failed) return false; // already tried to load, and failed if (this.loadedModules.indexOf(module) >= 0) return true; // already loaded rootModule = rootModule || module; if (module.meta.dependencies instanceof Array) { for (var i in module.meta.dependencies) { var dep = module.meta.dependencies[i]; values = dep + " :: " + module.meta.id; var depModule = this.modules[dep]; if (!depModule) { this.addNotification("error", langFile.ac_err_dep_not_found + values, "dependency", module.meta.id); module.failed = true; return false; } if (!this.loadModule(depModule, rootModule)) { this.addNotification("error", langFile.ac_err_dep_not_loaded + values, "dependency", module.meta.id); module.failed = true; return false; } 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; } } } console.log("Loading module " + module.meta.id + " from " + module.location); try { executeFile(module.location + "/index.js"); } catch (e) { values = module.meta.id + ": " + e.toString(); this.addNotification("error", langFile.ac_err_file_load + values, "core", "AutomationController"); console.log(e.stack); module.failed = true; return false; // skip this modules } if (!_module) { values = module.meta.id; this.addNotification("error", langFile.ac_err_invalid_module + values, "core", "AutomationController"); module.failed = true; return false; // skip this modules } // Monkey-patch module with basePath method _module.prototype.moduleBasePath = function() { return module.location; }; module.classRef = _module; _module = undefined; // Loading instances var count = 0; this.instances.filter(function(x) { return x.moduleId === module.meta.id; }).forEach(function(x) { if (this.instantiateModule(x) !== null) { count++; } }, this); if (count || instancesCount) { this.loadedModules.push(module); } return true; }; AutomationController.prototype.unloadModule = function(moduleId) { var self = this, activeInstances = [], result = 'failed'; try { // filter for instances of moduleId activeInstances = self.instances.filter(function(instance) { return instance.moduleId === moduleId; }).map(function(instance) { return instance.id; }); // remove all instances of moduleId if (activeInstances.length > 0) { activeInstances.forEach(function(instanceId) { self.deleteInstance(instanceId); }); } //remove from loaded Modules self.loadedModules = self.loadedModules.filter(function(module) { return module.meta.id !== moduleId; }); // remove from modules list if (self.modules[moduleId]) { delete self.modules[moduleId]; result = 'success'; } } catch (e) { result = e; } return result; }; AutomationController.prototype.installModule = function(moduleUrl, moduleName) { var result = "in progress"; console.log('Installing app', moduleName, '...'); if (moduleUrl) { installer.install( moduleUrl, moduleName, function() { result = "done"; }, function(e) { console.log("Error installing app " + moduleName + ": " + e); result = "failed"; } ); var d = Date.now() + 20000; // wait not more than 20 seconds while (Date.now() < d && result === "in progress") { processPendingCallbacks(); } if (result === "in progress") { result = "failed"; } } return result; }; AutomationController.prototype.uninstallModule = function(moduleId, reset) { var langFile = this.loadMainLang(), uninstall = false, reset = reset ? reset : false, unload = this.unloadModule(moduleId), result = "in progress"; if (unload === 'success') { try { installer.remove( moduleId, function() { result = "done"; }, function(e) { console.log("Error removing app " + moduleName + ": " + e); result = "failed"; } ); var d = Date.now() + 20000; // wait not more than 20 seconds while (Date.now() < d && result === "in progress") { processPendingCallbacks(); } if (result === "in progress") { result = "failed"; } if (result === "done") { 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); this.addNotification("error", langFile.ac_err_uninstall_mod + ': ' + moduleId, "core", "AutomationController"); } } return uninstall; }; AutomationController.prototype.loadInstalledModule = function(moduleId, rootDirectory, ignoreVersion) { var self = this, successful = false, ignoreVersion = ignoreVersion ? ignoreVersion : false; 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]) { self.loadModule(self.modules[moduleId]); successful = true; } } } catch (e) { console.log('Load app "' + moduleId + '" has failed. ERROR:', e); } return successful; }; AutomationController.prototype.reinitializeModule = function(moduleId, rootDirectory, ignoreVersion) { var self = this, successful = false, existingInstances = [], ignoreVersion = ignoreVersion ? ignoreVersion : false; // filter for active instances of moduleId 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) { console.log('Load app "' + moduleId + '" from folder ...'); successful = self.loadModuleFromFolder(moduleId, rootDirectory, ignoreVersion); if (successful && self.modules[moduleId]) { self.loadModule(self.modules[moduleId], undefined, existingInstances.length); // add and start instances of moduleId again existingInstances.forEach(function(instance) { self.createInstance(instance); }); successful = true; } } } catch (e) { console.log('Load app "' + moduleId + '" has failed. ERROR:', e); } return successful; }; AutomationController.prototype.instantiateModules = function() { var self = this, langFile = this.loadMainLang(), modules = Object.getOwnPropertyNames(this.modules), requiredBaseModules = ["Cron", "ZWave"], requiredWithDep = []; this.loadedModules = []; modules.splice(modules.indexOf('Cron'), 1); 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) { // load if it exists in modules list 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) { if (modules.indexOf(mod) > -1) { modules.splice(modules.indexOf(mod), 1); } }); } }, this); // first instantiate all required modules without dependencies 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) { // cache required modules with dependencies 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 requiredWithDep.forEach(function(mod) { this.loadModule(this.modules[mod]); }, this); // load all remaining modules modules.forEach(function(mod) { this.loadModule(this.modules[mod]); }, this); }; AutomationController.prototype.moduleInstance = function(instanceId) { return this.instances.hasOwnProperty(instanceId) ? this.instances[instanceId] : null; }; AutomationController.prototype.registerInstance = function(instance) { var self = this, langFile = this.loadMainLang(); if (!!instance) { var instanceId = instance.id, isExistInstance = self.registerInstances.hasOwnProperty(instanceId); if (!isExistInstance) { self.registerInstances[instanceId] = instance; self.emit('core.instanceRegistered', instanceId); } else { self.emit('core.error', new Error(langFile.ac_err_instance_already_exists + instanceId)); } } else { self.emit('core.error', new Error(langFile.ac_err_instance_empty + instance.id)); } }; AutomationController.prototype.listInstances = function() { var self = this, expInstances = []; 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 })); }); } else { expInstances = null; } return expInstances; } AutomationController.prototype.createInstance = function(reqObj) { //var instance = this.instantiateModule(id, className, config), var self = this, langFile = this.loadMainLang(), id = findSmallestNotAssignedIntegerValue(self.instances, 'id'), instance = null, module = _.find(self.modules, function(module) { return module.meta.id === reqObj.moduleId; }), result, alreadyExisting = []; if (!!module) { if (reqObj.id) { alreadyExisting = _.filter(this.instances, function(inst) { return inst.id === reqObj.id }); } instance = _.extend(reqObj, { id: alreadyExisting[0] ? reqObj.id : id, active: reqObj.active === 'true' || (typeof reqObj.active == 'boolean' && reqObj.active) ? true : false }); if (typeof instance.instanceId != 'undefined') { delete instance.instanceId; } self.instances.push(instance); self.saveConfig(true); 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(true); self.emit('core.instanceDeleted', id); } } else { self.emit('core.error', new Error(langFile.ac_err_create_instance + reqObj.moduleId + " :: " + id)); result = false; } return result; }; AutomationController.prototype.stopInstance = function(instance) { var self = this, langFile = this.loadMainLang(), instId = instance.id, values; try { instance.stop(); delete this.registerInstances[instId]; // get all devices created by instance instDevices = _.map(this.devices.filter(function(dev) { return dev.get('creatorId') === instId; }), function(dev) { return dev.id; }); // remove devices if (instDevices.length > 0) { instDevices.forEach(function(id) { // check for device entry again if (!!self.devices.get(id)) { self.devices.remove(id); } }); } } catch (e) { values = ((instance && instId) ? instId : "") + ": " + e.toString(); this.addNotification("error", langFile.ac_err_stop_mod + values, "core", "AutomationController"); console.log(e.stack); return; } }; AutomationController.prototype.reconfigureInstance = function(id, instanceObject) { var langFile = this.loadMainLang(), register_instance = this.registerInstances[id], instance = _.find(this.instances, function(model) { return model.id === id; }), index = this.instances.indexOf(instance), config = {}, result; if (instance) { if (register_instance && instance.active) { this.stopInstance(register_instance); } if (instanceObject.hasOwnProperty('params')) { if (Object.keys(instanceObject.params).length === 0) { config = instanceObject.params; } 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]; } } } _.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 }); if (typeof this.instances[index].instanceId != 'undefined') { delete this.instances[index].instanceId; } if (!!register_instance) { if (this.instances[index].active) { // here we read new config instead of existing register_instance.init(config); this.registerInstance(register_instance); } else { register_instance.saveNewConfig(config); } } else { if (this.instances[index].active) { this.instantiateModule(this.instances[index]); } } result = this.instances[index]; this.emit('core.instanceReconfigured', id) } else { result = null; this.emit('core.error', new Error(langFile.ac_err_refonfigure_instance + id)); } this.saveConfig(true); return result; }; AutomationController.prototype.removeInstance = function(id) { var instance = this.registerInstances[id], getInstFromList = []; getInstFromList = this.instances.filter(function(model) { return id === model.id; }); if (!!instance) { this.stopInstance(instance); this.emit('core.instanceStopped', id); this.saveConfig(true); } // remove from loaded singleton list if singleton if (getInstFromList.length > 0 && getInstFromList[0]) { var moduleId = getInstFromList[0].moduleId; var index = this._loadedSingletons.indexOf(moduleId); if (index > -1) { this._loadedSingletons.splice(index, 1); } } }; AutomationController.prototype.deleteInstance = function(id) { var instDevices = [], self = this; // get all devices created by instance instDevices = this.devices.filterByCreatorId(id).concat( Object.keys(self.vdevInfo).filter(function(__id, __vdev) { return __vdev.creatorId === id; }) ); this.removeInstance(id); this.instances = this.instances.filter(function(model) { return id !== model.id; }); // remove and cleanup if (instDevices.length > 0) { instDevices.forEach(function(vDev) { // check for vDevInfo entry self.devices.remove(vDev.id); self.devices.cleanup(vDev.id); }); } this.saveConfig(true); this.emit('core.instanceDeleted', id); }; 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(e) { console.log("Error installing skin " + skinName + ": " + e); result = "failed"; } ); var d = Date.now() + 20000; // wait not more than 20 seconds while (Date.now() < d && result === "in progress") { processPendingCallbacks(); } if (result === "in progress") { result = "failed"; } if (result === 'done') { if (index < 0) { // add new skin newSkin = { name: '', title: '', description: '', version: '', icon: false, author: '', homepage: '', active: false }; for (var property in reqObj) { if (reqObj.hasOwnProperty(property) && newSkin.hasOwnProperty(property)) { newSkin[property] = reqObj[property]; } } this.skins.push(newSkin); } else { // update entries for (var property in reqObj) { if (reqObj.hasOwnProperty(property) && this.skins[index].hasOwnProperty(property)) { this.skins[index][property] = reqObj[property]; } } this.skins[index].active = false; } saveObject("userSkins.json", this.skins, true); } } return result; }; AutomationController.prototype.uninstallSkin = function(skinName) { var langFile = this.loadMainLang(), result = "in progress"; try { skininstaller.remove( skinName, function() { result = "done"; }, function(e) { console.log("Error installing skin " + skinName + ": " + e); result = "failed"; } ); var d = Date.now() + 20000; // wait not more than 20 seconds while (Date.now() < d && result === "in progress") { processPendingCallbacks(); } if (result === "in progress") { result = "failed"; } //if (result === "done") { this.skins = _.filter(this.skins, function(skin) { return skin.name !== skinName; }); this.profiles.forEach(function(prof) { if (prof.skin === skinName) { prof.skin = 'default'; } }); saveObject("userSkins.json", this.skins, true); //} } catch (e) { console.log('Uninstalling or reseting of skin "' + skinName + '" has failed. ERROR:', e); this.addNotification("error", langFile.ac_err_uninstall_skin + ': ' + skinName, "core", "AutomationController"); } return result; }; AutomationController.prototype.setSkinState = function(skinName, reqObj) { var res = null; if (reqObj.hasOwnProperty('active')) { _.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; } else { // deactivate all skins and set default skin to active: true skin.active = skin.name === 'default' ? true : false; res = skin.name === 'default' ? skin : res; } }) saveObject("userSkins.json", this.skins, true); } return res; }; AutomationController.prototype.installIcon = function(option, reqObj, iconName, id) { var reply = { message: "in progress", files: [] }, filelist = [], input = "", name = "", update = false; 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; } if (input) { console.log('Installing icon', name, '...'); iconinstaller.install( input, iconName, id, function(success) { filelist = parseToObject(success); reply.message = "done"; }, function(e) { console.log("Error installing icons set " + name + ": " + e); reply.message = "failed"; } ); var d = Date.now() + 20000; // wait not more than 20 seconds while (Date.now() < d && reply.message === "in progress") { processPendingCallbacks(); } if (reply.message === "in progress") { reply.message = "failed"; } 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, 'name': iconName, 'id': id, 'timestamp': Math.floor(Date.now() / 1000), 'source_title': option === "local" ? iconName + " " + id : reqObj.title }; reply.files.push(filelist[file].filename); this.icons.push(icon); update = true; } } if (update) { saveObject("userIcons.json", this.icons, true); } } } if (reply.files.length == 1) { reply.files = reply.files[0]; } return reply; }; AutomationController.prototype.listIcons = function() { var result = "in progress", icons = {}; try { iconinstaller.list( function(success) { icons = success; result = "done"; }, function(e) { console.log("Error installing icons set " + name + ": " + e); result = "failed"; } ); var d = Date.now() + 20000; // wait not more than 20 seconds while (Date.now() < d && result === "in progress") { processPendingCallbacks(); } if (result == "in progress") { result = "failed"; } } catch (e) { console.log(e) } return icons; } AutomationController.prototype.uninstallIcon = function(iconName) { var langFile = this.loadMainLang(), result = "in progress"; try { iconinstaller.remove( iconName, function(success) { result = "done"; }, function(error) { if (error == "No such icon.") { result = "done"; } else { result = "failed"; } } ); var d = Date.now() + 20000; // wait not more than 20 seconds while (Date.now() < d && result === "in progress") { processPendingCallbacks(); } if (result === "in progress") { result = "failed"; } //if (result === "done") { this.icons = _.filter(this.icons, function(icon) { return icon.file !== iconName; }); saveObject("userIcons.json", this.icons, true); //} } 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"); } return result; } AutomationController.prototype.deleteCustomicon = function(iconName) { self = this; self.devices.each(function(dev) { if (!_.isEmpty(dev.get('customIcons'))) { var customIcon = dev.get('customIcons'); _.each(customIcon, function(value, key) { if (typeof value !== "object") { if (value === iconName) { customIcon = {}; dev.set('customIcons', customIcon, { silent: true }); return false; } } else { _.each(value, function(icon, level) { if (icon === iconName) { delete customIcon[key][level]; } }); if (_.isEmpty(customIcon[key])) { customIcon = {}; } dev.set('customIcons', customIcon, { silent: true }); } }); } }); } AutomationController.prototype.deleteAllCustomicons = function() { self = this; self.devices.each(function(dev) { dev.set('customIcons', {}, { silent: true }); }); } AutomationController.prototype.deviceExists = function(vDevId) { return Object.keys(this.devices).indexOf(vDevId) >= 0; }; AutomationController.prototype.getVdevInfo = function(id) { return this.vdevInfo[id] || {}; }; AutomationController.prototype.setVdevInfo = function(id, device) { this.vdevInfo[id] = _.pick(device, "deviceType", "probeType", "metrics", "location", // do not save locationName - recalculate on the fly "tags", "permanently_hidden", "creationTime", "creatorId", "customIcons", "order", "visibility", "hasHistory"); this.saveConfig(this.savePeriod); return this.vdevInfo[id]; }; AutomationController.prototype.clearVdevInfo = function(id) { delete this.vdevInfo[id]; this.profiles.forEach(function(p) { p.hide_single_device_events = p.hide_single_device_events.filter(function(item) { return item !== id; }); p.devices = p.devices.filter(function(item) { return item !== id; }); }); this.saveConfig(true); }; AutomationController.prototype.loadNotifications = function() { var self = this; this.notifications = new LimitedArray( loadObject("notifications") || [], function(arr) { saveObject('notifications', arr, self.savePeriod); }, 25, // check it every 25 notifications 2500, // save up to 2500 notifications 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; return (notification.id.toString().length <= 10 && notification.id >= s_tsSevenDaysBefore) || (notification.id.toString().length > 10 && notification.id >= ms_tsSevenDaysBefore); } ); }; AutomationController.prototype.addNotification = function(severity, message, type, source) { var self = this; var now = new Date(), notice = { id: Math.floor(now.getTime()), timestamp: now.toISOString(), level: severity, message: message, type: type || 'device', source: source, redeemed: false }; if (typeof message === 'object') { msg = JSON.stringify(message); } else { msg = message; } this.notifications.push(notice); console.log("Notification:", severity, "(" + type + "):", msg); this.emit("notifications.push", notice); // notify modules to allow SMS and E-Mail notifications }; 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) { return notification.id >= ts; }); console.log('---------- all notifications before ' + ts + ' deleted ----------'); } else { newNotificationList = this.notifications.get().filter(function(notification) { return notification.id !== ts; }); console.log('---------- notification with id ' + ts + ' deleted ----------'); } this.notifications.set(newNotificationList); if (typeof callback === 'function') { callback(true); } } }; AutomationController.prototype.deleteAllRedeemedNotifications = function(callback) { try { this.notifications.set(this.notifications.get().filter(function(notification) { return !notification.redeemed; })); if (typeof callback === 'function') { callback(true); } } catch (e) { console.log('deleteAllRedeemedNotifications - something went wrong:', e.message); if (typeof callback === 'function') { callback(false); } } }; 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 }); notifications[index].redeemed = r; this.notifications.set(notifications); if (typeof callback === 'function') { callback(true); } } else { if (typeof callback === 'function') { callback(false); } } }; AutomationController.prototype.redeemAllNotifications = function(redeemed, callback) { var r = redeemed !== undefined ? redeemed : true; try { var notifications = this.notifications.get(); _.forEach(notifications, function(notification) { notification.redeemed = r; }); this.notifications.set(notifications); if (typeof callback === 'function') { callback(true); } } catch (e) { if (typeof callback === 'function') { callback(false); } } }; AutomationController.prototype.locationName = function(locationId) { var location = _.find(this.locations, function(loc) { return loc.id === locationId; }); return location ? location.title : ""; }; AutomationController.prototype.getLocation = function(locations, locationId) { var location = [], nspc = null; location = locations.filter(function(location) { if (locationId === 'globalRoom') { return location.id === 0 && location.title === locationId; } else { return location.id === locationId; } }); 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) { return location.id === id; }); if (locations.length > 0) { if (typeof callback === 'function') { callback(false); } } else { var location = { id: id, title: locProps.title, user_img: locProps.user_img || '', default_img: locProps.default_img || '', img_type: locProps.img_type || '', show_background: locProps.show_background || false, main_sensors: locProps.main_sensors || [] }; this.locations.push(location); if (typeof callback === 'function') { callback(location); } this.saveConfig(true); this.emit('location.added', location); } }; AutomationController.prototype.removeLocation = function(id, callback) { var self = this, langFile = this.loadMainLang(), locations = this.locations.filter(function(location) { return location.id === id; }); if (locations.length > 0) { 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) { return location.id !== id; }); this.saveConfig(true); if (typeof callback === 'function') { callback(true); } this.emit('location.removed', id); } else { if (typeof callback === 'function') { callback(false); } this.emit('core.error', new Error(langFile.ac_err_location_not_found)); } }; 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) { return location.id === id; }); if (locations.length > 0) { var location = this.locations[this.locations.indexOf(locations[0])]; location.title = title; location.show_background = show_background; location.main_sensors = main_sensors; if (typeof user_img === 'string') { location.user_img = user_img; } if (typeof default_img === 'string') { location.default_img = default_img; } if (typeof img_type === 'string') { location.img_type = img_type; } if (typeof callback === 'function') { callback(location); } this.saveConfig(true); this.emit('location.updated', location); } else { if (typeof callback === 'function') { callback(false); } this.emit('core.error', new Error(langFile.ac_err_location_not_found)); } }; AutomationController.prototype.listNotifications = function(since, to) { since = parseInt(since) || 0; to = parseInt(to) || Math.floor(Date.now() / 1000); 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) { 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) { 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]); } } else { if (typeof callback === 'function') { callback(null); } } }; // 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; }))); }); } return _.uniq(dontSave.concat(matches)); }; AutomationController.prototype.safeProfile = function(profile, exclude) { var prof = {}, excl = ["password", "salt", "authTokens"].concat(exclude); for (var property in profile) { if (excl.indexOf(property) === -1) { prof[property] = profile[property]; } } // explicitelly copy authTokens if not in exclude list if (!exclude || exclude.indexOf("authTokens") === -1) { prof.authTokens = []; if (profile.authTokens) { profile.authTokens.forEach(function(authToken) { prof.authTokens.push({ sid: authToken.sid.substr(0,6) + "...", // first 6 symbols are uniq - see AuthController agent: authToken.agent, date: authToken.date, lastSeen: authToken.lastSeen, ip: authToken.ip, expire: authToken.expire }); }); } } return prof; }; // safely return all profiles removing sensitive data AutomationController.prototype.getListProfiles = function() { var getProfiles = [], self = this; this.profiles.forEach(function(profile) { getProfiles.push(self.safeProfile(profile, ["authTokens"])); }); return getProfiles; }; AutomationController.prototype.getProfile = function(id) { return _.find(this.profiles, function(profile) { return profile.id === parseInt(id); }) || null; }; AutomationController.prototype.createProfile = function(profile) { var id = 0, globalRoom = [0], profileIds = _.map(this.profiles, function(pro) { return parseInt(pro.id); }); // create latest id 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.salt = generateSalt(); profile.password = hashPassword(profile.password, profile.salt); profile.uuid = crypto.guid(); this.profiles.push(profile); this.updateProfileDevices(profile, [], [], profile.rooms, profile.devices); this.emit('profile.added', profile); this.saveConfig(true); return profile; }; // Detect devices added to the profile AutomationController.prototype.updateProfileDevices = function(profile, oldLocations, oldDevices, newLocations, newDevices) { oldLocations = oldLocations || []; oldDevices = oldDevices || []; newLocations = newLocations || []; newDevices = newDevices || []; var addedLocations = _.difference(newLocations, oldLocations), addedDevices = _.difference(newDevices, oldDevices), removedLocations = _.difference(oldLocations, newLocations), removedDevices = _.difference(oldDevices, newDevices); var devicesInAddedLocations = this.devices.filter(function(d) { return addedLocations.indexOf(d.get("location")) > -1; }).map(function(d) { return d.id; }), devicesInRemovedLocations = this.devices.filter(function(d) { return removedLocations.indexOf(d.get("location")) > -1; }).map(function(d) { return d.id; }); var devicesInOldLocations = this.devices.filter(function(d) { return oldLocations.indexOf(d.get("location")) > -1; }).map(function(d) { return d.id; }), devicesInNewLocations = this.devices.filter(function(d) { return newLocations.indexOf(d.get("location")) > -1; }).map(function(d) { return d.id; }); // Device is added if it was added in granted device list or in granted location and was not in those lists before var devicesAdded = _.difference(_.union(addedDevices, devicesInAddedLocations), _.union(oldDevices, devicesInOldLocations)), // Device is removed if it was deleted from granted device list or from granted location and is not in those lists now devicesRemoved = _.difference(_.union(removedDevices, devicesInRemovedLocations), _.union(newDevices, devicesInNewLocations)); this.emit('profile.deviceListUpdated', { profile: profile, added: devicesAdded, deleted: devicesRemoved }); }; 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); var oldLocations = profile.rooms, oldDevices = profile.devices; // update properties for (var property in object) { if (object.hasOwnProperty(property) && profile.hasOwnProperty(property)) { this.profiles[index][property] = object[property]; } } this.updateProfileDevices(profile, oldLocations, oldDevices, profile.rooms, profile.devices); this.emit('profile.updated', profile); } this.saveConfig(true); return this.profiles[index]; }; AutomationController.prototype.updateProfileAuth = function(object, id) { var profile = _.find(this.profiles, function(profile) { return profile.id === parseInt(id); }), index; if (profile) { 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); } if (object.hasOwnProperty('login') && object.login !== '' && !!object.login) { p.login = object.login; } this.saveConfig(true); return p; } else { return null; } }; AutomationController.prototype.removeProfile = function(profileId) { var that = this; this.profiles = this.profiles.filter(function(profile) { return profile.id !== profileId; }); this.emit('profile.removed', profileId); this.saveConfig(true); }; AutomationController.prototype.removeToken = function(profile, token, skipSave) { var indx = -1; if (!profile || !profile.authTokens) return false; profile.authTokens.forEach(function(authToken, index) { if (authToken.sid.substr(0, 6) === token.substr(0, 6)) { indx = index; } }); if (indx !== -1) { profile.authTokens.splice(indx, 1); if (!skipSave) this.saveConfig(true); return true; } else { return false; } }; AutomationController.prototype.permanentToken = function(profile, token) { var self = this; if (!profile.authTokens) return false; var found = false; profile.authTokens.forEach(function(authToken, index) { if (authToken.sid.substr(0, 6) === token.substr(0, 6)) { authToken.expire = 0; self.saveConfig(true); found = true; } }); return found; }; AutomationController.prototype.getIPAddress = function() { try { var ip = fs.loadJSON("localIP.json"); if (typeof ip === "string") { return ip; } } catch (e) { } try { if (checkBoxtype('zme_hub')) { return system(". /lib/functions/network.sh; network_get_ipaddr ip wan; echo $ip")[1].replace(/[\s\n]/g, ''); } else { return 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 ""; } AutomationController.prototype.getMACAddress = function() { var mac = ""; try { if (checkBoxtype('zme_hub')) { mac = system("cat /sys/class/net/eth0/address")[1].replace(/[\s\n]/g, ''); } else { mac = system("cat /sys/class/net/eth0/address")[1].replace(/[\s\n]/g, ''); } } catch (e) { console.log(e); } return mac; } AutomationController.prototype.getUUID = function() { return this.config.uuid; } AutomationController.prototype.getSerial = function() { return this.config.serial; } AutomationController.prototype.getQRCodeData = function(profile, password) { var data = { id: "", login: "", service: "find.z-wave.me", ssid: "", ip: "", wpa: "", passwd: "" }, url = "", ip = ""; data.passwd = password; data.login = profile.login; data.id = this.getRemoteId(); ip = this.getIPAddress(); if (ip) { data.ip = ip; } url = Object.keys(data).map(function(key) { return encodeURIComponent(key) + '=' + encodeURIComponent(data[key]); }).join('&'); url = Base64.encode(url); return url; } // namespaces AutomationController.prototype.generateNamespaces = function(callback, device, locationNspcOnly) { var that = this, devStillExists = that.devices.get(device.id), 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 (device) { 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) { var exists = []; // check if entry already exists exists = _.filter(entryArr, function(entry) { return entry.deviceId === devEntry.deviceId; }); if (!!devStillExists && exists.length < 1 && !devHidden) { // add entry entryArr.push(devEntry); } else if (!!devStillExists && exists[0] && !devHidden) { // change existing deviceName if (!_.isEqual(exists[0]['deviceName'], devEntry['deviceName'])) { exists[0]['deviceName'] = devEntry['deviceName']; } } else if (devStillExists === null || devHidden) { // remove entry entryArr = _.filter(entryArr, function(entry) { return entry.deviceId !== devEntry.deviceId; }); } return entryArr; }, 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]; } return object; }, cutType = [], cutSubType = '', paramEntry; // check for type entry typeEntryExists = _.filter(nspc, function(typeEntry) { return typeEntry.id === devTypeEntry; }); if (typeEntryExists.length > 0) { 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); paramEntry = paramEntry ? paramEntry : {}; // create 'none' entry to get an object with array of 'none probetype' entries if (_.isArray(paramEntry)) { paramEntry = { none: paramEntry }; } // check for CC sub type and add device namespaces if (cutType.length > 1) { // add CC type if (!paramEntry[cutType[0]]) { paramEntry[cutType[0]] = {}; } // add subtype if (!paramEntry[cutType[0]][cutSubType]) { paramEntry[cutType[0]][cutSubType] = []; } //check if entry is already there paramEntry[cutType[0]][cutSubType] = addRemoveEntry(paramEntry[cutType[0]][cutSubType]); // delete empty suptype entries paramEntry[cutType[0]] = deleteEmptyProp(paramEntry[cutType[0]], cutSubType); // add CC type } else { if (!paramEntry[cutType[0]]) { paramEntry[cutType[0]] = []; } //check if entry is already there paramEntry[cutType[0]] = addRemoveEntry(paramEntry[cutType[0]]); } // remove CC if empty paramEntry = deleteEmptyProp(paramEntry, cutType[0]); } else { // add entries to type entries paramEntry = paramEntry ? paramEntry : []; // create 'none' entry to get an object with array of 'none probetype' entries if (_.isArray(paramEntry)) { paramEntry = { none: paramEntry }; } else if (!_.isArray(paramEntry) && devProbeType === '') { paramEntry['none'] = paramEntry['none'] ? paramEntry['none'] : []; } paramEntry.none = addRemoveEntry(paramEntry.none); } // delete 'none' entry if it exists as a single entry if (!_.isArray(paramEntry) && Object.keys(paramEntry).length === 1 && paramEntry['none']) { paramEntry = paramEntry['none']; } // set namespaces that.setNamespace(devTypeEntry, nspc, paramEntry); // check for 'devices_all' devicesAll = _.filter(nspc, function(entries) { return entries.id === 'devices_all'; }); // add 'devices_all' with entries if (devicesAll.length < 1) { that.setNamespace('devices_all', nspc, [devEntry]); } else if (devicesAll[0].params && _.isArray(devicesAll[0].params)) { // check if entry is already there // add/remove entry to/from devices_all devicesAll[0].params = addRemoveEntry(devicesAll[0].params); } return nspc; }; // only triggered if there is no explicite location change - // on device: created, removed, destroy, change:metrics:title, change:permanently_hidden, change:metrics:removed // usual update of global namespaces // first setup of location namespace if (!locationNspcOnly) { // add to location namespaces if (!!location) { _.forEach(that.locations, function(l) { if (l.id === devLocation) { // add to namespace l.namespaces = that.genNspc(location.namespaces, device); } }); } // update global namespaces nspcArr = that.genNspc(that.namespaces, device); if (typeof callback === 'function') { callback(nspcArr); } // 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 device is assigned to location add to namespace devStillExists = that.devices.get(device.id); l.namespaces = that.genNspc(l.namespaces, device); } else { // remove from the other namespaces devStillExists = null; l.namespaces = that.genNspc(l.namespaces, device); } }); if (typeof callback === 'function') { callback(locNspcArr); } } } else { if (typeof callback === 'function') { callback(nspcArr); } } }; AutomationController.prototype.getListNamespaces = function(path, namespacesObj, setLocationTitle) { var self = this, result = [], namespaces = namespacesObj, path = path || null, pathArr = [], namespacesPath = '', nspc, v = setLocationTitle ? setLocationTitle : false; this.getNspcDevAll = function(nspcObj) { var devicesAll = [], obj = {}; if (_.isArray(nspcObj)) { nspcObj.forEach(function(nspcEntry) { if (!~devicesAll.indexOf(nspcEntry)) { devicesAll.push(nspcEntry); } }); } else { for (var prop in nspcObj) { devicesAll = devicesAll.concat(self.getNspcDevAll(nspcObj[prop])); } } return devicesAll; }; // map all entries (deviceName, deviceId) // if deviceName add also location title mapEntries = function(list) { // return list of entries return _.map(list, function(entry) { var locationTitle = ''; if (setLocationTitle && 0) { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 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() + ' - ' : ''; } return locationTitle + entry[currPath]; }); }; if (!!path && namespaces) { pathArr = path.split('.'); // add 'params' to path array if neccessary if (pathArr.length > 1 && pathArr.indexOf('params') === -1) { pathArr.splice(1, 0, 'params'); } // filter for type nspc = namespaces.filter(function(namespace) { return namespace.id === pathArr[0]; })[0]; //nspc = nspc[0]? nspc[0] : nspc; // get object/array by path if (nspc && pathArr.length > 1) { var shift = 1; for (var i = 0; i < pathArr.length; i++) { var currPath = pathArr[i + shift], obj = {}, lastPath = ['deviceId', 'deviceName', 'channelId', 'channelName', 'channelNameEx']; 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 } else if (~lastPath.indexOf(currPath)) { if (_.isArray(nspc)) { // map all device id's or device names result = mapEntries(nspc); } } else if (!nspc[currPath] && nspc['devices_all'] && i < 1) { nspc = nspc['devices_all']; result = nspc; // change shift to get last path entry shift = 0; } else if (currPath && !nspc[currPath] && i > 0) { nspc = []; result = nspc; break; } else if (currPath) { result = nspc; } } } else { result = nspc && nspc['params'] ? nspc['params'] : nspc; // if not return undefined } } else { result = namespaces; } return result; }; AutomationController.prototype.setNamespace = function(id, namespacesArr, data) { var result = null, namespace, index; id = id || null; if (id && this.getListNamespaces(id, namespacesArr)) { 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); result = namespacesArr; } else { namespacesArr[index].params = data; result = namespacesArr[index]; } } } else if ((_.isArray(data) && data.length > 0) || ((!_.isArray(data)) && Object.keys(data).length > 0)) { namespacesArr.push({ id: id, params: data }); result = null; } return result; }; AutomationController.prototype.getListModulesCategories = function(id) { var result = null, categories = this.modules_categories; if (Boolean(id)) { result = _.find(categories, function(category) { return category.id === id; }); } else { result = categories; } return result; }; AutomationController.prototype.applyLanguage = function(template, language) { if (language === null) return template; Object.keys(language).forEach(function(key) { var regExp = new RegExp('__' + key + '__', 'g'); if (language[key]) { template = template.replace(regExp, language[key]); } }); return template; }; AutomationController.prototype.getModuleData = function(moduleName) { var defaultLang = this.defaultLang, moduleMeta = this.modules[moduleName] && this.modules[moduleName].meta || null, languageFile = this.loadModuleLang(moduleName), data = {}; if (!this.modules[moduleName]) { return {}; // module not found (deleted from filesystem or broken?), return empty meta } try { metaStringify = JSON.stringify(moduleMeta); } catch (e) { try { metaStringify = JSON.stringify(fs.loadJSON('modules/' + moduleName + '/module.json')); } catch (e) { try { metaStringify = JSON.stringify(fs.loadJSON('userModules/' + moduleName + '/module.json')); } catch (e) { console.log('Cannot load lang file from module ' + moduleName + '. ERROR: ' + e); } } } if (languageFile !== null) { metaStringify = this.applyLanguage(metaStringify, languageFile); data = JSON.parse(metaStringify); } else { data = this.modules[moduleName].meta; } return data; }; AutomationController.prototype.replaceNamespaceFilters = function(moduleMeta) { var self = this, moduleMeta = moduleMeta || null, langFile = this.loadMainLang(); // loop through object function replaceNspcFilters(moduleMeta, obj, keys) { var objects = []; for (var i in obj) { 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 = {}; try { // overwrite old key with new namespaces array if (i === 'properties') { console.log("Room - Device relation found, try to preparate JSON's schema structure ..."); var dSRoom = _.extend({ "type": "", "field": "", "datasource": "", "enum": "", "title": "" }, obj[i]['room']), dSDevByRoom = _.extend({ "type": "", "datasource": "", "enum": "", "title": "", "dependencies": "" }, obj[i]['devicesByRoom']); 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'])) { var path = dSDevByRoom['enum'].substring(21).replace(/:/gi, '.'); if (k.length > 0) { k.forEach(function(key) { if (key === 'devicesByRoom') { dSRoom['enum'].forEach(function(roomId, index) { var locNspc = [], nspc = []; location = self.getLocation(self.locations, roomId); if (!!location) { nspc = self.getListNamespaces(path, location.namespaces); } dSDevByRoom['enum'] = nspc && nspc.length > 0 ? nspc : [langFile.no_devices_found]; dSDevByRoom['dependencies'] = "room"; newObj['devicesByRoom_' + roomId] = _.clone(dSDevByRoom); if (newObj['devicesByRoom_' + roomId]['title']) { newObj['devicesByRoom_' + roomId]['title'] = newObj['devicesByRoom_' + roomId]['title'] + '_' + roomId; } }); } else { newObj[key] = obj[i][key]; } }); } obj[i] = newObj; } } else { console.log("Room - Device relation found, try to preparate JSON's options structure ..."); var dSRoom = _.extend({ "type": "", "field": "", "optionLabels": "" }, obj[i]['room']), dSDevByRoom = _.extend({ "dependencies": {}, "type": "", "field": "", "optionLabels": "" }, obj[i]['devicesByRoom']); 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'])) { var path = dSDevByRoom['optionLabels'].substring(21).replace(/:/gi, '.'); if (k.length > 0) { k.forEach(function(key) { 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; 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 }; newObj['devicesByRoom_' + locationId] = _.clone(dSDevByRoom); if (newObj['devicesByRoom_' + locationId]['label']) { newObj['devicesByRoom_' + locationId]['label'] = newObj['devicesByRoom_' + locationId]['label'] + '_' + locationId; } }); } else { newObj[key] = obj[i][key]; } }); } obj[i] = newObj; } } } catch (e) { 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) { if (~keys.indexOf(index) && !_.isArray(p)) { obj[i][key][index] = getNspcFromFilters(moduleMeta, obj[i][key][index]); } }); } } else if (typeof obj[i] === 'object') { objects = objects.concat(replaceNspcFilters(moduleMeta, obj[i], keys)); } else if (keys.indexOf(i) > -1 && !_.isArray(obj[i])) { // overwrite old key with new namespaces array obj[i] = getNspcFromFilters(moduleMeta, obj[i]); } } return obj; }; // generate namespace arry from filter string function getNspcFromFilters(moduleMeta, nspcfilters) { var namespaces = [], filters = nspcfilters.split(','), apis = ['locations', 'users', 'namespaces', 'loadFunction'], filteredDev = []; try { if (!_.isArray(filters)) { return false; } // do it for each filter _.forEach(filters, function(flr, i) { var id = flr.split(':'), path; 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]]; }); // get namespaces of users - 'users:id' or 'users:title' } else if (id[0] === 'users' && (id[1] === 'id' || id[1] === 'title')) { namespaces = _.map(self.profiles, function(profile) { return profile[id[1] === 'id' ? 'id' : 'name']; }); // 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 } 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 (moduleMeta.moduleName) { jsFile = this.controller.applyLanguage(jsFile, this.controller.loadModuleLang(moduleMeta.moduleName)); } if (!!jsFile) { //compress string namespaces = jsFile.replace(/\s\s+|\t/g, ' '); } } // get namespaces of devices ignoring locations } else { // cut path path = flr.substring(id[0].length + 1).replace(/:/gi, '.'); // get namespaces nspc = self.getListNamespaces(path, self.namespaces, true); if (nspc) { namespaces = namespaces.concat(nspc); } } } }); return namespaces; } 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: '' }; // transform filters for (var property in params) { if (property === 'postRender' && moduleMeta[property] && !_.isArray(moduleMeta[property])) { moduleMeta[property] = getNspcFromFilters(moduleMeta, moduleMeta[property]); } else if (moduleMeta[property]) { moduleMeta[property] = replaceNspcFilters(moduleMeta, moduleMeta[property], params[property]); } } } return moduleMeta; }; // load module lang folder AutomationController.prototype.loadModuleLang = function(moduleId) { var moduleMeta = this.modules[moduleId] && this.modules[moduleId].meta || null, languageFile = null; if (!!moduleMeta) { languageFile = this.loadMainLang(moduleMeta.location + '/'); } return languageFile; }; // load lang folder with given prefix AutomationController.prototype.loadMainLang = function(pathPrefix) { var self = this, languageFile = null, prefix; if (pathPrefix === undefined || pathPrefix === null) { prefix = ''; } else { prefix = pathPrefix; } try { languageFile = fs.loadJSON(prefix + "lang/" + self.defaultLang + ".json"); } catch (e) { try { languageFile = fs.loadJSON(prefix + "lang/en.json"); } catch (e) { languageFile = null; } } return languageFile; }; AutomationController.prototype.loadModuleMedia = function(moduleName, fileName) { var contentTypes = { "png": "image/png", "jpg": "image/jpeg", "jpeg": "image/jpeg", "gif": "image/gif", "svg": "image/svg+xml", "css": "text/css", "js": "text/javascript", "txt": "text/txt", "rtf": "text/rtf", "xml": "text/xml", "htm": "text/htm", "html": "text/html", "shtml": "text/shtml", "mpeg": "video/mpeg", "mpg": "video/mpg", "mpe": "video/mpe", "qt": "video/qt", "mov": "video/mov", "viv": "video/viv", "vivo": "video/vivo", "avi": "video/avi", "movie": "video/movie", "mp4": "video/mp4", "doc": "application/msword", "dot": "application/msword", "docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template", "docm": "application/vnd.ms-word.document.macroEnabled.12", "dotm": "application/vnd.ms-word.template.macroEnabled.12", "xls": "application/vnd.ms-excel", "xlt": "application/vnd.ms-excel", "xla": "application/vnd.ms-excel", "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template", "xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12", "xltm": "application/vnd.ms-excel.template.macroEnabled.12", "xlam": "application/vnd.ms-excel.addin.macroEnabled.12", "xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12", "ppt": "application/vnd.ms-powerpoint", "pot": "application/vnd.ms-powerpoint", "pps": "application/vnd.ms-powerpoint", "ppa": "application/vnd.ms-powerpoint", "pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", "potx": "application/vnd.openxmlformats-officedocument.presentationml.template", "ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow", "ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12", "pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12", "potm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12", "ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", }, fe, resObject = { data: null, ct: null }; try { fe = fileName.split(".").pop().toLowerCase(); resObject.ct = contentTypes[fe] || "text/plain"; try { resObject.data = fs.load('userModules/' + moduleName + '/htdocs/' + fileName); } catch (e) { resObject.data = fs.load('modules/' + moduleName + '/htdocs/' + fileName); } } catch (e) { resObject = null; } return resObject; }; AutomationController.prototype.loadImage = function(fileName) { var data = null, img = loadObject(fileName); if (!!img) { data = Base64.decode(img); } return data; }; AutomationController.prototype.hashCode = function(str) { 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 } return hash; }; AutomationController.prototype.createBackup = function() { var self = this, backupJSON = {}, userModules = [], skins = [], result = null; var list = __storageContent, excludedFiles = this.getIgnoredStorageFiles(); try { // save all objects in storage for (var ind in list) { 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 })) { userModules.push({ name: moduleName, version: self.modules[moduleName] ? self.modules[moduleName].meta.version : '' }); } }); if (userModules.length > 0) { backupJSON['__userModules'] = userModules; } // add list of current skins excluding default skin _.forEach(this.skins, function(skin) { if (skins.indexOf(skin) === -1 && skin.name !== 'default') { skins.push({ name: skin.name, version: skin.version }); } }); if (skins.length > 0) { backupJSON['__userSkins'] = skins; } /* //TODO icon backup var ret = "in progress"; iconinstaller.backup( function(backup) { console.log("success", backup); console.log("success length", backup.length); ret = new Uint8Array(backup); }, function(error){ console.log("error", error); ret = "failed"; }); var d = Date.now() + 20000; // wait not more than 20 seconds while (Date.now() < d && ret === "in progress") { processPendingCallbacks(); } console.log(ret); if(ret !== "failed") { var bcp = ""; console.log(ret.length); for(var i = 0; i < ret.length; i++) { bcp += String.fromCharCode(ret[i]); } backupJSON["__Icons"] = bcp; } */ // save Z-Way, Zigbee and EnOcean objects if (!!global.ZWave) { backupJSON["__ZWay"] = {}; global.ZWave.list().forEach(function(zwayName) { var bcp = "", data = new Uint8Array(global.ZWave[zwayName].zway.controller.Backup()); for (var i = 0; i < data.length; i++) { bcp += String.fromCharCode(data[i]); } backupJSON["__ZWay"][zwayName] = bcp; }); } /* TODO if (!!global.Zigbee) { backupJSON["__ZBee"] = {}; global.Zigbee.list().forEach(function(zbeeName) { var bcp = "", data = new Uint8Array(global.Zigbee[zbeeName].zbee.controller.Backup()); for (var i = 0; i < data.length; i++) { bcp += String.fromCharCode(data[i]); } backupJSON["__ZBee"][zbeeName] = bcp; }); } */ /* TODO if (!!global.EnOcean) { backupJSON["__EnOcean"] = {}; global.EnOcean.list().forEach(function(zenoName) { // backupJSON["__EnOcean"][zenoName] = global.EnOcean[zenoName].zeno.controller.Backup(); }); } */ result = backupJSON; } catch (e) { throw e.toString(); } return result; }; AutomationController.prototype.getRemoteId = function() { var checkIfTypeError = true, result = null; if (typeof ZBWConnect === '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; } } catch (e) { try { zbw = new ZBWConnect('./zbw'); checkIfTypeError = zbw.getUserId() instanceof TypeError ? true : false; } catch (er) { console.log('Something went wrong. Reading remote id has failed. Error:' + er.message); } } if (checkIfTypeError) { console.log('Something went wrong. Reading remote id has failed.'); } else { result = zbw.getUserId(); } } else { console.log('Reading remote id has failed. Service is not available.'); } return result; }; 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; }); }; AutomationController.prototype.reoderDevices = function(list, action) { var self = this, result = false; try { _.each(list, function(id) { var vDev = self.devices.get(id), order = vDev.get('order'); order[action] = list.indexOf(id); vDev.set('order', order); }); result = true; } catch (e) { console.log(e); } return result; }; AutomationController.prototype.vDevFailedDetection = function(nodeId, isFailed, zwayName) { 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.updateNotificationChannelNamespace = function() { var self = this; this.setNamespace("notificationChannels", this.namespaces, Object.keys(this.notificationChannels).map(function(id) { var profile = self.getProfile(self.notificationChannels[id].user); return {channelId: id, userId: self.notificationChannels[id].user, channelName: self.notificationChannels[id].name, channelNameEx: self.notificationChannels[id].name + " (" + (profile ? profile.name : "-") + ")"}; })); }; AutomationController.prototype.registerNotificationChannel = function(id, user, name, handler) { console.log("Registering notification channel: " + name + ", user: " + (user ? user : '(not assigned to a user)')); this.notificationChannels[id] = {user: parseInt(user), name: name, handler: handler}; this.updateNotificationChannelNamespace(); }; AutomationController.prototype.unregisterNotificationChannel = function(id) { console.log("Unregistering notification channel: " + (this.notificationChannels[id] ? this.notificationChannels[id].name : "(not found)")); delete this.notificationChannels[id]; this.updateNotificationChannelNamespace(); }; AutomationController.prototype.notificationChannelSend = function(id, message) { if (!this.notificationChannels[id]) { console.log("Error: Notification channel not found: " + id + " (message: " + message + ")"); return; } this.notificationChannels[id].handler(message); }; AutomationController.prototype.notificationUserChannelsSend = function(id, message) { var self = this; Object.keys(this.notificationChannels).forEach(function(ncId) { if (self.notificationChannels[ncId].user === id) { self.notificationChannels[ncId].handler(message); } }); }; AutomationController.prototype.notificationAllChannelsSend = function(message) { var self = this; Object.keys(this.notificationChannels).forEach(function(ncId) { self.notificationChannels[ncId].handler(message); }); }; AutomationController.prototype.getNotificationChannel = function(id) { return this.notificationChannels[id]; }; 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') { // transform into advanced Rule newInstance.params.advanced = self.transformIntoRule('advanced', instance, newInstance.params.advanced); } else if (moduleName === 'IfThen') { // 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; }; AutomationController.prototype.profileByUser = function(userId) { return _.find(this.profiles, function(profile) { return profile.id === userId }); }; AutomationController.prototype.profilesByRole = function(role) { return this.profiles.filter(function(profile) { return profile.role === role; }).map(function(profile) { return profile.id; }); }; AutomationController.prototype.profilesByDevice = function(devId) { var self = this; var vDev = this.devices.get(devId); var room = vDev ? vDev.get("location") : null; return this.profiles.filter(function(profile) { return profile.role === self.auth.ROLE.ADMIN || (profile.devices && (profile.devices.indexOf(devId) !== -1)) || (room !== null && profile.rooms && (profile.rooms.indexOf(room) !== -1)); }).map(function(profile) { return profile.id; }); }; AutomationController.prototype.profilesByLocation = function(location) { return this.profiles.filter(function(profile) { return profile.rooms.indexOf(location) !== -1; }).map(function(profile) { return profile.id; }); }; AutomationController.prototype.devicesByUser = function(userId, filter) { var devices = this.devices.filter(filter), profile = this.profileByUser(userId); if (!profile) { return []; } if (profile.role === this.auth.ROLE.ADMIN) { return devices; } else { // explicitelly allowed devices var allowedDevices = []; if (!!profile.devices) { allowedDevices = devices.filter(function(dev) { return profile.devices.indexOf(dev.id) !== -1; }); } // devices from allowed rooms if (!!profile.rooms) { var allowedDevicesFromRooms = devices.filter(function(dev) { // show only devices from allowed rooms (don't show unallocated devices) return dev.get("location") != 0 && profile.rooms.indexOf(dev.get("location")) !== -1; }); // append explicitedly allowed devices (unique concat) allowedDevices.forEach(function(dev) { if (allowedDevicesFromRooms.map(function(d) { return d.id; }).indexOf(dev.id) === -1) { allowedDevicesFromRooms.push(dev); } }); return allowedDevicesFromRooms; } else { return allowedDevices; } } }; AutomationController.prototype.deviceByUser = function(vDevId, userId) { if (this.devicesByUser(userId).filter(function(device) { return device.id === vDevId }).length) { return this.devices.get(vDevId); } return null; }; AutomationController.prototype.locationsByUser = function(userId) { var profile = this.profileByUser(userId); if (!profile) { return []; } if (profile.role === this.auth.ROLE.ADMIN) { return this.locations; } else { if (!!profile.rooms) { return this.locations.filter(function(location) { return profile.rooms.indexOf(location.id) !== -1; }); } else { return []; } } }; AutomationController.prototype.findModulesReferencingDeviceId = function(device) { function findAllUsages(device) { var ret = []; for (var indx in controller.instances) { var instance = controller.instances[indx]; var moduleId = instance.moduleId; if (!controller.modules[moduleId]) { console.log("Module " + moduleId + " (" + instance.id + ") is not installed"); continue; } var schema = controller.modules[moduleId].meta.schema; var config = instance.params; try { if (findUsage(schema, config, device)) { ret.push({ moduleId: moduleId, instanceId: instance.id, instanceTitle: instance.title }); } } catch(e) { console.log("Error in searching for references in " + moduleId + " instance " + instance.id + ": " + e); } } return ret; } function findUsage(schema, config, device) { if (schema && config) { // module with a pretty config switch(schema.type) { case "object": for (var prop in schema.properties) { if (findUsage(schema.properties[prop], config[prop], device)) { return true; } } break; case "array": for (var index in config) { if (findUsage(schema.items, config[index], device)) { return true; } } break; case undefined: if (schema.field === "enum" && schema.datasource === "namespaces" && schema.enum.split(",").filter(function(f){ return f.match(/namespaces:.*:deviceId/); }).length) { if (config === device) { return true; } } break; default: /* if (config === device) { return true; } */ break; } return false; } else { // all other modules return config ? JSON.stringify(config).match(device) : false; } } return findAllUsages(device); }; ================================================ FILE: classes/AutomationModule.js ================================================ /*** Z-Way HA Automation module base class ************************************ Version: 1.0.0 ------------------------------------------------------------------------------- Author: Gregory Sitnin Copyright: (c) ZWave.Me, 2013 ******************************************************************************/ AutomationModule = function(id, controller) { var self = this; this.id = id; this.controller = controller; this.meta = this.getMeta(); this.actions = {}; this.actionFuncs = {}; this.metrics = {}; this.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) { result[key] = self.meta.defaults[key]; }); } if (!!config) { Object.keys(config).forEach(function(key) { result[key] = config[key]; }); } return result; }; AutomationModule.prototype.init = function(config) { console.log("--- Starting module " + this.meta.defaults.title); if (!!config) { this.saveNewConfig(config); } else { this.loadConfig(); } }; AutomationModule.prototype.saveNewConfig = function(config) { if (!!config) { this.config = this.defaultConfig(config); this.saveConfig(); } }; AutomationModule.prototype.stop = function() { console.log("--- Stopping module " + this.meta.defaults.title); }; AutomationModule.prototype.loadConfig = function() { var self = this; var cfg = loadObject("cfg" + this.id); if ("object" === typeof cfg) { Object.keys(cfg).forEach(function(key) { self.config[key] = cfg[key]; }); } }; AutomationModule.prototype.saveConfig = function(config) { var that = this, 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(true); }; AutomationModule.prototype.getName = function() { return this.constructor.name; }; // This method returns JSON representation AutomationModule.prototype.toJSON = function() { return { module: this.getName(), id: this.id, config: this.config }; }; 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() { if (!this.meta) { this.meta = this.controller.getModuleData(this.constructor.name); this.meta.id = this.constructor.name; } return this.meta; }; AutomationModule.prototype.loadModuleJSON = function(filename) { return fs.loadJSON(this.meta.location + "/" + filename); }; AutomationModule.prototype.getInstanceTitle = function() { var instanceId = this.id; 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; }; AutomationModule.prototype.loadModuleLang = function() { return this.controller.loadModuleLang(this.getName()); }; AutomationModule.prototype.addNotification = function(severity, message, type) { this.controller.addNotification(severity, message, type, this.getName()); }; // TODO: delete it, should not be here AutomationModule.prototype.prepareHTTPResponse = function(body) { var result = {}, ret = { status: 500, headers: { "Content-Type": "application/json" }, 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); } }; ================================================ FILE: classes/DevicesCollection.js ================================================ /*** Z-Way DevicesCollection class ************************************ Version: 1.0.0 ------------------------------------------------------------------------------- Author: Stanislav Morozov Copyright: (c) ZWave.Me, 2014 ******************************************************************************/ DevicesCollection = function (controller) { var that = this; that.controller = controller; that.config = {}; that.models = []; that.db = { cid: {}, id: {}, indexes: {}, hardwareId: {} }; that.changed = { remove: [], add: [], update: [] }; that.length = 0; that.initialize.apply(this, arguments); }; inherits(DevicesCollection, EventEmitter2); _.extend(DevicesCollection.prototype, { initialize: function () { 'use strict'; _.bindAll(this, 'updateLength', 'create'); }, updateLength: function () { this.length = _.size(this.models); }, create: function (options) { var that = this, vDev = null; console.log("Creating device " + (options.overlay.deviceType || options.defaults.deviceType) + " " + options.deviceId); vDev = new VirtualDevice(_.extend(options, {controller: that.controller})); if (vDev !== null) { vDev.init(); that.add(vDev); that.updateLength(); that.emit('created', vDev); that.controller.lastStructureChangeTime = Math.floor(Date.now() / 1000); } else { console.log("Error creating device"); } vDev.save(); return vDev; }, add: function (model) { if (model.hasOwnProperty('cid')) { if (this.db.cid[model.cid] === model) { delete this.db.cid[model.cid]; } delete model.cid; } model.cid = _.uniqueId('c'); this.db.cid[model.cid] = model; this.db.id[model.get('id')] = model; // add to collection this.models.push(model); model.index = this.models.indexOf(model); this.db.indexes[model.index] = model; return model; }, has: function (identificator) { var result = false; if (this.db.id.hasOwnProperty(identificator) || this.db.cid.hasOwnProperty(identificator)) { result = true; } return result; }, get: function (identificator) { var result; if (this.db.id.hasOwnProperty(identificator)) { result = this.db.id[identificator]; } else if (this.db.cid.hasOwnProperty(identificator)) { result = this.db.cid[identificator]; } return result || null; }, first: function () { return _.first(this.models); }, last: function () { return _.last(this.models); }, size: function () { return _.size(this.models); }, toJSON: function (options) { var models, result; options = options || {}; models = this.models.filter(function (device) { return !!options.since ? !!device.ready && device.toJSON().updateTime >= options.since : !!device.ready; }); models = models.map(function (model) { return model.toJSON(); }); return models; }, remove: function (identificator) { var that = this, model = that.get(identificator); if (!model) { return; } that.models = that.models.filter(function (object) { return object.cid !== model.cid; }); console.log("Deleting device " + model.get("deviceType") + " " + identificator); if (that.db.id.hasOwnProperty(identificator)) { delete that.db.id[identificator]; } else if (that.db.cid.hasOwnProperty(identificator)) { delete that.db.cid[identificator]; } else if (that.db.indexes.hasOwnProperty(model.index)) { delete that.db.indexes[model.index]; } delete model.cid; // events that.emit('removed', model); that.controller.lastStructureChangeTime = Math.floor(Date.now() / 1000); return model; }, cleanup: function (identificator) { var that = this, model = that.get(identificator); if (model) { console.log("Can not cleanup device info: device " + identificator + " exists"); } that.emit('wipedOut', identificator); that.controller.clearVdevInfo(identificator); }, where: function (obj) { var that = this, check, devices = _.filter(that.models, function (model) { check = true; Object.keys(obj).forEach(function (key) { if (model.get(key) !== obj[key] && check) { check = false; return; } }); return check; }); return devices.length && Boolean(devices) ? devices : []; }, findWhere: function (obj) { return _.first(this.where(obj)); }, filter: function (callback) { return _.filter(this.models, callback); }, map: function (callback) { return _.map(this.models, callback); }, each: function (callback) { return _.each(this.models, callback); }, forEach: function (callback) { return this.each(callback); }, on: function () { var vDevId = "", args = []; Array.prototype.push.apply(args, arguments); if (args.length < 2 || args.length > 3) { throw "Invalid number of arguments to on()"; } if (args.length > 2) { vDevId = args.shift() + ":"; } return EventEmitter2.prototype.on.call(this, vDevId + args[0], args[1]); }, off: function () { var vDevId = "", args = []; Array.prototype.push.apply(args, arguments); if (args.length < 2 || args.length > 3) { throw "Invalid number of arguments to off()"; } if (args.length > 2) { vDevId = args.shift() + ":"; } return EventEmitter2.prototype.off.call(this, vDevId + args[0], args[1]); }, // filter vDevs by nodeId and zway name filterByNode: function (nodeId, zwayName) { var self = this, getNodeVDevs = [], nodeId = nodeId, zwayName = zwayName, listZwayNames = ZWave.list().map(function(name) { return name }); // filter all nodes if (!zwayName) { matches = []; // prepare vDev id prefixes listZwayNames.forEach(function (name) { matches.push("ZWayVDev_" + name + "_" + nodeId); }); // get all vDev's by node matches.forEach(function (vDevIdPrefix) { getNodeVDevs = getNodeVDevs.concat(_.filter(self.models, function(vDev){ return vDev.id.indexOf(vDevIdPrefix) > -1; })); }); // filter all nodes by zway name } else if (zwayName && listZwayNames.indexOf(zwayName) > -1){ vDevIdPrefix = "ZWayVDev_" + zwayName + "_" + nodeId; getNodeVDevs = _.filter(this.models, function(vDev) { return vDev.id.indexOf(vDevIdPrefix) > -1; }); } return getNodeVDevs; }, // filter vDevs by creator id filterByCreatorId: function (instanceId) { var instanceVDevs = []; instanceVDevs = _.filter(this.models,function (dev) { return dev.get('creatorId') === instanceId; }); return instanceVDevs; } }); ================================================ FILE: classes/VirtualDevice.js ================================================ /*** Z-Way HA Virtual Device base class *************************************** Version: 2.0.0 ------------------------------------------------------------------------------- Author: Gregory Sitnin and Stanislav Morozov Copyright: (c) ZWave.Me, 2013-2014 ******************************************************************************/ VirtualDevice = function (options) { var probeType = options.defaults.probeType? options.defaults.probeType : (options.overlay.probeType? options.overlay.probeType : ''), permHidden = options.defaults.hasOwnProperty('permanently_hidden')? options.defaults.permanently_hidden : false, visibility = options.defaults.hasOwnProperty('visibility')? options.defaults.visibility : true, customicons = options.defaults.hasOwnProperty('customIcons') ? options.defaults.customIcons : {}, 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; _.defaults(options.defaults, { technology: "Virtual", manufacturer: "Z-Wave.Me", product: "Virtual device", firmware: zwayVersion.release }); _.extend(this, options, { id: options.deviceId, accessAttrs: [ 'id', 'deviceType', 'metrics', 'location', 'locationName', 'tags', 'updateTime', 'permanently_hidden', 'creatorId', 'h', 'hasHistory', 'visibility', 'creationTime', 'probeType', 'customIcons', 'manufacturer', 'product', 'firmware', 'order', 'technology', 'bindingName', 'nodeId' ], collection: options.controller.devices, metrics: {}, ready: false, location: 0, locationName: "", tags: [], updateTime: 0, attributes: { id: options.deviceId, metrics: _.extend(this.metrics, { isFailed: isFailed }), tags: tags, permanently_hidden: permHidden, location: location, locationName: "", h: options.controller.hashCode(options.deviceId), hasHistory: false, visibility: visibility, creationTime: creationTime, probeType: probeType, customIcons: customicons, order: order }, overlay: options.overlay || {}, defaults: options.defaults || {}, overlay_metrics: options.hasOwnProperty('overlay') ? options.overlay.metrics : {}, _previousAttributes: {} }); if (options.hasOwnProperty('overlay')) { delete options.overlay.metrics; } if (!!this.collection) { this.cid = _.uniqueId('c'); } if (!!options.moduleId) { this.attributes.creatorId = options.moduleId; } this.initialize.apply(this, arguments); return this; }; function inObj(obj, arr) { var result, findObj; while (arr.length > 0) { findObj = result === undefined ? obj : result; if (findObj.hasOwnProperty(arr[0])) { result = findObj[arr[0]]; } else { break; } arr.shift(); } return arr.length > 0 ? undefined : result; } function setObj(obj, arr, param) { var key; if (obj) { key = arr[0]; arr.shift(); if (arr.length === 0) { obj[key] = param; } else if (obj.hasOwnProperty(key) && arr.length > 0) { setObj(obj[key], arr, param); } else if (!obj.hasOwnProperty(key) && arr.length > 0) { obj[key] = {}; setObj(obj[key], arr, param); } } return obj; } _.extend(VirtualDevice.prototype, { initialize: function () { 'use strict'; _.bindAll(this, 'get', 'set'); _.extend(this.attributes, this.collection.controller.getVdevInfo(this.id)); _.extend(this.attributes, this.overlay); _.defaults(this.attributes, { "metrics" : {} }); _.extend(this.attributes.metrics, this.overlay_metrics); _.defaults(this.attributes, this.defaults); // set default params _.defaults(this.attributes.metrics, this.defaults.metrics); // set default metrics // check that location exists var self = this; var l = _.find(this.collection.controller.locations, function(loc) { return loc.id === self.attributes.location; }); if (!l) { this.attributes.location = 0; // reset to globalRoom if location is not found } _.extend(this.attributes, { locationName: this.collection.controller.locationName(this.attributes.location) }); // set device creation time this.setCreationTime(); this.attributes = this._sortObjectByKey(this.attributes); // cleanup delete this.overlay; delete this.overlay_metrics; delete this.defaults; }, setReady: function () { this.ready = true; this.attributes.updateTime = Math.floor(Date.now() / 1000); }, setCreationTime: function() { var vDevInfo = this.collection.controller.vdevInfo[this.id]; if (vDevInfo) { // check vdevInfo for creation time if (vDevInfo.creationTime) { this.attributes.creationTime = vDevInfo.creationTime > 0? vDevInfo.creationTime : Math.floor(Date.now() / 1000); // add new if it doesn't exist } else { this.attributes.creationTime = Math.floor(Date.now() / 1000); } // add new if it doesn't exist } else { this.attributes.creationTime = Math.floor(Date.now() / 1000); } }, get: function (param) { 'use strict'; var result; if (param.split(':').length === 1) { if (this.attributes.hasOwnProperty(param)) { result = this.attributes[param]; } } else if (param.split(':').length > 1) { result = inObj(this.toJSON(), param.split(':')); } return result; }, set: function (keyName, val, options) { var that = this, changes = [], current = _.clone(this.attributes), prev = this._previousAttributes, accessAttrs, attrs, findObj; function findX(obj, key) { var val = obj[key]; if (val !== undefined) { return obj; } for (var name in obj) { var result = findX(obj[name]); if (result !== undefined) { return obj; } } return undefined; } options = options || {}; accessAttrs = options.accessAttrs || that.accessAttrs; if (_.isString(keyName) && typeof(val) != "undefined" && keyName.split(':').length === 1) { // to track location change var profilesDevicesBefore; if ("location" === keyName) { profilesDevicesBefore = this.collection.controller.profiles.map(function(p) { return { id: p.id, devices: that.collection.controller.devicesByUser(p.id) }; }); } findObj = findX(this.attributes, keyName); if (findObj[keyName] !== val) { that.attributes[keyName] = val; changes.push(keyName); } if ("location" === keyName) { this.collection.controller.profiles.forEach(function(p) { var isPresent = that.collection.controller.devicesByUser(p.id).filter(function(d) { return d.id === that.id; }).length, wasPresent = profilesDevicesBefore.filter(function(pp) { return pp.id === p.id && pp.devices.filter(function(d) { return d.id === that.id; }).length; }).length; if (isPresent != wasPresent) { that.collection.controller.emit('profile.deviceListUpdated', { profile: p, added: isPresent ? [that.id] : [], deleted: wasPresent ? [that.id] : [] }); } }); } } else { if (_.isString(keyName) && val !== undefined && keyName.split(':').length > 1) { setObj(current, keyName.split(':'), val); _.extend(that.attributes, current); changes.push(keyName); } else { // to track location change var profilesDevicesBefore; if ("location" in keyName) { delete keyName["locationName"]; // remove locationName if present - it will be recalculated profilesDevicesBefore = this.collection.controller.profiles.map(function(p) { return { id: p.id, devices: that.collection.controller.devicesByUser(p.id) }; }); } attrs = _.extend(that.attributes, _.pick(keyName, accessAttrs)); Object.keys(attrs).forEach(function (key) { if (!_.isEqual(current[key], attrs[key])) { // if metrics has changed go deeper and add change identifier of changed metrics entry if (key === 'metrics') { Object.keys(current[key]).forEach(function (metricsKey) { if (!_.isEqual(current[key][metricsKey], attrs[key][metricsKey])) { changes.push('metrics:' + metricsKey); } }); // push also 'metrics' identifier changes.push(key); } else { changes.push(key); console.logJS(3, changes) } } }); if ("location" in keyName) { this.collection.controller.profiles.forEach(function(p) { var isPresent = that.collection.controller.devicesByUser(p.id).filter(function(d) { return d.id === that.id; }).length, wasPresent = profilesDevicesBefore.filter(function(pp) { return pp.id === p.id && pp.devices.filter(function(d) { return d.id === that.id; }).length; }).length; if (isPresent != wasPresent) { that.collection.controller.emit('profile.deviceListUpdated', { profile: p, added: isPresent ? [that.id] : [], deleted: wasPresent ? [that.id] : [] }); } }); } } } if (!options.silent) { if (changes.length) { that.attributes.updateTime = Math.floor(Date.now() / 1000); } changes.forEach(function (key) { if (!!that.collection) { that.collection.emit('change:' + key, that, key); } that.emit('change:' + key, that, key); }); } if (!options.setOnly) { that.save(); } return this; }, save: function (attrs, options) { if (!!attrs) { this.set(attrs, options); } this.collection.controller.setVdevInfo(this.id, this.attributes); return this; }, toJSON: function () { return _.clone(this.attributes); }, destroy: function () { this.unlink(); this.collection.emit('destroy', this); this.remove(); }, unlink: function () { this.collection.remove(this.cid); }, remove: function () { this.stopListening(); return this; }, stopListening: function () { this.removeAllListeners(); }, init: function () { this.setReady(); }, deviceTitle: function () { return this.attributes.metrics.hasOwnProperty('title') ? this.attributes.metrics.title : this.id; }, deviceIcon: function () { return this.metrics.icon = this.deviceType; }, performCommand: function () { console.log("--- ", this.id, "performCommand processing:", JSON.stringify(arguments)); if (typeof this.handler === "function") { try { return this.handler.apply(this, arguments); } catch(e) { var langFile = this.controller.loadMainLang(); this.controller.addNotification("error", langFile.vd_err_virtual_dev + e.toString(), "module", "VirtualDevice"); console.log(e.stack); } } }, addTag: function (tag) { var tags = this.get('tags'); this.set({'tags': _.uniq(_.union(tags, [tag]))}); }, removeTag: function (tag) { var tags = this.get('tags'); this.set({'tags': _.without(tags, tag)}); }, // wrappers for events on: function(eventName, func) { return this.collection.on(this.id + ":" + eventName, func); }, off: function(eventName, func) { return this.collection.off(this.id + ":" + eventName, func); }, emit: function(eventName, that) { return this.collection.emit(this.id + ":" + eventName, that); }, _sortObjectByKey: function(o){ var sorted = {}, key, a = []; for (key in o) { if (o.hasOwnProperty(key)) { a.push(key); } } a.sort(); for (key = 0; key < a.length; key++) { sorted[a[key]] = o[a[key]]; } return sorted; } }); ================================================ FILE: defaultConfigs/README ================================================ These configs are used to prepare distribs for different platforms NB! Please keep all theses files beautified! Use http://jsbeautifier.org/ to keep them well formatted. Short legenda: config.json_ttyACM0 - for Raspberry Pi with UZB config.json_ttyACM0_ZBW-no - for any Linux machine with UZB but without remote access functionality config.json_ttyACM0_ZBW-WD - WD special config config.json_ttyAMA0 - for RaZberry config.json_ttyAMA0_NonExpert - for RaZberry but no expert view config.json_ttyS0 - for ttyS0 for embedded boxes config.json_ttyS0-JBox - for James Box config.json_ttyS0-ReHub - for Rehau Hub config.json_ttyS1 - for ttyS1 for embedded boxes config.json_ttyUSB0_ZBW-no_vDev-no - for old white Z-Stick without any HA and with public Z-Wave API config.json_windows - for Windows ================================================ FILE: defaultConfigs/config.json ================================================ { "controller": { "initial": true }, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyACM0", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "InfoWidget", "active": true, "title": "Dear Expert User", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Dear Expert User", "text": "
If you still want to use ExpertUI please go, after you are successfully logged in, to
Menu > Devices > Manage with ExpertUI
or call
http://MYRASP:8083/expert
in your browser.

You could hide or remove this widget in menu
Apps > Active Tab.
", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Lieber Expert User", "text": "
Wenn Sie weiterhin die ExpertUI nutzen möchten, dann klicken Sie bitte, nachdem Sie sich erfolgreich angemeldet haben, auf
Menü > Geräte > Verwaltung mit ExpertUI
oder rufen Sie
http://MYRASP:8083/expert
in Ihrem Browser auf.

Sie können dieses Element im Menü
Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/logo-z-wave-z-only.png" }] } }, { "id": 6, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 7, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 8, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 9, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.\n(Added by default)", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 10, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.\n(Added by default)", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 11, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).\n(Added by default)", "params" : { "apps" : [] } }, { "id": 12, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 13, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 14, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: defaultConfigs/config.json_WB ================================================ { "controller": { "initial": true }, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyMOD4", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "configs/config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "InfoWidget", "active": true, "title": "Dear Expert User", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Dear Expert User", "text": "
If you still want to use ExpertUI please go, after you are successfully logged in, to
Menu > Devices > Manage with ExpertUI
or call
http://MYRASP:8083/expert
in your browser.

You could hide or remove this widget in menu
Apps > Active Tab.
", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Lieber Expert User", "text": "
Wenn Sie weiterhin die ExpertUI nutzen möchten, dann klicken Sie bitte, nachdem Sie sich erfolgreich angemeldet haben, auf
Menü > Geräte > Verwaltung mit ExpertUI
oder rufen Sie
http://MYRASP:8083/expert
in Ihrem Browser auf.

Sie können dieses Element im Menü
Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/logo-z-wave-z-only.png" }] } }, { "id": 6, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 7, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 8, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 9, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.\n(Added by default)", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 10, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.\n(Added by default)", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 11, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).\n(Added by default)", "params" : { "apps" : [] } }, { "id": 12, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 13, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 14, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }, { "id": 15, "moduleId": "Zigbee", "active": true, "title": "Zigbee Network Access", "params":{ "name": "zbee", "port": "/dev/ttyMOD3", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "config": "configs/config", "translations": "translations", "ZDDX": "ZDDX" } }, { "id": 16, "moduleId": "WBMQTTNative", "active": true, "title": "Wiren Board MQTT Integration Native", "params": { "clientId": "wbzway", "host": "localhost", "port": "1883", "user": "none", "password": "none", "topicPrefix": "/devices/zway", "topicPostfixSet": "on", "precision": 0.01, "groupByDevices": true, "debug": false } }, { "id": 17, "moduleId": "WBMQTTImport", "active": true, "title": "Wiren Board MQTT connector for Z-Way", "params": { "clientId": "zwaywb", "host": "localhost", "port": "1883", "user": "none", "password": "none", "debug": false } }] } ================================================ FILE: defaultConfigs/config.json_ttyACM0 ================================================ { "controller": { "initial": true }, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyACM0", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "InfoWidget", "active": true, "title": "Dear Expert User", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Dear Expert User", "text": "
If you still want to use ExpertUI please go, after you are successfully logged in, to
Menu > Devices > Manage with ExpertUI
or call
http://MYRASP:8083/expert
in your browser.

You could hide or remove this widget in menu
Apps > Active Tab.
", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Lieber Expert User", "text": "
Wenn Sie weiterhin die ExpertUI nutzen möchten, dann klicken Sie bitte, nachdem Sie sich erfolgreich angemeldet haben, auf
Menü > Geräte > Verwaltung mit ExpertUI
oder rufen Sie
http://MYRASP:8083/expert
in Ihrem Browser auf.

Sie können dieses Element im Menü
Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/logo-z-wave-z-only.png" }] } }, { "id": 6, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 7, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 8, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 9, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.\n(Added by default)", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 10, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.\n(Added by default)", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 11, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).\n(Added by default)", "params" : { "apps" : [] } }, { "id": 12, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 13, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 14, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: defaultConfigs/config.json_ttyACM0_ZBW-WD ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyACM0", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "./zbw", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 6, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 7, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 8, "moduleId": "InfoWidget", "active": true, "title": "Info: Licence Upgrade", "description": "Please follow the steps for licence upgrade.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Info: Licence Upgrade", "text": "Please enter the licence key to activate all of your WD SmartHome center’s functions. You can find the key on the keycard provided with your Smart Home Stick.

Go to the SmartHomeUI and select Menu > Management > Licence Upgrade and enter the licence key.", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Info: Lizenzerweiterung ", "text": "Um alle Funktionen Ihrer WD SmartHome Zentrale nutzen zu können, geben Sie bitte den Lizenzcode ein. Dieser liegt dem Smart Home Stick als Code-Karte bei.

Wählen Sie in der SmartHomeUI Menü > Management > Lizenzerweiterung und geben Sie den Lizenzcode ein.", "imgURI": "app/img/logo-z-wave-z-only.png" }] } }, { "id": 9, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 10, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 11, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).", "params" : { "apps" : [] } }, { "id": 12, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 13, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 14, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: defaultConfigs/config.json_ttyACM0_ZBW-no ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyACM0", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "InfoWidget", "active": true, "title": "Dear Expert User", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Dear Expert User", "text": "
If you still want to use ExpertUI please go, after you are successfully logged in, to
Menu > Devices > Manage with ExpertUI
or call
http://MYRASP:8083/expert
in your browser.

You could hide or remove this widget in menu
Apps > Active Tab.
", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Lieber Expert User", "text": "
Wenn Sie weiterhin die ExpertUI nutzen möchten, dann klicken Sie bitte, nachdem Sie sich erfolgreich angemeldet haben, auf
Menü > Geräte > Verwaltung mit ExpertUI
oder rufen Sie
http://MYRASP:8083/expert
in Ihrem Browser auf.

Sie können dieses Element im Menü
Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/logo-z-wave-z-only.png" }] } }, { "id": 5, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 6, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 7, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 8, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 9, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 10, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).", "params" : { "apps" : [] } }, { "id": 11, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 12, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 13, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: defaultConfigs/config.json_ttyAMA0 ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyAMA0", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "InfoWidget", "active": true, "title": "Dear Expert User", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Dear Expert User", "text": "
If you still want to use ExpertUI please go, after you are successfully logged in, to
Menu > Devices > Manage with ExpertUI
or call
http://MYRASP:8083/expert
in your browser.

You could hide or remove this widget in menu
Apps > Active Tab.
", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Lieber Expert User", "text": "
Wenn Sie weiterhin die ExpertUI nutzen möchten, dann klicken Sie bitte, nachdem Sie sich erfolgreich angemeldet haben, auf
Menü > Geräte > Verwaltung mit ExpertUI
oder rufen Sie
http://MYRASP:8083/expert
in Ihrem Browser auf.

Sie können dieses Element im Menü
Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/logo-z-wave-z-only.png" }] } }, { "id": 6, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 7, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 8, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 9, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 10, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 11, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).", "params" : { "apps" : [] } }, { "id": 12, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 13, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 14, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: defaultConfigs/config.json_ttyAMA0_NonExpert ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyAMA0", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 6, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 7, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 8, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 9, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 10, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).", "params" : { "apps" : [] } }, { "id": 11, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 12, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 13, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: defaultConfigs/config.json_ttyS0 ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyS0", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "InfoWidget", "active": true, "title": "Dear Expert User", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Dear Expert User", "text": "
If you still want to use ExpertUI please go, after you are successfully logged in, to
Menu > Devices > Manage with ExpertUI
or call
http://MYRASP:8083/expert
in your browser.

You could hide or remove this widget in menu
Apps > Active Tab.
", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Lieber Expert User", "text": "
Wenn Sie weiterhin die ExpertUI nutzen möchten, dann klicken Sie bitte, nachdem Sie sich erfolgreich angemeldet haben, auf
Menü > Geräte > Verwaltung mit ExpertUI
oder rufen Sie
http://MYRASP:8083/expert
in Ihrem Browser auf.

Sie können dieses Element im Menü
Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/logo-z-wave-z-only.png" }] } }, { "id": 6, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 7, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 8, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 9, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 10, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 11, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).", "params" : { "apps" : [] } }, { "id": 12, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 13, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 14, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: defaultConfigs/config.json_ttyS0-JBox ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyS0", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "disable", "config": "configs/config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 6, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 7, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 8, "moduleId": "PhilioHW", "active": true, "title": "Philio hardware support", "description": "Support for Philio hardware buttons, LEDs, power and battery management", "params": { "no_battery": false } }, { "id": 9, "moduleId": "ZMEOpenWRT", "active": true, "title": "Time zone and access from WAN", "params": { "timezone": "UTC", "wan_port_access": false } }, { "id": 10, "moduleId": "TamperAutoOff", "active": true, "title": "Tamper Auto Off", "params": { "devices": [], "timeout": 30 } }, { "id": 11, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 12, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 13, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).", "params" : { "apps" : [] } }, { "id": 14, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 15, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 16, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: defaultConfigs/config.json_ttyS0-ReHub ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyS0", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "disable", "config": "configs/config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 6, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 7, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 8, "moduleId": "PhilioHW", "active": true, "title": "Philio hardware support", "description": "Support for Philio hardware buttons, LEDs, power and battery management", "params": { "no_battery": false } }, { "id": 9, "moduleId": "ZMEOpenWRT", "active": true, "title": "Time zone and access from WAN", "params": { "timezone": "UTC", "wan_port_access": false } }, { "id": 10, "moduleId": "TamperAutoOff", "active": true, "title": "Tamper Auto Off", "params": { "devices": [], "timeout": 30 } }, { "id": 11, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 12, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 13, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).", "params" : { "apps" : [] } }, { "id": 14, "moduleId": "RemoteID_Transfer", "active": true, "title": "Transfer Remote Id to C-Module", "description": "Sends RemoteId to C-Modul", "params": {} }, { "id": 15, "moduleId": "SensorEvaluation", "active": true, "title": "Send sensordata to the C-Module", "description": "Send sensordata to the C-Module", "params": { "closeValveOption": true, "floodSensorOption": true, "cronSensorsEvaluationUpdateOption": 1, "cronSendLeckageDeviceDataOption": 1, "cronSendBatteryAndTempOption": 30 } }] } ================================================ FILE: defaultConfigs/config.json_ttyS1 ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyS1", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "RemoteAccess", "active": true, "title": "Remote Access", "description": "Is necessary to configure remote access in SmartHome UI.\n(Added by default)", "params": { "path": "", "userId": "", "actStatus": true, "sshStatus": false, "zbwStatus": true, "pass": "", "lastChange": {} } }, { "id": 5, "moduleId": "InfoWidget", "active": true, "title": "Dear Expert User", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Dear Expert User", "text": "
If you still want to use ExpertUI please go, after you are successfully logged in, to
Menu > Devices > Manage with ExpertUI
or call
http://MYRASP:8083/expert
in your browser.

You could hide or remove this widget in menu
Apps > Active Tab.
", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Lieber Expert User", "text": "
Wenn Sie weiterhin die ExpertUI nutzen möchten, dann klicken Sie bitte, nachdem Sie sich erfolgreich angemeldet haben, auf
Menü > Geräte > Verwaltung mit ExpertUI
oder rufen Sie
http://MYRASP:8083/expert
in Ihrem Browser auf.

Sie können dieses Element im Menü
Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/logo-z-wave-z-only.png" }] } }, { "id": 6, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 7, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 8, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 9, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 10, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 11, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).", "params" : { "apps" : [] } }, { "id": 12, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 13, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: defaultConfigs/config.json_ttyUSB0_ZBW-no_vDev-no ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "/dev/ttyUSB0", "speed": 115200, "enableAPI": true, "publicAPI": true, "createVDev": false, "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "InfoWidget", "active": true, "title": "Dear Expert User", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Dear Expert User", "text": "
If you still want to use ExpertUI please go, after you are successfully logged in, to
Menu > Open ExpertUI
or call
http://MYRASP:8083/expert
in your browser.

You could hide or remove this widget in menu
Apps > Active Tab.
", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Lieber Expert User", "text": "
Wenn Sie weiterhin die ExpertUI nutzen möchten, dann klicken Sie bitte, nachdem Sie sich erfolgreich angemeldet haben, auf
Menü > Öffne ExpertUI
oder rufen Sie
http://MYRASP:8083/expert
in Ihrem Browser auf.

Sie können dieses Element im Menü
Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/logo-z-wave-z-only.png" }] } },{ "id": 3, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 4, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }] } ================================================ FILE: defaultConfigs/config.json_windows ================================================ { "controller": {}, "vdevInfo": {}, "locations": [{ "id": 0, "title": "globalRoom", "user_img": "", "default_img": "", "img_type": "" }], "profiles": [{ "id": 1, "role": 1, "login": "admin", "password": "admin", "name": "Administrator", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }, { "id": 2, "role": 3, "login": "local", "password": "local", "name": "Local User", "lang": "en", "dashboard": [], "interval": 2000, "devices": [], "rooms": [0], "expert_view": true, "hide_all_device_events": false, "hide_system_events": false, "hide_single_device_events": [], "email": "", "skin": "", "night_mode": false, "beta": false }], "instances": [{ "id": 1, "moduleId": "ZWave", "params": { "name": "zway", "port": "\\\\.\\COM3", "speed": 115200, "enableAPI": true, "publicAPI": false, "createVDev": true, "enablePacketLog": "enable", "config": "config", "translations": "translations", "ZDDX": "ZDDX" }, "active": true, "title": "Z-Wave Network Access", "description": "Allows accessing Z-Wave devices from attached Z-Wave transceiver.\n(Added by default)" }, { "id": 2, "moduleId": "Cron", "params": {}, "active": true, "title": "System Clock (CRON)", "description": "Scheduler used by other modules\n(Added by default)" }, { "id": 3, "moduleId": "InbandNotifications", "params": {}, "active": true, "title": "Inband Notifier", "description": "Creates and records the presentation of events in the event list (Eventlog).\n(Added by default)" }, { "id": 4, "moduleId": "InfoWidget", "active": true, "title": "Dear Expert User", "description": "Dieses Modul erzeugt ein Informations Widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Dear Expert User", "text": "
If you still want to use ExpertUI please go, after you are successfully logged in, to
Menu > Devices > Manage with ExpertUI
or call
http://MYRASP:8083/expert
in your browser.

You could hide or remove this widget in menu
Apps > Active Tab.
", "imgURI": "app/img/logo-z-wave-z-only.png" }, { "lang": "de", "headline": "Lieber Expert User", "text": "
Wenn Sie weiterhin die ExpertUI nutzen möchten, dann klicken Sie bitte, nachdem Sie sich erfolgreich angemeldet haben, auf
Menü > Geräte > Verwaltung mit ExpertUI
oder rufen Sie
http://MYRASP:8083/expert
in Ihrem Browser auf.

Sie können dieses Element im Menü
Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/logo-z-wave-z-only.png" }] } }, { "id": 5, "moduleId": "SensorsPolling", "active": false, "title": "Periodical Sensor Polling", "description": "Queries in an adjustable interval all measurement sensors. It can be entered a list of sensors, which should not be queried.\n(Added by default)", "params": { "pollDevsWithBatteries": false, "devices": [], "devicesWithBattery": [], "period": "10" } }, { "id": 6, "moduleId": "BatteryPolling", "active": true, "title": "Battery Polling", "description": "Set up an interval that regularly polls the battery status of a battery devices.\n(Added by default)", "params": { "launchWeekDay": 0, "warningLevel": "20" } }, { "id": 7, "moduleId": "SwitchControlGenerator", "active": true, "title": "Trap events from Remotes and Sensors", "description": "", "params": { "trapNew": true, "banned": [], "generated": [] } }, { "id": 8, "moduleId": "CloudBackup", "active": true, "title": "CloudBackup", "description": "Gives possibility to upload and store your backups on the remote server.", "params": { "api": "/CloudBackupAPI/Backup", "user_active": false, "email": "", "email_log": "0", "remoteid": null, "days": "28", "weekDays": null, "hours": "23", "minutes": "59", "scheduler": "3" } }, { "id": 9, "moduleId": "InfoWidget", "active": true, "title": "Cloud Backup Instructions", "description": "This module creates an information widget.", "params": { "widgets": [], "internationalize": true, "widgetsInt": [{ "lang": "en", "headline": "Cloud Backup Instructions", "text": "
Cloud backup is conveniently saving up to 3 backup files on our server (using SSL encryption).
By default, an automatic backup is created every month on 28 at 23:59.
If you don’t like to see your backup file on our server, just deactivate this service or change the interval.
To change the settings, please click on
Menu > Management > Backup & Restore.

You could hide or remove this widget in
Menu >Apps > Active Tab.
", "imgURI": "app/img/icon_cloudbackup.png" }, { "lang": "de", "headline": "Cloud Backup Anleitung", "text": "
Cloud Backup sichert bis zu 3 Kopien Ihrer Daten automatisch auf unserem Server (mittels SSL Verschlüsselung).
Standard mäßig wird jeden Monat am 28 um 23:59 ein automatisches Backup erstellt.
Sollten Sie das nicht wünschen dann ändern Sie den Interval oder deaktivieren Sie diese Funktion.
Zum ändern der Einstellungen klicken Sie bitte auf
Menü > Management > Datensicherung & Wiederherstellung.

Sie können dieses Element im
Menü > Anwendungen > Aktiv
auschalten oder entfernen.
", "imgURI": "app/img/icon_cloudbackup.png" }] } }, { "id" : 10, "moduleId" : "MobileAppSupport", "active" : true, "title" : "Mobile App Support", "description": "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).", "params" : { "apps" : [] } }, { "id": 11, "moduleId": "HomeKitGate", "active": true, "title": "Apple HomeKit Gate", "params": { "name": "Z-Wave.Me", "skippedDevices": [], "idMapping": {} } }, { "id": 12, "moduleId": "NotificationFiltering", "active": true, "title": "Notification Filtering", "params": { "rules": [{ "recipient_type": "user", "user": 1, "logLevel": "errors,warnings", "devices": [] }], "autogenOnDeviceListUpdate": true, "normalizeRules": true } }, { "id": 13, "moduleId": "NotificationChannelEmail", "active": true, "title": "Notifications by E-mail", "params": { "subject": "Z-Way Notification", "channels": [] } }] } ================================================ FILE: lang/cn.json ================================================ { "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." } ================================================ FILE: lang/cz.json ================================================ { "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." } ================================================ FILE: lang/de.json ================================================ { "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." } ================================================ FILE: lang/en.json ================================================ { "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." } ================================================ FILE: lang/es.json ================================================ { "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." } ================================================ FILE: lang/fi.json ================================================ { "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." } ================================================ FILE: lang/fr.json ================================================ { "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." } ================================================ FILE: lang/it.json ================================================ { "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." } ================================================ FILE: lang/pt.json ================================================ { "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." } ================================================ FILE: lang/ru.json ================================================ { "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." } ================================================ FILE: lang/se.json ================================================ { "ac_warn_restart":"Automationskontrollern är omstartad.", "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":"Beroenden kan inte hittas för modul: [DEP]::[MODUL] = ", "ac_err_dep_not_loaded":"Kan inte ladda modulen då beroendet inte var laddad: [DEP]::[MODUL] = ", "ac_err_dep_not_init":"Failed to load module because dependency was not instanciated: [DEP]::[MODUL] = ", "ac_err_file_load":"Kan inte ladda ", "ac_err_invalid_module":"Felaktig modul ", "ac_err_load_mod_json":"Kan inte ladda modules.json från ", "ac_err_stop_mod":"Kan inte stoppa 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": "Administratör", "profile_descr": "Detta är adminprofilen. Adminprofilen skapades automatiskt.", "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." } ================================================ FILE: lang/sk.json ================================================ { "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." } ================================================ FILE: lang/sv.json ================================================ { "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." } ================================================ FILE: lib/BAOS_API_2011_01_29_001.js ================================================ // BAOS_API_2011_01_29_001 function BaosLib() { ////////////////////////////////////////////////// // Member variables // ////////////////////////////////////////////////// var m_strIpAddr; // IP address var m_IndicationSession = 0; // Indication session var m_IndFormat; // Format used for indications var m_bEnableCallbackRespRcvd = false; // Disable callback (response received) var m_bEnableCallbackIndicationUpdate = false; // Disable callback (indication update) var m_bEnableCallbackInvalidSettings = false; // Disable callback (invalid settings) var m_bEnableCallbackTransmitError = false; // Disable callback (transmit error) var m_CallbackRespRcvd; // Callback for response received var m_CallbackIndicationUpdate; // Callback for indication update var m_CallbackInvalidSettings; // Callback for invalid settings var m_CallbackTransmitError; // Callback for transmit error ////////////////////////////////////////////////// // Public functions // ////////////////////////////////////////////////// // This API function sets the IP address to the BAOS library this.API_SetIpAddress = function (strIpAddr) { m_strIpAddr = strIpAddr; // Set IP address }; // This API function sets the callback handler for the responses. this.API_SetCallbackRespRcvd = function (strCallback) { m_CallbackRespRcvd = strCallback; // Save callback reference m_bEnableCallbackRespRcvd = true; // Enable callback }; // This API function sets the callback handler for the indications. this.API_SetCallbackIndicationUpdate = function (strCallback) { m_CallbackIndicationUpdate = strCallback; // Save callback reference m_bEnableCallbackIndicationUpdate = true; // Enable callback }; // This API function sets the callback handler for informing about // invalid settings. this.API_SetCallbackInvalidSettings = function (strCallback) { m_CallbackInvalidSettings = strCallback; // Save callback reference m_bEnableCallbackInvalidSettings = true; // Enable callback }; // This API function sets the callback handler for informing about // transmission errors. this.API_SetCallbackTransmitError = function (strCallback) { m_CallbackTransmitError = strCallback; // Save callback reference m_bEnableCallbackTransmitError = true; // Enable callback }; // This API function gets server items from the BAOS device. this.API_GetServerItem = function (strStartItem, strItemCount) { var strUrl; // URL if((IsIpAddrValid(m_strIpAddr)) && (IsNumber(strStartItem)) && (IsNumber(strItemCount))) // If we have a valid IP address and valid parameters { strUrl = "http://" + m_strIpAddr + "/baos/GetServerItem?ItemStart=" + strStartItem + "&ItemCount=" + strItemCount; // Prepare URL for GetServerItem http.request ({ url: strUrl, method: "GET", async: true, success: function(rsp){ OnObjSrvRespRcvd(rsp.data); }, error: function(rsp){ console.log("Error description: " + JSON.stringify(rsp, null, 4)); OnObjSrvError(); }, complete: function(rsp){console.log("GetServerItem done");} }); } else // Else: We have invalid settings { OnObjSrvInvalidSettings(); // Call error handler (invalid settings) } }; // This API function gets datapoint descriptions from the BAOS device. this.API_GetDatapointDescription = function (strDatapointStart, strDatapointCount) { var strUrl; // URL if((IsIpAddrValid(m_strIpAddr)) && (IsNumber(strDatapointStart)) && (IsNumber(strDatapointCount))) // If we have a valid IP address and valid parameters { strUrl = "http://" + m_strIpAddr + "/baos/GetDatapointDescription?DatapointStart=" + strDatapointStart + "&DatapointCount="+ strDatapointCount; // Prepare URL for GetDatapointDescription http.request ({ url: strUrl, method: "GET", async: true, success: function(rsp){ OnObjSrvRespRcvd(rsp.data); }, error: function(rsp){ console.log("Error DP description: " + JSON.stringify(rsp, null, 4)); OnObjSrvError(rsp.data); }, complete: function(rsp){console.log("API_GetDatapointDescription done");} }); } else // Else: We have invalid settings { OnObjSrvInvalidSettings(); // Call error handler (invalid settings) } }; // This API function gets datapoint description strings from the // BAOS device. this.API_GetDescriptionString = function (strDatapointStart, strDatapointCount) { var strUrl; // URL if((IsIpAddrValid(m_strIpAddr)) && (IsNumber(strDatapointStart)) && (IsNumber(strDatapointCount))) // If we have a valid IP address and valid parameters { strUrl = "http://" + m_strIpAddr + "/baos/GetDescriptionString?DatapointStart=" + strDatapointStart + "&DatapointCount=" + strDatapointCount; // Prepare URL for GetDescriptionString http.request ({ url: strUrl, method: "GET", async: true, success: function(rsp){ OnObjSrvRespRcvd(rsp.data); }, error: function(rsp){ console.log("Error get description string: " + JSON.stringify(rsp, null, 4)); OnObjSrvError(rsp.data); }, complete: function(rsp){console.log("API_GetDescriptionString done");} }); } else // Else: We have invalid settings { OnObjSrvInvalidSettings(); // Call error handler (invalid settings) } }; // This API function gets datapoint values from the BAOS device. this.API_GetDatapointValue = function (strDatapointStart, strDatapointCount, strFormat) { var strUrl; // URL if((IsIpAddrValid(m_strIpAddr)) && (IsNumber(strDatapointStart)) && (IsNumber(strDatapointCount))) // If we have a valid IP address and valid parameters { strUrl = "http://" + m_strIpAddr + "/baos/GetDatapointValue?DatapointStart=" + strDatapointStart + "&DatapointCount=" + strDatapointCount + "&Format=" + strFormat; // Prepare URL for GetDatapointValule http.request ({ url: strUrl, method: "GET", async: true, success: function(rsp){ OnObjSrvRespRcvd(rsp.data); }, error: function(rsp){ console.log("Error Get DP Value: " + JSON.stringify(rsp, null, 4)); OnObjSrvError(rsp.data); }, complete: function(rsp){console.log("API_GetDatapointValue done");} }); } else // Else: We have invalid settings { OnObjSrvInvalidSettings(); // Call error handler (invalid settings) } }; // This API function sets datapoint values to the BAOS device. this.API_SetDatapointValue = function (strDatapoint, strFormat, strCommand, strLength, strValue1, strValue2, strValue3, strValue4, strValue5, strValue6) { var strUrl; // URL var strValue; // Value //strLength = ConvertLength(strLength); // Convert given length into integer value if((IsIpAddrValid(m_strIpAddr)) && (IsNumber(strDatapoint)) && (IsNumber(strLength))) // If we have a valid IP address and valid parameters { switch(strFormat) // Switch due to format { ////////////////////////////////////////// // Formats with 1 value parameter: // ////////////////////////////////////////// case "RAW": // Case: Format raw case "DPT1": // Case: Format DPT 1 case "DPT4": // Case: Format DPT 4 case "DPT5": // Case: Format DPT 5 case "DPT6": // Case: Format DPT 6 case "DPT7": // Case: Format DPT 7 case "DPT8": // Case: Format DPT 8 case "DPT9": // Case: Format DPT 9 case "DPT12": // Case: Format DPT 12 case "DPT13": // Case: Format DPT 13 case "DPT14": // Case: Format DPT 14 case "DPT16": // Case: Format DPT 16 strValue = "&Value=" + strValue1; // Assemble value break; case "DPT17": // Case: Format DPT 17 strValue = "&Scene=" + strValue1; // Assemble value break; ////////////////////////////////////////// // Formats with 2 value parameters: // ////////////////////////////////////////// case "DPT2": // Case: Format DPT 2 strValue = "&Control=" + strValue1 + "&Value=" + strValue2; // Assemble value break; case "DPT3": // Case: Format DPT 3 strValue = "&Control=" + strValue1 + "&StepCode=" + strValue2; // Assemble value break; case "DPT18": // Case: Format DPT 18 strValue = "&Control=" + strValue1 + "&Scene=" + strValue2; // Assemble value break; ////////////////////////////////////////// // Formats with 3 value parameters: // ////////////////////////////////////////// case "DPT11": // Case: Format DPT 11 strValue = "&Day=" + strValue1 + "&Month=" + strValue2 + "&Year=" + strValue3; // Assemble value break; ////////////////////////////////////////// // Formats with 4 value parameters: // ////////////////////////////////////////// case "DPT10": // Case: Format DPT 10 strValue = "&Weekday=" + strValue1 + "&Hour=" + strValue2 + "&Minute=" + strValue3 + "&Second=" + strValue4; // Assemble value break; ////////////////////////////////////////// // Formats with 6 value parameters: // ////////////////////////////////////////// case "DPT15": // Case: Format DPT 15 strValue = "&Code=" + strValue1 + "&Index=" + strValue2 + "&FlagError=" + strValue3 + "&FlagPermission=" + strValue4 + "&FlagReadDirection=" + strValue5 + "&FlagEncrypted=" + strValue6; // Assemble value break; // Not yet suopported by BAOS 771: case "DPT19": // Case: Format DPT 19 case "DPT20": // Case: Format DPT 20 break; } strUrl = "http://" + m_strIpAddr + "/baos/SetDatapointValue?Datapoint=" + strDatapoint + "&Format=" + strFormat + "&Command=" + strCommand + "&Length=" + strLength + strValue; console.log("Anfrage: " + strUrl); http.request ({ url: strUrl, method: "GET", async: true, success: function(rsp){ OnObjSrvRespRcvd(rsp.data); }, error: function(rsp){ console.log("Error Set DP Value: " + JSON.stringify(rsp, null, 4)); OnObjSrvError(rsp.data); }, complete: function(rsp){console.log("API_SetDatapointValue done");} }); } else // Else: We have invalid settings { OnObjSrvInvalidSettings(); // Call error handler (invalid settings) } }; // This API function gets parameter bytes (set by the ETS parameter // dialogue) from the BAOS device. this.API_GetParamByte = function (strByteStart, strByteCount) { var strUrl; // URL if((IsIpAddrValid(m_strIpAddr)) && (IsNumber(strByteStart)) && (IsNumber(strByteCount))) // If we have a valid IP address and valid parameters { strUrl = "http://" + m_strIpAddr + "/baos/GetParameterByte?ByteStart=" + strByteStart + "&ByteCount=" + strByteCount; // Prepare URL for GetParamByte http.request ({ url: strUrl, method: "GET", async: true, success: function(rsp){ OnObjSrvRespRcvd(rsp.data); }, error: function(rsp){ console.log("Error get param byte: " + JSON.stringify(rsp, null, 4)); OnObjSrvError(rsp.data); }, complete: function(rsp){console.log("API_GetParamByte done");} }); } else // Else: We have invalid settings { OnObjSrvInvalidSettings(); // Call error handler (invalid settings) } }; // This API function checks the validity of a IP address. this.API_IsIpAddrValid = function (strIpAddrToValidate) { return IsIpAddrValid(strIpAddrToValidate); }; // This API function starts the indication listener. this.API_StartIndicationListener = function (strFormat) { var strUrl; // URL m_IndFormat = strFormat; // Set format used for indications if(m_IndicationSession != 0) // If we already have an indication session { return false; // Return error } if(IsIpAddrValid(m_strIpAddr)) // If we have a valid IP address { strUrl = "http://" + m_strIpAddr + "/baos/StartIndicationSession"; // Prepare URL for StartIndicationSession http.request ({ url: strUrl, method: "GET", async: true, success: function(rsp){ if((rsp.data.Result == true) && (rsp.data.Service == "StartIndicationSession")) // If indication session could be started { m_IndicationSession = rsp.data.Data.SessionId; // Save indication session ID GetIndication(); // Start getting indications } }, error: function(rsp){ console.log("Error starting indication listener: " + JSON.stringify(rsp, null, 4)); OnObjSrvError(rsp.data); }, complete: function(rsp){console.log("API_StartIndicationListener done");} }); return true; // Return success } else // Else: We have invalid settings { OnObjSrvInvalidSettings(); // Call error handler (invalid settings) } return false; // Return error }; // This API function stops the indication listener. this.API_StopIndicationListener = function () { var strUrl; // URL if(m_IndicationSession == 0) // If no indication session is available { return false; // Return error } if(IsIpAddrValid(m_strIpAddr)) // If we have a valid IP address { strUrl = "http://" + m_strIpAddr + "/baos/StopIndicationSession?SessionId=" + m_IndicationSession; // Prepare URL for StopIndicationSession http.request ({ url: strUrl, method: "GET", async: true, success: function(rsp){ if((rsp.data.Result == true) && (rsp.data.Service == "StopIndicationSession")) // If indication session could be stopped { m_IndicationSession = 0; // Delete indication session } }, error: function(rsp){ console.log("Error stop indication listener: " + JSON.stringify(rsp, null, 4)); OnObjSrvError(rsp.data); }, complete: function(rsp){console.log("API_StopIndicationListener done");} }); return true; // Return success } else // Else: We have invalid settings { OnObjSrvInvalidSettings(); // Call error handler (invalid settings) } return false; // Return error }; ////////////////////////////////////////////////// // Private functions // ////////////////////////////////////////////////// // This private function checks the validity of a IP address. function IsIpAddrValid(strIpAddrToValidate) { var strIpAddrSub = new Array(); // Array used for IP address strIpAddrSub = strIpAddrToValidate.split("."); // Split IP address into sub parts if(strIpAddrSub.length != 4) // If IP address has not 4 sub parts { return false; // Return error } for (nIndex = 0; nIndex 255)) // If it is out of range { return false; // Return error } } return true; // Return success } // This private function checks, if a string contains only numbers. function IsNumber(strToValidate) { for(position=0; position "9")) // If character is not a number { return false; // Return: Character is not a number } } return true; // Return: Character is a number } // This private function does a length conversion. function ConvertLength(strLength) { switch(strLength) // Switch due to length { case "1 Bit": // Case: 1 bit case "2 Bit": // Case: 2 bit case "4 Bit": // Case: 4 bit case "1 Byte": // Case: 1 byte return 1; // Return: Length 1 byte case "2 Byte": // Case: 2 byte return 2; // Return: Length 2 byte case "3 Byte": // Case: 3 byte return 3; // Return: Length 3 byte case "4 Byte": // Case: 4 byte return 4; // Return: Length 4 byte case "6 Byte": // Case: 6 byte return 6; // Return: Length 6 byte case "8 Byte": // Case: 8 byte return 8; // Return: Length 8 byte case "14 Byte": // Case: 14 byte return 14; // Return: Length 14 byte default: return 0; // Return error } } // This private function creates and evaluates a "get indication" for the BAOS. function GetIndication() { var strUrl; // URL if(m_IndicationSession == 0) // If no indication session is available { return false; // Return error } if(IsIpAddrValid(m_strIpAddr)) // If we have a valid IP address { strUrl = "http://" + m_strIpAddr + "/baos/GetIndication?SessionId=" + m_IndicationSession + "&Timeout=20" + "&Format=" + m_IndFormat; // Prepare URL for GetIndication http.request ({ url: strUrl, method: "GET", async: true, success: function(rsp){ if(rsp.data.Result == false) // If we got an error { if(rsp.data.Error == "IndTimeout") // If we have a timeout { if(m_IndicationSession != 0) // If indication session is valid { GetIndication(); // Try to get new indication } } } if(rsp.data.Service == "GetIndication") // If we have a GetIndication response { if(rsp.data.Result == true) // If we got an indication { OnIndicationUpdate(rsp.data); // Indication update if(m_IndicationSession != 0) // If indication session is valid { GetIndication(); // Try to get new indication } } } }, error: function(rsp){ console.log("Error get indication: " + JSON.stringify(rsp, null, 4)); OnObjSrvError(rsp.data); }, complete: function(rsp){console.log("GetIndication done");} }); return true; // Return success } else // Else: We have invalid settings { OnObjSrvInvalidSettings(); // Call error handler (invalid settings) } return false; // Return error } // This private function calls the response handler, if available. function OnObjSrvRespRcvd(dataObjSrvResp) { if(m_bEnableCallbackRespRcvd == true) // If callback is enabled { m_CallbackRespRcvd(dataObjSrvResp); // Call callback function } } // This private function calls the indication handler, if available. function OnIndicationUpdate(dataIndUpdate) { if(m_bEnableCallbackIndicationUpdate == true) // If callback is enabled { m_CallbackIndicationUpdate(dataIndUpdate); // Call callback function } } // This private function calls the handler for invalid settings, // if available. function OnObjSrvInvalidSettings() { if(m_bEnableCallbackInvalidSettings == true) // If callback is enabled { m_CallbackInvalidSettings(); // Call callback function } } // This private function calls the handler for errors, if available. function OnObjSrvError(rsp) { if(m_bEnableCallbackTransmitError == true) // If callback is enabled { m_CallbackTransmitError(rsp); // Call callback function } } } ================================================ FILE: lib/IntelHex2bin.js ================================================ // Converts IntelHex into binary 128 kB file function IntelHex2bin(hex) { function hex2dec(digits) { return parseInt(digits, 16); } var BIN_SIZE = 128*1024; var DATA = 0, END_OF_FILE = 1, EXT_SEGMENT_ADDR = 2, START_SEGMENT_ADDR = 3, EXT_LINEAR_ADDR = 4, START_LINEAR_ADDR = 5; var EMPTY_VALUE = 0xFF; var LINE_MIN_LENGTH = 11; var hexLines = hex.split("\n"); var bin = new Uint8Array(BIN_SIZE); // Init binary array for (var i = 0; i < BIN_SIZE; i++) { bin[i] = EMPTY_VALUE; } // Start processing var bankOffset = 0, maxOffset = 0; var end = false; for (var i = 0; i < hexLines.length && !end; i++) { var line = hexLines[i]; line = line.replace(/\r/g, ""); if (line.length < LINE_MIN_LENGTH) throw("Hex file contains too short line " + i + " - " + line); if (line.substr(0, 1) !== ":") throw("Line " + i + " should start with : - " + line); var dataLength = hex2dec(line.substr(1, 2)); if (dataLength > 32) throw("Data length should not exceed 32 bytes (line " + i + ") - " + line); if (line.length !== LINE_MIN_LENGTH + dataLength * 2) throw("Data length do not match line length: " + i + " - " + line); var crc = 0; for (var n = 0; n < 1 + 2 + 1 + dataLength + 1; n++) { crc += hex2dec(line.substr(1 + n * 2, 2)); } crc &= 0xff; if (crc !== 0) throw("CRC do not match in line " + i + " - " + line); var offset = hex2dec(line.substr(3, 4)); var recType = hex2dec(line.substr(7, 2)); switch (recType) { case DATA: for (var j = 0; j < dataLength; j++) { if (bankOffset + offset + j > BIN_SIZE) throw("Address " + (bankOffset + offset + j) + " is beyond " + BIN_SIZE + " - line " + i + " - " + line); bin[bankOffset + offset + j] = hex2dec(line.substr(LINE_MIN_LENGTH - 2 + 2*j, 2)); if (maxOffset < bankOffset + offset + j) { maxOffset = bankOffset + offset + j; } } break; case END_OF_FILE: end = true; break; case EXT_LINEAR_ADDR: bankOffset = hex2dec(line.substr(LINE_MIN_LENGTH - 2, 4))*256*256; break; } } var bin_size = maxOffset + 1; // last addr + 1 var bin_sliced = new Uint8Array(bin_size); for (var k = 0; k < bin_size; k++) { bin_sliced[k] = bin[k]; } return bin_sliced; } ================================================ FILE: lib/LimitedArray.js ================================================ /* * This class creates an object that can be called to save data stored in object using a saver function. * The object will be saved only periodically and will be stripped to a limit and filtered using filter function. * Object can grow in memory not bigger than to limit + period - then it will be stripped and saved. */ function LimitedArray(initial, saver, period, limit, filter) { this.object = Array.isArray(initial) ? initial : []; this.saver = saver; // function this.period = period; // integer this.limit = limit; // integer this.filter = filter; // function this.counter = this.period; } LimitedArray.prototype.save = function(force) { if (force) this.counter = 1; if (this.counter-- === 0) { this.counter = this.period; // first filter the object if (this.filter) { this.object = this.object.filter(this.filter); } // then limit the object size if (this.limit) { this.object = this.object.slice(-this.limit); } // save the object this.saver(this.object); } }; LimitedArray.prototype.get = function() { return this.object; }; LimitedArray.prototype.set = function(value) { this.object = value; this.save(); }; LimitedArray.prototype.push = function(value) { this.object.push(value); this.save(); }; LimitedArray.prototype.clear = function() { this.object = []; this.save(true); // force save }; LimitedArray.prototype.finalize = function() { this.save(true); // force save // release this.saver = null; this.filter = null; }; ================================================ FILE: lib/base64.js ================================================ // Public domain version of Base64 encoder/decoder found somewhere in the Internet var Base64 = { _keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t}, decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(f>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t}, _utf8_encode:function(e){var t="";for(var n=0;n127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t}, _utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t} }; ================================================ FILE: lib/eventemitter2.js ================================================ /*! EventEmitter2 https://github.com/hij1nx/EventEmitter2 Copyright (c) 2013 hij1nx Licensed under the MIT license.*/ ;!function(undefined) { var isArray = Array.isArray ? Array.isArray : function _isArray(obj) { return Object.prototype.toString.call(obj) === "[object Array]"; }; var defaultMaxListeners = 10; function init() { this._events = {}; if (this._conf) { configure.call(this, this._conf); } } function configure(conf) { if (conf) { this._conf = conf; conf.delimiter && (this.delimiter = conf.delimiter); conf.maxListeners && (this._events.maxListeners = conf.maxListeners); conf.wildcard && (this.wildcard = conf.wildcard); conf.newListener && (this.newListener = conf.newListener); if (this.wildcard) { this.listenerTree = {}; } } } function EventEmitter(conf) { this._events = {}; this.newListener = false; configure.call(this, conf); } // // Attention, function return type now is array, always ! // It has zero elements if no any matches found and one or more // elements (leafs) if there are matches // function searchListenerTree(handlers, type, tree, i) { if (!tree) { return []; } var listeners=[], leaf, len, branch, xTree, xxTree, isolatedBranch, endReached, typeLength = type.length, currentType = type[i], nextType = type[i+1]; if (i === typeLength && tree._listeners) { // // If at the end of the event(s) list and the tree has listeners // invoke those listeners. // if (typeof tree._listeners === 'function') { handlers && handlers.push(tree._listeners); return [tree]; } else { for (leaf = 0, len = tree._listeners.length; leaf < len; leaf++) { handlers && handlers.push(tree._listeners[leaf]); } return [tree]; } } if ((currentType === '*' || currentType === '**') || tree[currentType]) { // // If the event emitted is '*' at this part // or there is a concrete match at this patch // if (currentType === '*') { for (branch in tree) { if (branch !== '_listeners' && tree.hasOwnProperty(branch)) { listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+1)); } } return listeners; } else if(currentType === '**') { endReached = (i+1 === typeLength || (i+2 === typeLength && nextType === '*')); if(endReached && tree._listeners) { // The next element has a _listeners, add it to the handlers. listeners = listeners.concat(searchListenerTree(handlers, type, tree, typeLength)); } for (branch in tree) { if (branch !== '_listeners' && tree.hasOwnProperty(branch)) { if(branch === '*' || branch === '**') { if(tree[branch]._listeners && !endReached) { listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], typeLength)); } listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i)); } else if(branch === nextType) { listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i+2)); } else { // No match on this one, shift into the tree but not in the type array. listeners = listeners.concat(searchListenerTree(handlers, type, tree[branch], i)); } } } return listeners; } listeners = listeners.concat(searchListenerTree(handlers, type, tree[currentType], i+1)); } xTree = tree['*']; if (xTree) { // // If the listener tree will allow any match for this part, // then recursively explore all branches of the tree // searchListenerTree(handlers, type, xTree, i+1); } xxTree = tree['**']; if(xxTree) { if(i < typeLength) { if(xxTree._listeners) { // If we have a listener on a '**', it will catch all, so add its handler. searchListenerTree(handlers, type, xxTree, typeLength); } // Build arrays of matching next branches and others. for(branch in xxTree) { if(branch !== '_listeners' && xxTree.hasOwnProperty(branch)) { if(branch === nextType) { // We know the next element will match, so jump twice. searchListenerTree(handlers, type, xxTree[branch], i+2); } else if(branch === currentType) { // Current node matches, move into the tree. searchListenerTree(handlers, type, xxTree[branch], i+1); } else { isolatedBranch = {}; isolatedBranch[branch] = xxTree[branch]; searchListenerTree(handlers, type, { '**': isolatedBranch }, i+1); } } } } else if(xxTree._listeners) { // We have reached the end and still on a '**' searchListenerTree(handlers, type, xxTree, typeLength); } else if(xxTree['*'] && xxTree['*']._listeners) { searchListenerTree(handlers, type, xxTree['*'], typeLength); } } return listeners; } function growListenerTree(type, listener) { type = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); // // Looks for two consecutive '**', if so, don't add the event at all. // for(var i = 0, len = type.length; i+1 < len; i++) { if(type[i] === '**' && type[i+1] === '**') { return; } } var tree = this.listenerTree; var name = type.shift(); while (name) { if (!tree[name]) { tree[name] = {}; } tree = tree[name]; if (type.length === 0) { if (!tree._listeners) { tree._listeners = listener; } else if(typeof tree._listeners === 'function') { tree._listeners = [tree._listeners, listener]; } else if (isArray(tree._listeners)) { tree._listeners.push(listener); if (!tree._listeners.warned) { var m = defaultMaxListeners; if (typeof this._events.maxListeners !== 'undefined') { m = this._events.maxListeners; } if (m > 0 && tree._listeners.length > m) { tree._listeners.warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', tree._listeners.length); console.trace(); } } } return true; } name = type.shift(); } return true; } // By default EventEmitters will print a warning if more than // 10 listeners are added to it. This is a useful default which // helps finding memory leaks. // // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.delimiter = '.'; EventEmitter.prototype.setMaxListeners = function(n) { this._events || init.call(this); this._events.maxListeners = n; if (!this._conf) this._conf = {}; this._conf.maxListeners = n; }; EventEmitter.prototype.event = ''; EventEmitter.prototype.once = function(event, fn) { this.many(event, 1, fn); return this; }; EventEmitter.prototype.many = function(event, ttl, fn) { var self = this; if (typeof fn !== 'function') { throw new Error('many only accepts instances of Function'); } function listener() { if (--ttl === 0) { self.off(event, listener); } fn.apply(this, arguments); } listener._origin = fn; this.on(event, listener); return self; }; EventEmitter.prototype.emit = function() { this._events || init.call(this); var type = arguments[0]; if (type === 'newListener' && !this.newListener) { if (!this._events.newListener) { return false; } } // Loop through the *_all* functions and invoke them. if (this._all) { var l = arguments.length; var args = new Array(l - 1); for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; for (i = 0, l = this._all.length; i < l; i++) { this.event = type; this._all[i].apply(this, args); } } // If there is no 'error' event listener then throw. if (type === 'error') { if (!this._all && !this._events.error && !(this.wildcard && this.listenerTree.error)) { if (arguments[1] instanceof Error) { throw arguments[1]; // Unhandled 'error' event } else { throw new Error("Uncaught, unspecified 'error' event."); } return false; } } var handler; if(this.wildcard) { handler = []; var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); searchListenerTree.call(this, handler, ns, this.listenerTree, 0); } else { handler = this._events[type]; } if (typeof handler === 'function') { this.event = type; if (arguments.length === 1) { handler.call(this); } else if (arguments.length > 1) switch (arguments.length) { case 2: handler.call(this, arguments[1]); break; case 3: handler.call(this, arguments[1], arguments[2]); break; // slower default: var l = arguments.length; var args = new Array(l - 1); for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; handler.apply(this, args); } return true; } else if (handler) { var l = arguments.length; var args = new Array(l - 1); for (var i = 1; i < l; i++) args[i - 1] = arguments[i]; var listeners = handler.slice(); for (var i = 0, l = listeners.length; i < l; i++) { this.event = type; listeners[i].apply(this, args); } return (listeners.length > 0) || !!this._all; } else { return !!this._all; } }; EventEmitter.prototype.on = function(type, listener) { if (typeof type === 'function') { this.onAny(type); return this; } if (typeof listener !== 'function') { throw new Error('on only accepts instances of Function'); } this._events || init.call(this); // To avoid recursion in the case that type == "newListeners"! Before // adding it to the listeners, first emit "newListeners". this.emit('newListener', type, listener); if(this.wildcard) { growListenerTree.call(this, type, listener); return this; } if (!this._events[type]) { // Optimize the case of one listener. Don't need the extra array object. this._events[type] = listener; } else if(typeof this._events[type] === 'function') { // Adding the second element, need to change to array. this._events[type] = [this._events[type], listener]; } else if (isArray(this._events[type])) { // If we've already got an array, just append. this._events[type].push(listener); // Check for listener leak if (!this._events[type].warned) { var m = defaultMaxListeners; if (typeof this._events.maxListeners !== 'undefined') { m = this._events.maxListeners; } if (m > 0 && this._events[type].length > m) { this._events[type].warned = true; console.error('(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length); console.trace(); } } } return this; }; EventEmitter.prototype.onAny = function(fn) { if (typeof fn !== 'function') { throw new Error('onAny only accepts instances of Function'); } if(!this._all) { this._all = []; } // Add the function to the event listener collection. this._all.push(fn); return this; }; EventEmitter.prototype.addListener = EventEmitter.prototype.on; EventEmitter.prototype.off = function(type, listener) { if (typeof listener !== 'function') { throw new Error('removeListener only takes instances of Function'); } var handlers,leafs=[]; if(this.wildcard) { var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0); } else { // does not use listeners(), so no side effect of creating _events[type] if (!this._events[type]) return this; handlers = this._events[type]; leafs.push({_listeners:handlers}); } for (var iLeaf=0; iLeaf 0) { recursivelyGarbageCollect(root[key]); } if (Object.keys(obj).length === 0) { delete root[key]; } } } recursivelyGarbageCollect(this.listenerTree); return this; }; EventEmitter.prototype.offAny = function(fn) { var i = 0, l = 0, fns; if (fn && this._all && this._all.length > 0) { fns = this._all; for(i = 0, l = fns.length; i < l; i++) { if(fn === fns[i]) { fns.splice(i, 1); return this; } } } else { this._all = []; } return this; }; EventEmitter.prototype.removeListener = EventEmitter.prototype.off; EventEmitter.prototype.removeAllListeners = function(type) { if (arguments.length === 0) { !this._events || init.call(this); return this; } if(this.wildcard) { var ns = typeof type === 'string' ? type.split(this.delimiter) : type.slice(); var leafs = searchListenerTree.call(this, null, ns, this.listenerTree, 0); for (var iLeaf=0; iLeaf=0&&t<=m}}function J(n){return function(r){return null==r?void 0:r[n]}}var G=J("byteLength"),H=K(G),Q=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;var X=c?function(n){return h?h(n)&&!q(n):H(n)&&Q.test(a.call(n))}:C(!1),Y=J("length");function Z(n,r){r=function(n){for(var r={},t=n.length,e=0;e":">",'"':""","'":"'","`":"`"},$n=zn(Ln),Cn=zn(_n(Ln)),Kn=tn.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g},Jn=/(.)^/,Gn={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},Hn=/\\|'|\r|\n|\u2028|\u2029/g;function Qn(n){return"\\"+Gn[n]}var Xn=/^\s*(\w|\$)+\s*$/;var Yn=0;function Zn(n,r,t,e,u){if(!(e instanceof r))return n.apply(t,u);var o=Mn(n.prototype),i=n.apply(o,u);return _(i)?i:o}var nr=j((function(n,r){var t=nr.placeholder,e=function(){for(var u=0,o=r.length,i=Array(o),a=0;a1)er(a,r-1,t,e),u=e.length;else for(var f=0,c=a.length;f0&&(t=r.apply(this,arguments)),n<=1&&(r=null),t}}var cr=nr(fr,2);function lr(n,r,t){r=Pn(r,t);for(var e,u=nn(n),o=0,i=u.length;o0?0:u-1;o>=0&&o0?a=o>=0?o:Math.max(o+f,a):f=o>=0?Math.min(o+1,f):o+f+1;else if(t&&o&&f)return e[o=t(e,u)]===u?o:-1;if(u!=u)return(o=r(i.call(e,a,f),$))>=0?o+a:-1;for(o=n>0?a:f-1;o>=0&&o0?0:i-1;for(u||(e=r[o?o[a]:a],a+=n);a>=0&&a=3;return r(n,Rn(t,u,4),e,o)}}var wr=_r(1),Ar=_r(-1);function xr(n,r,t){var e=[];return r=Pn(r,t),mr(n,(function(n,t,u){r(n,t,u)&&e.push(n)})),e}function Sr(n,r,t){r=Pn(r,t);for(var e=!tr(n)&&nn(n),u=(e||n).length,o=0;o=0}var Er=j((function(n,r,t){var e,u;return D(r)?u=r:(r=Bn(r),e=r.slice(0,-1),r=r[r.length-1]),jr(n,(function(n){var o=u;if(!o){if(e&&e.length&&(n=Nn(n,e)),null==n)return;o=n[r]}return null==o?o:o.apply(n,t)}))}));function Br(n,r){return jr(n,Dn(r))}function Nr(n,r,t){var e,u,o=-1/0,i=-1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;ao&&(o=e);else r=Pn(r,t),mr(n,(function(n,t,e){((u=r(n,t,e))>i||u===-1/0&&o===-1/0)&&(o=n,i=u)}));return o}var Ir=/[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;function Tr(n){return n?U(n)?i.call(n):S(n)?n.match(Ir):tr(n)?jr(n,Tn):jn(n):[]}function kr(n,r,t){if(null==r||t)return tr(n)||(n=jn(n)),n[Un(n.length-1)];var e=Tr(n),u=Y(e);r=Math.max(Math.min(r,u),0);for(var o=u-1,i=0;i1&&(e=Rn(e,r[1])),r=an(n)):(e=qr,r=er(r,!1,!1),n=Object(n));for(var u=0,o=r.length;u1&&(t=r[1])):(r=jr(er(r,!1,!1),String),e=function(n,t){return!Mr(r,t)}),Ur(n,e,t)}));function zr(n,r,t){return i.call(n,0,Math.max(0,n.length-(null==r||t?1:r)))}function Lr(n,r,t){return null==n||n.length<1?null==r||t?void 0:[]:null==r||t?n[0]:zr(n,n.length-r)}function $r(n,r,t){return i.call(n,null==r||t?1:r)}var Cr=j((function(n,r){return r=er(r,!0,!0),xr(n,(function(n){return!Mr(r,n)}))})),Kr=j((function(n,r){return Cr(n,r)}));function Jr(n,r,t,e){A(r)||(e=t,t=r,r=!1),null!=t&&(t=Pn(t,e));for(var u=[],o=[],i=0,a=Y(n);ir?(e&&(clearTimeout(e),e=null),a=c,i=n.apply(u,o),e||(u=o=null)):e||!1===t.trailing||(e=setTimeout(f,l)),i};return c.cancel=function(){clearTimeout(e),a=0,e=u=o=null},c},debounce:function(n,r,t){var e,u,o,i,a,f=function(){var c=Wn()-u;r>c?e=setTimeout(f,r-c):(e=null,t||(i=n.apply(a,o)),e||(o=a=null))},c=j((function(c){return a=this,o=c,u=Wn(),e||(e=setTimeout(f,r),t&&(i=n.apply(a,o))),i}));return c.cancel=function(){clearTimeout(e),e=o=a=null},c},wrap:function(n,r){return nr(r,n)},negate:ar,compose:function(){var n=arguments,r=n.length-1;return function(){for(var t=r,e=n[r].apply(this,arguments);t--;)e=n[t].call(this,e);return e}},after:function(n,r){return function(){if(--n<1)return r.apply(this,arguments)}},before:fr,once:cr,findKey:lr,findIndex:pr,findLastIndex:vr,sortedIndex:hr,indexOf:dr,lastIndexOf:gr,find:br,detect:br,findWhere:function(n,r){return br(n,kn(r))},each:mr,forEach:mr,map:jr,collect:jr,reduce:wr,foldl:wr,inject:wr,reduceRight:Ar,foldr:Ar,filter:xr,select:xr,reject:function(n,r,t){return xr(n,ar(Pn(r)),t)},every:Sr,all:Sr,some:Or,any:Or,contains:Mr,includes:Mr,include:Mr,invoke:Er,pluck:Br,where:function(n,r){return xr(n,kn(r))},max:Nr,min:function(n,r,t){var e,u,o=1/0,i=1/0;if(null==r||"number"==typeof r&&"object"!=typeof n[0]&&null!=n)for(var a=0,f=(n=tr(n)?n:jn(n)).length;ae||void 0===t)return 1;if(t= 0 && length <= MAX_ARRAY_INDEX; }; // Collection Functions // -------------------- // The cornerstone, an `each` implementation, aka `forEach`. // Handles raw objects in addition to array-likes. Treats all // sparse array-likes as if they were dense. _.each = _.forEach = function(obj, iteratee, context) { iteratee = optimizeCb(iteratee, context); var i, length; if (isArrayLike(obj)) { for (i = 0, length = obj.length; i < length; i++) { iteratee(obj[i], i, obj); } } else { var keys = _.keys(obj); for (i = 0, length = keys.length; i < length; i++) { iteratee(obj[keys[i]], keys[i], obj); } } return obj; }; // Return the results of applying the iteratee to each element. _.map = _.collect = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; }; // Create a reducing function iterating left or right. function createReduce(dir) { // Optimized iterator function as using arguments.length // in the main function will deoptimize the, see #1991. function iterator(obj, iteratee, memo, keys, index, length) { for (; index >= 0 && index < length; index += dir) { var currentKey = keys ? keys[index] : index; memo = iteratee(memo, obj[currentKey], currentKey, obj); } return memo; } return function(obj, iteratee, memo, context) { iteratee = optimizeCb(iteratee, context, 4); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, index = dir > 0 ? 0 : length - 1; // Determine the initial value if none is provided. if (arguments.length < 3) { memo = obj[keys ? keys[index] : index]; index += dir; } return iterator(obj, iteratee, memo, keys, index, length); }; } // **Reduce** builds up a single result from a list of values, aka `inject`, // or `foldl`. _.reduce = _.foldl = _.inject = createReduce(1); // The right-associative version of reduce, also known as `foldr`. _.reduceRight = _.foldr = createReduce(-1); // Return the first value which passes a truth test. Aliased as `detect`. _.find = _.detect = function(obj, predicate, context) { var key; if (isArrayLike(obj)) { key = _.findIndex(obj, predicate, context); } else { key = _.findKey(obj, predicate, context); } if (key !== void 0 && key !== -1) return obj[key]; }; // Return all the elements that pass a truth test. // Aliased as `select`. _.filter = _.select = function(obj, predicate, context) { var results = []; predicate = cb(predicate, context); _.each(obj, function(value, index, list) { if (predicate(value, index, list)) results.push(value); }); return results; }; // Return all the elements for which a truth test fails. _.reject = function(obj, predicate, context) { return _.filter(obj, _.negate(cb(predicate)), context); }; // Determine whether all of the elements match a truth test. // Aliased as `all`. _.every = _.all = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (!predicate(obj[currentKey], currentKey, obj)) return false; } return true; }; // Determine if at least one element in the object matches a truth test. // Aliased as `any`. _.some = _.any = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length; for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; if (predicate(obj[currentKey], currentKey, obj)) return true; } return false; }; // Determine if the array or object contains a given item (using `===`). // Aliased as `includes` and `include`. _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { if (!isArrayLike(obj)) obj = _.values(obj); if (typeof fromIndex != 'number' || guard) fromIndex = 0; return _.indexOf(obj, item, fromIndex) >= 0; }; // Invoke a method (with arguments) on every item in a collection. _.invoke = function(obj, method) { var args = slice.call(arguments, 2); var isFunc = _.isFunction(method); return _.map(obj, function(value) { var func = isFunc ? method : value[method]; return func == null ? func : func.apply(value, args); }); }; // Convenience version of a common use case of `map`: fetching a property. _.pluck = function(obj, key) { return _.map(obj, _.property(key)); }; // Convenience version of a common use case of `filter`: selecting only objects // containing specific `key:value` pairs. _.where = function(obj, attrs) { return _.filter(obj, _.matcher(attrs)); }; // Convenience version of a common use case of `find`: getting the first object // containing specific `key:value` pairs. _.findWhere = function(obj, attrs) { return _.find(obj, _.matcher(attrs)); }; // Return the maximum element (or element-based computation). _.max = function(obj, iteratee, context) { var result = -Infinity, lastComputed = -Infinity, value, computed; if (iteratee == null && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value > result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed > lastComputed || computed === -Infinity && result === -Infinity) { result = value; lastComputed = computed; } }); } return result; }; // Return the minimum element (or element-based computation). _.min = function(obj, iteratee, context) { var result = Infinity, lastComputed = Infinity, value, computed; if (iteratee == null && obj != null) { obj = isArrayLike(obj) ? obj : _.values(obj); for (var i = 0, length = obj.length; i < length; i++) { value = obj[i]; if (value < result) { result = value; } } } else { iteratee = cb(iteratee, context); _.each(obj, function(value, index, list) { computed = iteratee(value, index, list); if (computed < lastComputed || computed === Infinity && result === Infinity) { result = value; lastComputed = computed; } }); } return result; }; // Shuffle a collection, using the modern version of the // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). _.shuffle = function(obj) { var set = isArrayLike(obj) ? obj : _.values(obj); var length = set.length; var shuffled = Array(length); for (var index = 0, rand; index < length; index++) { rand = _.random(0, index); if (rand !== index) shuffled[index] = shuffled[rand]; shuffled[rand] = set[index]; } return shuffled; }; // Sample **n** random values from a collection. // If **n** is not specified, returns a single random element. // The internal `guard` argument allows it to work with `map`. _.sample = function(obj, n, guard) { if (n == null || guard) { if (!isArrayLike(obj)) obj = _.values(obj); return obj[_.random(obj.length - 1)]; } return _.shuffle(obj).slice(0, Math.max(0, n)); }; // Sort the object's values by a criterion produced by an iteratee. _.sortBy = function(obj, iteratee, context) { iteratee = cb(iteratee, context); return _.pluck(_.map(obj, function(value, index, list) { return { value: value, index: index, criteria: iteratee(value, index, list) }; }).sort(function(left, right) { var a = left.criteria; var b = right.criteria; if (a !== b) { if (a > b || a === void 0) return 1; if (a < b || b === void 0) return -1; } return left.index - right.index; }), 'value'); }; // An internal function used for aggregate "group by" operations. var group = function(behavior) { return function(obj, iteratee, context) { var result = {}; iteratee = cb(iteratee, context); _.each(obj, function(value, index) { var key = iteratee(value, index, obj); behavior(result, value, key); }); return result; }; }; // Groups the object's values by a criterion. Pass either a string attribute // to group by, or a function that returns the criterion. _.groupBy = group(function(result, value, key) { if (_.has(result, key)) result[key].push(value); else result[key] = [value]; }); // Indexes the object's values by a criterion, similar to `groupBy`, but for // when you know that your index values will be unique. _.indexBy = group(function(result, value, key) { result[key] = value; }); // Counts instances of an object that group by a certain criterion. Pass // either a string attribute to count by, or a function that returns the // criterion. _.countBy = group(function(result, value, key) { if (_.has(result, key)) result[key]++; else result[key] = 1; }); // Safely create a real, live array from anything iterable. _.toArray = function(obj) { if (!obj) return []; if (_.isArray(obj)) return slice.call(obj); if (isArrayLike(obj)) return _.map(obj, _.identity); return _.values(obj); }; // Return the number of elements in an object. _.size = function(obj) { if (obj == null) return 0; return isArrayLike(obj) ? obj.length : _.keys(obj).length; }; // Split a collection into two arrays: one whose elements all satisfy the given // predicate, and one whose elements all do not satisfy the predicate. _.partition = function(obj, predicate, context) { predicate = cb(predicate, context); var pass = [], fail = []; _.each(obj, function(value, key, obj) { (predicate(value, key, obj) ? pass : fail).push(value); }); return [pass, fail]; }; // Array Functions // --------------- // Get the first element of an array. Passing **n** will return the first N // values in the array. Aliased as `head` and `take`. The **guard** check // allows it to work with `_.map`. _.first = _.head = _.take = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[0]; return _.initial(array, array.length - n); }; // Returns everything but the last entry of the array. Especially useful on // the arguments object. Passing **n** will return all the values in // the array, excluding the last N. _.initial = function(array, n, guard) { return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); }; // Get the last element of an array. Passing **n** will return the last N // values in the array. _.last = function(array, n, guard) { if (array == null) return void 0; if (n == null || guard) return array[array.length - 1]; return _.rest(array, Math.max(0, array.length - n)); }; // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. // Especially useful on the arguments object. Passing an **n** will return // the rest N values in the array. _.rest = _.tail = _.drop = function(array, n, guard) { return slice.call(array, n == null || guard ? 1 : n); }; // Trim out all falsy values from an array. _.compact = function(array) { return _.filter(array, _.identity); }; // Internal implementation of a recursive `flatten` function. var flatten = function(input, shallow, strict, startIndex) { var output = [], idx = 0; for (var i = startIndex || 0, length = getLength(input); i < length; i++) { var value = input[i]; if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) { //flatten current level of array or arguments object if (!shallow) value = flatten(value, shallow, strict); var j = 0, len = value.length; output.length += len; while (j < len) { output[idx++] = value[j++]; } } else if (!strict) { output[idx++] = value; } } return output; }; // Flatten out an array, either recursively (by default), or just one level. _.flatten = function(array, shallow) { return flatten(array, shallow, false); }; // Return a version of the array that does not contain the specified value(s). _.without = function(array) { return _.difference(array, slice.call(arguments, 1)); }; // Produce a duplicate-free version of the array. If the array has already // been sorted, you have the option of using a faster algorithm. // Aliased as `unique`. _.uniq = _.unique = function(array, isSorted, iteratee, context) { if (!_.isBoolean(isSorted)) { context = iteratee; iteratee = isSorted; isSorted = false; } if (iteratee != null) iteratee = cb(iteratee, context); var result = []; var seen = []; for (var i = 0, length = getLength(array); i < length; i++) { var value = array[i], computed = iteratee ? iteratee(value, i, array) : value; if (isSorted) { if (!i || seen !== computed) result.push(value); seen = computed; } else if (iteratee) { if (!_.contains(seen, computed)) { seen.push(computed); result.push(value); } } else if (!_.contains(result, value)) { result.push(value); } } return result; }; // Produce an array that contains the union: each distinct element from all of // the passed-in arrays. _.union = function() { return _.uniq(flatten(arguments, true, true)); }; // Produce an array that contains every item shared between all the // passed-in arrays. _.intersection = function(array) { var result = []; var argsLength = arguments.length; for (var i = 0, length = getLength(array); i < length; i++) { var item = array[i]; if (_.contains(result, item)) continue; for (var j = 1; j < argsLength; j++) { if (!_.contains(arguments[j], item)) break; } if (j === argsLength) result.push(item); } return result; }; // Take the difference between one array and a number of other arrays. // Only the elements present in just the first array will remain. _.difference = function(array) { var rest = flatten(arguments, true, true, 1); return _.filter(array, function(value){ return !_.contains(rest, value); }); }; // Zip together multiple lists into a single array -- elements that share // an index go together. _.zip = function() { return _.unzip(arguments); }; // Complement of _.zip. Unzip accepts an array of arrays and groups // each array's elements on shared indices _.unzip = function(array) { var length = array && _.max(array, getLength).length || 0; var result = Array(length); for (var index = 0; index < length; index++) { result[index] = _.pluck(array, index); } return result; }; // Converts lists into objects. Pass either a single array of `[key, value]` // pairs, or two parallel arrays of the same length -- one of keys, and one of // the corresponding values. _.object = function(list, values) { var result = {}; for (var i = 0, length = getLength(list); i < length; i++) { if (values) { result[list[i]] = values[i]; } else { result[list[i][0]] = list[i][1]; } } return result; }; // Generator function to create the findIndex and findLastIndex functions function createPredicateIndexFinder(dir) { return function(array, predicate, context) { predicate = cb(predicate, context); var length = getLength(array); var index = dir > 0 ? 0 : length - 1; for (; index >= 0 && index < length; index += dir) { if (predicate(array[index], index, array)) return index; } return -1; }; } // Returns the first index on an array-like that passes a predicate test _.findIndex = createPredicateIndexFinder(1); _.findLastIndex = createPredicateIndexFinder(-1); // Use a comparator function to figure out the smallest index at which // an object should be inserted so as to maintain order. Uses binary search. _.sortedIndex = function(array, obj, iteratee, context) { iteratee = cb(iteratee, context, 1); var value = iteratee(obj); var low = 0, high = getLength(array); while (low < high) { var mid = Math.floor((low + high) / 2); if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; } return low; }; // Generator function to create the indexOf and lastIndexOf functions function createIndexFinder(dir, predicateFind, sortedIndex) { return function(array, item, idx) { var i = 0, length = getLength(array); if (typeof idx == 'number') { if (dir > 0) { i = idx >= 0 ? idx : Math.max(idx + length, i); } else { length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; } } else if (sortedIndex && idx && length) { idx = sortedIndex(array, item); return array[idx] === item ? idx : -1; } if (item !== item) { idx = predicateFind(slice.call(array, i, length), _.isNaN); return idx >= 0 ? idx + i : -1; } for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { if (array[idx] === item) return idx; } return -1; }; } // Return the position of the first occurrence of an item in an array, // or -1 if the item is not included in the array. // If the array is large and already in sort order, pass `true` // for **isSorted** to use binary search. _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex); _.lastIndexOf = createIndexFinder(-1, _.findLastIndex); // Generate an integer Array containing an arithmetic progression. A port of // the native Python `range()` function. See // [the Python documentation](http://docs.python.org/library/functions.html#range). _.range = function(start, stop, step) { if (stop == null) { stop = start || 0; start = 0; } step = step || 1; var length = Math.max(Math.ceil((stop - start) / step), 0); var range = Array(length); for (var idx = 0; idx < length; idx++, start += step) { range[idx] = start; } return range; }; // Function (ahem) Functions // ------------------ // Determines whether to execute a function as a constructor // or a normal function with the provided arguments var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) { if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); var self = baseCreate(sourceFunc.prototype); var result = sourceFunc.apply(self, args); if (_.isObject(result)) return result; return self; }; // Create a function bound to a given object (assigning `this`, and arguments, // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if // available. _.bind = function(func, context) { if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function'); var args = slice.call(arguments, 2); var bound = function() { return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; return bound; }; // Partially apply a function by creating a version that has had some of its // arguments pre-filled, without changing its dynamic `this` context. _ acts // as a placeholder, allowing any combination of arguments to be pre-filled. _.partial = function(func) { var boundArgs = slice.call(arguments, 1); var bound = function() { var position = 0, length = boundArgs.length; var args = Array(length); for (var i = 0; i < length; i++) { args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i]; } while (position < arguments.length) args.push(arguments[position++]); return executeBound(func, bound, this, this, args); }; return bound; }; // Bind a number of an object's methods to that object. Remaining arguments // are the method names to be bound. Useful for ensuring that all callbacks // defined on an object belong to it. _.bindAll = function(obj) { var i, length = arguments.length, key; if (length <= 1) throw new Error('bindAll must be passed function names'); for (i = 1; i < length; i++) { key = arguments[i]; obj[key] = _.bind(obj[key], obj); } return obj; }; // Memoize an expensive function by storing its results. _.memoize = function(func, hasher) { var memoize = function(key) { var cache = memoize.cache; var address = '' + (hasher ? hasher.apply(this, arguments) : key); if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; memoize.cache = {}; return memoize; }; // Delays a function for the given number of milliseconds, and then calls // it with the arguments supplied. _.delay = function(func, wait) { var args = slice.call(arguments, 2); return setTimeout(function(){ return func.apply(null, args); }, wait); }; // Defers a function, scheduling it to run after the current call stack has // cleared. _.defer = _.partial(_.delay, _, 1); // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run // as much as it can, without ever going more than once per `wait` duration; // but if you'd like to disable the execution on the leading edge, pass // `{leading: false}`. To disable execution on the trailing edge, ditto. _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = _.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; }; // Returns a function, that, as long as it continues to be invoked, will not // be triggered. The function will be called after it stops being called for // N milliseconds. If `immediate` is passed, trigger the function on the // leading edge, instead of the trailing. _.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { var last = _.now() - timestamp; if (last < wait && last >= 0) { timeout = setTimeout(later, wait - last); } else { timeout = null; if (!immediate) { result = func.apply(context, args); if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; timestamp = _.now(); var callNow = immediate && !timeout; if (!timeout) timeout = setTimeout(later, wait); if (callNow) { result = func.apply(context, args); context = args = null; } return result; }; }; // Returns the first function passed as an argument to the second, // allowing you to adjust arguments, run code before and after, and // conditionally execute the original function. _.wrap = function(func, wrapper) { return _.partial(wrapper, func); }; // Returns a negated version of the passed-in predicate. _.negate = function(predicate) { return function() { return !predicate.apply(this, arguments); }; }; // Returns a function that is the composition of a list of functions, each // consuming the return value of the function that follows. _.compose = function() { var args = arguments; var start = args.length - 1; return function() { var i = start; var result = args[start].apply(this, arguments); while (i--) result = args[i].call(this, result); return result; }; }; // Returns a function that will only be executed on and after the Nth call. _.after = function(times, func) { return function() { if (--times < 1) { return func.apply(this, arguments); } }; }; // Returns a function that will only be executed up to (but not including) the Nth call. _.before = function(times, func) { var memo; return function() { if (--times > 0) { memo = func.apply(this, arguments); } if (times <= 1) func = null; return memo; }; }; // Returns a function that will be executed at most one time, no matter how // often you call it. Useful for lazy initialization. _.once = _.partial(_.before, 2); // Object Functions // ---------------- // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; function collectNonEnumProps(obj, keys) { var nonEnumIdx = nonEnumerableProps.length; var constructor = obj.constructor; var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto; // Constructor is a special case. var prop = 'constructor'; if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop); while (nonEnumIdx--) { prop = nonEnumerableProps[nonEnumIdx]; if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) { keys.push(prop); } } } // Retrieve the names of an object's own properties. // Delegates to **ECMAScript 5**'s native `Object.keys` _.keys = function(obj) { if (!_.isObject(obj)) return []; if (nativeKeys) return nativeKeys(obj); var keys = []; for (var key in obj) if (_.has(obj, key)) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; // Retrieve all the property names of an object. _.allKeys = function(obj) { if (!_.isObject(obj)) return []; var keys = []; for (var key in obj) keys.push(key); // Ahem, IE < 9. if (hasEnumBug) collectNonEnumProps(obj, keys); return keys; }; // Retrieve the values of an object's properties. _.values = function(obj) { var keys = _.keys(obj); var length = keys.length; var values = Array(length); for (var i = 0; i < length; i++) { values[i] = obj[keys[i]]; } return values; }; // Returns the results of applying the iteratee to each element of the object // In contrast to _.map it returns an object _.mapObject = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = _.keys(obj), length = keys.length, results = {}, currentKey; for (var index = 0; index < length; index++) { currentKey = keys[index]; results[currentKey] = iteratee(obj[currentKey], currentKey, obj); } return results; }; // Convert an object into a list of `[key, value]` pairs. _.pairs = function(obj) { var keys = _.keys(obj); var length = keys.length; var pairs = Array(length); for (var i = 0; i < length; i++) { pairs[i] = [keys[i], obj[keys[i]]]; } return pairs; }; // Invert the keys and values of an object. The values must be serializable. _.invert = function(obj) { var result = {}; var keys = _.keys(obj); for (var i = 0, length = keys.length; i < length; i++) { result[obj[keys[i]]] = keys[i]; } return result; }; // Return a sorted list of the function names available on the object. // Aliased as `methods` _.functions = _.methods = function(obj) { var names = []; for (var key in obj) { if (_.isFunction(obj[key])) names.push(key); } return names.sort(); }; // Extend a given object with all the properties in passed-in object(s). _.extend = createAssigner(_.allKeys); // Assigns a given object with all the own properties in the passed-in object(s) // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) _.extendOwn = _.assign = createAssigner(_.keys); // Returns the first key on an object that passes a predicate test _.findKey = function(obj, predicate, context) { predicate = cb(predicate, context); var keys = _.keys(obj), key; for (var i = 0, length = keys.length; i < length; i++) { key = keys[i]; if (predicate(obj[key], key, obj)) return key; } }; // Return a copy of the object only containing the whitelisted properties. _.pick = function(object, oiteratee, context) { var result = {}, obj = object, iteratee, keys; if (obj == null) return result; if (_.isFunction(oiteratee)) { keys = _.allKeys(obj); iteratee = optimizeCb(oiteratee, context); } else { keys = flatten(arguments, false, false, 1); iteratee = function(value, key, obj) { return key in obj; }; obj = Object(obj); } for (var i = 0, length = keys.length; i < length; i++) { var key = keys[i]; var value = obj[key]; if (iteratee(value, key, obj)) result[key] = value; } return result; }; // Return a copy of the object without the blacklisted properties. _.omit = function(obj, iteratee, context) { if (_.isFunction(iteratee)) { iteratee = _.negate(iteratee); } else { var keys = _.map(flatten(arguments, false, false, 1), String); iteratee = function(value, key) { return !_.contains(keys, key); }; } return _.pick(obj, iteratee, context); }; // Fill in a given object with default properties. _.defaults = createAssigner(_.allKeys, true); // Creates an object that inherits from the given prototype object. // If additional properties are provided then they will be added to the // created object. _.create = function(prototype, props) { var result = baseCreate(prototype); if (props) _.extendOwn(result, props); return result; }; // Create a (shallow-cloned) duplicate of an object. _.clone = function(obj) { if (!_.isObject(obj)) return obj; return _.isArray(obj) ? obj.slice() : _.extend({}, obj); }; // Invokes interceptor with the obj, and then returns obj. // The primary purpose of this method is to "tap into" a method chain, in // order to perform operations on intermediate results within the chain. _.tap = function(obj, interceptor) { interceptor(obj); return obj; }; // Returns whether an object has a given set of `key:value` pairs. _.isMatch = function(object, attrs) { var keys = _.keys(attrs), length = keys.length; if (object == null) return !length; var obj = Object(object); for (var i = 0; i < length; i++) { var key = keys[i]; if (attrs[key] !== obj[key] || !(key in obj)) return false; } return true; }; // Internal recursive comparison function for `isEqual`. var eq = function(a, b, aStack, bStack) { // Identical objects are equal. `0 === -0`, but they aren't identical. // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). if (a === b) return a !== 0 || 1 / a === 1 / b; // A strict comparison is necessary because `null == undefined`. if (a == null || b == null) return a === b; // Unwrap any wrapped objects. if (a instanceof _) a = a._wrapped; if (b instanceof _) b = b._wrapped; // Compare `[[Class]]` names. var className = toString.call(a); if (className !== toString.call(b)) return false; switch (className) { // Strings, numbers, regular expressions, dates, and booleans are compared by value. case '[object RegExp]': // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') case '[object String]': // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is // equivalent to `new String("5")`. return '' + a === '' + b; case '[object Number]': // `NaN`s are equivalent, but non-reflexive. // Object(NaN) is equivalent to NaN if (+a !== +a) return +b !== +b; // An `egal` comparison is performed for other numeric values. return +a === 0 ? 1 / +a === 1 / b : +a === +b; case '[object Date]': case '[object Boolean]': // Coerce dates and booleans to numeric primitive values. Dates are compared by their // millisecond representations. Note that invalid dates with millisecond representations // of `NaN` are not equivalent. return +a === +b; } var areArrays = className === '[object Array]'; if (!areArrays) { if (typeof a != 'object' || typeof b != 'object') return false; // Objects with different constructors are not equivalent, but `Object`s or `Array`s // from different frames are. var aCtor = a.constructor, bCtor = b.constructor; if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor && _.isFunction(bCtor) && bCtor instanceof bCtor) && ('constructor' in a && 'constructor' in b)) { return false; } } // Assume equality for cyclic structures. The algorithm for detecting cyclic // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. // Initializing stack of traversed objects. // It's done here since we only need them for objects and arrays comparison. aStack = aStack || []; bStack = bStack || []; var length = aStack.length; while (length--) { // Linear search. Performance is inversely proportional to the number of // unique nested structures. if (aStack[length] === a) return bStack[length] === b; } // Add the first object to the stack of traversed objects. aStack.push(a); bStack.push(b); // Recursively compare objects and arrays. if (areArrays) { // Compare array lengths to determine if a deep comparison is necessary. length = a.length; if (length !== b.length) return false; // Deep compare the contents, ignoring non-numeric properties. while (length--) { if (!eq(a[length], b[length], aStack, bStack)) return false; } } else { // Deep compare objects. var keys = _.keys(a), key; length = keys.length; // Ensure that both objects contain the same number of properties before comparing deep equality. if (_.keys(b).length !== length) return false; while (length--) { // Deep compare each member key = keys[length]; if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false; } } // Remove the first object from the stack of traversed objects. aStack.pop(); bStack.pop(); return true; }; // Perform a deep comparison to check if two objects are equal. _.isEqual = function(a, b) { return eq(a, b); }; // Is a given array, string, or object empty? // An "empty" object has no enumerable own-properties. _.isEmpty = function(obj) { if (obj == null) return true; if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0; return _.keys(obj).length === 0; }; // Is a given value a DOM element? _.isElement = function(obj) { return !!(obj && obj.nodeType === 1); }; // Is a given value an array? // Delegates to ECMA5's native Array.isArray _.isArray = nativeIsArray || function(obj) { return toString.call(obj) === '[object Array]'; }; // Is a given variable an object? _.isObject = function(obj) { var type = typeof obj; return type === 'function' || type === 'object' && !!obj; }; // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError. _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) { _['is' + name] = function(obj) { return toString.call(obj) === '[object ' + name + ']'; }; }); // Define a fallback version of the method in browsers (ahem, IE < 9), where // there isn't any inspectable "Arguments" type. if (!_.isArguments(arguments)) { _.isArguments = function(obj) { return _.has(obj, 'callee'); }; } // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8, // IE 11 (#1621), and in Safari 8 (#1929). if (typeof /./ != 'function' && typeof Int8Array != 'object') { _.isFunction = function(obj) { return typeof obj == 'function' || false; }; } // Is a given object a finite number? _.isFinite = function(obj) { return isFinite(obj) && !isNaN(parseFloat(obj)); }; // Is the given value `NaN`? (NaN is the only number which does not equal itself). _.isNaN = function(obj) { return _.isNumber(obj) && obj !== +obj; }; // Is a given value a boolean? _.isBoolean = function(obj) { return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; }; // Is a given value equal to null? _.isNull = function(obj) { return obj === null; }; // Is a given variable undefined? _.isUndefined = function(obj) { return obj === void 0; }; // Shortcut function for checking if an object has a given property directly // on itself (in other words, not on a prototype). _.has = function(obj, key) { return obj != null && hasOwnProperty.call(obj, key); }; // Utility Functions // ----------------- // Run Underscore.js in *noConflict* mode, returning the `_` variable to its // previous owner. Returns a reference to the Underscore object. _.noConflict = function() { root._ = previousUnderscore; return this; }; // Keep the identity function around for default iteratees. _.identity = function(value) { return value; }; // Predicate-generating functions. Often useful outside of Underscore. _.constant = function(value) { return function() { return value; }; }; _.noop = function(){}; _.property = property; // Generates a function for a given object that returns a given property. _.propertyOf = function(obj) { return obj == null ? function(){} : function(key) { return obj[key]; }; }; // Returns a predicate for checking whether an object has a given set of // `key:value` pairs. _.matcher = _.matches = function(attrs) { attrs = _.extendOwn({}, attrs); return function(obj) { return _.isMatch(obj, attrs); }; }; // Run a function **n** times. _.times = function(n, iteratee, context) { var accum = Array(Math.max(0, n)); iteratee = optimizeCb(iteratee, context, 1); for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }; // Return a random integer between min and max (inclusive). _.random = function(min, max) { if (max == null) { max = min; min = 0; } return min + Math.floor(Math.random() * (max - min + 1)); }; // A (possibly faster) way to get the current timestamp as an integer. _.now = Date.now || function() { return new Date().getTime(); }; // List of HTML entities for escaping. var escapeMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; var unescapeMap = _.invert(escapeMap); // Functions for escaping and unescaping strings to/from HTML interpolation. var createEscaper = function(map) { var escaper = function(match) { return map[match]; }; // Regexes for identifying a key that needs to be escaped var source = '(?:' + _.keys(map).join('|') + ')'; var testRegexp = RegExp(source); var replaceRegexp = RegExp(source, 'g'); return function(string) { string = string == null ? '' : '' + string; return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; }; }; _.escape = createEscaper(escapeMap); _.unescape = createEscaper(unescapeMap); // If the value of the named `property` is a function then invoke it with the // `object` as context; otherwise, return it. _.result = function(object, property, fallback) { var value = object == null ? void 0 : object[property]; if (value === void 0) { value = fallback; } return _.isFunction(value) ? value.call(object) : value; }; // Generate a unique integer id (unique within the entire client session). // Useful for temporary DOM ids. var idCounter = 0; _.uniqueId = function(prefix) { var id = ++idCounter + ''; return prefix ? prefix + id : id; }; // By default, Underscore uses ERB-style template delimiters, change the // following template settings to use alternative delimiters. _.templateSettings = { evaluate : /<%([\s\S]+?)%>/g, interpolate : /<%=([\s\S]+?)%>/g, escape : /<%-([\s\S]+?)%>/g }; // When customizing `templateSettings`, if you don't want to define an // interpolation, evaluation or escaping regex, we need one that is // guaranteed not to match. var noMatch = /(.)^/; // Certain characters need to be escaped so that they can be put into a // string literal. var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\u2028': 'u2028', '\u2029': 'u2029' }; var escaper = /\\|'|\r|\n|\u2028|\u2029/g; var escapeChar = function(match) { return '\\' + escapes[match]; }; // JavaScript micro-templating, similar to John Resig's implementation. // Underscore templating handles arbitrary delimiters, preserves whitespace, // and correctly escapes quotes within interpolated code. // NB: `oldSettings` only exists for backwards compatibility. _.template = function(text, settings, oldSettings) { if (!settings && oldSettings) settings = oldSettings; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. var matcher = RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source ].join('|') + '|$', 'g'); // Compile the template source, escaping string literals appropriately. var index = 0; var source = "__p+='"; text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { source += text.slice(index, offset).replace(escaper, escapeChar); index = offset + match.length; if (escape) { source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; } else if (interpolate) { source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; } else if (evaluate) { source += "';\n" + evaluate + "\n__p+='"; } // Adobe VMs need the match returned to produce the correct offest. return match; }); source += "';\n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + source + 'return __p;\n'; try { var render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } var template = function(data) { return render.call(this, data, _); }; // Provide the compiled source as a convenience for precompilation. var argument = settings.variable || 'obj'; template.source = 'function(' + argument + '){\n' + source + '}'; return template; }; // Add a "chain" function. Start chaining a wrapped Underscore object. _.chain = function(obj) { var instance = _(obj); instance._chain = true; return instance; }; // OOP // --------------- // If Underscore is called as a function, it returns a wrapped object that // can be used OO-style. This wrapper holds altered versions of all the // underscore functions. Wrapped objects may be chained. // Helper function to continue chaining intermediate results. var result = function(instance, obj) { return instance._chain ? _(obj).chain() : obj; }; // Add your own custom functions to the Underscore object. _.mixin = function(obj) { _.each(_.functions(obj), function(name) { var func = _[name] = obj[name]; _.prototype[name] = function() { var args = [this._wrapped]; push.apply(args, arguments); return result(this, func.apply(_, args)); }; }); }; // Add all of the Underscore functions to the wrapper object. _.mixin(_); // Add all mutator Array functions to the wrapper. _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { var obj = this._wrapped; method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; return result(this, obj); }; }); // Add all accessor Array functions to the wrapper. _.each(['concat', 'join', 'slice'], function(name) { var method = ArrayProto[name]; _.prototype[name] = function() { return result(this, method.apply(this._wrapped, arguments)); }; }); // Extracts the result from a wrapped and chained object. _.prototype.value = function() { return this._wrapped; }; // Provide unwrapping proxy for some methods used in engine operations // such as arithmetic and JSON stringification. _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; _.prototype.toString = function() { return '' + this._wrapped; }; // AMD registration happens at the end for compatibility with AMD loaders // that may not enforce next-turn semantics on modules. Even though general // practice for AMD registration is to be anonymous, underscore registers // as a named module because, like jQuery, it is a base library that is // popular enough to be bundled in a third party lib, but not be part of // an AMD load request. Those cases could generate an error when an // anonymous define() is called outside of a loader request. if (typeof define === 'function' && define.amd) { define('underscore', [], function() { return _; }); } }.call(this)); ================================================ FILE: main.js ================================================ /*** Z-Way Home Automation Engine main executable ***************************** Version: 0.1.2 (c) ZWave.Me, 2013 ------------------------------------------------------------------------------- Author: Gregory Sitnin Description: This is a main executable script which is loaded and executed solely by the z-way-server daemon. The very magic starts here. ******************************************************************************/ //--- Define global variables and helpers var window = global = this; // load utilities executeFile("Utils.js"); // do transition script to adopt old versions to new executeFile("updateBackendConfig.js"); // overload saveObject to allow backup/restore of all JSON files in storage __saveObject = saveObject; __saveObjectTimer = {}; __storageContent = loadObject("__storageContent") || []; // check against storage if listed files really exists __storageContent = __storageContent.filter(function(name) { try { return !!loadObject(name); } catch (e) { console.log("Error loading " + name + " from storage: JSON file corrupted: " + e.toString()); return false; } }); // Saves object as name, adds name to the storageContent. // Defer save: immediate can be undefined (to use default seconds), true or 0 (save now), integer (to defer by N seconds) saveObject = function(name, object, immediate) { // defer tests var deferTime; if (immediate === true || immediate === 0) { deferTime = 0; } else if (immediate > 0) { deferTime = immediate; } else { deferTime = 5 * 60; } deferTime *= 1000; if (!__saveObjectTimer[name]) { __saveObjectTimer[name] = { timer: null, lastSave: 0 }; } if (__saveObjectTimer[name].timer) { clearTimeout(__saveObjectTimer[name].timer); __saveObjectTimer[name].timer = null; } deferTime -= Date.now() - __saveObjectTimer[name].lastSave; if (deferTime > 0) { // restart the time with the new object and remaining time __saveObjectTimer[name].object = object; __saveObjectTimer[name].saver = function() { __saveObjectTimer[name].timer = null; __saveObjectTimer[name].saver = null; saveObject(name, __saveObjectTimer[name].object, true); // call itself }; __saveObjectTimer[name].timer = setTimeout(__saveObjectTimer[name].saver, deferTime); return; // defer save } __saveObjectTimer[name].lastSave = Date.now(); // 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); }; function __saveObjectsNow() { for (var name in __saveObjectTimer) { if (__saveObjectTimer[name].timer != null && __saveObjectTimer[name].saver != null) { clearTimeout(__saveObjectTimer[name].timer); __saveObjectTimer[name].timer = null; __saveObjectTimer[name].saver(); __saveObjectTimer[name].saver = null; } } } //--- Load configuration var config, files, templates, schemas, modules, namespaces; try { config = loadObject("config.json"); files = loadObject("files.json") || {}; schemas = loadObject("schemas.json") || []; } catch (ex) { console.log("Error loading config.json or files.json:", ex.message); } if (!config && config === null) { console.log("Can't read config.json or it's broken."); console.log("ZAutomation engine not started."); } else { config.libPath = "lib"; config.classesPath = "classes"; config.resourcesPath = "res"; // add modules_categories config.modules_categories = fs.loadJSON("modulesCategories.json"); //--- Load 3d-party dependencies executeFile(config.libPath + "/eventemitter2.js"); // executeFile(config.libPath + "/underscore.js"); executeFile(config.libPath + "/underscore-umd-min.js"); executeFile(config.libPath + "/papaparse.min.js"); executeFile(config.libPath + "/zlib_and_gzip.min.js"); // TODO Test! executeFile(config.libPath + "/BAOS_API_2011_01_29_001.js"); executeFile(config.libPath + "/IntelHex2bin.js"); executeFile(config.libPath + "/base64.js"); executeFile(config.libPath + "/LimitedArray.js"); //--- Load Automation subsystem classes executeFile(config.classesPath + "/VirtualDevice.js"); executeFile(config.classesPath + "/DevicesCollection.js"); executeFile(config.classesPath + "/AutomationController.js"); executeFile(config.classesPath + "/AuthController.js"); executeFile(config.classesPath + "/AutomationModule.js"); executeFile("Webserver.js"); executeFile("WebserverRequestRouter.js"); executeFile("ZAutomationAPIProvider.js"); executeFile("StorageProvider.js"); //--- Instantiate Automation Controller var api = null, storage = null, controller = new AutomationController(config), now = new Date(); controller.on('core.init', function () { console.log('Starting ZWay Automation webserver'); // first start up config.controller.first_start_up = !config.controller.first_start_up? now : config.controller.first_start_up; // count server restarts config.controller.count_of_reconnects = config.controller.count_of_reconnects? parseInt(config.controller.count_of_reconnects, 10) + 1 : 1; // generate the uuid if (!config.controller.uuid) config.controller.uuid = crypto.guid(); if (!config.controller.serial) config.controller.serial = config.controller.uuid; }); controller.on('core.start', function () { console.log('ZWay Automation started'); }); controller.on('core.stop', function () { __saveObjectsNow() console.log('ZWay Automation stopped'); }); controller.on('core.error', function (err) { console.log("--- ERROR:", err.message); controller.addNotification("error", err.message, "core", "core controller"); }); //--- main controller.init(); controller.start(); //--- Init JS handler for Admin JS = function() { return { status: 400, body: "Bad JS request" }; }; ws.allowExternalAccess("JS", controller.auth.ROLE.ADMIN); JS.Run = function(url) { // skip trailing slash url = url.substring(1); try { var r = eval(url); if (typeof r === "function") { // special case for functions, otherwise they show up as JSON 'null' return { status: 204, headers: { "Content-Type": "application/json" } } } return { status: 200, headers: { "Content-Type": "application/json" }, body: JSON.stringify(r) }; } catch (e) { console.log("Error handling request " + url + ": " + e.toString()); return { status: 500, body: e.toString() }; } }; ws.allowExternalAccess("JS.Run", controller.auth.ROLE.ADMIN); // overwrite console.debug function console.debug = function(){ var arr = []; // use JS API /JS/Run/controller.debug=true / false to activate debug output if (controller.debug){ for (var key in arguments) { var arg = ''; //format objects automatically if (typeof arguments[key] === 'object' && !!arguments[key]) { arg = JSON.stringify(arguments[key], null, 4); } else { arg = arguments[key]; } arr.push(arg); } debugPrint(arr); } }; } ================================================ FILE: modules/Alexa/index.js ================================================ /*** 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 = Date.now(); 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) }; } ================================================ FILE: modules/Alexa/lang/de.json ================================================ { "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." } ================================================ FILE: modules/Alexa/lang/en.json ================================================ { "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 #'." } ================================================ FILE: modules/Alexa/module.json ================================================ { "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": {} } ================================================ FILE: modules/Alexa/patchnotes.txt ================================================ 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 ================================================ FILE: modules/AutoLock/index.js ================================================ /*** AutoLock Z-Way Home Automation module ************************************* Version: 1.2 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Yurkin Vitaliy Description: Door/Window Sensor automatically closes lock after delay when door is closed ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function AutoLock (id, controller) { // Call superconstructor first (AutomationModule) AutoLock.super_.call(this, id, controller); // Create instance variables this.timer = null; }; inherits(AutoLock, AutomationModule); _module = AutoLock; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- AutoLock.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) AutoLock.super_.prototype.init.call(this, config); var self = this; // handler - it is a name of your function this.handler = function (vDev) { var nowSensorStatus = vDev.get("metrics:level"); // Clear delay if door opened if (nowSensorStatus === "on") { clearTimeout(self.timer); self.timer = null; } // Close lock if sensor false if (nowSensorStatus === "off") { // Start Timer self.timer = setTimeout(function () { 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); } }; // Setup metric update event listener this.controller.devices.on(this.config.BinarySensor, 'change:metrics:level', this.handler); }; AutoLock.prototype.stop = function () { AutoLock.super_.prototype.stop.call(this); if (this.timer) clearTimeout(this.timer); this.controller.devices.off(this.config.BinarySensor, 'change:metrics:level', this.handler); }; ================================================ FILE: modules/AutoLock/lang/de.json ================================================ { "m_title":"Automatisches Schließen einer Tür", "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:
  • Wählen Sie den Türsensor, der die Türstellung erkennt.
  • Wählen Sie das zu steuernde Türschloss.
  • Wählen Sie den Zeitverzug der Verriegelung nach erkannter Türschließung
", "l_dw_sensor":"Tür-/Fenstersensor", "l_doorlock":"Türverriegelung", "l_delay_sec":"Verzögerung in Sekunden", "rl_doNotSendCommand":"Don't send Lock command if doorlock already closed" } ================================================ FILE: modules/AutoLock/lang/en.json ================================================ { "m_title":"Automated Locking of Door", "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:
  • Select the door sensor detecting the position of the door
  • Select the door lock to be controlled
  • Select a delay between the detected closed door and the looking
", "l_dw_sensor":"Door/Window Sensor", "l_doorlock":"Door Lock", "l_delay_sec":"Delay in seconds", "rl_doNotSendCommand":"Don't send Lock command if doorlock already closed" } ================================================ FILE: modules/AutoLock/lang/ru.json ================================================ { "m_title":"Авто закрытие", "m_descr":"Датчик открытия двери автоматически закрывает замок при закрытии двери.", "l_dw_sensor":"Датчик открытия двери", "l_doorlock":"Замок", "l_delay_sec":"Задержка на закрытие в секундах", "rl_doNotSendCommand":"Не отправлять команду закрытия, если замок уже закрыт" } ================================================ FILE: modules/AutoLock/module.json ================================================ { "dependencies": [], "singleton": false, "category": "device_enhancements", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "AutoLock", "version": "1.2", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "BinarySensor": null, "DoorLock": null, "delay": 5, "doNotSendCommand": false }, "schema": { "type": "object", "properties": { "BinarySensor": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorBinary:deviceId", "required": true }, "DoorLock": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId,namespaces:devices_switchBinary:deviceId", "required": true }, "delay": { "type": "integer", "minimum": 1, "required": true }, "doNotSendCommand": { "type": "boolean", "required": true } }, "required": true }, "options": { "fields": { "BinarySensor": { "label": "__l_dw_sensor__", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorBinary:deviceName,namespaces:devices_switchBinary:deviceName" }, "DoorLock": { "label": "__l_doorlock__", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName" }, "delay": { "label": "__l_delay_sec__", "type": "integer" }, "doNotSendCommand": { "type": "checkbox", "rightLabel": "__rl_doNotSendCommand__" } } } } ================================================ FILE: modules/AutoOff/index.js ================================================ /*** AutoOff Z-Way Home Automation module ************************************* Version: 1.0.3 (c) Z-Wave.Me, 2019 ----------------------------------------------------------------------------- Author: Gregory Sitnin , Poltorak Serguei , Vitaliy Yurkin Description: This module listens given VirtualDevice (which MUSt be typed as switch) level metric update events and switches off device after configured timeout if this device has been switched on. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function AutoOff (id, controller) { // Call superconstructor first (AutomationModule) AutoOff.super_.call(this, id, controller); // Create instance variables this.timer = null; }; inherits(AutoOff, AutomationModule); _module = AutoOff; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- AutoOff.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) AutoOff.super_.prototype.init.call(this, config); // Remember "this" for detached callbacks (such as event listener callbacks) var self = this; this.handler = function (vDev) { var value = vDev.get("metrics:level"); if ("on" === value || "open" === value || (parseInt(value) && value > 0) ) { // Device reported "on", set (or reset) timer to new timeout if (self.timer && self.config.ignoreUpdates) { // We ignore updates and do not restart the timer, keeping the old one running return; } if (self.timer) { // Timer is set, so we destroy it clearTimeout(self.timer); self.timer = null; } // Notice: self.config.timeout set in seconds self.timer = setTimeout(function () { // Timeout fired, so we send "off" command to the virtual device // (every switch device should handle it) if (vDev.get("deviceType") === "doorlock") { vDev.performCommand("close"); } else { vDev.performCommand("off"); } // And clearing out this.timer variable self.timer = null; }, self.config.timeout*1000); } else { // Turned off if (self.timer) { // Timer is set, so we destroy it clearTimeout(self.timer); self.timer = null; } } }; // Setup metric update event listener this.controller.devices.on(this.config.device, 'change:metrics:level', this.handler); }; AutoOff.prototype.stop = function () { AutoOff.super_.prototype.stop.call(this); if (this.timer){ clearTimeout(this.timer); } this.controller.devices.off(this.config.device, 'change:metrics:level', this.handler); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- // This module doesn't have any additional methods ================================================ FILE: modules/AutoOff/lang/de.json ================================================ { "m_title":"Automatisiertes Ausschalten", "m_descr":"Diese App schaltet ein ausgewähltes Gerät (Schalter, Dimmer, Motorsteuerung) automatisch nach einer bestimmten Zeit ab. Das macht insbesondere bei Geräten Sinn, wie der Beleuchtung im Flur, die nicht zu lang eingeschalten bleiben sollen. Ist es bereits möglich, das Gerät innerhalb der „Timeout-Zeit“ abzuschalten, wird in diesem Fall nichts geschehen.

Einstellungen:
  • Wählen Sie das Gerät, welches ausgeschalten werden soll.
  • Wählen Sie die Zeit zum Abschalten.
", "l_dev":"Gerät:", "l_timeout":"Timeout in Sekunden:", "rl_ignoreUpdates": "Ignore status update", "h_ignoreUpdates": "Do not restart the timer on device status update (on On value received)" } ================================================ FILE: modules/AutoOff/lang/en.json ================================================ { "m_title":"Automated Switch Off", "m_descr":"This app will automatically turn off a selected device (switch, dimmer, motor control) after a defined time. This may make sense for lights in the basements of other devices that shall not be turned on for too long. It is already possible to turn of the selected device within the timeout period. In this case nothing will happen.

Settings:
  • Pick the device to be turned off
  • Pick the time out time
", "l_dev":"Device:", "l_timeout":"Timeout in seconds:", "rl_ignoreUpdates": "Ignore status update", "h_ignoreUpdates": "Do not restart the timer on device status update (on On value received)" } ================================================ FILE: modules/AutoOff/lang/ru.json ================================================ { "m_title":"Авто выключение", "m_descr":"Автоматическое выключение устройства через определенное время.", "l_dev":"Устройство, которое должно быть выключено", "l_timeout":"Задержка на выключение в секундах", "rl_ignoreUpdates": "Игнорировать обновление статуса", "h_ignoreUpdates": "Не перезапускать таймер при получении обновлении статуса устройства (значения Вкл)" } ================================================ FILE: modules/AutoOff/module.json ================================================ { "singleton": false, "dependencies": [], "category": "device_enhancements", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "AutoOff", "version": "1.0.3", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "timeout": 5, "ignoreUpdates": false, "device": "" }, "schema": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchBinary:deviceId,namespaces:devices_switchMultilevel:deviceId,namespaces:devices_doorlock:deviceId", "required": true }, "timeout": { "type": "number", "required": true }, "ignoreUpdates": { "type": "boolean" } }, "required": false }, "options": { "fields": { "device": { "label": "__l_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchBinary:deviceName,namespaces:devices_switchMultilevel:deviceName,namespaces:devices_doorlock:deviceName" }, "timeout": { "label": "__l_timeout__" }, "ignoreUpdates": { "type": "checkbox", "rightLabel": "__rl_ignoreUpdates__", "helper": "__h_ignoreUpdates__" } } } } ================================================ FILE: modules/BatteryPolling/index.js ================================================ /*** BatteryPolling Z-Way HA module ******************************************* Version: 2.3.0 (c) Z-Wave.Me, 2020 ----------------------------------------------------------------------------- Author: Gregory Sitnin and Serguei Poltorak , Karsten Reichel Description: This module periodically requests all battery devices for battery level report ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function BatteryPolling (id, controller) { // Call superconstructor first (AutomationModule) BatteryPolling.super_.call(this, id, controller); } inherits(BatteryPolling, AutomationModule); _module = BatteryPolling; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- BatteryPolling.prototype.init = function (config) { BatteryPolling.super_.prototype.init.call(this, config); var self = this; // timestamp of last triggered notification this.lastTriggered = []; // polling function this.onPoll = function () { self.controller.devices.filter(function (el) { return el.get("deviceType") === "battery" && (el.id != self.getName() + "_" + self.id); }).map(function(el) { el.performCommand("update"); }); }; // create vDev this.vDev = this.controller.devices.create({ deviceId: this.getName() + "_" + this.id, defaults: { deviceType: "battery", metrics: { probeTitle: "Battery", scaleTitle: "%", title: self.getInstanceTitle(), icon: "battery" } }, overlay: {}, handler: this.onPoll, moduleId: this.id }); this.onMetricUpdated = function (vDev) { if (!vDev || vDev.id === self.vDev.id) { return; // prevent infinite loop with updates from itself and allows first fake update } if (vDev.get("deviceType") !== "battery") { return; } self.vDev.set("metrics:level", self.minimalBatteryValue()); var now = Math.round(Date.now() / 1000); if (vDev.get("metrics:level") <= self.config.warningLevel && (typeof self.lastTriggered[vDev.id] === 'undefined' || self.lastTriggered[vDev.id] <= (now - 12*60*60))) { var value = vDev.get("metrics:title"), langFile = self.loadModuleLang(); self.addNotification("device-info", langFile.warning + value, "battery", self.vDev.id); self.lastTriggered[vDev.id] = now; } }; // Setup event listeners this.controller.devices.on("change:metrics:level", self.onMetricUpdated); // set up cron handler this.controller.on("batteryPolling.poll", this.onPoll); // Every Day is equal -1 in module.json var everyDay = -1; if (this.config.launchWeekDay == everyDay) { // add cron schedule every day this.controller.emit("cron.addTask", "batteryPolling.poll", { minute: 0, hour: 12, weekDay: null, day: null, month: null }); } else { // add cron schedule every week this.controller.emit("cron.addTask", "batteryPolling.poll", { minute: 0, hour: 12, weekDay: this.config.launchWeekDay, day: null, month: null }); } // run first time to set up the value this.onMetricUpdated(); }; BatteryPolling.prototype.stop = function () { BatteryPolling.super_.prototype.stop.call(this); var self = this; this.controller.devices.remove(this.vDev.id); this.controller.devices.off("change:metrics:level", self.onMetricUpdated); this.controller.emit("cron.removeTask", "batteryPolling.poll"); this.controller.off("batteryPolling.poll", this.onPoll); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- BatteryPolling.prototype.minimalBatteryValue = function () { var self = this, arr; arr = this.controller.devices.filter(function(vDev) { return vDev.get("deviceType") === "battery" && vDev.id != self.vDev.id; }).map(function(vDev) { return vDev.get("metrics:level"); }); arr.push(100); return Math.min.apply(null, arr); } ================================================ FILE: modules/BatteryPolling/lang/de.json ================================================ { "m_title":"Batteriestatus", "m_descr":"Die Batteriestandabfrage aktualisiert den Batteriestatus durch wöchentliche Abfrage aller batteriebetriebener Geräte über deren aktuellen Batteriestatus. Sie können den Wochentag und die Warnstufe auswählen, wann dieses Modul Ihnen eine Warnmeldung sendet.", "l_launch_wd":"Wochentag, an dem der Batteriestatus abgefragt werden soll:", "every_d":"täglich", "mon":"Montag", "tue":"Dienstag", "wed":"Mittwoch", "thu":"Donnerstag", "fri":"Freitag", "sat":"Samstag", "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: " } ================================================ FILE: modules/BatteryPolling/lang/en.json ================================================ { "m_title":"Battery Polling", "m_descr":"The battery polling will update the battery status by asking all battery-operated device once a week for a battery status update.. You can pick the day of the week and a warning level, when this module will send you a warning note.", "l_launch_wd":"Do battery polling on", "every_d":"Every Day", "mon":"Monday", "tue":"Tuesday", "wed":"Wednesday", "thu":"Thursday", "fri":"Friday", "sat":"Saturday", "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: " } ================================================ FILE: modules/BatteryPolling/lang/ru.json ================================================ { "m_title":"Опрос батарей", "m_descr":"Оповещение о низком заряде батарей.", "l_launch_wd":"Делать опрос заряда батарей", "every_d":"Каждый день", "mon":"Понедельник", "tue":"Вторник", "wed":"Среда", "thu":"Четверг", "fri":"Пятница", "sat":"Суббота", "sun":"Воскресенье", "l_warning_lvl":"Низкий уровень", "h_warning_lvl":"Оповестить если заряд любой батареи опустится ниже этого уровня", "warning":"Внимание! Батарея разряжена: " } ================================================ FILE: modules/BatteryPolling/module.json ================================================ { "singleton": true, "dependencies": ["Cron"], "category": "basic_gateway_modules", "author": "Z-Wave.Me", "homepage": "https://z-wave.me", "icon": "icon.png", "moduleName": "BatteryPolling", "version": "2.3.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "launchWeekDay": 0, "warningLevel": 20 }, "schema": { "type": "object", "properties": { "launchWeekDay": { "type": "number", "required": true, "enum": [-1, 1, 2, 3, 4, 5, 6, 0] }, "warningLevel": { "type": "select", "required": true, "enum": [5, 10, 15, 20] } }, "required": false }, "options": { "fields": { "launchWeekDay": { "label": "__l_launch_wd__", "optionLabels": ["__every_d__", "__mon__", "__tue__", "__wed__", "__thu__", "__fri__", "__sat__", "__sun__"] }, "warningLevel": { "label": "__l_warning_lvl__", "helper": "__h_warning_lvl__", "optionLabels": ["5%", "10%", "15%", "20%"] } } } } ================================================ FILE: modules/BindDevices/index.js ================================================ /*** BindDevices Z-Way HA module ******************************************* Version: 1.0.3 (c) Z-Wave.Me, 2018 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: Bind actions on one device to others ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function BindDevices (id, controller) { // Call superconstructor first (AutomationModule) BindDevices.super_.call(this, id, controller); } inherits(BindDevices, AutomationModule); _module = BindDevices; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- BindDevices.prototype.init = function (config) { BindDevices.super_.prototype.init.call(this, config); var self = this; this.handlerLevel = function (sDev) { var that = self, actionBinary = null, actionMultilevel = null, value = sDev.get("metrics:level"); if (value === 255 || value === true || value === "on") { actionBinary = "on"; } else if (value === 0 || value === false || value === "off") { actionBinary = "off"; } else { actionBinary = "on"; actionMultilevel = value; } self.config.targetDevices.forEach(function(el) { var vDev = that.controller.devices.get(el); if (vDev) { if (vDev.get("deviceType") === "switchBinary" || vDev.get("deviceType") === "scene" || vDev.get("deviceType") === "switchMultilevel" && actionMultilevel === null) { 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); } } }); }; this.handlerChange = function (sDev) { var action = sDev.get("metrics:change"); self.config.targetDevices.forEach(function(el) { var vDev = self.controller.devices.get(el); if (vDev) { if ((vDev.get("deviceType") === "switchMultilevel") || (vDev.get("deviceType") === "thermostat")) { vDev.performCommand(action); } } }); }; // Setup metric update event listener self.config.sourceDevices.forEach(function(x) { self.controller.devices.on(x, 'change:metrics:level', self.handlerLevel); self.controller.devices.on(x, 'change:metrics:change', self.handlerChange); }); }; BindDevices.prototype.stop = function () { var self = this; self.config.sourceDevices.forEach(function(x) { self.controller.devices.off(x, 'change:metrics:level', self.handlerLevel); self.controller.devices.off(x, 'change:metrics:change', self.handlerChange); }); BindDevices.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/BindDevices/lang/de.json ================================================ { "m_title":"Assoziation", "m_descr":"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" } ================================================ FILE: modules/BindDevices/lang/en.json ================================================ { "m_title":"Association", "m_descr":"The association is the foundation of automation. A selected action (THEN) is executed automatically at the moment when a certain event (IF) has happen. This app allows defining these relationships. The (IF) event must be an 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 send 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" } ================================================ FILE: modules/BindDevices/lang/ru.json ================================================ { "m_title":"Ассоциации", "m_descr":"Прямое ассоциирование устройств друг с другом.", "l_event_sources":"Список источников событий", "l_actors":"Список исполнителей" } ================================================ FILE: modules/BindDevices/module.json ================================================ { "singleton": false, "dependencies": [], "category": "automation_basic", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "BindDevices", "version": "1.0.3", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "sourceDevices": [], "targetDevices": [] }, "schema": { "type": "object", "properties": { "sourceDevices": { "type": "array", "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,namespaces:devices_sensorMultilevel:deviceId", "required": true } }, "targetDevices": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "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 } } }, "required": false }, "options": { "fields": { "sourceDevices": { "label": "__l_event_sources__", "fields": { "item": { "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,namespaces:devices_sensorMultilevel:deviceName" } } }, "targetDevices": { "label": "__l_actors__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "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" } } } } } } ================================================ FILE: modules/Camera/index.js ================================================ /*** Camera Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Stanislav Morozov Description: This module stores params of camera ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function Camera (id, controller) { // Call superconstructor first (AutomationModule) Camera.super_.call(this, id, controller); } inherits(Camera, AutomationModule); _module = Camera; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- _.extend(Camera.prototype, { init: function (config) { Camera.super_.prototype.init.call(this, config); var that = this, vDevId = "CameraDevice_" + this.id; var opener = function(command) { config.doorDevices.forEach(function(el) { var vDev = that.controller.devices.get(el); if (vDev) { var type = vDev.get("deviceType"); if (type === "switchBinary" || type === "switchMultilevel") { vDev.performCommand(command == "open" ? "on" : "off"); } else if (type === "doorlock") { vDev.performCommand(command); } } }); }; if (config.screenUrl) { this.screen_url = "/" + vDevId + "/screen"; ws.proxify(this.screen_url, config.screenUrl, config.user, config.password); } this.proxy_url = "/" + vDevId + "/stream"; ws.proxify(this.proxy_url, config.url, config.user, config.password); this.vDev = this.controller.devices.create({ deviceId: vDevId, defaults: { deviceType: "camera", metrics: { icon: "camera", title: that.getInstanceTitle() } }, overlay: { metrics: { url: this.proxy_url, screenUrl: this.screen_url, hasZoomIn: !!config.zoomInUrl, hasZoomOut: !!config.zoomOutUrl, hasLeft: !!config.leftUrl, hasRight: !!config.rightUrl, hasUp: !!config.upUrl, hasDown: !!config.downUrl, hasOpen: !!config.openUrl || !!(config.doorDevices && config.doorDevices.length), hasClose: !!config.closeUrl || !!(config.doorDevices && config.doorDevices.length), externalUrl: config.externalUrl } }, handler: function(command) { var url = null; if (command == "zoomIn") { url = config.zoomInUrl; } else if (command == "zoomOut") { url = config.zoomOutUrl; } else if (command == "left") { url = config.leftUrl; } else if (command == "right") { url = config.rightUrl; } else if (command == "up") { url = config.upUrl; } else if (command == "down") { url = config.downUrl; } else if (command == "open") { url = config.openUrl; opener(command); } else if (command == "close") { url = config.closeUrl; opener(command); }; if (url) { http.request({ url: url, async: true, auth: { login: config.user, password: config.password } }); } }, moduleId: this.id }); }, stop: function () { Camera.super_.prototype.stop.call(this); ws.proxify(this.proxy_url, null); if (this.screen_url) { ws.proxify(this.screen_url, null); } if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } } }); ================================================ FILE: modules/Camera/lang/de.json ================================================ { "m_title":"Web Kamera", "m_descr":"Dieses Modul konfiguriert die eingebundene Kamera und erstellt ein virtuelles Gerät.", "l_cam_url": "URL - Kamera", "l_screen_url": "URL - Screenshot", "l_zoomin": "URL - Herein zommen", "l_zoomout": "URL - Heraus zoomen", "l_left": "URL - Drehung nach links", "l_right": "URL - Drehung nach rechts", "l_up": "URL - Drehung nach oben", "l_down": "URL - Drehung nach unten", "l_open": "URL - Öffen", "l_close": "URL - Schließen", "l_external_url": "URL to open on External URL click", "l_user": "Benutzername für HTTP Authentifizierung", "h_http_auth": "Lassen Sie das Feld frei, wenn die Kamera nicht mit einem Passwort geschütztt ist.", "l_pw":"Passwort für HTTP Authentifiziereung.", "l_door_dev": "Door device" } ================================================ FILE: modules/Camera/lang/en.json ================================================ { "m_title":"Web Camera", "m_descr":"This module include configuration for a camera and create camera device.", "l_cam_url":"Camera URL", "l_screen_url": "Screenshot URL", "l_zoomin":"ZoomIn URL", "l_zoomout":"ZoomOut URL", "l_left":"Left URL", "l_right":"Right URL", "l_up":"Up URL", "l_down":"Down URL", "l_open":"Open URL", "l_close":"Close URL", "l_external_url": "URL to open on External URL click", "l_user": "Username for HTTP authentication", "l_pw": "Password for HTTP authentication", "h_http_auth": "Leave empty if camera is not protected by a password", "l_door_dev": "Door device" } ================================================ FILE: modules/Camera/lang/ru.json ================================================ { "m_title":"Web Камера", "m_descr":"Этот модуль позволяет настроить управление камерой.", "l_cam_url":"URL Камеры", "l_screen_url": "URL Screenshot", "l_zoomin":"URL Приблизить", "l_zoomout":"URL Отдалить", "l_left":"URL Влево", "l_right":"URL Вправо", "l_up":"URL Вверх", "l_down":"URL Вниз", "l_open":"URL Открыть", "l_close":"URL Закрыть", "l_external_url": "URL, который открывать по нажатиб на Внешний URL", "l_user": "Имя пользователя", "l_pw": "Пароль", "h_http_auth": "Оставьте это поле пустым, если камера не использует HTTP авторизацию", "l_door_dev": "Устройство управления доступом двери" } ================================================ FILE: modules/Camera/module.json ================================================ { "singleton": false, "dependencies": [], "category": "surveillance", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"Camera", "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__", "url": "", "screenUrl": "", "zoomInUrl": "", "zoomOutUrl": "", "leftUrl": "", "rightUrl": "", "upUrl": "", "downUrl": "", "openUrl": "", "closeUrl": "", "externalUrl": "", "user": "", "password": "", "doorDevices": [] }, "schema": { "type": "object", "properties": { "url": { "format": "uri", "required": true }, "screenUrl": { "format": "uri", "required": true }, "zoomInUrl": { "format": "uri", "required": false }, "zoomOutUrl": { "format": "uri", "required": false }, "leftUrl": { "format": "uri", "required": false }, "rightUrl": { "format": "uri", "required": false }, "upUrl": { "format": "uri", "required": false }, "downUrl": { "format": "uri", "required": false }, "openUrl": { "format": "uri", "required": false }, "closeUrl": { "format": "uri", "required": false }, "externalUrl": { "format": "uri", "required": false }, "user": { "required": false }, "password": { "format": "password", "required": false }, "doorDevices": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId,namespaces:devices_switchBinary:deviceId,namespaces:devices_switchMultilevel:deviceId", "required": false } } }, "required": false }, "options": { "fields": { "url": { "label": "__l_cam_url__", "type": "url", "allowIntranet": true, "required": true }, "screenUrl": { "label": "__l_screen_url__", "type": "url", "allowIntranet": true, "required": false }, "zoomInUrl": { "label": "__l_zoomin__", "type": "url", "allowIntranet": true, "required": false }, "zoomOutUrl": { "label": "__l_zoomout__", "type": "url", "allowIntranet": true, "required": false }, "leftUrl": { "label": "__l_left__", "type": "url", "allowIntranet": true, "required": false }, "rightUrl": { "label": "__l_right__", "type": "url", "allowIntranet": true, "required": false }, "upUrl": { "label": "__l_up__", "type": "url", "allowIntranet": true, "required": false }, "downUrl": { "label": "__l_down__", "type": "url", "allowIntranet": true, "required": false }, "openUrl": { "label": "__l_open__", "type": "url", "allowIntranet": true, "required": false }, "closeUrl": { "label": "__l_close__", "type": "url", "allowIntranet": true, "required": false }, "externalUrl": { "label": "__l_external_url__", "type": "url", "allowIntranet": true, "required": false }, "user": { "label": "__l_user__", "helper": "__h_http_auth__", "required": false }, "password": { "label": "__l_pw__", "helper": "__h_http_auth__", "required": false }, "doorDevices": { "label": "__l_door_dev__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName,namespaces:devices_switchBinary:deviceName,namespaces:devices_switchMultilevel:deviceName" } } } } } } ================================================ FILE: modules/CloudBackup/Readme.md ================================================ ================================================ FILE: modules/CloudBackup/htdocs/js/postRender.js ================================================ function modulePostRender(control) { var scheduler = control.childrenByPropertyId['scheduler']; var api = control.data.api; if(scheduler.getValue() == 0) { addButton(); } scheduler.on('change', function() { if(this.getValue() == 0) { addButton(); } else { removeButton(); } }); function addButton() { var $button = $(''); var $i = $(''); $button.addClass('btn btn-default btn-sm'); $i.addClass('fa fa-cloud-upload'); $button.append($i); $button.attr('href', "http://"+location.host+api); $button.append('Upload Backup'); $button.on('click', function(event) { event.preventDefault(); var $this = $(this); var url = $this.attr('href'); $.get(url); }); $('.cloudBackupScheduler').append($button); } function removeButton() { $('.cloudBackupScheduler').find('a').remove(); } } ================================================ FILE: modules/CloudBackup/index.js ================================================ /*** CloudBackup Z-Way HA module ******************************************* Version: 0.1.3 beta (c) Z-Wave.Me, 2016 ----------------------------------------------------------------------------- Author: Michael Hensche Description: Gives possibility to upload and store your backups on the remote server. ******************************************************************************/ function CloudBackup (id, controller) { // Call superconstructor first (AutomationModule) CloudBackup.super_.call(this, id, controller); } inherits(CloudBackup, AutomationModule); _module = CloudBackup; CloudBackup.prototype.init = function(config) { CloudBackup.super_.prototype.init.call(this, config); this.backupcreate_url = "https://service.z-wave.me/cloudbackup/?uri=backupcreate"; this.usercreate_url = "https://service.z-wave.me/cloudbackup/?uri=usercreate"; this.userupdate_url = "https://service.z-wave.me/cloudbackup/?uri=userupdate"; var self = this, langFile = self.loadModuleLang(); // load remote_id try { this.remoteid = self.controller.getRemoteId(); self.config.remoteid = this.remoteid; } catch(e) { console.log(e.message); return; } this.defineHandlers(); this.externalAPIAllow(); global["CloudBackupAPI"] = this.CloudBackupAPI; this.uploadBackup = function() { console.log("###### start Backup "); var ret = false; try { var backupJSON = self.controller.createBackup(); var now = new Date(); // create a timestamp in format yyyy-MM-dd-HH-mm 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); if(!_.isNull(backupJSON)) { var data = {"data": Base64.encode(JSON.stringify(backupJSON))}; var formElements = [ { name: 'remote_id', value: self.config.remoteid.toString() },{ name: 'email_report', value: self.config.email_log.toString() },{ name: 'file', filename: "z-way-backup-" + ts + ".zab", type: 'application/octet-stream', value: JSON.stringify(data) }]; var res = formRequest.send(formElements, "POST", self.backupcreate_url); if(res.status === -1) { //error e.g. no connection to server self.addNotification("error", res.statusText, "module"); } else { if(res.status === 200) { ret = true; self.addNotification("info", res.data.message, "module"); } else { self.addNotification("error", res.data.message, "module"); } } } else { console.log("Backup file empty!"); } } catch(e) { console.log(e); } console.log("###### finisch Backup "); return ret; }; if(self.config.email !== "" && !_.isNull(self.config.remoteid) && !self.config.user_active) { console.log("userCreate"); this.userCreate(); } else { self.config.user_active = false; } if(self.config.email !== "" && !_.isNull(self.config.remoteid)) { console.log("userUpdate"); this.userUpdate(); } if(self.config.scheduler !== "0" && self.config.user_active == true) { // manual this.updateTask(); } else { this.controller.emit("cron.removeTask", "CloudBackup.upload"); } // set up cron handler self.controller.on("CloudBackup.upload", self.uploadBackup); }; CloudBackup.prototype.stop = function() { this.controller.off("CloudBackup.upload", this.uploadBackup); this.controller.emit("cron.removeTask", "CloudBackup.upload"); delete global["CloudBackupAPI"]; CloudBackup.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- CloudBackup.prototype.updateTask = function() { var self = this; switch(self.config.scheduler) { case "0": // manual self.config.minutes = null; self.config.hours = null; self.config.weekDays = null; self.config.days = null; break; case "1": // daily self.config.weekDays = null; self.config.days = null; break; case "2": // weekly self.config.days = null; break; case "3": // monthly self.config.weekDays = null; break; }; console.log("### Backuptime ###"); console.log("h", _.isNull(self.config.hours) ? "null" : parseInt(self.config.hours)); console.log("m", _.isNull(self.config.minutes) ? "null" : parseInt(self.config.minutes)); console.log("wd", _.isNull(self.config.weekDays) ? "null" : parseInt(self.config.weekDays)); console.log("d", _.isNull(self.config.days) ? "null" : parseInt(self.config.days)); console.log("##################"); self.controller.emit("cron.addTask", "CloudBackup.upload", { minute: _.isNull(self.config.minutes) ? null : parseInt(self.config.minutes), hour: _.isNull(self.config.hours) ? null : parseInt(self.config.hours), weekDay: _.isNull(self.config.weekDays) ? null : parseInt(self.config.weekDays), day: _.isNull(self.config.days) ? null : parseInt(self.config.days), month: null }); }; CloudBackup.prototype.userCreate = function() { var self = this; var obj = { url: this.usercreate_url, method: "POST", data: { remote_id: self.config.remoteid, email: self.config.email } }; var res = http.request(obj); if(res.status === -1) { //error e.g. no connection to server self.config.service_status = false; self.config.user_active = false; } else { if(res.status === 200) { self.config.user_active = true; } else { self.config.user_active = false; } } /*if(res.data.status === 200) { self.config.user_active = true self.addNotification("info", res.data.message, "core"); } else { self.addNotification("error", res.data.message, "core"); }*/ }; CloudBackup.prototype.userUpdate = function() { var self = this; var obj = { url: this.userupdate_url, method: "POST", data: { remote_id: self.config.remoteid, email: self.config.email, email_log: self.config.email_log } }; var res = http.request(obj); if(res.status === -1) { //error e.g. no connection to server console.log("error"); self.config.service_status = false; self.config.user_active = false; } else { self.config.service_status = true; if(res.status === 200) { self.config.user_active = true; } else { self.config.user_active = false; } } /*if(res.data.status === 200) { console.log(res.data.message); //self.addNotification("info", "User update "+res.data.message, "core"); } else { console.log(res.data.message); //self.addNotification("error", res.data.message, "core"); }*/ }; // --------------- Public HTTP API ------------------- CloudBackup.prototype.externalAPIAllow = function (name) { var _name = !!name ? ("CloudBackup." + name) : "CloudBackupAPI"; ws.allowExternalAccess(_name, this.controller.auth.ROLE.ADMIN); ws.allowExternalAccess(_name + ".Backup", this.controller.auth.ROLE.ADMIN); }; CloudBackup.prototype.externalAPIRevoke = function (name) { var _name = !!name ? ("CloudBackup." + name) : "CloudBackupAPI"; ws.revokeExternalAccess(_name); ws.revokeExternalAccess(_name + ".Backup"); }; CloudBackup.prototype.defineHandlers = function () { var self = this; this.CloudBackupAPI = function () { return {status: 400, body: "Bad CloudBackupAPI request "}; }; this.CloudBackupAPI.Backup = function () { var ret = {status: 500, body: {"error": true} }; var state = self.uploadBackup(); if(state) { ret.status = 200; ret.body = {"error": false}; } console.log(JSON.stringify(ret)); return ret; }; } ================================================ FILE: modules/CloudBackup/lang/de.json ================================================ { "m_title":"CloudBackup", "m_descr":"Diese App gibt Ihnen die Möglichkeit, Ihre Backups auf dem Remote-Server hochzuladen und zu speichern.", "l_email":"E-Mail", "l_email_log":"E-Mail Benachrichtigung zur Datensicherung", "l_no_log":"Kein E-Mail Benachrichtigung", "l_only_log":"E-Mail Benachrichtigung bei fehlgeschlagener Datensicherung", "l_log_notification":"E-Mail Benachrichtigung bei fehlgeschlagener und erfolgreicher Datensicherung", "l_hours":"Stunden", "l_minutes":"Minuten", "l_weekDays":"Wochentage", "l_su":"Sonntag", "l_mo":"Montag", "l_tu":"Dienstag", "l_we":"Mittwoch", "l_th":"Donnerstag", "l_fr":"Freitag", "l_sa":"Samstag", "l_days":"Tage", "l_scheduler":"Zeitplan", "l_daily":"Täglich", "l_weekly":"Wöchentlich", "l_monthly":"Monatlich", "l_manual":"Manuell" } ================================================ FILE: modules/CloudBackup/lang/en.json ================================================ { "m_title":"CloudBackup", "m_descr":"Gives possibility to upload and store your backups on the remote server.", "l_email":"E-Mail", "l_email_log":"E-Mail notification", "l_no_log":"Do not send me log by email", "l_only_log":"Send me error log only by email", "l_log_notification":"Send me error log and notifications by email", "l_hours":"Hours", "l_minutes":"Minutes", "l_weekDays":"Weekdays", "l_su":"Sunday", "l_mo":"Monday", "l_tu":"Tuesday", "l_we":"Wednseday", "l_th":"Thursday", "l_fr":"Friday", "l_sa":"Saturday", "l_days":"Days", "l_scheduler":"Scheduler", "l_daily":"Daily", "l_weekly":"Weekly", "l_monthly":"Monthly", "l_manual":"Manual" } ================================================ FILE: modules/CloudBackup/module.json ================================================ { "dependencies": [ "Cron" ], "singleton": true, "autoload": true, "category": "", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"CloudBackup", "version": "0.1.3", "maturity": "beta", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "api": "/CloudBackupAPI/Backup", "user_active": false, "service_status": true, "email": "", "email_log": "0", "remoteid": null, "days": null, "weekDays": null, "hours": null, "minutes": null, "scheduler": "0" }, "schema" : { "type": "object", "properties": { "email": { "type": "string", "format": "email", "required": true }, "email_log": { "type": "string", "enum": ["0","1","2"], "required": true }, "remoteid": { "type": "integer", "readonly": true, "required": true }, "scheduler": { "type": "string", "enum": ["0", "1", "2", "3"], "required": true }, "hours": { "type": "string", "enum": ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23"] }, "minutes": { "type": "string", "enum": ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23","24","25","26","27","28","29","30","31","32","33","34","35","36","37","38","39","40","41","42","43","44","45","46","47","48","49","50","51","52","53","54","55","56","57","58","59"] }, "weekDays": { "type": "string", "enum": ["0", "1", "2", "3", "4", "5", "6"] }, "days": { "type": "string", "enum": ["01","02","03","04","05","06","07","08","09","10","11","12","13","14","15","16","17","18","19","20","21","22","23","24","25","26","27","28","29","30","31"] } }, "dependencies": { "scheduler": ["remoteid", "email"], "minutes": ["scheduler"], "weekDays": ["scheduler"], "days": ["scheduler"], "hours": ["scheduler"] } }, "options": { "fields": { "remoteid": { "type": "hidden" }, "email": { "type": "hidden", "label": "__l_email__" }, "email_log": { "type": "hidden", "emptySelectFirst": true, "label": "__l_email_log__", "optionLabels": ["__l_no_log__", "__l_only_log__", "__l_log_notification__"] }, "scheduler": { "type": "hidden", "emptySelectFirst": true, "fieldClass": "cloudBackupScheduler", "label": "__l_scheduler__", "optionLabels": ["__l_manual__", "__l_daily__", "__l_weekly__", "__l_monthly__"] }, "hours": { "type": "hidden", "label": "__l_hours__", "dependencies": { "scheduler": ["1","2","3"] } }, "minutes": { "type": "hidden", "label": "__l_minutes__", "dependencies": { "scheduler": ["1","2","3"] } }, "weekDays": { "type": "hidden", "optionLabels": ["__l_su__", "__l_mo__", "__l_tu__", "__l_we__", "__l_th__", "__l_fr__", "__l_sa__"], "label": "__l_weekDays__", "dependencies": { "scheduler": ["2"] } }, "days": { "type": "hidden", "label": "__l_days__", "dependencies": { "scheduler": ["3"] } } } }, "postRender": "loadFunction:postRender.js" } ================================================ FILE: modules/CodeDevice/index.js ================================================ /*** CodeDevice Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: Implements virtual device based on JavaScript code ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function CodeDevice (id, controller) { // Call superconstructor first (AutomationModule) CodeDevice.super_.call(this, id, controller); } inherits(CodeDevice, AutomationModule); _module = CodeDevice; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- CodeDevice.prototype.init = function (config) { CodeDevice.super_.prototype.init.call(this, config); var self = this, icon = "", level = "", scaleTitle = "", deviceType = this.config.deviceType; probeType = "" switch(deviceType) { case "sensorBinary": icon = this.config.iconSensorBinary; probeType = this.config.iconSensorBinary == "door" ? "door-window" : this.config.iconSensorBinary; level = "off"; break; case "sensorMultilevel": icon = this.config.iconSensorMultilevel; probeType = this.config.iconSensorMultilevel; scaleTitle = this.config.scale_sensorMultilevel; level = 0; break; case "switchBinary": icon = "switch"; level = "off"; break; case "switchMultilevel": icon = "multilevel"; level = 0; break; case "toggleButton": icon = "gesture"; level = "on"; break; } var defaults = { metrics: { title: self.getInstanceTitle() } }; var overlay = { deviceType: deviceType, probeType: probeType, metrics: { icon: icon, level: level, scaleTitle: scaleTitle } }; var vDev = self.controller.devices.create({ deviceId: "Code_Device_" + deviceType + "_" + this.id, defaults: defaults, overlay: overlay, handler: function (command, args) { var vDevType = deviceType; if (command === "update" && (vDevType === "sensorBinary" || vDevType === "sensorMultilevel" || vDevType === "switchBinary" || vDevType === "switchMultilevel")) { self.update(this); } if (command === "on" && (vDevType === "toggleButton" || vDevType === "switchBinary")) { self.act(this, "On", null, (vDevType === "switchBinary" ? "on" : null)); } if (command === "off" && vDevType === "switchBinary") { self.act(this, "Off", null, "off"); } if ((command === "off" || command === "on" || command === "exact") && vDevType === "switchMultilevel") { var level = command === "exact" ? parseInt(args.level, 10) : (command === "on" ? 99 : 0); self.act(this, "Level", level, level); } }, moduleId: this.id }); if (vDev && this.config["getter_" + deviceType] && this.config["getterPollInterval_" + deviceType]) { this.timer = setInterval(function() { self.update(vDev); }, this.config["getterPollInterval_" + deviceType] * 1000); } }; CodeDevice.prototype.stop = function () { if (this.timer) { clearInterval(this.timer); } this.controller.devices.remove("Code_Device_" + this.config.deviceType + "_" + this.id); CodeDevice.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- CodeDevice.prototype.update = function (vDev) { var deviceType = this.config.deviceType, getterCode = this.config["getter_" + deviceType]; if (getterCode) { var newValue = eval(getterCode); if (this.config.skipEventIfSameValue !== true || newValue !== vDev.get("metrics:level")) { vDev.set("metrics:level", newValue); } } }; CodeDevice.prototype.act = function (vDev, action, subst, selfValue) { var self = this, deviceType = this.config.deviceType, setterCode = this.config["setter" + action + "_" + deviceType]; if (!!setterCode) { if (subst != null) { setterCode = setterCode.replace(/%%/g, subst); } eval(setterCode); } if ((!setterCode || this.config.updateOnAction === true) && selfValue !== null) { vDev.set("metrics:level", selfValue); } }; ================================================ FILE: modules/CodeDevice/lang/de.json ================================================ { "m_title":"Virtuelle Gerät (JavaScript)", "m_descr":"Die Anwendung dieser App erfordert solide Kenntnisse in der JavaScript Programmierung. Diese App ermöglicht die Erstellung eines neuen Gerätes basierend auf einigen JavaScript Anweisungen. Das JavaScript kann alle möglichen Daten abfragen und berechnen so lange diese per JavaScript ausführbar sind. Das Gerät kann sowohl ein Sensor sein, welcher lediglich Daten anzeigt, als auch ein Java Script Code ausführender Aktor.

Einstellungen:
  • Choose type of device
  • Set the code for actions
  • Set the code to get the value
  • Set the polling interval

Beispiel: Die App soll ein Element erstellen, welches wahllos “An” oder “Aus” anzeigt. Der verwendete JavaScript Code lautet: Math.random() > 0.5 ? 'on' : 'off'

You can create a virtual device without code and update the value using the HTTP API, for example:
http://192.168.1.108:8083/JS/Run/controller.devices.get('Code_Device_sensorMultilevel_38').set('metrics:level',50)
http://192.168.1.108:8083/JS/Run/controller.devices.get('Code_Device_sensorBinary_39').set('metrics:level','on')", "l_icon":"Icon", "l_setterOn_toggleButton":"Code für den Befehl 'on'", "h_getter_sensorBinary":"Rückgabe der Werte 'on' oder 'off'", "h_getter_sensorMultilevel":"Rückgabe einer Zahlenwertes", "l_scale_sensorMultilevel":"Sensoreinheit", "l_setterOn_switchBinary":"Code für den Befehl 'on'", "h_setterOn_switchBinary":"%% steht für 'on'", "l_setterOff_switchBinary":"Code für den Befehl 'off'", "h_setterOff_switchBinary":"%% steht für 'off'", "h_getter_switchBinary":"Rückgabe der Werte 'on' oder 'off'", "l_setterLevel_switchMultilevel":"Code für Aktion", "h_setterLevel_switchMultilevel":"%% steht für einen Wert von 0 bis 99", "l_getter_value":"Code um einen Wert abzufragen", "h_getter_switchMultilevel":"Rückgabe eines Wertes von 0 bis 99", "l_getterPollInterval":"Abfrageintervall in Sekunden:", "h_getterPollInterval":"0 oder eine leere Eingabe deaktivieren die periodische Abfrage (explizite Update-Kommandos werden weiterhin ausgeführt)", "rl_updateOnAction": "Update value on action", "h_updateOnAction": "On actions (on/off/dimming) update value accordingly before it is polled", "rl_skipEventIfSameValue": "Don't send update event if value has not changed", "h_skipEventIfSameValue": "If same value is reported, do not send update event. In this case no update listeners will be called." } ================================================ FILE: modules/CodeDevice/lang/en.json ================================================ { "m_title":"Virtual Device (JavaScript)", "m_descr":"The use of this app requires solid knowledge of JavaScript programming. This app allows creating a new virtual device based on JavaScript code. This JavaScript can poll and calculate any data from anywhere as long as this is doable using JavaScript. The device can be a sensor just showing data but it can also be an actor executing some JavaScript code.

Settings:
  • Choose type of device
  • Set the code for actions
  • Set the code to get the value
  • Set the polling interval

Example: The app shall create an element showing randomly 'on' or 'off'. The JavaScript Code used is: Math.random() > 0.5 ? 'on' : 'off'

You can create a virtual device without code and update the value using the HTTP API, for example:
http://192.168.1.108:8083/JS/Run/controller.devices.get('Code_Device_sensorMultilevel_38').set('metrics:level',50)
http://192.168.1.108:8083/JS/Run/controller.devices.get('Code_Device_sensorBinary_39').set('metrics:level','on')", "l_icon":"Icon", "l_setterOn_toggleButton":"Code for action On", "h_getter_sensorBinary":"Should return 'on' or 'off' values", "h_getter_sensorMultilevel":"Should return number", "l_scale_sensorMultilevel":"Sensor scale", "l_setterOn_switchBinary":"Code for action On", "h_setterOn_switchBinary":"%% will represent value 'on'", "l_setterOff_switchBinary":"Code for action Off", "h_setterOff_switchBinary":"%% will represent value 'off'", "h_getter_switchBinary":"Should return 'on' or 'off' value", "l_setterLevel_switchMultilevel":"Code for action", "h_setterLevel_switchMultilevel":"%% will represent value from 0 to 99", "l_getter_value":"Code to get value", "h_getter_switchMultilevel":"Should return value from 0 to 99", "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)", "rl_updateOnAction": "Update value on action", "h_updateOnAction": "On actions (on/off/dimming) update value accordingly before it is polled", "rl_skipEventIfSameValue": "Don't send update event if value has not changed", "h_skipEventIfSameValue": "If same value is reported, do not send update event. In this case no update listeners will be called." } ================================================ FILE: modules/CodeDevice/lang/ru.json ================================================ { "m_title":"Виртуальное устройство (JavaScript)", "m_descr":"Использование этого приложения требует базовых знаний программирования на JavaScript. Возможно создать виртуальное устройство исполняющее любой JavaScript код или виртуальный датчик, который периодически опрашивает и вычисляет данные.

Настройка:
  • Выберите тип устройства
  • Задайте код для действий
  • Задайте код для получения значения
  • Задайте интервал опроса

Например: Бинарный датчик, который произвольно показывает состояние 'on' или 'off'. Период опроса: 5 секунд. Код для получения значения: Math.random() > 0.5 ? 'on' : 'off'

Можно создать виртуальное устройство без кода и обновлять значение с помощью HTTP API, например:
http://192.168.1.108:8083/JS/Run/controller.devices.get('Code_Device_sensorMultilevel_38').set('metrics:level',50)
http://192.168.1.108:8083/JS/Run/controller.devices.get('Code_Device_sensorBinary_39').set('metrics:level','on')", "l_icon":"Иконка", "l_setterOn_toggleButton":"Код для действия Включить", "h_getter_sensorBinary":"Должен вернуть значение 'on' или 'off'", "h_getter_sensorMultilevel":"Должен вернуть число", "l_scale_sensorMultilevel":"Единица измерения", "l_setterOn_switchBinary":"Код для действия Включить", "h_setterOn_switchBinary":"Символы %% содержат значение 'on'", "l_setterOff_switchBinary":"Код для действия Выключить", "h_setterOff_switchBinary":"Символы %% содержат значение 'off'", "h_getter_switchBinary":"Должен вернуть значение 'on' или 'off'", "l_setterLevel_switchMultilevel":"Код для действия", "h_setterLevel_switchMultilevel":"Символы %% содержат значение от 0 до 99", "l_getter_value":"Код для получения значения", "h_getter_switchMultilevel":"Должен вернуть значение от 0 до 99", "l_getterPollInterval":"Интервал в секундах опроса значения", "h_getterPollInterval":"Пусто или 0 отключают периодический опрос (запросить значение можно вручную командой update)", "rl_updateOnAction": "Менять состояние при действиях", "h_updateOnAction": "При действиях с устройством (включение/выключение/диммирование), менять состояние на соответствующее действию, не дожидаясь получения опроса значения.", "rl_skipEventIfSameValue": "Не отправлять событие обновления, если значение не поменялось", "h_skipEventIfSameValue": "Если получено значение, совпадающее с предыдущим, не обновлять значение. В этом случае не будут вызваны подписчики на события обновления." } ================================================ FILE: modules/CodeDevice/module.json ================================================ { "singleton": false, "dependencies": [], "category": "developers_stuff", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "CodeDevice", "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__", "deviceType": "", "iconSensorBinary": "", "iconSensorMultilevel": "", "setterOn_toggleButton": "", "getter_sensorBinary": "", "getterPollInterval_sensorBinary": 0, "getter_sensorMultilevel": "", "getterPollInterval_sensorMultilevel": 0, "scale_sensorMultilevel": "", "setterOn_switchBinary": "", "setterOff_switchBinary": "", "getter_switchBinary": "", "getterPollInterval_switchBinary": 0, "setterLevel_switchMultilevel": "", "getter_switchMultilevel": "", "getterPollInterval_switchMultilevel": 0, "updateOnAction": false, "skipEventIfSameValue": false }, "schema": { "type": "object", "properties": { "deviceType": { "type": "string", "enum": ["toggleButton", "sensorBinary", "sensorMultilevel", "switchBinary", "switchMultilevel"], "default": "toggleButton", "required": true }, "iconSensorBinary": { "type": "string", "enum": ["alarm", "motion", "smoke", "co", "flood","cooling","tamper","door"], "default": "alarm", "required": true, "dependencies": "deviceType" }, "iconSensorMultilevel": { "type": "string", "enum": ["temperature", "luminosity", "energy", "humidity", "barometer","seismic","ultraviolet","acceleration_x","acceleration_y","acceleration_z"], "default": "temperature", "required": true, "dependencies": "deviceType" }, "setterOn_toggleButton": { "type": "string", "required": true, "dependencies": "deviceType" }, "getter_sensorBinary": { "type": "string", "required": true, "dependencies": "deviceType" }, "getterPollInterval_sensorBinary": { "type": "integer", "required": false, "dependencies": "deviceType" }, "getter_sensorMultilevel": { "type": "string", "required": true, "dependencies": "deviceType" }, "getterPollInterval_sensorMultilevel": { "type": "integer", "required": false, "dependencies": "deviceType" }, "scale_sensorMultilevel": { "type": "string", "required": false, "dependencies": "deviceType" }, "setterOn_switchBinary": { "type": "string", "required": true, "dependencies": "deviceType" }, "setterOff_switchBinary": { "type": "string", "required": true, "dependencies": "deviceType" }, "getter_switchBinary": { "type": "string", "required": false, "dependencies": "deviceType" }, "getterPollInterval_switchBinary": { "type": "integer", "required": false, "dependencies": "deviceType" }, "setterLevel_switchMultilevel": { "type": "string", "required": true, "dependencies": "deviceType" }, "getter_switchMultilevel": { "type": "string", "required": false, "dependencies": "deviceType" }, "getterPollInterval_switchMultilevel": { "type": "integer", "required": false, "dependencies": "deviceType" }, "updateOnAction": { "type": "boolean", "required": true, "dependencies": "deviceType" }, "skipEventIfSameValue": { "type": "boolean", "required": true } } }, "options": { "fields": { "deviceType": { "type": "select" }, "iconSensorBinary": { "type": "select", "label": "__l_icon__", "dependencies": { "deviceType": "sensorBinary" } }, "iconSensorMultilevel": { "type": "select", "label": "__l_icon__", "dependencies": { "deviceType": "sensorMultilevel" } }, "setterOn_toggleButton": { "label": "__l_setterOn_toggleButton__", "placeholder": "somefunction()", "dependencies": { "deviceType": "toggleButton" } }, "getter_sensorBinary": { "label": "__l_getter_value__", "placeholder": "somefunction()", "helper": "__h_getter_sensorBinary__", "dependencies": { "deviceType": "sensorBinary" } }, "getterPollInterval_sensorBinary": { "label": "__l_getterPollInterval__", "helper": "__h_getterPollInterval__", "dependencies": { "deviceType": "sensorBinary" } }, "getter_sensorMultilevel": { "label": "__l_getter_value__", "placeholder": "somefunction()", "helper": "__h_getter_sensorMultilevel__", "dependencies": { "deviceType": "sensorMultilevel" } }, "getterPollInterval_sensorMultilevel": { "label": "__l_getterPollInterval__", "helper": "__h_getterPollInterval__", "dependencies": { "deviceType": "sensorMultilevel" } }, "scale_sensorMultilevel": { "label": "__l_scale_sensorMultilevel__", "dependencies": { "deviceType": "sensorMultilevel" } }, "setterOn_switchBinary": { "label": "__l_setterOn_switchBinary__", "placeholder": "somefunction(%%)", "helper": "__h_setterOn_switchBinary__", "dependencies": { "deviceType": "switchBinary" } }, "setterOff_switchBinary": { "label": "__l_setterOff_switchBinary__", "placeholder": "somefunction(%%)", "helper": "__h_setterOff_switchBinary__", "dependencies": { "deviceType": "switchBinary" } }, "getter_switchBinary": { "label": "__l_getter_value__", "placeholder": "somefunction()", "helper": "__h_getter_switchBinary__", "dependencies": { "deviceType": "switchBinary" } }, "getterPollInterval_switchBinary": { "label": "__l_getterPollInterval__", "helper": "__h_getterPollInterval__", "dependencies": { "deviceType": "switchBinary" } }, "setterLevel_switchMultilevel": { "label": "__l_setterLevel_switchMultilevel__", "placeholder": "somefunction(%%)", "helper": "__h_setterLevel_switchMultilevel__", "dependencies": { "deviceType": "switchMultilevel" } }, "getter_switchMultilevel": { "label": "__l_getter_value__", "placeholder": "somefunction()", "helper": "__h_getter_switchMultilevel__", "dependencies": { "deviceType": "switchMultilevel" } }, "getterPollInterval_switchMultilevel": { "label": "__l_getterPollInterval__", "helper": "__h_getterPollInterval__", "dependencies": { "deviceType": "switchMultilevel" } }, "updateOnAction": { "type": "checkbox", "rightLabel": "__rl_updateOnAction__", "helper": "__h_updateOnAction__", "dependencies": { "deviceType": [ "switchBinary", "switchMultilevel"] } }, "skipEventIfSameValue": { "type": "checkbox", "rightLabel": "__rl_skipEventIfSameValue__", "helper": "__h_skipEventIfSameValue__" } } } } ================================================ FILE: modules/CorrectValue/index.js ================================================ /*** CorrectValue Z-Way Home Automation module ************************************* Version: 1.0.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Yurkin Vitaliy Description: Correct value of sensors like temperature, humidity, etc. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function CorrectValue (id, controller) { // Call superconstructor first (AutomationModule) CorrectValue.super_.call(this, id, controller); }; inherits(CorrectValue, AutomationModule); _module = CorrectValue; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- CorrectValue.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) CorrectValue.super_.prototype.init.call(this, config); // Remember "this" for detached callbacks (such as event listener callbacks) var self = this; this.handler = function (vDev) { var value = parseFloat(vDev.get("metrics:level")); self.vDevNew.set("metrics:level", value + parseFloat(self.config.correctionValue)); }; this.createCorrectedDev = function () { var sensor = self.controller.devices.get(self.config.device); sensor.set("visibility", !(self.config.hide)); self.vDevNew = self.controller.devices.create({ deviceId: self.id+"_CorrectValue_"+self.config.correctionValue, defaults: { deviceType: "sensorMultilevel", metrics: { probeTitle: sensor.get("metrics:probeTitle"), scaleTitle: sensor.get("metrics:scaleTitle"), level: parseFloat(sensor.get("metrics:level")) + parseFloat(self.config.correctionValue), icon: sensor.get("metrics:icon"), title: sensor.get("metrics:title") + " " + self.config.correctionValue, } }, overlay: {}, moduleId: self.id }); // Setup metric update event listener self.controller.devices.on(self.config.device, 'change:metrics:level', self.handler); } this.deviceCreated = function (vDev) { if (vDev.id === self.config.device) { self.createCorrectedDev(); } } this.deviceRemoved = function (vDev) { if (vDev.id === self.controller.devices.get(self.config.device)) { self.controller.devices.remove(self.id+"_Corrected_"+self.config.correctionValue); } } // Bind to event "Added new device" -- > Bind to new device this.controller.devices.on('created', this.deviceCreated); // Bind to event "Removed device" --> Unbind device this.controller.devices.on('removed', this.deviceRemoved); if (this.controller.devices.get(this.config.device)) { self.createCorrectedDev(); } }; CorrectValue.prototype.stop = function () { CorrectValue.super_.prototype.stop.call(this); this.controller.devices.off(this.config.device, 'change:metrics:level', this.handler); this.controller.devices.remove(this.id+"_Corrected_"+this.config.correctionValue); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- // This module doesn't have any additional methods ================================================ FILE: modules/CorrectValue/lang/de.json ================================================ { "m_title":"Correct sensor value", "m_descr":"If sensor show not real value, you can correct it", "l_dev":"Sensor", "l_correctionValue":"Correction value", "h_correctionValue":"As example, +4 or -3", "rl_hide_original":"Hide original sensor" } ================================================ FILE: modules/CorrectValue/lang/en.json ================================================ { "m_title":"Correct sensor value", "m_descr":"If sensor show not real value, you can correct it", "l_dev":"Sensor", "l_correctionValue":"Correction value", "h_correctionValue":"As example, +4 or -3", "rl_hide_original":"Hide original sensor" } ================================================ FILE: modules/CorrectValue/lang/ru.json ================================================ { "m_title":"Корректировка значения датчика", "m_descr":"Если датчик отображает не точное значение, то можно задать значение корректировки, как + так и -.", "l_dev":"Датчик", "l_correctionValue":"Корректировка", "h_correctionValue":"Например, +4 или -3", "rl_hide_original":"Скрыть оригинальный датчик" } ================================================ FILE: modules/CorrectValue/module.json ================================================ { "singleton": false, "dependencies": [], "category": "device_enhancements", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "CorrectValue", "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__", "correctionValue": 0, "device": "", "hide":true }, "schema": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorMultilevel:deviceId", "required": true }, "correctionValue": { "type": "number", "required": true }, "hide": { "type": "boolean", "required": true } }, "required": true }, "options": { "fields": { "device": { "label": "__l_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorMultilevel:deviceName" }, "correctionValue": { "label": "__l_correctionValue__", "helper": "__h_correctionValue__" }, "hide": { "type": "checkbox", "rightLabel": "__rl_hide_original__" } } } } ================================================ FILE: modules/CounterTriggeringSensor/index.js ================================================ /*** Counter triggering binary sensor Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Yurkin Vitaliy Description: The module considers how many times have triggered the sensor. Used in the calculation of water flow. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function CounterTriggeringSensor (id, controller) { // Call superconstructor first (AutomationModule) CounterTriggeringSensor.super_.call(this, id, controller); } inherits(CounterTriggeringSensor, AutomationModule); _module = CounterTriggeringSensor; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- CounterTriggeringSensor.prototype.init = function (config) { CounterTriggeringSensor.super_.prototype.init.call(this, config); var self = this; this.vDev = this.controller.devices.create({ deviceId: "CounterTriggeringSensor_" + this.id, defaults: { deviceType: "sensorMultilevel", metrics: { level: this.config.initialValue, icon: "meter", title: self.getInstanceTitle() } }, overlay: { metrics: { scaleTitle: this.config.scaleTitle } }, handler: function(command, args) {console.log("CounterTriggeringSensor_" + this.id + " updated")}, moduleId: this.id }); // Plus 1, when binary sensor triggered this.handler = function (sensor) { if (sensor.get("metrics:level") === self.config.eventSensor) { var currentValue = parseFloat(self.vDev.get("metrics:level")); if (isNaN(currentValue)) { currentValue = 0; } currentValue = currentValue + self.config.valueToAdd self.vDev.set("metrics:level", currentValue); } } // Setup metric update event listener this.controller.devices.on(this.config.binarySensor, 'change:metrics:level', this.handler); }; CounterTriggeringSensor.prototype.stop = function () { if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } this.controller.devices.off(this.config.binarySensor, 'change:metrics:level', this.handler); CounterTriggeringSensor.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/CounterTriggeringSensor/lang/de.json ================================================ { "m_title":"Counter triggering binary sensor", "m_descr":"The module considers how many times have triggered the sensor. Used in the calculation of water flow.", "l_BinarySensor":"Binary sensor", "on":"An", "off":"Aus", "l_EventSensor":"Event from sensor", "l_InitialValue":"Initial value", "l_ValueToAdd":"Value to add", "l_ScaleTitle":"Sensoreinheit" } ================================================ FILE: modules/CounterTriggeringSensor/lang/en.json ================================================ { "m_title":"Counter triggering binary sensor", "m_descr":"The module considers how many times have triggered the sensor. Used in the calculation of water flow.", "l_BinarySensor":"Binary sensor", "on":"On", "off":"Off", "l_EventSensor":"Event from sensor", "l_InitialValue":"Initial value", "l_ValueToAdd":"Value to add", "l_ScaleTitle":"Sensor scale" } ================================================ FILE: modules/CounterTriggeringSensor/lang/ru.json ================================================ { "m_title":"Счетчик срабатывания бинарного датчика", "m_descr":"Модуль считает сколько раз сработал датчик. Используется в подсчете расхода воды.", "l_BinarySensor":"Бинарный датчик", "on":"Включить", "off":"Выключить", "l_EventSensor":"Событие датчика", "l_InitialValue":"Начальное значение", "l_ValueToAdd":"Прибавляемое значение", "l_ScaleTitle":"Единица измерения" } ================================================ FILE: modules/CounterTriggeringSensor/module.json ================================================ { "dependencies": [], "singleton": false, "category": "automation_basic", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"CounterTriggeringSensor", "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__", "binarySensor": null, "eventSensor": "on", "initialValue":"0.0", "valueToAdd": "1.0", "scaleTitle": "L" }, "schema": { "type": "object", "properties": { "binarySensor": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorBinary:deviceId,namespaces:devices_switchBinary:deviceId", "required": true }, "eventSensor": { "type": "integer", "required": true, "enum": ["on", "off"] }, "initialValue": { "type": "number", "required": true }, "valueToAdd": { "type": "number", "required": true }, "scaleTitle": { "type": "string", "required": true } }, "required": false }, "options": { "fields": { "binarySensor": { "label": "__l_BinarySensor__", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorBinary:deviceName,namespaces:devices_switchBinary:deviceName" }, "eventSensor": { "label": "__l_EventSensor__", "optionLabels": ["__on__", "__off__"] }, "initialValue": { "label": "__l_InitialValue__" }, "valueToAdd": { "label": "__l_ValueToAdd__" }, "scaleTitle": { "label": "__l_ScaleTitle__" } } } } ================================================ FILE: modules/Cron/index.js ================================================ /*** Cron ZAutomation module ************************************************** Version: 1.0.0 (c) Z-Wave.Me, 2013 ------------------------------------------------------------------------------- Authors: Serguei Poltorak Gregory Sitnin Description: This modules impements Unix cron like scheduler. Emits: - [any event asked by other modules] Listens: - cron.addTask - String: emitted event name - Object: task schedule - Mixed (null = any; [from, to, divider]; Number = exact): minute - Mixed: hour - Mixed: weekDay - Mixed: day - Mixed: month - Mixed (null; Array): eventArguments - cron.removeTask - String: emitted event name to be removed ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function Cron (id, controller) { // Call superconstructor first (AutomationModule) Cron.super_.call(this, id, controller); // Create instance variables this.schedules = {}; this.timer; this.bounds = { minute: { from: 0, to: 59 }, hour: { from: 0, to: 23 }, day: { from: 0, to: 30 }, weekDay: { from: 0, to: 6 }, month: { from: 0, to: 11 } }; this.shift = { minute: 0, hour: 0, day: 1, weekDay: 0, month: 1 }; this.lastFired = 0; } inherits(Cron, AutomationModule); _module = Cron; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- Cron.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) Cron.super_.prototype.init.call(this, config); // Remember "this" for detached callbacks (such as event listener callbacks) var self = this; // Setup metric update event listener this.controller.on('cron.addTask', function (eventName, schedule, eventArgs) { if (!self.schedules.hasOwnProperty(eventName)) { self.schedules[eventName] = []; } var time = {"time": schedule}; self.schedules[eventName].push([self.renderSchedule(schedule), time, eventArgs]); self.config.schedules = self.schedules; // console.log(JSON.stringify(self.renderSchedule(schedule))); // debug ! }); this.controller.on('cron.removeTask', function (eventName) { delete self.schedules[eventName]; self.config.schedules = self.schedules; }); this.timer = setInterval(function () { var date = new Date(); var timestampMin = Math.floor(date.getTime() / 60000); if (self.lastFired === timestampMin) return; // this minute is already handled self.lastFired = timestampMin; var curTime = { minute: date.getMinutes(), hour: date.getHours(), day: date.getDate() - 1, weekDay: date.getDay(), // NOTE! Sunday is 0. Handle this in the UI!!!! !@#%^& month: date.getMonth() }; Object.keys(self.schedules).forEach(function (eventName) { var schedules_arr = self.schedules[eventName]; schedules_arr.forEach(function (schedule_pair) { var flag = true; var keys = ["minute", "hour", "day", "weekDay", "month"]; for (var k = 0; k < keys.length; k++) { if (-1 === schedule_pair[0][keys[k]].indexOf(curTime[keys[k]])) { flag = false; break; } } if (flag) { self.controller.emit.apply(self.controller, [eventName].concat(schedule_pair[1])); } }); }); }, 1000); }; Cron.prototype.stop = function () { Cron.super_.prototype.stop.call(this); clearInterval(this.timer); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- Cron.prototype.renderTimeRange = function (range, bounds, shift) { var r = [], f, t, s; if (range === null) { f = bounds.from; t = bounds.to; s = 1; } else if (typeof(range) === "number") { if (range - shift < bounds.from || range - shift > bounds.to) { console.log("ERROR: value " + range.toString() + " is out of range " + bounds.from.toString() + ".." + bounds.to.toString()); return []; } f = range - shift; t = range - shift; s = 1; } else if (typeof(range) === "object" && Array.isArray(range) && range.length == 3) { if (range[0] - shift < bounds.from || range[1] - shift > bounds.to || range[0] > range[1]) { console.log("ERROR: value " + range[0].toString() + ".." + range[1].toString() + " is should not be outside of range " + bounds.from.toString() + ".." + bounds.to.toString()); return []; } f = range[0] - shift; t = range[1] - shift; s = range[2]; } for (var n = f; n <= t; n+=s) { r.push(n); } return r; }; Cron.prototype.renderSchedule = function (schedule) { var renderedSchedule = {}; var self = this; ["minute", "hour", "day", "weekDay", "month"].forEach(function(key) { renderedSchedule[key] = self.renderTimeRange(schedule[key], self.bounds[key], self.shift[key]); }); return renderedSchedule; }; ================================================ FILE: modules/Cron/lang/de.json ================================================ { "m_title":"Systemuhr (CRON)", "m_descr":"Diese App generiert die ganze Zeit gesteuerte Ereignisse und sollte aus diesem Grund stets aktiviert sein. In der Verwendung mit diesem Modul kann sehr viel falsch gemacht werden, daher lassen Sie es unberührt, solange es nicht wirklich benötigt wird." } ================================================ FILE: modules/Cron/lang/en.json ================================================ { "m_title":"System Clock (CRON)", "m_descr":"This app generates all time driven events and should therefore be active all the time. There is nothing to be done right with the module but a lot of things that can be done wrong. This means, don't touch it unless it is really needed." } ================================================ FILE: modules/Cron/lang/ru.json ================================================ { "m_title":"Cron планировщик", "m_descr":"Планировщик используют другие модули." } ================================================ FILE: modules/Cron/module.json ================================================ { "singleton": true, "dependencies": [], "category": "basic_gateway_modules", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "Cron", "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__", "schedules": {} }, "schema": { }, "options": { } } ================================================ FILE: modules/CustomUserCode/index.js ================================================ /*** CustomUserCode ZAutomation module **************************************** Version: 1.0.0 (c) Z-Wave.Me, 2013 ------------------------------------------------------------------------------- Author: Poltorak Serguei Description: This module executes custom JS code listed in configuration parameters. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function CustomUserCode (id, controller) { // Call superconstructor first (AutomationModule) CustomUserCode.super_.call(this, id, controller); } inherits(CustomUserCode, AutomationModule); _module = CustomUserCode; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- CustomUserCode.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) CustomUserCode.super_.prototype.init.call(this, config); // TODO: executeJS errors are impossible to catch! //try { executeJS(this.config.customCode); //} catch (e) { // var langFile = this.loadModuleLang(), // values = this.get("metrics:title"); // // this.addNotification("warning", langFile.err_load + values, "module"); //} }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- // This module has no additional methods ================================================ FILE: modules/CustomUserCode/lang/de.json ================================================ { "m_title":"Benutzer JavaScript Code", "m_descr":"Die Anwendung dieser App erfordert solide Kenntnisse in der JavaScript Programmierung. Der in dieser App zur Verfügung gestellte Code wird ausgeführt, wenn die App geladen wird. Ein Element kann nicht erstellt werden, jedoch ist es möglich, auf die Inhaber der Netzwerkdaten zuzugreifen. Diese App richtet sich ausschließlich an JavaScript Experten.", "l_options":"Ihr JavaScript Code:", "err_load":"Das Skript konnte nicht geladen werden: " } ================================================ FILE: modules/CustomUserCode/lang/en.json ================================================ { "m_title":"Load custom JavaScript code", "m_descr":"The use of this app requires solid knowledge of JavaScript programming. The code provided in this app will be executed when the app is loaded. There is no way to create an element but its possible to access the Network data holders. This app is for JavaScript professionals only.", "err_load":"Failed to load custom user code: ", "l_options":"Your JavaScript code:" } ================================================ FILE: modules/CustomUserCode/lang/ru.json ================================================ { "m_title":"Пользовательский JavaScript код", "m_descr":"Позволяет загружать пользовательский JavaScript код после загрузки системы.", "err_load":"Не удалось загрузить пользовательский JS код: ", "l_options":"Ваш JavaScript код:" } ================================================ FILE: modules/CustomUserCode/module.json ================================================ { "singleton": false, "dependencies": [], "category": "developers_stuff", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "CustomUserCode", "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__", "customCode": "" }, "schema": { "type": "object", "properties": { "customCode": { "type": "string" } }, "required": false }, "options": { "toolbarSticky": true, "fields": { "customCode": { "type": "textarea", "label": "__l_options__", "cols": 40 } } } } ================================================ FILE: modules/CustomUserCodeLoader/index.js ================================================ /*** CustomUserCodeLoader ZAutomation module **************************************** Version: 1.0.0 (c) Z-Wave.Me, 2013 ------------------------------------------------------------------------------- Author: Poltorak Serguei Description: This module executes custom JS code listed in configuration parameters. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function CustomUserCodeLoader (id, controller) { // Call superconstructor first (AutomationModule) CustomUserCodeLoader.super_.call(this, id, controller); } inherits(CustomUserCodeLoader, AutomationModule); _module = CustomUserCodeLoader; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- CustomUserCodeLoader.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) CustomUserCodeLoader.super_.prototype.init.call(this, config); this.config.customCodeFiles.forEach(function (file) { if (!file) return; var stat = fs.stat(file); if (stat && stat.type === "file") { executeFile(file); } else { console.log("File " + file + " not found"); } }); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- // This module has no additional methods ================================================ FILE: modules/CustomUserCodeLoader/lang/de.json ================================================ { "m_title":"Lade JavaScript-Dateien von Benutzer", "m_descr":"Die Anwendung dieser App erfordert solide Kenntnisse in der JavaScript Programmierung. Der in der Datei bereitgestellte Code, auf welchen die App Einstellung hinweist, wird ausgeführt, wenn die App geladen wird. Ein Element kann nicht erstellt werden, jedoch ist es möglich, auf die Inhaber der Netzwerkdaten zuzugreifen. Diese App richtet sich ausschließlich an JavaScript Experten.", "customCodeFiles_title":"Dateiname", "err_load":"JavaScript-Datei konnte nicht geladen werden: ", "h_options":"Fügt Pfad relativ zum 'automation' Verzeichnis hinzu." } ================================================ FILE: modules/CustomUserCodeLoader/lang/en.json ================================================ { "m_title":"Load custom JavaScript file", "m_descr":"The use of this app requires solid knowledge of JavaScript programming. The code provided in the file the app setup points to will be executed when the app is loaded. There is no way to create an element but its possible to access the Network data holders. This app is for JavaScript professionals only.", "customCodeFiles_title":"File name", "err_load":"Failed to load JavaScript file: ", "h_options":"Add paths to files relative to automation/ folder." } ================================================ FILE: modules/CustomUserCodeLoader/lang/ru.json ================================================ { "m_title":"Пользовательский JavaScript файл", "m_descr":"Позволяет загружать пользовательские JavaScript файлы из папки automation/ после загрузки системы.", "customCodeFiles_title":"Имя файла", "err_load":"Не удалось загрузить пользовательский JS файл: ", "h_options":"Указывайте полный путь до JS файла относительно папки automation/." } ================================================ FILE: modules/CustomUserCodeLoader/module.json ================================================ { "singleton": false, "dependencies": [], "category": "developers_stuff", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "CustomUserCodeLoader", "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__", "customCodeFiles": [ ] }, "schema": { "type": "object", "properties": { "customCodeFiles": { "type": "array", "items": { "title": "__customCodeFiles_title__", "type": "string" } } }, "required": false }, "options": { "fields": { "customCodeFiles": { "type": "array", "helper": "__h_options__" } } } } ================================================ FILE: modules/CustomUserCodeZWay/index.js ================================================ /*** CustomUserCodeZWay ZAutomation module **************************************** Version: 1.0.0 (c) Z-Wave.Me, 2015 ------------------------------------------------------------------------------- Author: Poltorak Serguei Description: This module executes custom JS code listed in configuration parameters on Z-Way start/stop. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function CustomUserCodeZWay (id, controller) { // Call superconstructor first (AutomationModule) CustomUserCodeZWay.super_.call(this, id, controller); } inherits(CustomUserCodeZWay, AutomationModule); _module = CustomUserCodeZWay; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- CustomUserCodeZWay.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) CustomUserCodeZWay.super_.prototype.init.call(this, config); var self = this; this.loaded = false; this.bindZWay = function (zwayName) { if (zwayName !== self.config.zway) { return; } self.loaded = true; // TODO: executeJS errors are impossible to catch! //try { executeJS(self.config.customCodeOnLoad); //} catch (e) { // var langFile = this.loadModuleLang(); // // this.addNotification("warning", langFile.err_load, "module"); //} }; this.unbindZWay = function (zwayName) { if (zwayName !== self.config.zway) { return; } self.loaded = false; // TODO: executeJS errors are impossible to catch! //try { executeJS(self.config.customCodeOnUnload); //} catch (e) { // var langFile = this.loadModuleLang(); // // this.addNotification("warning", langFile.err_load, "module"); //} }; if (global.ZWave && global.ZWave[this.config.zway]) { this.bindZWay(this.config.zway); } global.controller.on("ZWave.register", this.bindZWay); global.controller.on("ZWave.unregister", this.unbindZWay); }; CustomUserCodeZWay.prototype.stop = function() { if (this.loaded === true) { this.unbindZWay(this.config.zway); } global.controller.off("ZWave.register", this.bindZWay); global.controller.off("ZWave.unregister", this.unbindZWay); CustomUserCodeZWay.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- // This module has no additional methods ================================================ FILE: modules/CustomUserCodeZWay/lang/de.json ================================================ { "m_title":"Benutzer JavaScript Code für Z-Wave Applikation", "m_descr":"Die Anwendung dieser App erfordert solide Kenntnisse in der JavaScript Programmierung. Der in dieser App zur Verfügung gestellte Code wird ausgeführt, wenn die App geladen wird. Ein Element kann nicht erstellt werden, jedoch ist es möglich, auf die Inhaber der Netzwerkdaten zuzugreifen. Diese App richtet sich ausschließlich an JavaScript Experten.", "err_load":"Das Skript konnte nicht geladen werden: " "l_choose_zway":"Z-Way instance for which execute your code:", "l_customCodeOnLoad":"Ihr JavaScript code on Z-Way load:", "l_customCodeOnUnload":"Ihr JavaScript code on Z-Way unload (for clear restart of Z-Wave application or on UZB plug/unplug):" } ================================================ FILE: modules/CustomUserCodeZWay/lang/en.json ================================================ { "m_title":"Load custom JavaScript code for Z-Wave application", "m_descr":"The use of this app requires solid knowledge of JavaScript programming. The code provided in this app will be executed when Z-Wave module is loaded. There is no way to create an element but its possible to access the Network data holders. This app is for JavaScript professionals only.", "err_load":"Failed to load custom user code: ", "l_choose_zway":"Z-Way instance for which execute your code:", "l_customCodeOnLoad":"Your JavaScript code on Z-Way load:", "l_customCodeOnUnload":"Your JavaScript code on Z-Way unload (for clear restart of Z-Wave application or on UZB plug/unplug):" } ================================================ FILE: modules/CustomUserCodeZWay/lang/ru.json ================================================ { "m_title":"Пользовательский JavaScript код для приложения Z-Wave", "m_descr":"Позволяет загружать пользовательский JavaScript код после загрузки приложения Z-Wave.", "err_load":"Не удалось загрузить пользовательский JS код: ", "l_choose_zway":"Z-Way, для которого выполнять скрипт:", "l_customCodeOnLoad":"Ваш JavaScript код при загрузке Z-Way:", "l_customCodeOnUnload":"Ваш JavaScript код при выгрузке Z-Way (для перезагрузки приложения Z-Wave или при отключении/подключении стика UZB):" } ================================================ FILE: modules/CustomUserCodeZWay/module.json ================================================ { "singleton": false, "dependencies": [], "category": "developers_stuff", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "CustomUserCodeZWay", "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__", "zway": "", "customCodeOnLoad": "", "customCodeOnUnload": "" }, "schema": { "type": "object", "properties": { "zway": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:zways:zwayName", "required": true }, "customCodeOnLoad": { "type": "string" }, "customCodeOnUnload": { "type": "string" } }, "required": false }, "options": { "toolbarSticky": true, "fields": { "zway": { "type": "select", "label": "__l_choose_zway__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:zways:zwayName" }, "customCodeOnLoad": { "type": "textarea", "label": "__l_customCodeOnLoad__", "cols": 40 }, "customCodeOnUnload": { "type": "textarea", "label": "__l_customCodeOnUnload__", "cols": 40 } } } } ================================================ FILE: modules/DecomposeRGB/index.js ================================================ /*** DecomposeRGB Z-Way HA module ******************************************* Version: 1.0.0 (c) Z-Wave.Me, 2021 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: Decompose device RGB in several dimmers ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function DecomposeRGB (id, controller) { // Call superconstructor first (AutomationModule) DecomposeRGB.super_.call(this, id, controller); } inherits(DecomposeRGB, AutomationModule); _module = DecomposeRGB; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- DecomposeRGB.prototype.init = function (config) { DecomposeRGB.super_.prototype.init.call(this, config); var self = this; function levelToColor(command, args) { switch (command) { case "on": return 255; case "off": return 0; case "exact": return parseInt(args.level) * 255.0/99.0 default: return 0; } } function colorToLevel(color) { return Math.round(color * 99.0 / 255.0); } var title = this.getInstanceTitle(), devId = "DecomposeRGB_" + this.id + "_", defaults = { deviceType: "switchMultilevel", metrics: { icon: 'multilevel', title: '', level: 0 } }; defaults.metrics.title = title + ' Red ' + this.id; this.vDevR = this.controller.devices.create({ deviceId: devId + "R", defaults: defaults, overlay: {}, handler: function (command, args) { self.handleLevel({ red: levelToColor(command, args) }); }, moduleId: this.id }); defaults.metrics.title = title + 'Green ' + this.id; this.vDevG = this.controller.devices.create({ deviceId: devId + "G", defaults: defaults, overlay: {}, handler: function (command, args) { self.handleLevel({ green: levelToColor(command, args) }); }, moduleId: this.id }); defaults.metrics.title = title + 'Blue ' + this.id; this.vDevB = this.controller.devices.create({ deviceId: devId + "B", defaults: defaults, overlay: {}, handler: function (command, args) { self.handleLevel({ blue: levelToColor(command, args) }); }, moduleId: this.id }); this.handleLevel = function(colorObject) { var vDevRGB = self.controller.devices.get(self.config.rgb); var c = vDevRGB.get('metrics:color'); vDevRGB.performCommand("exact", _.extend({ red: c.r, green: c.g, blue: c.b }, colorObject)); }; this.handleRGB = function (vDev) { if (vDev.get("metrics:level") === "off") { self.vDevR.set("metrics:level", 0); self.vDevG.set("metrics:level", 0); self.vDevB.set("metrics:level", 0); } else { self.vDevR.set("metrics:level", colorToLevel(vDev.get("metrics:color:r"))); self.vDevG.set("metrics:level", colorToLevel(vDev.get("metrics:color:g"))); self.vDevB.set("metrics:level", colorToLevel(vDev.get("metrics:color:b"))); } }; this.controller.devices.on(this.config.rgb, "change:metrics:level", this.handleRGB); }; DecomposeRGB.prototype.stop = function () { this.controller.devices.off(this.config.rgb, "change:metrics:level", this.handleRGB); this.handle = null; if (this.vDevR) { this.controller.devices.remove(this.vDevR.id); this.vDevR = null; } if (this.vDevG) { this.controller.devices.remove(this.vDevG.id); this.vDevG = null; } if (this.vDevB) { this.controller.devices.remove(this.vDevB.id); this.vDevB = null; } DecomposeRGB.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/DecomposeRGB/lang/en.json ================================================ { "m_title":"Decompose RGB on dimmers", "m_descr":"Decompose RGB on three different dimmers to control them individually by colors", "l_rgb":"RGB device" } ================================================ FILE: modules/DecomposeRGB/lang/ru.json ================================================ { "m_title":"Разложение RGB на диммеры", "m_descr":"Раскладывает RGB устройство на три диммера для индивидуального управления каждым цветом", "l_rgb":"Устройство RGB" } ================================================ FILE: modules/DecomposeRGB/module.json ================================================ { "dependencies": [], "singleton": false, "category": "legacy_products_workaround", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"DecomposeRGB", "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__", "rgb": null }, "schema": { "type": "object", "properties": { "rgb": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchRGBW:deviceId", "required": true } }, "required": true }, "options": { "fields": { "rgb": { "label": "__l_rgb__", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchRGBW:deviceName" } } } } ================================================ FILE: modules/DelayedScene/index.js ================================================ /*** DelayedScene Z-Way HA module ******************************************* Version: 1.0.0 (c) Z-Wave.Me, 2014 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: Implements light scene based on virtual devices of type dimmer, switch or anothe scene ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function DelayedScene (id, controller) { // Call superconstructor first (AutomationModule) DelayedScene.super_.call(this, id, controller); } inherits(DelayedScene, AutomationModule); _module = DelayedScene; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- DelayedScene.prototype.init = function (config) { DelayedScene.super_.prototype.init.call(this, config); var self = this; this.timer = null; this.timerHandler = this.handler; this.triggerHandler = function() { if (self.config.singleTimer && self.timer) { clearTimeout(self.timer); } self.timer = setTimeout(function() { self.timerHandler(); }, self.config.delay * 1000); // we do not care about clearing setTimouts in non-singleTimer mode. Just emptying handler if need. // If someone knows how to track many handlers and remove them from a list upon fire - you are welcome to improve the code. }; this.controller.devices.on(this.config.triggerScene, "change:metrics:level", this.triggerHandler); }; DelayedScene.prototype.stop = function () { this.controller.devices.off(this.config.triggerScene, "change:metrics:level", this.triggerHandler); if (this.timer) { clearTimeout(this.timer); } this.timerHandler = function () {}; // this is to clear actions on all remaining setTimouts without clearing them. // We are emtying not the prototype, but function on instance DelayedScene.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- DelayedScene.prototype.handler = function() { var vDev = this.controller.devices.get(this.config.delayedScene); if (vDev) { vDev.performCommand("on"); } }; ================================================ FILE: modules/DelayedScene/lang/de.json ================================================ { "m_title":"Verzögerte Aktion", "m_descr":"Diese App kann nach einer gewissen Verzögerungszeit auf eine bereits ausgelöste Szene eine weitere Szene auslösen.Zur Ausführung bedarf die App mindestens zweier Szenen (App Lichtszene): der auslösenden sowie der auszulösenden Szene. Die Verzögerungszeit zwischen beider Szenen kann individuell eingestellt werden.

Einstellungen:
  • Wählen Sie die Szene, die eine weitere Szene auslösen soll.
  • Wählen Sie die Szene, die durch die Erste ausgelöst werden soll.
  • Definieren Sie die zeitliche Verzögerung in Sekunden zwischen beiden Szenen.
", "r_l_options":"Starte den Timer neu, wenn eine Szene/Aktion ausgelöst wird.", "l_triggerScene":"Szene/Aktion die den Timer startet.", "l_delayedScene":"Verzögerte Szene/Aktion:", "l_delay":"Verzögerung in Sekunden:" } ================================================ FILE: modules/DelayedScene/lang/en.json ================================================ { "m_title":"Delayed Action", "m_descr":"This app can trigger one scene after a defined delay after another scene was triggered. The app requires at least two scenes (app Light Scene) to function: The triggering scene and the triggered scene. The delay between the two scenes can be defines too.

Settings:
  • Select the scene that shall trigger another scene
  • Select the scene that shall be triggered by the first scene
  • Define the delay in Seconds between the two scenes
", "r_l_options":"Restart timer on trigger instead of starting new timer", "l_triggerScene":"Scene that trigger timer start", "l_delayedScene":"Delayed scene:", "l_delay":"Delay in seconds:" } ================================================ FILE: modules/DelayedScene/lang/ru.json ================================================ { "m_title":"Отложенная сцена", "m_descr":"Запуск сцен с задержкой.", "r_l_options":"Перезапустить таймер при срабатывании, вместо запуска нового таймера", "l_triggerScene":"Сцена, которая запускается при старте таймера", "l_delayedScene":"Отложенная сцена:", "l_delay":"Задержка в секундах:" } ================================================ FILE: modules/DelayedScene/module.json ================================================ { "singleton": false, "dependencies": [], "category": "automation_basic", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "DelayedScene", "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__", "singleTimer": true, "triggerScene": "", "delayedScene": "" }, "schema": { "type": "object", "properties": { "singleTimer": { "type": "boolean" }, "triggerScene": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true }, "delayedScene": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true }, "delay": { "type": "integer", "minimum": 1 } }, "required": false }, "options": { "fields": { "singleTimer": { "label": "", "rightLabel": "__r_l_options__" }, "triggerScene": { "label": "__l_triggerScene__", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" }, "delayedScene": { "label": "__l_delayedScene__", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" }, "delay": { "label": "__l_delay__", "type": "integer" } } } } ================================================ FILE: modules/DeviceHistory/index.js ================================================ /*** DeviceHistory Z-Way HA module ******************************************* Version: 2.0.0 (c) Z-Wave.Me, 2015 ----------------------------------------------------------------------------- Author: Niels Roche Description: Creates a module that stores 24h data of specific devices. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function DeviceHistory (id, controller) { // Call supervarructor 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); _module = DeviceHistory; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- _.extend(DeviceHistory.prototype, { init: function (config) { DeviceHistory.super_.prototype.init.call(this, config); var self = this; this.defineHandlers(); this.externalAPIAllow(); global["HistoryAPI"] = this.HistoryAPI; // set history of excluded devices to false this.config.devices.forEach(function (devId) { var vDevd = self.controller.devices.get(devId); if (vDevd && vDevd.get('hasHistory') === true) { vDevd.set('hasHistory', false, {silent: true}); } }); this.initializeDevices(); // try to restore old histories var 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, true); 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) { var vDev = self.controller.devices.get(vDevId); if(vDev && vDev.get("hasHistory") === true){ vDev.set("hasHistory", false,{ silent: true }); } self.removeHistory(vDev); }); 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, true); }, 100, // check it every 100 entries 0, // save unlimited entries, since anyway it is clead based on time limit function (devHistory){ var now = Math.floor(Date.now() / 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.round(Date.now() / 1000), l: +dev.get("metrics:level") }; self.history[dev.id].push(change); } catch(e) { self.addNotification('error', self.langFile.err_store_history + dev.get('metrics:title') + " ERROR: " + e.toString(), 'module'); } }; _.forEach(this.config.allRegisteredDevices, function(vdevId){ var vDev = self.controller.devices.get(vdevId); // create new LimitedArray for self.setHistory(vdevId); // set hasHistory true if(vDev && vDev.get("hasHistory") === false){ vDev.set("hasHistory", true,{ silent: true }); } 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; this.assignHistory = function (vDev) { if (vDev && vDev.id && vDev.get('permanently_hidden') === false && // only none permanently_hidden 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, true); 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); // 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}); } } }; // 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; return this.controller.devices.filter(function(dev){ return dev.get('permanently_hidden') === false && // only none permanently_hidden 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)); } }); storedDevHistories.forEach(function(historyFileName){ if (!self.history[historyFileName]) { self.addNotification('info', historyFileName + self.langFile.info_transformation, 'module'); saveObject('history_' + historyFileName, null, true); } }); } }, // --------------- 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; this.HistoryAPI = function() { return { status: 400, body: "Bad HistoryAPI request " }; }; 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, now = Math.floor(Date.now() / 1000), body = { updateTime: now }; var sec = 0; var entries = []; var averageEntries = []; if (devId && self.history[devId]) { var 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 (var i = 0; i < items; i++) { var from = Math.floor(now - sec * (items - i)); var to = Math.floor(now - sec * (items - (i + 1))); // filter values between from and to var range = hist.filter(function (metric) { return metric.id >= from && metric.id <= to; }); var l = null; // calculate level if (range.length > 0) { l = range.reduce(function (acc, cur) { return +cur.l + acc; }, 0) / range.length; if (l === +l && l !== (l | 0)) { // round to one position after '.' l = +l.toPrecision(3); } } // push new averaged entry to averageEntries.push({ id: to, l: l }); } entries = averageEntries; } else { entries = hist; } // filter meta entries by since body.history = since > 0 ? entries.filter(function (metric) { return metric.id >= since; }) : entries; 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; }); }); } return self.prepareHTTPResponse(body); }; this.HistoryAPI.Delete = function(url, request) { var q = request.query || null, vDevId = q && q.hasOwnProperty('id')? q.id : null, body = { updateTime: Math.floor(Date.now() / 1000) }; if (vDevId && self.history[vDevId]) { self.history[vDevId].set([]); var vdev = self.controller.devices.get(vDevId); self.addNotification('info', self.langFile.info_clear_id_history + vdev? vdev.get('metrics:title') : vDevId, 'module'); body.code = 201; } 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; } return self.prepareHTTPResponse(body); }; } }); ================================================ FILE: modules/DeviceHistory/lang/de.json ================================================ { "m_title":"24 Stunden Gerätehistorie", "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: ", "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 ..." } ================================================ FILE: modules/DeviceHistory/lang/en.json ================================================ { "m_title":"24 Hours Device History", "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: ", "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 ..." } ================================================ FILE: modules/DeviceHistory/module.json ================================================ { "singleton" : true, "dependencies": ["Cron"], "category" : "device_enhancements", "author" : "Z-Wave.Me", "homepage" : "http://razberry.z-wave.me", "icon" : "icon.png", "moduleName":"DeviceHistory", "version" : "2.0.0", "maturity" : "stable", "repository" : { "type" : "git", "source" : "https://github.com/Z-Wave-Me/home-automation" }, "defaults" : { "title" : "__m_title__", "description" : "__m_descr__", "devices": [], "allRegisteredDevices": [] }, "schema": { "properties": { "devices": { "type": "array", "items": { "field":"enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorMultilevel:deviceId", "required": false } } } }, "options": { "fields": { "devices": { "label": "__l_devices__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorMultilevel:deviceName" } }, "helper": "__h_devices__" } } } } ================================================ FILE: modules/DeviceHistory/patchnotes.txt ================================================ v1.3.0 - remove support of virtual devices with binary states, because SHUI view has changed and makes it unecessary - minor refactoring v1.2.1 - type switchControl added - bugfix: preselecting virtual devices to add history - minor refactoring v1.2.0 - special handling of type toggleButton added - is 0 by default and 1 if triggerd - so if toggle was triggered during history interval it wil set a point on 0.5 - refactoring - hash entries removed - language keys added - set maturity to stable ================================================ FILE: modules/DummyDevice/index.js ================================================ /*** DummyDevice Z-Way HA module ******************************************* Version: 1.2.0 (c) Z-Wave.Me, 2022 ----------------------------------------------------------------------------- Author: Poltorak Serguei , Ray Glendenning Description: Creates a Dummy device ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function DummyDevice (id, controller) { // Call superconstructor first (AutomationModule) DummyDevice.super_.call(this, id, controller); } inherits(DummyDevice, AutomationModule); _module = DummyDevice; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- DummyDevice.prototype.init = function (config) { DummyDevice.super_.prototype.init.call(this, config); var lastLevel = loadObject("DummyDevice_" + this.id + "_level"); var self = this, deviceType, probeType, icon, level, configType = this.config.deviceType; switch(configType) { case "switchBinary": deviceType = "switchBinary"; probeType = "switch"; icon = "switch"; level = lastLevel || "off"; break; case "switchBinarySiren": deviceType = "switchBinary"; probeType = "siren" icon = "siren"; level = lastLevel || "off"; break; case "switchBinaryValve": deviceType = "switchBinary"; probeType = "valve" icon = "valve"; level = lastLevel || "off"; break; case "doorlock": deviceType = "doorlock"; probeType = ""; icon = "door"; level = lastLevel || "close"; break; case "switchMultilevel": deviceType = "switchMultilevel"; probeType = ""; icon = "multilevel"; level = lastLevel || 0; break; case "switchMultilevelMotor": deviceType = "switchMultilevel" probeType = "motor" icon = "blinds"; level = lastLevel || 0; break; } var defaults = { metrics: { title: self.getInstanceTitle(), icon: icon, // here to allow changing icon to custom one level: level // here to restore last value } }; var overlay = { deviceType: deviceType, // here to allow changing type probeType: probeType }; this.vDev = this.controller.devices.create({ deviceId: this.getName() + "_" + this.id, defaults: defaults, overlay: overlay, handler: function(command, args) { if (command != 'update') { var level = command; if (this.get('deviceType') === "switchMultilevel") { if (command === "on") { level = 99; } else if (command === "off") { level = 0; } else { level = parseInt(args.level, 10); } } this.set("metrics:level", level); saveObject(this.id + "_level" , level); // not critical, allow lazy save } }, moduleId: this.id }); }; DummyDevice.prototype.stop = function () { if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } DummyDevice.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/DummyDevice/lang/de.json ================================================ { "m_title":"Gerätattrappe", "m_descr":"Wie der Name verrät, erstellt diese App ein virtuelles Gerät, welches nur auf der UI existiert, jedoch mit keiner physischen Hardware verknüpft ist. Das kann zur Einstellung bestimmter Szenen oder zum Austesten mancher Situationen sinnvoll sein.

Einstellungen:
  • Sie können bestimmen, ob Ihr Dummy Gerät als Binärschaltung oder als Multilevel-Schaltung auftreten soll (Dimmer, Motorsteuerung).
", "l_options":"Wählen Sie einen Gerätetyp:" } ================================================ FILE: modules/DummyDevice/lang/en.json ================================================ { "m_title":"Dummy Device", "m_descr":"As the name suggests this app creates a virtual device that just exists on the UI but does not link to any physical hardware. This may be useful for setting up certain scenes or to debug certain situations.
  • You can define if your dummy device shall appear as binary switch or as multilevel l switch (dimmer, motor control).
", "l_options":"Select device type" } ================================================ FILE: modules/DummyDevice/lang/ru.json ================================================ { "m_title":"Устройство пустышка", "m_descr":"Устройство пустышку можно использовать например, как флаг, обозначающий режимы: 'На охране' и 'Снят с охраны'.", "l_options":"Выберите тип устройства" } ================================================ FILE: modules/DummyDevice/module.json ================================================ { "singleton" : false, "dependencies": [], "category": "automation_basic", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "DummyDevice", "version": "1.2.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults" : { "title" : "__m_title__", "description" : "__m_descr__", "deviceType" : "switchBinary" }, "schema" : { "type" : "object", "properties" : { "deviceType" : { "type" : "string", "enum" : ["switchBinary", "switchMultilevel", "doorlock", "switchMultilevelMotor", "switchBinarySiren", "switchBinaryValve"], "default" : "switchBinary", "required" : true } } }, "options" : { "fields" : { "deviceType" : { "label" : "__l_options__", "type" : "select", "optionLabels" : ["Binary switch", "Multilevel switch", "Door lock", "Blinds", "Siren", "Water valve"] } } } } ================================================ FILE: modules/EasyScripting/htdocs/js/compile.sh ================================================ #!/bin/bash compileJS() { local IFILE=$1 local OFILE=$2 local OPT_LEVEL=$3 URL=$(wget -q https://closure-compiler.appspot.com/compile --post-data 'output_format=json&output_info=compiled_code&output_info=warnings&output_info=errors&output_info=statistics&compilation_level='${OPT_LEVEL}'&warning_level=verbose&output_file_name='${OFILE}'.js&js_code='$(hexdump -v -e '/1 "%02x"' ${IFILE}.js | sed 's/\(..\)/%\1/g') -O - | python -c "import sys, json; print json.load(sys.stdin)['outputFilePath']") wget -q https://closure-compiler.appspot.com/${URL} -O ${OFILE}.js } compileJS postRender-with-comments postRender SIMPLE_OPTIMIZATIONS ================================================ FILE: modules/EasyScripting/htdocs/js/postRender-with-comments.js ================================================ function modulePostRender(control) { // advanced code editor is available only on Chome if (!window.chrome) { $(".alpaca-field.alpaca-field-textarea textarea").parent().prepend($('
').text('__h_unsupported__')); return; } // detect cursor position on screen // https://openbase.io/js/textarea-caret function getCaretCoordinates(element, position, options) { // We'll copy the properties below into the mirror div. // Note that some browsers, such as Firefox, do not concatenate properties // into their shorthand (e.g. padding-top, padding-bottom etc. -> padding), // so we have to list every single property explicitly. var properties = [ 'direction', // RTL support 'boxSizing', 'width', // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does 'height', 'overflowX', 'overflowY', // copy the scrollbar for IE 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth', 'borderStyle', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft', // https://developer.mozilla.org/en-US/docs/Web/CSS/font 'fontStyle', 'fontVariant', 'fontWeight', 'fontStretch', 'fontSize', 'fontSizeAdjust', 'lineHeight', 'fontFamily', 'textAlign', 'textTransform', 'textIndent', 'textDecoration', // might not make a difference, but better be safe 'letterSpacing', 'wordSpacing', 'tabSize', 'MozTabSize' ]; var isFirefox = window.mozInnerScreenX != null; // The mirror div will replicate the textarea's style var div = document.createElement('div'); div.id = 'input-textarea-caret-position-mirror-div'; document.body.appendChild(div); var style = div.style; var computed = window.getComputedStyle ? window.getComputedStyle(element) : element.currentStyle; // currentStyle for IE < 9 // Default textarea styles style.whiteSpace = 'pre-wrap'; style.wordWrap = 'break-word'; // only for textarea-s // Position off-screen style.position = 'absolute'; // required to return coordinates properly style.visibility = 'hidden'; // not 'display: none' because we want rendering // Transfer the element's properties to the div properties.forEach(function (prop) { style[prop] = computed[prop]; }); if (isFirefox) { // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275 if (element.scrollHeight > parseInt(computed.height)) style.overflowY = 'scroll'; } else { style.overflow = 'hidden'; // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll' } div.textContent = element.value.substring(0, position); // The second special handling for input type="text" vs textarea: // spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037 var span = document.createElement('span'); // Wrapping must be replicated *exactly*, including when a long word gets // onto the next line, with whitespace at the end of the line before (#7). // The *only* reliable way to do that is to copy the *entire* rest of the // textarea's content into the created at the caret position. // For inputs, just '.' would be enough, but no need to bother. span.textContent = element.value.substring(position) || '.'; // || because a completely empty faux span doesn't render at all div.appendChild(span); var coordinates = { top: span.offsetTop + parseInt(computed['borderTopWidth']), left: span.offsetLeft + parseInt(computed['borderLeftWidth']), height: parseInt(computed['lineHeight']) }; document.body.removeChild(div); return coordinates; } // syntax highlight $(".alpaca-field.alpaca-field-textarea textarea").addClass('editor allow-tabs').wrap( $('
').addClass('scroller') ).parent().append( $('
').append(
			$('').addClass('syntax-highight javascript')
		)
	).wrap(
		$('
').addClass('highlight-editor-holder') ).parent().prepend( $('
    ').addClass('toolbar') ); // menu handling function modalBackground(id) { var suffix = '-modal'; $('#' + id + suffix).remove(); return $('
    ') .attr('id', id + suffix) .attr('role', 'menu-modal') .addClass('dropdown-menu-modal') .click(function(event) { $(this).css({ 'display': 'none' }); var id = $(this).attr('id'); id = id.substr(0, id.length - suffix.length); $('#' + id).css({ 'display': 'none' }); }); } function modalMenu(id) { $('#' + id).remove(); return $('
      ') .attr('id', id) .attr('role', 'menu') .addClass('dropdown-menu'); } function menu(id) { $('body') .append(modalBackground(id + '-menu')) .append(modalMenu(id + '-menu')); } function menuOpen(menu_id, pos) { $('#' + menu_id + '-menu').css({ position: "fixed", display: "block", left: pos.left + 'px', top: pos.top + 'px' }); $('#' + menu_id + '-menu-modal').css({ 'display': 'block' }); } function menuAttach(el, menu_id, openon) { return el .attr('id', menu_id) .attr('context', menu_id + '-menu') .attr('openon', openon) .bind(openon, function(event) { event.preventDefault(); var pos = this.getBoundingClientRect(); menuOpen(menu_id, { left: openon === 'mouseover' ? pos.right : event.clientX, top: openon === 'mouseover' ? pos.top : event.clientY }); }); } function menuItemAdd(menu_id, item_id, text, action) { $('#' + menu_id + '-menu').append( $('
    • ') .append( $('
      ') .text(text) .click(function() { menuClose(this); action(this); }) ) .attr('id', menu_id + '-menu-item-' + item_id) ); } function menuClose(item) { var menuName = item + '-menu'; if (typeof item !== "string") { menuName = $(item).closest('[role="menu"]').attr('id'); } $('#' + menuName).css({ 'display': 'none' }); $('#' + menuName + '-modal').css({ 'display': 'none' }); } function menuItemAddText(menu_id, text, data, textarea) { $('#' + menu_id + '-menu').append( $('
    • ') .append( $('') .text(text) .click(function() { menuClose(this); textareaTextAt(data, textarea); }) ) ); } function menuItemAddDelimiter(menu_id) { $('#' + menu_id + '-menu').append($('
    • ').addClass('divider')); } function toolbarMenuAdd(menu_id, icon, name) { $('.highlight-editor-holder .toolbar').append( menuAttach( $('
    • ') .append( $('').append( $(' ' + name +'').addClass('fas ' + icon) ) ), menu_id, 'click' ) ); menu(menu_id); } // fill toolbar with menus toolbarMenuAdd('easy-scripting-devices-events', 'fa-play', '__m_events__'); toolbarMenuAdd('easy-scripting-devices-objects', 'fa-lightbulb', '__m_devices__'); toolbarMenuAdd('easy-scripting-syntax', 'fa-code', '__m_expressions__'); // fill menu with devices $.ajax('/ZAutomation/api/v1/devices') .done(function (response) { response.data.devices.sort(function(a, b) { if (a.metrics.title === b.metrics.title) return (a.id < b.id) ? -1 : 1; return (a.metrics.title < b.metrics.title) ? -1 : 1; }).forEach(function(dev) { // events menuItemAdd("easy-scripting-devices-events", dev.id, dev.metrics.title + ' (' + dev.id + ')', function() { textareaTextAtTop('### ' + dev.id + ' // ' + dev.metrics.title + '\n'); }); menuItemAdd("easy-scripting-devices-objects", dev.id, dev.metrics.title + ' (' + dev.id + ')', function() { textareaTextAt('vdev("' + dev.id + '")'); }); }); }) .fail(function () { alert('no devices'); }); // special characters var exprBool = '\u229c', exprVal = '\u2299', expression = '\u2026', placeHolders = [exprBool, exprVal, expression]; var textArea = $(".alpaca-field.alpaca-field-textarea textarea"); menu('easy-scripting-syntax'); menuItemAddText('easy-scripting-syntax', '__m_if__', 'if (' + exprBool + ') {\n ' + expression + '\n}'); menuItemAddText('easy-scripting-syntax', '__m_for_loop__', 'for (var i = 0; i < ' + exprVal + '; i++) {\n ' + expression + '\n}'); menuItemAddText('easy-scripting-syntax', '__m_while_loop__', 'while (' + exprBool + ') {\n ' + expression + '\n}'); menuItemAddDelimiter('easy-scripting-syntax'); menuItemAddText('easy-scripting-syntax', '__m_or__', '' + exprBool + ' || ' + exprBool + ''); menuItemAddText('easy-scripting-syntax', '__m_and__', '' + exprBool + ' && ' + exprBool + ''); menuItemAddDelimiter('easy-scripting-syntax'); menuItemAddText('easy-scripting-syntax', '__m_http_request__', 'http.request({method: "GET", async: true, url: ' + exprVal + '});'); menuItemAddText('easy-scripting-syntax', '__m_set_timeout__', 'setTimer("' + expression + '", function() {\n ' + expression + '\n}, ' + exprVal + ')'); menuItemAddText('easy-scripting-syntax', '__m_remove_timeout__', 'stopTimer("' + expression + '")'); menu('easy-scripting-device-methods'); menuItemAddText('easy-scripting-device-methods', '__m_turn_on__', 'on()'); menuItemAddText('easy-scripting-device-methods', '__m_turn_off__', 'off()'); menuItemAddText('easy-scripting-device-methods', '__m_turn_set__', 'set(' + exprVal + ')'); menuItemAddText('easy-scripting-device-methods', '__m_update__', 'update()'); menuItemAddDelimiter('easy-scripting-device-methods'); menuItemAddText('easy-scripting-device-methods', '__m_is_on__', 'value() === "on"'); menuItemAddText('easy-scripting-device-methods', '__m_is_off__', 'value() === "off"'); menuItemAddText('easy-scripting-device-methods', '__m_equals__', 'value() === ' + exprVal); menuItemAddText('easy-scripting-device-methods', '__m_ne__', 'value() !== ' + exprVal); menuItemAddText('easy-scripting-device-methods', '__m_gt__', 'value() > ' + exprVal); menuItemAddText('easy-scripting-device-methods', '__m_ge__', 'value() >= ' + exprVal); menuItemAddText('easy-scripting-device-methods', '__m_lt__', 'value() < ' + exprVal); menuItemAddText('easy-scripting-device-methods', '__m_le__', 'value() <= ' + exprVal); menuItemAddText('easy-scripting-device-methods', '__m_value__', 'value()'); // TextArea functions // find current ident function getSpaces() { var textAreaDOM = textArea.get(0), start = textAreaDOM.selectionStart; var textToPosition = textArea.val().substr(0, start), ident = '', ch, i = 1; while ((ch = textToPosition.substr(-i, 1)) === ' ' || ch === '\t') { i++; ident = ch + ident; } return ident; } function getIdent() { var textAreaDOM = textArea.get(0), start = textAreaDOM.selectionStart; var lastNL = textAreaDOM.value.substr(0, start).lastIndexOf('\n') + 1; // works with -1 as well var line = textAreaDOM.value.substr(lastNL, start - lastNL), ident = '', i = 0; while ((ch = line.substr(i, 1)) === ' ' || ch === '\t') { i++; ident += ch; } if (line.substr(line.length - 1, 1) === '{') { ident += ' '; } return ident; } // add text in textarea to cursor position or intead of selection function textareaTextAt(data) { var textAreaDOM = textArea.get(0), start = textAreaDOM.selectionStart; // add ident to the string data = data.split('\n').map(function(line, j) { return (j === 0 ? '' : getSpaces()) + line; }).join('\n'); // add at position if (textArea.setRangeText) { // if setRangeText function is supported by current browser textArea.setRangeText(data); } else { textArea.focus(); document.execCommand('insertText', false, data); } function indexOfPlaceholder(string) { for (var i = 0; i < placeHolders.length; i++) { var j = string.indexOf(placeHolders[i]); if (j !== -1) return j; } return -1; } // select first placeholder to fill if placeholder was in data var ii = indexOfPlaceholder(data); if (ii !== -1) { textAreaDOM.selectionStart = ii + start; textAreaDOM.selectionEnd = textAreaDOM.selectionStart + 1; } // trigger event for code highlighter textArea.blur(); textArea.focus(); } // add text in textarea to cursor position or intead of selection function textareaTextAtTop(data) { // trimEnd and addind \n is to trigger event for code highlighter textArea.val(data + textArea.val().trimEnd()); textareaTextAt('\n'); } $(textArea).click(function() { if (this.selectionStart === this.selectionEnd && placeHolders.indexOf(this.value.substr(this.selectionStart, 1)) !== -1) { this.selectionEnd = this.selectionStart + 1; } else if (this.selectionStart === this.selectionEnd && placeHolders.indexOf(this.value.substr(this.selectionStart - 1, 1)) !== -1) { this.selectionEnd = this.selectionStart; this.selectionStart--; } }); $(textArea).keypress(function(e) { if (e.key === '.') { // check that before is vdev("...") if (this.value.substr(0, this.selectionStart).match(/.*vdev\("[\w-]+"\)$/)) { // get cursor position var pos = getCaretCoordinates(this, this.selectionEnd), thisPos = this.getBoundingClientRect(); pos.top += thisPos.top + 10; // add small shift pos.left += thisPos.left + 10; menuOpen('easy-scripting-device-methods', pos); } } else if (e.key === 'Enter') { var ident = getIdent(); setTimeout(function() { textareaTextAt(ident); }, 0); } else { menuClose('easy-scripting-device-methods'); } }); // highlight description document.querySelectorAll('pre code').forEach(function(block) { hljs.highlightElement(block); }); // trigger highlight textareaTextAtTop(''); } ================================================ FILE: modules/EasyScripting/htdocs/js/postRender.js ================================================ function modulePostRender(C){function y(a,b,c){var e=null!=window.mozInnerScreenX;c=document.createElement("div");c.id="input-textarea-caret-position-mirror-div";document.body.appendChild(c);var f=c.style,g=window.getComputedStyle?window.getComputedStyle(a):a.currentStyle;f.whiteSpace="pre-wrap";f.wordWrap="break-word";f.position="absolute";f.visibility="hidden";"direction boxSizing width height overflowX overflowY borderTopWidth borderRightWidth borderBottomWidth borderLeftWidth borderStyle paddingTop paddingRight paddingBottom paddingLeft fontStyle fontVariant fontWeight fontStretch fontSize fontSizeAdjust lineHeight fontFamily textAlign textTransform textIndent textDecoration letterSpacing wordSpacing tabSize MozTabSize".split(" ").forEach(function(k){f[k]= g[k]});e?a.scrollHeight>parseInt(g.height)&&(f.overflowY="scroll"):f.overflow="hidden";c.textContent=a.value.substring(0,b);e=document.createElement("span");e.textContent=a.value.substring(b)||".";c.appendChild(e);a={top:e.offsetTop+parseInt(g.borderTopWidth),left:e.offsetLeft+parseInt(g.borderLeftWidth),height:parseInt(g.lineHeight)};document.body.removeChild(c);return a}function z(a){$("#"+a+"-modal").remove();return $("
      ").attr("id",a+"-modal").attr("role","menu-modal").addClass("dropdown-menu-modal").click(function(b){$(this).css({display:"none"}); b=$(this).attr("id");b=b.substr(0,b.length-6);$("#"+b).css({display:"none"})})}function n(a){var b=$("body").append(z(a+"-menu")),c=b.append;a+="-menu";$("#"+a).remove();a=$("
        ").attr("id",a).attr("role","menu").addClass("dropdown-menu");c.call(b,a)}function u(a,b){$("#"+a+"-menu").css({position:"fixed",display:"block",left:b.left+"px",top:b.top+"px"});$("#"+a+"-menu-modal").css({display:"block"})}function A(a,b,c){return a.attr("id",b).attr("context",b+"-menu").attr("openon",c).bind(c,function(e){e.preventDefault(); var f=this.getBoundingClientRect();u(b,{left:"mouseover"===c?f.right:e.clientX,top:"mouseover"===c?f.top:e.clientY})})}function v(a,b,c,e){$("#"+a+"-menu").append($("
      • ").append($("").text(c).click(function(){p(this);e(this)})).attr("id",a+"-menu-item-"+b))}function p(a){var b=a+"-menu";"string"!==typeof a&&(b=$(a).closest('[role="menu"]').attr("id"));$("#"+b).css({display:"none"});$("#"+b+"-modal").css({display:"none"})}function d(a,b,c,e){$("#"+a+"-menu").append($("
      • ").append($("").text(b).click(function(){p(this); l(c,e)})))}function q(a){$("#"+a+"-menu").append($("
      • ").addClass("divider"))}function r(a,b,c){$(".highlight-editor-holder .toolbar").append(A($("
      • ").append($("").append($(" "+c+"").addClass("fas "+b))),a,"click"));n(a)}function B(){var a=h.get(0),b=a.selectionStart,c=a.value.substr(0,b).lastIndexOf("\n")+1;a=a.value.substr(c,b-c);b="";for(c=0;" "===(ch=a.substr(c,1))||"\t"===ch;)c++,b+=ch;"{"===a.substr(a.length-1,1)&&(b+=" ");return b}function l(a){var b=h.get(0), c=b.selectionStart;a=a.split("\n").map(function(e,f){if(0===f)var g="";else{var k=h.get(0).selectionStart;k=h.val().substr(0,k);for(var t="",w=1;" "===(g=k.substr(-w,1))||"\t"===g;)w++,t=g+t;g=t}return g+e}).join("\n");h.setRangeText?h.setRangeText(a):(h.focus(),document.execCommand("insertText",!1,a));a=function(e){for(var f=0;f").addClass("scroller")).parent().append($("
        ").append($("").addClass("syntax-highight javascript"))).wrap($("
        ").addClass("highlight-editor-holder")).parent().prepend($("
          ").addClass("toolbar"));r("easy-scripting-devices-events","fa-play","__m_events__");r("easy-scripting-devices-objects","fa-lightbulb","__m_devices__"); r("easy-scripting-syntax","fa-code","__m_expressions__");$.ajax("/ZAutomation/api/v1/devices").done(function(a){a.data.devices.sort(function(b,c){return b.metrics.title===c.metrics.title?b.id \u2299");d("easy-scripting-device-methods","__m_ge__","value() >= \u2299"); d("easy-scripting-device-methods","__m_lt__","value() < \u2299");d("easy-scripting-device-methods","__m_le__","value() <= \u2299");d("easy-scripting-device-methods","__m_value__","value()");$(h).click(function(){this.selectionStart===this.selectionEnd&&-1!==m.indexOf(this.value.substr(this.selectionStart,1))?this.selectionEnd=this.selectionStart+1:this.selectionStart===this.selectionEnd&&-1!==m.indexOf(this.value.substr(this.selectionStart-1,1))&&(this.selectionEnd=this.selectionStart,this.selectionStart--)}); $(h).keypress(function(a){if("."===a.key){if(this.value.substr(0,this.selectionStart).match(/.*vdev\("[\w-]+"\)$/)){a=y(this,this.selectionEnd);var b=this.getBoundingClientRect();a.top+=b.top+10;a.left+=b.left+10;u("easy-scripting-device-methods",a)}}else if("Enter"===a.key){var c=B();setTimeout(function(){l(c)},0)}else p("easy-scripting-device-methods")});document.querySelectorAll("pre code").forEach(function(a){hljs.highlightElement(a)});x("")}else $(".alpaca-field.alpaca-field-textarea textarea").parent().prepend($("
          ").text("__h_unsupported__"))} ; ================================================ FILE: modules/EasyScripting/index.js ================================================ /*** EasyScripting Z-Way HA module ******************************************* (c) Z-Wave.Me, 2021 ----------------------------------------------------------------------------- Author: Poltorak Serguei , Yurkin Vitaliy Description: Easy Scripting language for home automation scripts ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function EasyScripting (id, controller) { // Call superconstructor first (AutomationModule) EasyScripting.super_.call(this, id, controller); } inherits(EasyScripting, AutomationModule); _module = EasyScripting; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- EasyScripting.prototype.init = function (config) { EasyScripting.super_.prototype.init.call(this, config); // make sure to hide all local variables in script execution var self = this; // helpers in script this.setHelpers(); // extract events and the code this.code = this.config.script.split('\n').filter(function(s) { return !s.match(/^###/); }).join('\n'); this.events = this.config.script.split('\n').filter(function(s) { return s.match(/^###/); }).map(function(s) { return s.match(/^###\W*([\w-]*)\W*/)[1]; }); try { eval('this.script = function(global, vdev, setTimer, stopTimer, on, off) { "use strict";' + this.code + '};'); } catch (e) { this.addNotification("error", e.toString(), "module"); return; } this.onEvent = function(trigger) { if (self.running) { self.addNotification("error", "Loop detected", "module"); return; } try { self.running = true; self.trigger = trigger; // make sure to hide outer scope variables and global variables var _script = self.script; (function(global, self, vdev, setTimer, stopTimer) { _script(global, vdev, setTimer, stopTimer, "on", "off"); })(EasyScripting.globals, undefined, self.vDevHelper, self.setTimer, self.stopTimer); } finally { self.running = false; } }; // event handlers for each vDevId self.eventHandlers = []; this.events.forEach(function(vDevId) { try { var onEvent = self.eventHandlers[vDevId]; if (!onEvent) { onEvent = function() { self.onEvent(vDevId); }; self.eventHandlers[vDevId] = onEvent; } self.controller.devices.on(vDevId, "change:metrics:level", onEvent); } catch(e) { self.addNotification("error", e.toString(), "module"); } }); }; EasyScripting.prototype.stop = function () { EasyScripting.super_.prototype.stop.call(this); var self = this; this.events.forEach(function(vDevId) { try { var onEvent = self.eventHandlers[vDevId]; if (onEvent) self.controller.devices.off(vDevId, "change:metrics:level", onEvent); } catch(e) { } }); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- EasyScripting.prototype.setHelpers = function() { var self = this; var constr = this.constructor; // glabal.xxx in scrpits // create one static propery for globals if (!constr.globals) { constr.globals = {}; constr.timers = {}; } // Timers this.setTimer = function(name, func, timeout) { self.stopTimer(name); constr.timers[name] = setTimeout(func, timeout*1000); }; this.stopTimer = function(name) { if (constr.timers[name]) { clearTimeout(constr.timers[name]); } }; // VDev var vDevWrapper = function(vDevId) { this.dev = self.controller.devices.get(vDevId); }; vDevWrapper.prototype.on = function() { this.dev.performCommand("on"); }; vDevWrapper.prototype.off = function() { this.dev.performCommand("off"); }; vDevWrapper.prototype.update = function() { this.dev.performCommand("update"); }; vDevWrapper.prototype.set = function(level) { this.dev.performCommand("exact", { level: level }); }; vDevWrapper.prototype.value = function() { return this.dev.get("metrics:level"); }; vDevWrapper.prototype.open = function() { this.dev.performCommand("open"); }; vDevWrapper.prototype.close = function() { this.dev.performCommand("close"); }; vDevWrapper.prototype.up = function() { this.dev.performCommand("up"); }; vDevWrapper.prototype.down = function() { this.dev.performCommand("down"); }; vDevWrapper.prototype.max = function() { this.dev.performCommand("max"); }; vDevWrapper.prototype.min = function() { this.dev.performCommand("min"); }; vDevWrapper.prototype.stop = function() { this.dev.performCommand("stop"); }; vDevWrapper.prototype.startUp = function() { this.dev.performCommand("startUp"); }; vDevWrapper.prototype.startDown = function() { this.dev.performCommand("startDown"); }; // export to local instance this.vDevHelper = function(vDevId) { return new vDevWrapper(vDevId); } }; ================================================ FILE: modules/EasyScripting/lang/en.json ================================================ { "m_title":"Easy Scripting", "m_descr":"Examples how to easy make automation on JavaScript (ECMA5).

          Directive to run the script when the device state changes (there may be several):
          ### ZWayVDev_zway_6-1-48
          ### ZWayVDev_zway_4-1-37

          Turn on/off device:
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").on()
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").off()

          Set device level:
          vdev(\\\"ZWayVDev_zway_5-1-38\\\").set(55)

          Get device value:
          var sensorValue = vdev(\\\"ZWayVDev_zway_6-1-48\\\").value()

          Device that started the script:
          selt.trigger

          Get the value of the device that started the script:
          var sensorValue = vdev(selt.trigger).value()

          Global variables for interaction between different applications. Create global variable:
          if (isNaN(global.myVar)) {global.myVar = 0}

          Set global variable:
          global.myVar = 10

          Send HTTP request:
          var req = {method: \\\"GET\\\", async: true}
          req.url = \\\"http://192.168.1.108/19-1\\\"
          http.request(req)

          On/Off according on the sensor value,
          If-Then-Else expression:

          ### ZWayVDev_zway_6-1-48
          if (vdev(\\\"ZWayVDev_zway_6-1-48\\\").value() == \\\"on\\\") {
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").on()
          }
          else {
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").off()
          }

          HTTP request according on the sensor value,
          If-Then-Else expression:

          ### ZWayVDev_zway_6-1-48
          var req = {method: \\\"GET\\\", async: true}
          if (vdev(\\\"ZWayVDev_zway_6-1-48\\\").value() == \\\"on\\\") {
          req.url = \\\"http://192.168.1.108/on\\\"
          }
          else {
          req.url = \\\"http://192.168.1.108/off\\\"
          }
          http.request(req)

          Perform any action after the expiration of time (time is set in seconds)
          ### ZWayVDev_zway_4-1-37
          setTimer(\\\"myTimer\\\", function() {
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").on()
          }, 50)

          Cancel timer (function will not be executed)
          stopTimer(\\\"myTimer\\\")

          List of supported commands:
          on, off, update, set, value, open, close, up, down ,max, min, stop, startUp, startDown

          Don't do infinite or slow loops!", "h_script":"The JavaScript (ECMA5) programming language is used for writing scripts.", "m_events":"Events", "m_devices":"Devices", "m_expressions":"Expressions", "m_if":"If then", "m_for_loop":"Loop from-to", "m_while_loop":"Loop while", "m_or":"Or", "m_and":"And", "m_http_request":"HTTP request", "m_set_timeout":"Set timer", "m_remove_timeout": "Remove timer", "m_turn_on":"turn on", "m_turn_off":"turn Off", "m_turn_set":"set to value", "m_update": "request value update", "m_value":"value", "m_is_on":"is on", "m_is_off":"is off", "m_equals":"equal to", "m_ne":"not equal to", "m_gt":"greater than", "m_ge":"greater than or equal to", "m_lt":"less than", "m_le":"less than or equal to", "h_unsupported":"Unfortunatelly only Google Chrome supports advanced code editor. You can still use this app without pretty code editor." } ================================================ FILE: modules/EasyScripting/lang/ru.json ================================================ { "m_title":"Простые Скрипты", "m_descr":"Примеры, как просто писать скрипты автоматизации на JavaScript (ECMA5).

          В начале определяем устройства, при изменении состояния которых будет запускаться скрипт (может быть несколько):
          ### ZWayVDev_zway_6-1-48
          ### ZWayVDev_zway_4-1-37

          Включить/выключить устройство:
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").on()
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").off()

          Установить уровень:
          vdev(\\\"ZWayVDev_zway_5-1-38\\\").set(55)

          Получить значение:
          var sensorValue = vdev(\\\"ZWayVDev_zway_6-1-48\\\").value()

          Устройство, которое запустило скрипт:
          selt.trigger

          Получить значение устройства, которое запустило скрипт:
          var sensorValue = vdev(selt.trigger).value()

          Глобальные переменные для взаимодействия между разными приложениями. Создать глобальную переменную:
          if (isNaN(global.myVar)) {global.myVar = 0}

          Задать глобальную переменную:
          global.myVar = 10

          Отправить HTTP запрос:
          var req = {method: \\\"GET\\\", async: true}
          req.url = \\\"http://192.168.1.108/19-1\\\"
          http.request(req)

          Включить/выключить устройство в зависимости от состояния датчика,
          Если-Тогда-Иначе выражение:

          ### ZWayVDev_zway_6-1-48
          if (vdev(\\\"ZWayVDev_zway_6-1-48\\\").value() == \\\"on\\\") {
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").on()
          }
          else {
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").off()
          }

          Отправить HTTP запрос в зависимости от состояния датчика,
          Если-Тогда-Иначе выражение:

          ### ZWayVDev_zway_6-1-48
          var req = {method: \\\"GET\\\", async: true}
          if (vdev(\\\"ZWayVDev_zway_6-1-48\\\").value() == \\\"on\\\") {
          req.url = \\\"http://192.168.1.108/on\\\"
          }
          else {
          req.url = \\\"http://192.168.1.108/off\\\"
          }
          http.request(req)

          Выполнить какое либо действие по истечении времени (время задается в секундах)
          ### ZWayVDev_zway_4-1-37
          setTimer(\\\"myTimer\\\", function() {
          vdev(\\\"ZWayVDev_zway_4-1-37\\\").on()
          }, 50)

          Отменить таймер (функция не будет выполнена)
          stopTimer(\\\"myTimer\\\")

          Список поддерживаемых команд:
          on, off, update, set, value, open, close, up, down ,max, min, stop, startUp, startDown

          Не делайте бесконечные или медленные циклы!", "h_script":"Для написания скриптов используется язык программирование JavaScript (ECMA5).", "m_events":"События", "m_devices":"Устройства", "m_expressions":"Выражения", "m_if":"Если, то", "m_for_loop":"Цикл от и до", "m_while_loop":"Цикл пока", "m_or":"ИЛИ", "m_and":"И", "m_http_request":"HTTP запрос", "m_set_timeout":"Установить таймер", "m_remove_timeout": "Удалить таймер", "m_turn_on":"включить", "m_turn_off":"выключить", "m_turn_set":"установить значение", "m_update": "запросить обновление значения", "m_value":"значение", "m_is_on":"включено", "m_is_off":"выключено", "m_equals":"равно", "m_ne":"не равно", "m_gt":"больше", "m_ge":"больше или равно", "m_lt":"меньше", "m_le":"меньше или равно", "h_unsupported":"К сожалению продвинутые функции редактора кода поддерживаются только в Google Chrome. Вы можете использовать данное приложение без продвинутого редактора кода." } ================================================ FILE: modules/EasyScripting/module.json ================================================ { "singleton": false, "dependencies": [], "category": "developers_stuff", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "EasyScripting", "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__", "script": "" }, "schema": { "type": "object", "properties": { "script": { "type": "string", "required": false } } }, "options": { "fields": { "script": { "type": "textarea", "helper": "__h_script__" } } }, "postRender": "loadFunction:postRender.js" } ================================================ FILE: modules/EdimaxSP1101/index.js ================================================ /*** 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', 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); }; ================================================ FILE: modules/EdimaxSP1101/lang/de.json ================================================ { "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'" } ================================================ FILE: modules/EdimaxSP1101/lang/en.json ================================================ { "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'" } ================================================ FILE: modules/EdimaxSP1101/module.json ================================================ { "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 } } } } ================================================ FILE: modules/EdimaxSP2101/index.js ================================================ /*** 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', 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); }; ================================================ FILE: modules/EdimaxSP2101/lang/de.json ================================================ { "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'" } ================================================ FILE: modules/EdimaxSP2101/lang/en.json ================================================ { "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'" } ================================================ FILE: modules/EdimaxSP2101/module.json ================================================ { "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 } } } } ================================================ FILE: modules/EnOcean/index.js ================================================ /*** EnOcean Binding module ******************************************************** Version: 1.0.0 ------------------------------------------------------------------------------- Author: Serguei Poltorak Copyright: (c) Z-Wave.Me, 2015 ******************************************************************************/ function EnOcean (id, controller) { // if called without "new", return list of loaded EnOcean instances if (!(this instanceof EnOcean)) return EnOcean.list(); EnOcean.super_.call(this, id, controller); this.ENOCEAN_DEVICE_CHANGE_TYPES = { "DeviceAdded": 0x01, "DeviceRemoved": 0x02, "DeviceProfileChanged": 0x04, "ZDDXSaved": 0x100, "EnumerateExisting": 0x200 }; this.ZWAY_DATA_CHANGE_TYPE = { "Updated": 0x01, // Value updated or child created "Invalidated": 0x02, // Value invalidated "Deleted": 0x03, // Data holder deleted - callback is called last time before being deleted "ChildCreated": 0x04, // New direct child node created // ORed flags "PhantomUpdate": 0x40, // Data holder updated with same value (only updateTime changed) "ChildEvent": 0x80 // Event from child node }; } // Module inheritance and setup inherits(EnOcean, AutomationModule); _module = EnOcean; Object.defineProperty(EnOcean, "list", { value: function () { return Object.keys(EnOcean); }, enumerable: false, writable: false, configurable: false }); ws.allowExternalAccess("EnOcean.list", controller.auth.ROLE.ADMIN); EnOcean.prototype.init = function (config) { EnOcean.super_.prototype.init.call(this, config); var self = this; this.startBinding(); if (!this.zeno) { return; } this.controller.on("EnOcean.dataBind", this._dataBind); this.controller.on("EnOcean.dataUnbind", this.dataUnbind); this.controller.emit("EnOcean.register", this.config.name); }; EnOcean.prototype.startBinding = function () { var self = this, langFile = self.loadModuleLang(); try { this.zeno = new EnoceanBinding(this.config.name, this.config.port, { configFolder: this.config.config || 'config', terminationCallback: function() { self.terminating.call(self); } }); try { this.zeno.discover(); } catch (e1) { this.zeno.stop(); throw e1; } } catch(e) { this.addNotification("critical", langFile.err_binding_start + e.toString(), "enocean"); this.zeno = null; return; } this.fastAccess = false; if (!global.zeno) { // this is the first zeno - make fast shortcut this.fastAccess = true; } global.EnOcean[this.config.name] = { "zeno": this.zeno, "port": this.config.port, "fastAccess": this.fastAccess }; this.stopped = false; if (this.config.enableAPI !== false) { this.defineHandlers(); } if (this.fastAccess) { if (this.config.enableAPI !== false) { this.externalAPIAllow(); } global["zeno"] = this.zeno; // global variable global["EnOceanAPI"] = this.EnOceanAPI; } if (this.config.enableAPI !== false) { this.externalAPIAllow(this.config.name); } _.extend(global["EnOcean"][this.config.name], this.EnOceanAPI); if (this.config.createVDev !== false) { this.gateDevicesStart(); } // save data every hour for hot start this.saveDataXMLTimer = setInterval(function() { self.zeno.devices.SaveData(); }, 3600 * 1000); // unregister function this.zeno.unregisterDevice = function(name) { delete self.zeno.devices[name]; }; }; EnOcean.prototype.stop = function () { console.log("--- EnOcean.stop()"); EnOcean.super_.prototype.stop.call(this); this.stopBinding(); this.controller.off("EnOcean.dataBind", this._dataBind); this.controller.off("EnOcean.dataUnbind", this.dataUnbind); }; EnOcean.prototype.stopBinding = function () { this.controller.emit("EnOcean.unregister", this.config.name); if (this.config.createVDev !== false) { this.gateDevicesStop(); } if (this.fastAccess) { if (this.config.enableAPI !== false) { this.externalAPIRevoke(); } if (global.zeno) { delete global["zeno"]; delete global["EnOceanAPI"]; } } if (this.config.enableAPI !== false) { this.externalAPIRevoke(this.config.name); } if (global.EnOcean) { delete global.EnOcean[this.config.name]; } if (this.saveDataXMLTimer) { clearInterval(this.saveDataXMLTimer); this.saveDataXMLTimer = undefined; } this.stopped = true; if (this.zeno) { try { this.zeno.stop(); } catch(e) { // EnOcean has already gone } this.zeno = null; } }; EnOcean.prototype.terminating = function () { if (!this.stopped) { console.log("Terminating EnOcean binding"); this.stopBinding(); var self = this; setTimeout(function() { // retry open after 5 seconds console.log("Restarting EnOcean binding"); self.startBinding(); }, 5000); } }; // --------------- Public HTTP API ------------------- EnOcean.prototype.externalAPIAllow = function (name) { var _name = !!name ? ("EnOcean." + name) : "EnOceanAPI"; ws.allowExternalAccess(_name, this.config.publicAPI ? this.controller.auth.ROLE.ADMIN : this.controller.auth.ROLE.ANONYMOUS); ws.allowExternalAccess(_name + ".Run", this.config.publicAPI ? this.controller.auth.ROLE.ADMIN : this.controller.auth.ROLE.ANONYMOUS); ws.allowExternalAccess(_name + ".Data", this.config.publicAPI ? this.controller.auth.ROLE.ADMIN : this.controller.auth.ROLE.ANONYMOUS); }; EnOcean.prototype.externalAPIRevoke = function (name) { var _name = !!name ? ("EnOcean." + name) : "EnOceanAPI"; ws.revokeExternalAccess(_name); ws.revokeExternalAccess(_name + ".Run"); ws.revokeExternalAccess(_name + ".Data"); }; EnOcean.prototype.defineHandlers = function () { var zeno = this.zeno; this.EnOceanAPI = function() { return { status: 400, body: "Bad EnOceanAPI request " }; }; this.EnOceanAPI.Run = function(url) { url = "with(zeno) { " + url.substring(1) + " }"; try { var r = eval(url); return { status: 200, headers: { "Content-Type": "application/json" }, body: r }; } catch (e) { return { status: 500, body: e.toString() }; } }; this.EnOceanAPI.Data = function(url) { var timestamp = parseInt(url.substring(1), 10) || 0; return { status: 200, headers: { "Content-Type": "application/json" }, body: zeno.data(timestamp) }; }; }; // ------------- Data Binding -------------- EnOcean.prototype._dataBind = function(dataBindings, zenoName, nodeId, path, func, type) { if (zenoName === this.config.name && self.zeno) { this.dataBind(dataBindings, this.zeno, nodeId, path, func, type); } }; EnOcean.prototype.dataBind = function(dataBindings, zeno, nodeId, path, func, type) { // two prototypes: // (dataBindings, zeno, nodeId, path, func) // (dataBindings, zeno, path, func) // bind to controller data var pathArr = [], data = null, ctrlBind = is_function(path); if (ctrlBind) { var t = path; path = nodeId; func = t; nodeId = undefined; data = zeno.controller.data; } else { data = zeno.devices[nodeId].data; } if (path) { pathArr = path.split("."); } if (!func) { console.log("Function passed to dataBind is undefined"); return; } while (pathArr.length) { data = data[pathArr.shift()]; if (!data) { break; } } if (data) { if (ctrlBind) { dataBindings.push({ "zeno": zeno, "path": path, "func": data.bind(func, false) }); } else { dataBindings.push({ "zeno": zeno, "nodeId": nodeId, "path": path, "func": data.bind(func, nodeId, false) }); if (type === "value") { func.call(data, this.ZWAY_DATA_CHANGE_TYPE.Updated); } } } else { console.log("Can not find data path:", nodeId, path); } }; EnOcean.prototype.dataUnbind = function(dataBindings) { dataBindings.forEach(function (item) { var ctrlBind = !("nodeId" in item), devBind = ("nodeId" in item); if (item.zeno && item.zeno.isRunning() && (ctrlBind || item.zeno.devices[item.nodeId])) { var data = ctrlBind ? item.zeno.controller.data : item.zeno.devices[item.nodeId].data, pathArr = item.path ? item.path.split(".") : []; while (pathArr.length) { data = data[pathArr.shift()]; if (!data) { break; } } if (data) { data.unbind(item.func); } else { console.log("Can not find data path:", item.nodeId, item.path); } } }); dataBindings = null; }; // ----------------- Devices Creator --------------- EnOcean.prototype.gateDevicesStart = function () { var self = this; this.gateDataBinding = []; // Bind to all future CommandClasses changes this.gateBinding = this.zeno.bind(function (type, nodeId) { if (type === self.ENOCEAN_DEVICE_CHANGE_TYPES["DeviceRemoved"] || type === self.ENOCEAN_DEVICE_CHANGE_TYPES["DeviceProfileChanged"]) { self.removeProfile(nodeId); } if (type === self.ENOCEAN_DEVICE_CHANGE_TYPES["DeviceAdded"] && self.zeno.devices[nodeId].data.funcId !== null && self.zeno.devices[nodeId].data.typeId !== null || type === self.ENOCEAN_DEVICE_CHANGE_TYPES["DeviceProfileChanged"]) { self.parseProfile(nodeId); } if (type === self.ENOCEAN_DEVICE_CHANGE_TYPES["DeviceAdded"]) { self.dataBind(self.gateDataBinding, self.zeno, nodeId, "channels", function(type) { if (type === self.ZWAY_DATA_CHANGE_TYPE["Updated"]) { self.parseGenericProfile(nodeId); } }, "value"); } }, this.ENOCEAN_DEVICE_CHANGE_TYPES["DeviceAdded"] | this.ENOCEAN_DEVICE_CHANGE_TYPES["DeviceRemoved"] | this.ENOCEAN_DEVICE_CHANGE_TYPES["DeviceProfileChanged"] | this.ENOCEAN_DEVICE_CHANGE_TYPES["EnumerateExisting"]); this.dataBind(this.gateDataBinding, this.zeno, "lastExcludedDevice", function(type) { if (this.value) { self.cleanupProfile(this.value); } }, ""); }; EnOcean.prototype.gateDevicesStop = function () { var self = this; // delete devices this.controller.devices.map(function (el) { return el.id; }).filter(function(el) { return el.indexOf("ZEnoVDev_" + self.config.name + "_") === 0; }).forEach(function(el) { self.controller.devices.remove(el); }); // releasing bindings try { if (this.gateDataBinding) { this.dataUnbind(this.gateDataBinding); } if (this.zeno) { this.zeno.unbind(this.gateBinding); } } catch(e) { // EnOcean already gone, skip deallocation //this.zeno = null; } }; EnOcean.prototype.removeProfile = function (nodeId) { var self = this; // delete devices this.vDevByNodeId(nodeId).forEach(function(el) { self.controller.devices.remove(el); }); }; EnOcean.prototype.cleanupProfile = function (nodeId) { var self = this; // delete and cleanup devices this.vDevByNodeId(nodeId).forEach(function(el) { self.controller.devices.remove(el); self.controller.devices.cleanup(el); }); }; EnOcean.prototype.vDevByNodeId = function (nodeId) { var self = this; return this.controller.devices.filter(function(el) { return el.id.indexOf("ZEnoVDev_" + self.config.name + "_" + nodeId + '_') === 0; }).map(function(el) { return el.id; }).concat( Object.keys(self.controller.vdevInfo).filter(function(__id) { return __id.indexOf("ZEnoVDev_" + self.config.name + "_" + nodeId + '_') === 0; }) ); }; // EnOcean Equipment Profiles (EEP) EnOcean.prototype.parseProfile = function (nodeId) { var self = this, deviceData = this.zeno.devices[nodeId].data, vDevIdPrefix = "ZEnoVDev_" + this.config.name + "_" + nodeId + "_"; // vDev is not in this scope, but in {} scope for each type of device to allow reuse it without closures try { function matchDevice(rorg, funcId, typeId) { // deviceData is defined in outer scope return deviceData.rorg.value === rorg && deviceData.funcId.value === funcId && deviceData.typeId.value === typeId; } // below vDevIdPrefix and nodeId comes from this scope function binarySensor(dh, type, title, withTimeout, handler) { if (self.controller.devices.get(vDevIdPrefix + type)) return; var vDev = self.controller.devices.create({ deviceId: vDevIdPrefix + type, defaults: { deviceType: 'sensorBinary', technology: "EnOcean", bindingName: self.config.name, nodeId: nodeId, metrics: { probeTitle: type, scaleTitle: '', icon: type, level: '', title: title } }, overlay: {}, handler: function(command) {}, moduleId: self.id }); if (vDev) { self.dataBind(self.gateDataBinding, self.zeno, nodeId, dh, function(type) { try { var val = this.value; if (handler) { val = handler(val); if (val === null) return; // don't hanlde if null } vDev.set("metrics:level", val ? "on" : "off"); if (withTimeout && val) { setTimeout(function() { vDev.set("metrics:level", "off"); }, 1000); } } catch (e) {} }, "value"); } } function multilevelSensor(dh, type, scale, title) { if (self.controller.devices.get(vDevIdPrefix + type)) return; var vDev = self.controller.devices.create({ deviceId: vDevIdPrefix + type, defaults: { deviceType: "sensorMultilevel", technology: "EnOcean", bindingName: self.config.name, nodeId: nodeId, metrics: { probeTitle: type, scaleTitle: scale, level: '', icon: type, title: title } }, overlay: {}, handler: function(command) {}, moduleId: self.id }); if (vDev) { self.dataBind(self.gateDataBinding, self.zeno, nodeId, dh, function(type) { try { vDev.set("metrics:level", this.value); } catch (e) {} }, "value"); } } function rockerSwitch() { if (self.controller.devices.get(vDevIdPrefix + "switch" + "_left") || self.controller.devices.get(vDevIdPrefix + "switch" + "_right")) return; var vDevL = self.controller.devices.create({ deviceId: vDevIdPrefix + "switch" + "_left", defaults: { deviceType: "switchControl", technology: "EnOcean", bindingName: self.config.name, nodeId: nodeId, metrics: { level: '', icon: '', title: 'Left Rocker', change: '' } }, overlay: {}, handler: function(command) { }, moduleId: self.id }), vDevR = self.controller.devices.create({ deviceId: vDevIdPrefix + "switch" + "_right", defaults: { deviceType: "switchControl", technology: "EnOcean", bindingName: self.config.name, nodeId: nodeId, metrics: { level: '', icon: '', title: 'Right Rocker', change: '' } }, overlay: {}, handler: function(command) { if (command === "on" || command === "off") { this.set("metrics:level", command); } if (command === "upstart" || command === "upstop" || command === "downstart" || command === "downstop") { this.set("metrics:change", command); } }, moduleId: self.id }), dt = 500, // in ms timer = -1, leftPressed = false, rightPressed = false, leftUp = false, rightUp = false; // memorize pressed status if (vDevL && vDevR) { self.dataBind(self.gateDataBinding, self.zeno, nodeId, "energyBow", function(type) { if (type === self.ZWAY_DATA_CHANGE_TYPE["Deleted"]) return; if (deviceData.energyBow.value) { leftPressed = deviceData.value1.value < 2 || deviceData.secondAction.value && deviceData.value2.value < 2; leftUp = deviceData.value1.value === 1 || deviceData.secondAction.value && deviceData.value2.value === 1; rightPressed = deviceData.value1.value >= 2 || deviceData.secondAction.value && deviceData.value2.value >= 2; rightUp = deviceData.value1.value === 3 || deviceData.secondAction.value && deviceData.value2.value === 3; timer = setTimeout(function () { // timeout fired - this is a hold try{ if (leftPressed) { vDevL.set("metrics:change", leftUp ? "upstart" : "downstart"); } if (rightPressed) { vDevR.set("metrics:change", rightUp ? "upstart" : "downstart"); } } catch (e) {} timer = -1; }, dt); } else { var isHold = true; if (timer !== -1) { clearTimeout(timer); timer = -1; isHold = false; } try { if (isHold) { // release was long after press - this is a release if (leftPressed) { vDevL.set("metrics:change", leftUp ? "upstop" : "downstop"); } if (rightPressed) { vDevR.set("metrics:change", rightUp ? "upstop" : "downstop"); } } else { // release was short after press - this is a click if (leftPressed) { vDevL.set("metrics:level", leftUp ? "on" : "off"); } if (rightPressed) { vDevR.set("metrics:level", rightUp ? "on" : "off"); } } } catch (e) {} } }, ""); } } function thermostat(dh, type, scale, title) { if (self.controller.devices.get(vDevIdPrefix + type)) return; var vDev = self.controller.devices.create({ deviceId: vDevIdPrefix + type, defaults: { deviceType: "thermostat", technology: "EnOcean", bindingName: self.config.name, nodeId: nodeId, metrics: { scaleTitle: scale, // TODO!!! Check if F scale is defined in EnOcean level: '', min: 5, max: 40, icon: 'thermostat', title: title } }, overlay: {}, handler: function(command, args) { this.set("metrics:level", args.level); }, moduleId: self.id }); if (vDev) { self.dataBind(self.gateDataBinding, self.zeno, nodeId, dh, function(type) { try { vDev.set("metrics:level", this.value); } catch (e) {} }, "value"); } } function binarySwitch(dh, type, title, valToVDev, vDevToVal) { if (self.controller.devices.get(vDevIdPrefix + type)) return; var vDev = self.controller.devices.create({ deviceId: vDevIdPrefix + type, defaults: { deviceType: 'switchBinary', technology: "EnOcean", bindingName: self.config.name, nodeId: nodeId, metrics: { icon: 'switch', level: '', title: title } }, overlay: {}, handler: function(command) { if (command === "on" || command === "off") { if (vDevToVal) { self.zeno.devices[nodeId].data[dh].value = vDevToVal(command); } else { self.zeno.devices[nodeId].data[dh].value = command === "on" ? true : false; } vDev.set("metrics:level", command); } }, moduleId: self.id }); if (vDev) { self.dataBind(self.gateDataBinding, self.zeno, nodeId, dh, function(type) { try { if (valToVDev) { vDev.set("metrics:level", valToVDev(this.value)); } else { vDev.set("metrics:level", this.value ? "on" : "off"); } } catch (e) {} }, "value"); } } if (matchDevice(0xf6, 0x02, 0x01)) { // Rocker rockerSwitch(); } if (matchDevice(0xd5, 0x00, 0x01)) { // Door binarySensor("contact", "door", "Door Sensor"); } if (matchDevice(0xf6, 0x10, 0x00)) { // Window binarySensor("open", "window", "Window Open Sensor"); binarySensor("tilt", "window_tilt", "Window Tilt Sensor"); } if (matchDevice(0xf6, 0x10, 0x01)) { // Window binarySensor("open", "window", "Window Open Sensor"); binarySensor("tilt", "window_tilt", "Window Tilt Sensor"); } if (matchDevice(0xa5, 0x04, 0x01)) { // Tempretature & Humidity multilevelSensor("humidity", "humidity", '%', "Humidity Sensor"); if (deviceData.TSensor.value) { multilevelSensor("temperature", "temperature", '°C', "Temperature Sensor"); } } if (matchDevice(0xa5, 0x06, 0x01)) { // Luminance multilevelSensor("illumination" + deviceData.rangeSelect.value.toString(), "luminance", 'lux', "Luminance"); } if (matchDevice(0xa5, 0x02, 0x05)) { // Tempretature multilevelSensor("temperature", "temperature", '°C', "Temperature Sensor"); } if (matchDevice(0xa5, 0x07, 0x03)) { // Motion, Luminance and Supply Voltage multilevelSensor("illumination", "luminance", 'lux', "Luminance"); multilevelSensor("voltage", "voltage", 'V', "Supply Voltage"); binarySensor("pir", "motion", "Motion Sensor", true); } if (matchDevice(0xa5, 0x08, 0x01)) { // Motion, Luminance and Temperature Sensor multilevelSensor("illumination", "luminance", 'lux', "Luminance"); multilevelSensor("temperature", "temperature", '°C', "Temperature Sensor"); multilevelSensor("voltage", "voltage", 'V', "Supply Voltage"); binarySensor("pir", "motion", "Motion Sensor", true); binarySensor("occupancy", "general_purpose", "Occupancy button"); } if (matchDevice(0xa5, 0x09, 0x04)) { // CO2 & Tempretature & Humidity multilevelSensor("concentration", "co", 'ppm', "CO2 Sensor"); if (deviceData.HSensor.value) { multilevelSensor("humidity", "humidity", '%', "Humidity Sensor"); } if (deviceData.TSensor.value) { multilevelSensor("temperature", "temperature", '°C', "Temperature Sensor"); } } if (matchDevice(0xa5, 0x10, 0x03)) { // Temperature Sensor, Set Point Control thermostat("setpoint", "heat", '°C', "Set Point Control"); multilevelSensor("temperature", "temperature", '°C', "Temperature Sensor"); } if (matchDevice(0xa5, 0x10, 0x05)) { // Temperature Sensor, Set Point and Occupancy Control thermostat("setpoint", "heat", '°C', "Set Point Control"); multilevelSensor("temperature", "temperature", '°C', "Temperature Sensor"); binarySensor("occupancy", "motion", "Motion Sensor"); } if (matchDevice(0xa5, 0x10, 0x0a)) { // Temperature Sensor, Set Point Adjust and Single Input thermostat("setpoint", "heat", '°C', "Set Point Control"); multilevelSensor("temperature", "temperature", '°C', "Temperature Sensor"); binarySensor("contact", "door", "Door Sensor"); } if (matchDevice(0xd2, 0x06, 0x10)) { // Door binarySensor("windowOpen", "door", "Door Sensor"); binarySensor("preAlarm", "motion", "Pre Alarm"); binarySensor("alarm", "alarm", "Alarm"); binarySensor("preAlarmEnabled", "config1", "Pre Alarm"); binarySwitch("setPreAlarm", "config2", "Set Pre Alarm"); binarySwitch("setAlarm", "config3", "Set Alarm"); } if (matchDevice(0xd2, 0x06, 0x11)) { // Door binarySensor("windowOpen", "door", "Door Sensor"); binarySensor("windowTilt", "window_tilt", "Tilt Sensor", undefined, function(val) { switch(val) { case 0: return false; // treat unknow as non-tilted case 1: return true; case 2: return false; case 3: return null; // reserved - don't handle } }); binarySensor("preAlarm", "motion", "Pre Alarm"); binarySensor("alarm", "alarm", "Alarm"); binarySensor("preAlarmEnabled", "config1", "Pre Alarm"); binarySwitch("setPreAlarm", "config2", "Set Pre Alarm"); binarySwitch("setAlarm", "config3", "Set Alarm"); binarySwitch("setPIRSensitivity", "config4", "Set PIR Sensitivity", function(val) { return (val === 2) ? "off" : "on"; }, function(command) { return (command === "off") ? 2 : 1; }); } if (matchDevice(0xd2, 0x06, 0xff)) { // Door // TODO(Rehau Smart Guard XT development sample - to be removed after Aug 2021) binarySensor("windowOpen", "door", "Door Sensor"); binarySensor("windowTilt", "window_tilt", "Tilt Sensor"); binarySensor("preAlarm", "motion", "Pre Alarm"); binarySensor("alarm", "alarm", "Alarm"); binarySensor("preAlarmEnabled", "config1", "Pre Alarm"); binarySwitch("setPreAlarm", "config2", "Set Pre Alarm"); binarySwitch("setAlarm", "config3", "Set Alarm"); } // save ZDDX self.zeno.devices.SaveData(); // handling of Signal Telegrams self.dataBind(self.gateDataBinding, self.zeno, nodeId, null, function(type) { if (self.zeno.devices[nodeId] && self.zeno.devices[nodeId].data["battery"] && !self.controller.devices.get(vDevIdPrefix + "battery")) { multilevelSensor("battery", "battery", '%', "Battery level"); self.zeno.devices.SaveData(); // save ZDDX } }, "value"); } catch (e) { var langFile = this.loadModuleLang(), values = nodeId + ": " + e.toString(); this.addNotification("error", langFile.err_dev_create + values, "core"); console.log(e.stack); } }; // Enocean Generic Profile (GP) EnOcean.prototype.parseGenericProfile = function (nodeId) { var self = this, deviceData = this.zeno.devices[nodeId].data, vDevIdPrefix = "ZEnoVDev_" + this.config.name + "_" + nodeId + "_"; // vDev is not in this scope, but in {} scope for each type of device to allow reuse it without closures try { if (deviceData.rorg.value != 0xb0) return; function binarySensorGP(o, type, title) { if (self.controller.devices.get(vDevIdPrefix + type + "_" + o)) return; var vDev = self.controller.devices.create({ deviceId: vDevIdPrefix + type + "_" + o, defaults: { deviceType: 'sensorBinary', technology: "EnOcean", bindingName: self.config.name, nodeId: nodeId, metrics: { probeTitle: type, scaleTitle: '', icon: type, level: '', title: title } }, overlay: {}, handler: function(command) {}, moduleId: self.id }); if (vDev) { self.dataBind(self.gateDataBinding, self.zeno, nodeId, "channels." + o + ".level", function(type) { try { vDev.set("metrics:level", this.value ? "on" : "off"); } catch (e) {} }, "value"); } } function binarySwitchGP(o, i, type, title) { if (self.controller.devices.get(vDevIdPrefix + type + "_" + o + "_" + i)) return; var vDev = self.controller.devices.create({ deviceId: vDevIdPrefix + type + "_" + o + "_" + i, defaults: { deviceType: 'switchBinary', technology: "EnOcean", bindingName: self.config.name, nodeId: nodeId, metrics: { icon: 'switch', level: '', title: title } }, overlay: {}, handler: function(command) { if (command === "on" || command === "off") { self.zeno.devices[nodeId].data.channels[i].level.value = command === "on" ? true : false; // check if it reports back // vDev.set("metrics:level", command); } }, moduleId: self.id }); if (vDev) { self.dataBind(self.gateDataBinding, self.zeno, nodeId, "channels." + o + ".level", function(type) { try { vDev.set("metrics:level", this.value ? "on" : "off"); } catch (e) {} }, "value"); } } if (deviceData.manufacturerId.value == 0x02d && deviceData.productId.value == 0x08) { // Afriso siren binarySwitchGP("o1", "i1", "switch", "Siren"); binarySwitchGP("o2", "i2", "switch", "Arm Status"); binarySensorGP("o3", "alarm", "Power status"); } // save ZDDX self.zeno.devices.SaveData(); } catch (e) { var langFile = this.loadModuleLang(), values = nodeId + ": " + e.toString(); this.addNotification("error", langFile.err_dev_create + values, "core"); console.log(e.stack); } }; ================================================ FILE: modules/EnOcean/lang/de.json ================================================ { "m_title":"EnOcean Netzwerksteuerung", "m_descr":"Ermöglicht die Nutzung von EnOcean Geräten, die mit dem Gateway verbunden sind", "l_port":"Serieller Port der verwendet werden soll:", "l_name":"Interner Name", "h_name":"Dies sollte ein valider JS key String sein. Achtung! Nur ändern, wenn Sie wissen was Sie tun.", "rl_enableAPI":"Enable EnOcean API", "h_enableAPI":"Handler HTTP requests to /EnOceanAPI/* and /EnOcean//*", "rl_publicAPI":"Allow public access to EnOcean API", "h_publicAPI":"Do not ask for authentication to access EnOcean API", "rl_createVDev":"Create virtual devices in Home Automation engine", "h_createVDev":"Without this EnOcean devices will be visible only in Expert UI and via EnOcean API", "l_config":"Pfad zum Ordner /config", "h_change_sth":"Achtung! Nur ändern, wenn Sie wissen was Sie tun.", "err_binding_start":"Fehler beim Starten der EnOcean Bindung: ", "err_dev_create":"vDev konnte mit den folgenden Daten nicht erstellt werden: " } ================================================ FILE: modules/EnOcean/lang/en.json ================================================ { "m_title":"EnOcean Network Access", "m_descr":"Allows accessing EnOcean devices from attached EnOcean transceiver.", "l_port":"Serial port to EnOcean dongle", "l_name":"Internal name", "h_name":"Should be a valid JS key string. Don't change unless you know what you are doing", "rl_enableAPI":"Enable EnOcean API", "h_enableAPI":"Handler HTTP requests to /EnOcean/* and /EnOcean//*", "rl_publicAPI":"Allow public access to EnOcean API", "h_publicAPI":"Do not ask for authentication to access EnOcean API", "rl_createVDev":"Create virtual devices in Home Automation engine", "h_createVDev":"Without this EnOcean devices will be visible only in Expert UI and via EnOcean API", "l_config":"Path to config folder", "h_change_sth":"Don't change unless you know what you are doing", "err_binding_start":"Cannot start EnOcean binding: ", "err_dev_create":"Cannot create vDev based on: " } ================================================ FILE: modules/EnOcean/lang/ru.json ================================================ { "m_title":"EnOcean движок", "m_descr":"Активация EnOcean движка.", "l_port":"Порт для EnOcean платы/стика", "l_name":"Внутреннее имя", "h_name":"Должно быть допустимой JS строкой. Не изменяйте, если не знаете, что делаете", "rl_enableAPI":"Включить EnOcean API", "h_enableAPI":"Обрабатывать HTTP запросы /EnOceanAPI/* и /EnOcean/<внутреннее имя zway>/*", "rl_publicAPI":"Разрешить доступ к EnOcean API без аутентификации", "h_publicAPI":"Не требовать аутентификации при обработке запросов к EnOcean API", "rl_createVDev":"Создавать виртуальные устройства домашней автоматизации", "h_createVDev":"Если отключить, то EnOcean устройства будут видны только в Экспертном интерфейсе и через EnOcean API", "l_config":"Путь к папке с настройками config/", "h_change_sth":"Не изменяйте, если не знаете, что делаете", "err_binding_start":"Невозможно запустить EnOcean движок: ", "err_dev_create":"Не возможно создать vDev основанное на: " } ================================================ FILE: modules/EnOcean/module.json ================================================ { "dependencies": [], "singleton": true, "category": "peripherals", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.svg", "moduleName":"EnOcean", "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__", "name": "zeno", "port": "/dev/ttyUSB0", "enableAPI": true, "publicAPI": false, "createVDev": true, "config": "config" }, "schema": { "type": "object", "properties": { "port": { "type": "string", "required": true }, "name": { "type": "string", "required": true }, "enableAPI": { "type": "boolean", "default": true }, "publicAPI": { "type": "boolean", "default": true }, "createVDev": { "type": "boolean", "default": true }, "config": { "type": "string", "required": true } }, "required": false }, "options": { "fields": { "port": { "label": "__l_port__" }, "name": { "label": "__l_name__", "helper": "__h_name__" }, "enableAPI": { "type": "checkbox", "rightLabel": "__rl_enableAPI__", "helper": "__h_enableAPI__" }, "publicAPI": { "type": "checkbox", "rightLabel": "__rl_publicAPI__", "helper": "__h_publicAPI__" }, "createVDev": { "type": "checkbox", "rightLabel": "__rl_createVDev__", "helper": "__h_createVDev__" }, "config": { "hidden": true, "label": "__l_config__", "helper": "__h_change_sth__" } } } } ================================================ FILE: modules/FosCam9805/index.js ================================================ /*** FosCam9805 Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Stanislav Morozov Description: This module stores params of FosCam9805 ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function FosCam9805 (id, controller) { // Call superconstructor first (AutomationModule) FosCam9805.super_.call(this, id, controller); } inherits(FosCam9805, AutomationModule); _module = FosCam9805; // ---------------------------------------------------------------------------- // --- Module insFosCam98059828tance initialized // ---------------------------------------------------------------------------- _.extend(FosCam9805.prototype, { init: function (config) { FosCam9805.super_.prototype.init.call(this, config); var self=this; var that = this, vDevId = "CameraDevice_" + this.id; this.proxy_url = "/" + vDevId + "/stream"; var authent = "&usr=" + config.user + "&pwd=" + config.password; this.cgi = "/cgi-bin/CGIProxy.fcgi?cmd="; var urlzoomin = config.url + this.cgi + "zoomIn" + authent; var urlzoomout = config.url + this.cgi + "zoomOut" + authent; var urlzoomstop = config.url + this.cgi + "zoomStop" + authent; var opener = function(command) { config.doorDevices.forEach(function(el) { var vDev = that.controller.devices.get(el); if (vDev) { var type = vDev.get("deviceType"); if (type === "switchBinary") { vDev.performCommand(command == "open" ? "on" : "off"); } else if (type === "doorlock") { vDev.performCommand(command); } } }); }; this.setup = config.url+ "/cgi-bin/CGIProxy.fcgi?cmd=setSubStreamFormat&format=1" + authent; if (this.setup) { http.request({ url: this.setup, async: true }); } this.url = config.url + "/cgi-bin/CGIStream.cgi?cmd=GetMJStream" + authent; ws.proxify(this.proxy_url, this.url, config.user, config.password); this.vDev = this.controller.devices.create({ deviceId: vDevId, defaults: { deviceType: "camera", metrics: { icon: "camera", title: self.getInstanceTitle() } }, overlay: { metrics: { url: this.proxy_url, hasZoomIn: !!urlzoomin, hasZoomOut: !!urlzoomout, hasClose: !!urlzoomstop || (config.doorDevices && config.doorDevices.length) } }, handler: function(command) { var url = null; if (command == "zoomIn") { url = urlzoomin; } else if (command == "zoomOut") { url = urlzoomout; } else if (command == "open") { url = urlopen; opener(command); } else if (command == "close") { url = urlzoomstop; // opener(command); } if (url) { http.request({ url: url, async: true, auth: { login: config.user, password: config.password } }); } }, moduleId: this.id }); }, stop: function () { FosCam9805.super_.prototype.stop.call(this); ws.proxify(this.proxy_url, null); if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } } }); ================================================ FILE: modules/FosCam9805/lang/de.json ================================================ { "m_descr":"Ünterstützung der FosCam Outdoor Kameras (FI9805W,FI8905W,FI9805E)", "ip":"Kamera IP-Adresse", "h_ip":"Beispielformat: 'http://IPADDRESS:PORT'", "user":"Benutzername", "h_user":"Sie benötigen den Benutzernamen, den Sie in der Kamerakonfiguration angegeben haben.", "pw":"Password", "h_pw":"Sie benötigen das Password, das Sie in der Kamerakonfiguration angegeben haben.", "door_dev":"Türsensor" } ================================================ FILE: modules/FosCam9805/lang/en.json ================================================ { "m_descr":"Support the FosCam Outdoor Cameras (FI9805W,FI8905W,FI9805E)", "ip":"Camera IP URL", "h_ip":"in the format 'http://IPADDRESS:PORT'", "user":"Username", "h_user":"This is the username that was set up during camera configuration", "pw":"Password", "h_pw":"This is the password that was set up during camera configuration", "door_dev":"Door device" } ================================================ FILE: modules/FosCam9805/lang/ru.json ================================================ { "m_descr":"Поддержка Уличных Камер FosCam (FI9805W,FI8905W,FI9805E).", "ip":"URL Камеры", "h_ip":"В формате 'http://IPADDRESS:PORT'", "user":"Имя пользователя", "h_user":"Имя пользователя, которое установлено в настройках камеры", "pw":"Пароль", "h_pw":"Пароль, который установлен в настройках камеры", "door_dev":"Устройство управления доступом двери" } ================================================ FILE: modules/FosCam9805/module.json ================================================ { "singleton": false, "dependencies": [], "category": "surveillance", "author": "Z-Wave.Me", "homepage": "http://www.foscam.com/", "icon": "icon.png", "moduleName":"FosCam9805", "version": "1.1.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "FosCam FI9805", "description": "__m_descr__", "url": "", "doorDevices": [] }, "schema": { "type": "object", "properties": { "url": { "required": true }, "user": { "required": false }, "password": { "format": "password", "required": false }, "doorDevices": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId,namespaces:devices_switchBinary:deviceId", "required": false } } }, "required": false }, "options": { "fields": { "url": { "label": "__ip__", "helper": "__h_ip__", "required": true }, "user": { "label": "__user__", "helper": "__h_user__", "required": false }, "password": { "label": "__pw__", "helper": "__h_pw__", "required": false }, "doorDevices": { "label": "__door_dev__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName,namespaces:devices_switchBinary:deviceName" } } } } } } ================================================ FILE: modules/FosCam9821/index.js ================================================ /*** FosCam9821 Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Stanislav Morozov Description: This module stores params of FosCam9821 ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function FosCam9821 (id, controller) { // Call superconstructor first (AutomationModule) FosCam9821.super_.call(this, id, controller); } inherits(FosCam9821, AutomationModule); _module = FosCam9821; // ---------------------------------------------------------------------------- // --- Module insFosCam98219828tance initialized // ---------------------------------------------------------------------------- _.extend(FosCam9821.prototype, { init: function (config) { FosCam9821.super_.prototype.init.call(this, config); var self=this; var that = this, vDevId = "CameraDevice_" + this.id; this.proxy_url = "/" + vDevId + "/stream"; var authent = "&usr=" + config.user + "&pwd=" + config.password; this.cgi = "/cgi-bin/CGIProxy.fcgi?cmd="; var urlup = config.url + this.cgi + "ptzMoveUp" + authent; var urldown = config.url + this.cgi + "ptzMoveDown" + authent; var urlleft = config.url + this.cgi + "ptzMoveLeft" + authent; var urlright = config.url + this.cgi + "ptzMoveRight" + authent; var urlstop = config.url + this.cgi + "ptzStopRun" + authent; var opener = function(command) { config.doorDevices.forEach(function(el) { var vDev = that.controller.devices.get(el); if (vDev) { var type = vDev.get("deviceType"); if (type === "switchBinary") { vDev.performCommand(command == "open" ? "on" : "off"); } else if (type === "doorlock") { vDev.performCommand(command); } } }); }; this.setup = config.url+ "/cgi-bin/CGIProxy.fcgi?cmd=setSubStreamFormat&format=1" + authent; if (this.setup) { http.request({ url: this.setup, async: true }); } this.url = config.url + "/cgi-bin/CGIStream.cgi?cmd=GetMJStream" + authent; ws.proxify(this.proxy_url, this.url, config.user, config.password); this.vDev = this.controller.devices.create({ deviceId: vDevId, defaults: { deviceType: "camera", metrics: { icon: "camera", title: self.getInstanceTitle() } }, overlay: { metrics: { url: this.proxy_url, hasLeft: !!urlleft, hasRight: !!urlright, hasUp: !!urlup, hasDown: !!urldown, hasClose: !!urlstop || (config.doorDevices && config.doorDevices.length) } }, handler: function(command) { var url = null; console.log(urlstop); if (command == "left") { url = urlleft; } else if (command == "right") { url = urlright } else if (command == "up") { url = urlup; } else if (command == "down") { url = urldown; } else if (command == "open") { url = urlopen; opener(command); } else if (command == "close") { url = urlstop; // opener(command); } if (url) { http.request({ url: url, async: true, auth: { login: config.user, password: config.password } }); } }, moduleId: this.id }); }, stop: function () { FosCam9821.super_.prototype.stop.call(this); ws.proxify(this.proxy_url, null); if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } } }); ================================================ FILE: modules/FosCam9821/lang/de.json ================================================ { "m_descr":"Ünterstützung der FosCam Indoor Kameras ohne Zoom (FI9821, FI9831, FI8910)", "ip":"Kamera IP-Adresse", "h_ip":"Beispielformat: 'http://IPADDRESS:PORT'", "user":"Benutzername", "h_user":"Sie benötigen den Benutzernamen, den Sie in der Kamerakonfiguration angegeben haben.", "pw":"Password", "h_pw":"Sie benötigen das Password, das Sie in der Kamerakonfiguration angegeben haben.", "door_dev":"Türsensor" } ================================================ FILE: modules/FosCam9821/lang/en.json ================================================ { "m_descr":"Support the FosCam Indoor Cameras without Zoom (FI9821, FI9831, FI8910)", "ip":"Camera IP URL", "h_ip":"in the format 'http://IPADDRESS:PORT'", "user":"Username", "h_user":"This is the username that was set up during camera configuration", "pw":"Password", "h_pw":"This is the password that was set up during camera configuration", "door_dev":"Door device" } ================================================ FILE: modules/FosCam9821/lang/ru.json ================================================ { "m_descr":"Поддержка Камер FosCam без зума (FI9821, FI9831, FI8910).", "ip":"URL Камеры", "h_ip":"В формате 'http://IPADDRESS:PORT'", "user":"Имя пользователя", "h_user":"Имя пользователя, которое установлено в настройках камеры", "pw":"Пароль", "h_pw":"Пароль, который установлен в настройках камеры", "door_dev":"Устройство управления доступом двери" } ================================================ FILE: modules/FosCam9821/module.json ================================================ { "singleton": false, "dependencies": [], "category": "surveillance", "author": "Z-Wave.Me", "homepage": "http://www.foscam.com/", "icon": "icon.png", "moduleName":"FosCam9821", "version": "1.1.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "FosCam FI9821", "description": "__m_descr__", "url": "", "doorDevices": [] }, "schema": { "type": "object", "properties": { "url": { "required": true }, "user": { "required": false }, "password": { "format": "password", "required": false }, "doorDevices": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId,namespaces:devices_switchBinary:deviceId", "required": false } } }, "required": false }, "options": { "fields": { "url": { "label": "__ip__", "helper": "__h_ip__", "required": true }, "user": { "label": "__user__", "helper": "__h_user__", "required": false }, "password": { "label": "__pw__", "helper": "__h_pw__", "required": false }, "doorDevices": { "label": "__door_dev__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName,namespaces:devices_switchBinary:deviceName" } } } } } } ================================================ FILE: modules/FosCam9826/index.js ================================================ /*** FosCam9826 Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Stanislav Morozov Description: This module stores params of FosCam9826 ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function FosCam9826 (id, controller) { // Call superconstructor first (AutomationModule) FosCam9826.super_.call(this, id, controller); } inherits(FosCam9826, AutomationModule); _module = FosCam9826; // ---------------------------------------------------------------------------- // --- Module insFosCam98269828tance initialized // ---------------------------------------------------------------------------- _.extend(FosCam9826.prototype, { init: function (config) { FosCam9826.super_.prototype.init.call(this, config); var self=this; var that = this, vDevId = "CameraDevice_" + this.id; this.proxy_url = "/" + vDevId + "/stream"; var authent = "&usr=" + config.user + "&pwd=" + config.password; this.cgi = "/cgi-bin/CGIProxy.fcgi?cmd="; var urlup = config.url + this.cgi + "ptzMoveUp" + authent; var urldown = config.url + this.cgi + "ptzMoveDown" + authent; var urlleft = config.url + this.cgi + "ptzMoveLeft" + authent; var urlright = config.url + this.cgi + "ptzMoveRight" + authent; var urlstop = config.url + this.cgi + "ptzStopRun" + authent; var urlzoomin = config.url + this.cgi + "zoomIn" + authent; var urlzoomout = config.url + this.cgi + "zoomOut" + authent; var urlzoomstop = config.url + this.cgi + "zoomStop" + authent; var opener = function(command) { config.doorDevices.forEach(function(el) { var vDev = that.controller.devices.get(el); if (vDev) { var type = vDev.get("deviceType"); if (type === "switchBinary") { vDev.performCommand(command == "open" ? "on" : "off"); } else if (type === "doorlock") { vDev.performCommand(command); } } }); }; this.setup = config.url+ "/cgi-bin/CGIProxy.fcgi?cmd=setSubStreamFormat&format=1" + authent; if (this.setup) { http.request({ url: this.setup, async: true }); } this.url = config.url + "/cgi-bin/CGIStream.cgi?cmd=GetMJStream" + authent; ws.proxify(this.proxy_url, this.url, config.user, config.password); this.vDev = this.controller.devices.create({ deviceId: vDevId, defaults: { deviceType: "camera", metrics: { icon: "camera", title: self.getInstanceTitle() } }, overlay: { metrics: { url: this.proxy_url, hasZoomIn: !!urlzoomin, hasZoomOut: !!urlzoomout, hasLeft: !!urlleft, hasRight: !!urlright, hasUp: !!urlup, hasDown: !!urldown, hasClose: !!urlstop || (config.doorDevices && config.doorDevices.length) } }, handler: function(command) { var url = null; console.log(urlstop); if (command == "zoomIn") { url = urlzoomin; } else if (command == "zoomOut") { url = urlzoomout; } else if (command == "left") { url = urlleft; } else if (command == "right") { url = urlright } else if (command == "up") { url = urlup; } else if (command == "down") { url = urldown; } else if (command == "open") { url = urlopen; opener(command); } else if (command == "close") { url = urlstop; url = urlzoomstop; // opener(command); } if (url) { http.request({ url: url, async: true, auth: { login: config.user, password: config.password } }); } }, moduleId: this.id }); }, stop: function () { FosCam9826.super_.prototype.stop.call(this); ws.proxify(this.proxy_url, null); if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } } }); ================================================ FILE: modules/FosCam9826/lang/de.json ================================================ { "m_descr":"Ünterstützung der Indoor Zoom Kameras (FI9826W)", "ip":"Kamera IP-Adresse", "h_ip":"Beispielformat: 'http://IPADDRESS:PORT'", "user":"Benutzername", "h_user":"Sie benötigen den Benutzernamen, den Sie in der Kamerakonfiguration angegeben haben.", "pw":"Password", "h_pw":"Sie benötigen das Password, das Sie in der Kamerakonfiguration angegeben haben.", "door_dev":"Türsensor" } ================================================ FILE: modules/FosCam9826/lang/en.json ================================================ { "m_descr":"Support the FosCam Indoor Zoom Cameras (FI9826W)", "ip":"Camera IP URL", "h_ip":"in the format 'http://IPADDRESS:PORT'", "user":"Username", "h_user":"This is the username that was set up during camera configuration", "pw":"Password", "h_pw":"This is the password that was set up during camera configuration", "door_dev":"Door device" } ================================================ FILE: modules/FosCam9826/lang/ru.json ================================================ { "m_descr":"Поддержка Камер FosCam с зумом (FI9826W).", "ip":"URL Камеры", "h_ip":"В формате 'http://IPADDRESS:PORT'", "user":"Имя пользователя", "h_user":"Имя пользователя, которое установлено в настройках камеры", "pw":"Пароль", "h_pw":"Пароль, который установлен в настройках камеры", "door_dev":"Устройство управления доступом двери" } ================================================ FILE: modules/FosCam9826/module.json ================================================ { "singleton": false, "dependencies": [], "category": "surveillance", "author": "Z-Wave.Me", "homepage": "http://www.foscam.com/", "icon": "icon.png", "moduleName":"FosCam9826", "version": "1.1.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "FosCam FI9826", "description": "__m_descr__", "url": "", "doorDevices": [] }, "schema": { "type": "object", "properties": { "url": { "required": true }, "user": { "required": false }, "password": { "format": "password", "required": false }, "doorDevices": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId,namespaces:devices_switchBinary:deviceId", "required": false } } }, "required": false }, "options": { "fields": { "url": { "label": "__ip__", "helper": "__h_ip__", "required": true }, "user": { "label": "__user__", "helper": "__h_user__", "required": false }, "password": { "label": "__pw__", "helper": "__h_pw__", "required": false }, "doorDevices": { "label": "__door_dev__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName,namespaces:devices_switchBinary:deviceName" } } } } } } ================================================ FILE: modules/FosCam9828/index.js ================================================ /*** FosCam9828 Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Stanislav Morozov Description: This module stores params of FosCam9828 ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function FosCam9828 (id, controller) { // Call superconstructor first (AutomationModule) FosCam9828.super_.call(this, id, controller); } inherits(FosCam9828, AutomationModule); _module = FosCam9828; // ---------------------------------------------------------------------------- // --- Module insFosCam98289828tance initialized // ---------------------------------------------------------------------------- _.extend(FosCam9828.prototype, { init: function (config) { FosCam9828.super_.prototype.init.call(this, config); var self=this; var that = this, vDevId = "CameraDevice_" + this.id; this.proxy_url = "/" + vDevId + "/stream"; var authent = "&usr=" + config.user + "&pwd=" + config.password; this.cgi = "/cgi-bin/CGIProxy.fcgi?cmd="; var urlup = config.url + this.cgi + "ptzMoveUp" + authent; var urldown = config.url + this.cgi + "ptzMoveDown" + authent; var urlleft = config.url + this.cgi + "ptzMoveLeft" + authent; var urlright = config.url + this.cgi + "ptzMoveRight" + authent; var urlstop = config.url + this.cgi + "ptzStopRun" + authent; var urlzoomin = config.url + this.cgi + "zoomIn" + authent; var urlzoomout = config.url + this.cgi + "zoomOut" + authent; var urlzoomstop = config.url + this.cgi + "zoomStop" + authent; var opener = function(command) { config.doorDevices.forEach(function(el) { var vDev = that.controller.devices.get(el); if (vDev) { var type = vDev.get("deviceType"); if (type === "switchBinary") { vDev.performCommand(command == "open" ? "on" : "off"); } else if (type === "doorlock") { vDev.performCommand(command); } } }); }; this.setup = config.url+ "/cgi-bin/CGIProxy.fcgi?cmd=setSubStreamFormat&format=1" + authent; if (this.setup) { http.request({ url: this.setup, async: true }); } this.url = config.url + "/cgi-bin/CGIStream.cgi?cmd=GetMJStream" + authent; ws.proxify(this.proxy_url, this.url, config.user, config.password); this.vDev = this.controller.devices.create({ deviceId: vDevId, defaults: { deviceType: "camera", metrics: { icon: "camera", title: self.getInstanceTitle() } }, overlay: { metrics: { url: this.proxy_url, hasZoomIn: !!urlzoomin, hasZoomOut: !!urlzoomout, hasLeft: !!urlleft, hasRight: !!urlright, hasUp: !!urlup, hasDown: !!urldown, hasClose: !!urlstop || (config.doorDevices && config.doorDevices.length) } }, handler: function(command) { var url = null; console.log(urlstop); if (command == "zoomIn") { url = urlzoomin; } else if (command == "zoomOut") { url = urlzoomout; } else if (command == "left") { url = urlleft; } else if (command == "right") { url = urlright } else if (command == "up") { url = urlup; } else if (command == "down") { url = urldown; } else if (command == "open") { url = urlopen; opener(command); } else if (command == "close") { url = urlstop; url = urlzoomstop; // opener(command); } if (url) { http.request({ url: url, async: true, auth: { login: config.user, password: config.password } }); } }, moduleId: this.id }); }, stop: function () { FosCam9828.super_.prototype.stop.call(this); ws.proxify(this.proxy_url, null); if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } } }); ================================================ FILE: modules/FosCam9828/lang/de.json ================================================ { "m_descr":"Ünterstützung der FosCam Dome Outdoor Kameras (FI9828, FI9819)", "ip":"Kamera IP-Adresse", "h_ip":"Beispielformat: 'http://IPADDRESS:PORT'", "user":"Benutzername", "h_user":"Sie benötigen den Benutzernamen, den Sie in der Kamerakonfiguration angegeben haben.", "pw":"Password", "h_pw":"Sie benötigen das Password, das Sie in der Kamerakonfiguration angegeben haben.", "door_dev":"Türsensor" } ================================================ FILE: modules/FosCam9828/lang/en.json ================================================ { "m_descr":"Support the FosCam dome Outdoor Cameras (FI9828, FI9819)", "ip":"Camera IP URL", "h_ip":"in the format 'http://IPADDRESS:PORT'", "user":"Username", "h_user":"This is the username that was set up during camera configuration", "pw":"Password", "h_pw":"This is the password that was set up during camera configuration", "door_dev":"Door device" } ================================================ FILE: modules/FosCam9828/lang/ru.json ================================================ { "m_descr":"Поддержка Уличных Камер FosCam (FI9828, FI9819).", "ip":"URL Камеры", "h_ip":"В формате 'http://IPADDRESS:PORT'", "user":"Имя пользователя", "h_user":"Имя пользователя, которое установлено в настройках камеры", "pw":"Пароль", "h_pw":"Пароль, который установлен в настройках камеры", "door_dev":"Устройство управления доступом двери" } ================================================ FILE: modules/FosCam9828/module.json ================================================ { "singleton": false, "dependencies": [], "category": "surveillance", "author": "Z-Wave.Me", "homepage": "http://www.foscam.com/", "icon": "icon.png", "moduleName":"FosCam9828", "version": "1.1.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "FosCam FI9828", "description": "__m_descr__", "url": "", "doorDevices": [] }, "schema": { "type": "object", "properties": { "url": { "required": true }, "user": { "required": false }, "password": { "format": "password", "required": false }, "doorDevices": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId,namespaces:devices_switchBinary:deviceId", "required": false } } }, "required": false }, "options": { "fields": { "url": { "label": "__ip__", "helper": "__h_ip__", "required": true }, "user": { "label": "__user__", "helper": "__h_user__", "required": false }, "password": { "label": "__pw__", "helper": "__h_pw__", "required": false }, "doorDevices": { "label": "__door_dev__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName,namespaces:devices_switchBinary:deviceName" } } } } } } ================================================ FILE: modules/GlobalCache/index.js ================================================ /*** GlobalCache Z-Way HA module ******************************************* Version: 1.0.0 (c) Z-Wave.Me, 2014 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: Implements Global Cache support ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function GlobalCache (id, controller) { // Call superconstructor first (AutomationModule) GlobalCache.super_.call(this, id, controller); } inherits(GlobalCache, AutomationModule); _module = GlobalCache; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- GlobalCache.prototype.init = function (config) { GlobalCache.super_.prototype.init.call(this, config); var self = this; var vDev = self.controller.devices.create({ deviceId: "GlobalCache_Device_" + this.id, defaults: { deviceType: "toggleButton", metrics: { title: 'Global Cache ' + this.id, icon: "" } }, overlay: {}, handler: function (command, args) { try { var sock = new sockets.tcp(); sock.connect(self.config.host, 4998); sock.send(self.config.data + "\r\n\r\n"); } catch (e) { console.log("Failed to send data to GlobalCache: " + e.toString() + " (IP: " + self.config.host + ", data: \"" + self.config.data + "\")"); } finally { sock.close(); } self.vDev.set("metrics:level", "on"); // update on ourself to allow catch this event }, moduleId: this.id }); }; GlobalCache.prototype.stop = function () { this.controller.devices.remove("GlobalCache_Device_" + this.id); GlobalCache.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/GlobalCache/lang/de.json ================================================ { "m_title":"Global Caché", "m_descr":"Global Cache ist ein Hersteller verschiedener Geräte, die per IP Befehl an- bzw. ausgeschalten werden können. Diese App erstellt ein neues Element und verbindet dessen Schaltfläche mit einem bestimmten Befehl, der an ein Global Cache Gerät gesendet wird.

          Einstellungen:
          • IP Adresse oder Hostname des Global Cache Gerätes
          • Befehls-String zur Ausführung der gewünschten Aktion am Global Cache Gerät. Informationen zur Befehlsbestimmung entnehmen Sie bitte den Global Cache Anleitungen.
          ", "l_host":"Global Caché IP or hostname", "l_data":"Command to send", "h_data":"Command as reported by Global Caché IR Learning Utility WITHOUT final new line symbols (\r\n\r\n)." } ================================================ FILE: modules/GlobalCache/lang/en.json ================================================ { "m_title":"Global Caché", "m_descr":"Global Cache is a manufacturer of various devices that can be turned on/off using an IP command. This app will create a new element and connect the toggle button of this element with a defined command sent to a Global Cache device.

          Settings:
          • IP Address or host name of the Global Cache device
          • Command String to execute the desired action on the Global Cache device. Please refer to the Global Cache manuals for information how to define this command.
          ", "l_host":"Global Caché IP or hostname", "l_data":"Command to send", "h_data":"Command as reported by Global Caché IR Learning Utility WITHOUT final new line symbols (\r\n\r\n)." } ================================================ FILE: modules/GlobalCache/lang/ru.json ================================================ { "m_title":"Global Caché", "m_descr":"Создать кнопку, отправляющую команду устройству Global Caché.", "l_host":"IP или имя хоста "Global Caché", "l_data":"Отправляемая команда", "h_data":"Команда, полученная из Global Caché IR Learning Utility БЕЗ символов переноса строки (\r\n\r\n)." } ================================================ FILE: modules/GlobalCache/module.json ================================================ { "singleton": false, "dependencies": [], "category": "support_external_dev", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "GlobalCache", "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__", "host": "", "data": "" }, "schema": { "type": "object", "properties": { "host": { "type": "string", "required": true }, "data": { "type": "string", "required": true } } }, "options": { "fields": { "host": { "label": "__l_host__", "placeholder": "192.168.0.100 or hostname" }, "data": { "label": "__l_data__", "placeholder": "sendir,1:1,7,35874,1,1,.......,2500", "helper": "__h_data__" } } } } ================================================ FILE: modules/GoogleHome/index.js ================================================ /*** GoogleHome Z-Way HA module ******************************************* Version: 0.0.2 beta (c) Z-Wave.Me, 2019 ----------------------------------------------------------------------------- Author: Avaliani Alexander Description: ******************************************************************************/ function GoogleHome (id, controller) { // Call superconstructor first (AutomationModule) GoogleHome.super_.call(this, id, controller); } inherits(GoogleHome, AutomationModule); _module = GoogleHome; GoogleHome.prototype.init = function(config) { var self = this; GoogleHome.super_.prototype.init.call(this, config); this.HOMEGRAPH_URL = 'https://ghome.z-wave.me:5002/zway/api/v1.0/'; this.GOOGLE_PROFILE_NAME = 'https://oauth-redirect.googleusercontent.com'; this.deferredVDevCreation = []; this.deferredVDevCreationTimer = null; this.deferredVDevReport = []; this.deferredVDevReportTimer = null; this.delayedRequestSyncDevicesHandler = function(dev) { self.delayedRequestSyncDevices(dev); }; this.delayedReportStateDevicesHandler = function(dev) { if (!dev.get('permanently_hidden')) { self.delayedReportStateDevices(dev); } }; this.requestSyncProfilesHandler = function(profile) { self.requestSyncProfiles(self.getActiveAgentUsers().filter(function(agentUser) { return agentUser.name === profile.login; }).map(function(agentUser) { return agentUser.agentId; })); }; this.controller.devices.on('change:metrics:level', this.delayedReportStateDevicesHandler); this.controller.devices.on('change:metrics:title', this.delayedRequestSyncDevicesHandler); this.controller.devices.on('change:location', this.delayedRequestSyncDevicesHandler); this.controller.devices.on('created', this.delayedRequestSyncDevicesHandler); this.controller.on('profile.updated', this.requestSyncProfilesHandler); }; GoogleHome.prototype.stop = function() { this.controller.devices.off('change:metrics:level', this.delayedReportStateDevicesHandler); this.controller.devices.off('change:metrics:title', this.delayedRequestSyncDevicesHandler); this.controller.devices.off('change:location', this.delayedRequestSyncDevicesHandler); this.controller.devices.off('created', this.delayedRequestSyncDevicesHandler); this.controller.off('profile.updated', this.requestSyncProfilesHandler); GoogleHome.super_.prototype.stop.call(this); }; GoogleHome.prototype.delayedReportStateDevices = function(dev) { var self = this; // remove the device from the list if it was already there for (var i = 0; i < this.deferredVDevReport.length; i++) { if (this.deferredVDevReport[i].id === dev.id) { this.deferredVDevReport.splice(i, 1); break; } } this.deferredVDevReport.push(dev); // set a short timer and send after the delay if (!this.deferredVDevReportTimer) { this.deferredVDevReportTimer = setTimeout(function() { self.reportState(self.deferredVDevReport); self.deferredVDevReport = []; self.deferredVDevReportTimer = null; }, 3*1000); } }; GoogleHome.prototype.delayedRequestSyncDevices = function(dev) { var self = this; // remove the device from the list if it was already there for (var i = 0; i < this.deferredVDevCreation.length; i++) { if (this.deferredVDevCreation[i].id === dev.id) { this.deferredVDevCreation.splice(i, 1); break; } } this.deferredVDevCreation.push(dev); // restart the timer everty time to collect more data if (this.deferredVDevCreationTimer) { clearTimeout(this.deferredVDevCreationTimer); } this.deferredVDevCreationTimer = setTimeout(function() { self.requestSyncDevices(self.deferredVDevCreation); self.deferredVDevCreation = []; self.deferredVDevCreationTimer = null; }, 10*1000); }; GoogleHome.prototype.requestSyncProfiles = function(agentUserIds) { if (agentUserIds.length != 0) { data = JSON.stringify({'agentUserIds': agentUserIds}); http.request({ method: 'POST', url: this.HOMEGRAPH_URL + 'requestSync', async: true, data: data, headers: { "Content-Type": "application/json" }, success: function(response) { console.log("Google Home updated"); }, error: function(response) { console.log("Google Home update error: " + response.statusText + ", " + response.data); } }); } }; GoogleHome.prototype.requestSyncDevices = function(devices) { var self = this, agentUserIds = []; var agentUsers = this.getActiveAgentUsers(); devices.forEach(function(dev) { agentUsers.forEach(function(agentUser) { if (agentUserIds.indexOf(agentUser.agentId) === -1 && self.controller.deviceByUser(dev.id, agentUser.id)) { agentUserIds.push(agentUser.agentId); } }); }); this.requestSyncProfiles(agentUserIds); } GoogleHome.prototype.reportState = function(devices) { var self = this, data = []; devices.forEach(function(dev) { // check every profile if its list of devices has changed and make request to server self.getActiveAgentUsers().forEach(function(profile) { if (self.controller.deviceByUser(dev.id, profile.id)) { data.push({ agentUserId: profile.agentId, device: dev }) } }); if (data.length != 0) { http.request({ method: 'POST', url: self.HOMEGRAPH_URL + 'reportState', async: true, data: JSON.stringify(data), headers: { "Content-Type": "application/json" }, success: function(response) { // not to spam in the log // console.log("Google Home updated"); }, error: function(response) { console.log("Google Home update error: " + response.statusText + ", " + response.data + ", " + JSON.stringify(data)); } }); } }); }; GoogleHome.prototype.getActiveAgentUsers = function() { var self = this; // get only active Google OAuth profiles return this.controller.profiles.filter(function(profile) { // TODO: Change this to some special mark of token return profile.login.indexOf(self.GOOGLE_PROFILE_NAME) !== -1; }).map(function(profile) { return { id: profile.id, name: profile.login, agentId: profile.uuid }; }); } // TODO: API to notify this module that the token has to be marked as Google Home (and the profile too) // TODO: !!! Check that you can link two times the same profile with two Google accounts and Google will not complain on this (same agentId). In that case change agentID to tokenID ================================================ FILE: modules/GoogleHome/lang/de.json ================================================ { "m_title":"Google Home", "m_descr":"Mit diesem Modul können Sie Ihre Z-Way Geräte über den Google-Assistenten steuern." } ================================================ FILE: modules/GoogleHome/lang/en.json ================================================ { "m_title":"Google Home", "m_descr":"This module let you control your Z-Way devices through the Google Assistant." } ================================================ FILE: modules/GoogleHome/module.json ================================================ { "dependencies": [], "singleton": true, "category": "support_external_dev", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"GoogleHome", "version": "0.1.0", "maturity": "beta", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title" : "__m_title__", "description": "__m_descr__", "devices": [] } } ================================================ FILE: modules/GroupDevices/index.js ================================================ /*** GroupDevices Z-Way HA module ******************************************* Version: 2.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: Groups several devices together to make a new virtual device Changelog: 16.12.2015 (aivs) - Added selector to set how group widget associated with devices in group: 1) not associated, 2) show biggest value, 3) show smaller value ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function GroupDevices (id, controller) { // Call superconstructor first (AutomationModule) GroupDevices.super_.call(this, id, controller); } inherits(GroupDevices, AutomationModule); _module = GroupDevices; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- GroupDevices.prototype.init = function (config) { GroupDevices.super_.prototype.init.call(this, config); var self = this, icon = "", level = "", deviceType = this.config.isDimmable ? "switchMultilevel" : "switchBinary"; switch(deviceType) { case "switchBinary": icon = "switch"; level = "off"; break; case "switchMultilevel": icon = "multilevel"; level = 0; break; } var defaults = { metrics: { title: self.getInstanceTitle() } }; var overlay = { deviceType: deviceType, metrics: { icon: icon, level: level } }; this.vDev = this.controller.devices.create({ deviceId: "GroupDevices_" + this.id, defaults: defaults, overlay: overlay, handler: function(command, args) { self.config.devices.forEach(function(dev) { var vDev = self.controller.devices.get(dev.device); if (vDev) { if (command === "on" || command === "off" || (command === "exact" && vDev.get("deviceType") === "switchBinary")) { vDev.performCommand((dev.invert ^ (command === "on" || (command === "exact" && args.level > 0)))? "on" : "off"); } else if (command === "exact") { vDev.performCommand("exact", { level: dev.invert ? (99-args.level) : args.level }); } } }); if (command === "on" || command === "off") { var scenes = command === "on" ? self.config.scenesOn : self.config.scenesOff; scenes.forEach(function(scene) { var vDev = self.controller.devices.get(scene); if (vDev) { vDev.performCommand("on"); } }); } var level = command; if (self.config.isDimmable) { if (command === "on") { level = 99; } else if (command === "off") { level = 0; } else { level = args.level; } } this.set("metrics:level", level); }, moduleId: this.id }); this.handler = function() { var associationType = self.config.associationType; switch(associationType) { case "noAssociation": // widget doesn't change break; case "oneOff-widgetOff": // for dimmers show minimal value from group // for switch show off var res = 99; // max value self.config.devices.forEach(function(xdev) { var devValue = self.controller.devices.get(xdev.device).get("metrics:level"); // check dimmer value if (devValue < res) { res = devValue; } // check switch value. If one of all switch turned off, show 0 if (devValue === "off") { res = 0; } }); if (self.config.isDimmable) { self.vDev.set("metrics:level", res); } else { self.vDev.set("metrics:level", res > 0 ? "on" : "off"); } break; case "oneOn-widgetOn": // for dimmers show maximum value from group // for switch show on var res = 0; // min value self.config.devices.forEach(function(xdev) { var devValue = self.controller.devices.get(xdev.device).get("metrics:level"); // check dimmer value if (devValue > res) { res = devValue; } // check switch value. If all dimmers turned OFF and one of all switches turned on, show 1 if (devValue === "on" && res === 0) { res = 1; } }); if (self.config.isDimmable) { self.vDev.set("metrics:level", res); } else { self.vDev.set("metrics:level", res > 0 ? "on" : "off"); } break; } }; this.config.devices.forEach(function(dev) { this.controller.devices.on(dev.device, "change:metrics:level", self.handler); }); }; GroupDevices.prototype.stop = function () { var self = this; if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } this.config.devices.forEach(function(dev) { this.controller.devices.off(dev.device, "change:metrics:level", self.handler); }); GroupDevices.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/GroupDevices/lang/de.json ================================================ { "m_title":"Gerätegruppe", "m_descr":"Gruppiert Geräte in einem Element. Das Element kann sowohl ein- und ausgeschalten werden und ist zusätzlich dimmbar. Es können außerdem Szenen aktiviert werden. Diese App ist im Grunde genommen eine andere Art und Weise, um Szenen zu definieren.

          Einstellungen:
          • Anzuwählen, wenn die Gerätegruppe ein einfacher An-/Ausschalter oder dimmbar sein soll.
          • Wählen Sie eine Reihe an Geräten als Teil der Gerätegruppe aus. Es ist möglich, die Aktion pro Gerät zu invertieren. Das heißt, dass das Einschalten der Gerätegruppe bedeutet, das Gerät mit einer invertierten Aktion zu schalten.
          • Wählen Sie weitere Lichtszenen aus, die Sie auf AN oder AUS aktivieren möchten. Hieran verdeutlicht sich der Vorteil einer Gerätegruppe im Vergleich zu einer Szene. Eine Szene kann nur aktiviert werden. Hat man zwei Szenen ermöglicht diese App zwischen beiden Szenen hin- und her zu schalten.
          ", "rl_options":"Dimmbar", "l_devices":"Gerätegruppe", "rl_invert":"Umgekehrte Aktionen", "l_scenesOn":"Liste von Szenen/Aktionen, die beim Auslösen aktiviert werden (On).", "l_scenesOff":"Liste von Szenen/Aktionen, die beim Auslösen deaktiviert werden (Off).", "noAssociation":"The group value does not depend on the values of the devices in the group", "oneOff-widgetOff":"The group value corresponds to the smallest value of the devices in the group", "oneOn-widgetOn":"The group value corresponds to the highest value of the devices in the group" } ================================================ FILE: modules/GroupDevices/lang/en.json ================================================ { "m_title":"Group devices", "m_descr":"Groups devices into one element. The element can either be on/off or even dimmable. Scenes can also be activated. This app is essentially a different way to define a scene.

          Settings:
          • Select if the group device shall be a simple on/off switch or dimmable.
          • Select a list of devices as part of the group. Its possible to invert the action per device. This means that turning the group device on mean to turn of the device with inverted action.
          • Select further light scenes you like to activate on ON or OFF. Here you see the large advantage of the group device again a scene. A scene can only be activated. Having two scenes this app allows to toggle the two scenes.
          ", "rl_options":"Dimmable", "l_devices":"Group devices", "rl_invert":"Invert action", "l_scenesOn":"List of scenes to activate on On action", "l_scenesOff":"List of scenes to activate on Off action", "noAssociation":"The group value does not depend on the values of the devices in the group", "oneOff-widgetOff":"The group value corresponds to the smallest value of the devices in the group", "oneOn-widgetOn":"The group value corresponds to the highest value of the devices in the group" } ================================================ FILE: modules/GroupDevices/lang/ru.json ================================================ { "m_title":"Группа устройств", "m_descr":"Объединить несколько устройств в одну группу.", "rl_options":"Диммируемая группа устройств", "l_devices":"Устройства в группе", "rl_invert":"Инвертировать действие", "l_scenesOn":"Список сцен запускаемых при Включении группы", "l_scenesOff":"Список сцен запускаемых при Выключении группы", "noAssociation":"Значение группы не зависит от значения устройств в группе", "oneOff-widgetOff":"Значение группы соответствует наименьшему значению устройства в группе", "oneOn-widgetOn":"Значение группы соответствует наибольшему значению устройства в группе" } ================================================ FILE: modules/GroupDevices/module.json ================================================ { "singleton": false, "dependencies": [], "category": "device_enhancements", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "GroupDevices", "version": "2.1.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "isDimmable": false, "associationType": "noAssociation", "devices": [], "scenesOn": [], "scenesOff": [] }, "schema": { "type": "object", "properties": { "isDimmable": { "type": "boolean" }, "associationType": { "type": "string", "required": true, "enum": ["noAssociation", "oneOff-widgetOff", "oneOn-widgetOn"] }, "devices": { "type": "array", "items": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchBinary:deviceId,namespaces:devices_switchMultilevel:deviceId,namespaces:devices_doorlock:deviceId", "required": true }, "invert": { "type": "boolean", "default": false } } } }, "scenesOn": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true } }, "scenesOff": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true } } }, "required": false }, "options": { "fields": { "isDimmable": { "label": "", "rightLabel": "__rl_options__" }, "associationType": { "type": "select", "optionLabels": ["__noAssociation__", "__oneOff-widgetOff__", "__oneOn-widgetOn__"] }, "devices": { "label": "__l_devices__", "fields": { "item": { "fields": { "device": { "label": "", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchBinary:deviceName,namespaces:devices_switchMultilevel:deviceName,namespaces:devices_doorlock:deviceName" }, "invert": { "type": "checkbox", "rightLabel": "__rl_invert__" } } } } }, "scenesOn": { "label": "__l_scenesOn__", "fields": { "item": { "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" } } }, "scenesOff": { "label": "__l_scenesOff__", "fields": { "item": { "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" } } } } } } ================================================ FILE: modules/HTTPDevice/index.js ================================================ /*** HTTPDevice Z-Way HA module ******************************************* Version: 2.2.1 (c) Z-Wave.Me, 2020 ----------------------------------------------------------------------------- Author: Poltorak Serguei , Yurkin Vitaliy Description: Implements virtual device based on HTTP object ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function HTTPDevice (id, controller) { // Call superconstructor first (AutomationModule) HTTPDevice.super_.call(this, id, controller); } inherits(HTTPDevice, AutomationModule); _module = HTTPDevice; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- HTTPDevice.prototype.init = function (config) { HTTPDevice.super_.prototype.init.call(this, config); var self = this, icon = "", level = "", scaleTitle = "", deviceType = this.config.deviceType; probeType = "" switch(deviceType) { case "sensorBinary": icon = this.config.iconSensorBinary; probeType = this.config.iconSensorBinary == "door" ? "door-window" : this.config.iconSensorBinary; level = "off"; break; case "sensorMultilevel": icon = this.config.iconSensorMultilevel; probeType = this.config.iconSensorMultilevel; scaleTitle = this.config.scale_sensorMultilevel; level = 0; break; case "switchBinary": icon = "switch"; level = "off"; break; case "switchMultilevel": icon = "multilevel"; level = 0; break; case "toggleButton": icon = "gesture"; level = "on"; break; } var defaults = { metrics: { title: self.getInstanceTitle() } }; var overlay = { deviceType: deviceType, probeType: probeType, metrics: { icon: icon, level: level, scaleTitle: scaleTitle } }; var vDev = self.controller.devices.create({ deviceId: "HTTP_Device_" + deviceType + "_" + this.id, defaults: defaults, overlay: overlay, handler: function (command, args) { var vDevType = deviceType; if (command === "update" && (vDevType === "sensorBinary" || vDevType === "sensorMultilevel" || vDevType === "switchBinary" || vDevType === "switchMultilevel")) { self.update(this); } if (command === "on" && (vDevType === "toggleButton" || vDevType === "switchBinary")) { self.act(this, "On", null, (vDevType === "switchBinary" ? "on" : null)); } if (command === "off" && vDevType === "switchBinary") { self.act(this, "Off", null, "off"); } if ((command === "off" || command === "on" || command === "exact") && vDevType === "switchMultilevel") { var level = command === "exact" ? parseInt(args.level, 10) : (command === "on" ? 99 : 0); self.act(this, "Level", level, level); } }, moduleId: this.id }); if (vDev && this.config["getter_" + deviceType] && this.config["getterPollInterval_" + deviceType]) { this.timer = setInterval(function() { self.update(vDev); }, this.config["getterPollInterval_" + deviceType] * 1000); } }; HTTPDevice.prototype.stop = function () { if (this.timer) { clearInterval(this.timer); } this.controller.devices.remove("HTTP_Device_" + this.config.deviceType + "_" + this.id); HTTPDevice.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- HTTPDevice.prototype.update = function (vDev) { var self = this, deviceType = vDev.get("deviceType"), url = this.config["getter_" + deviceType], parser = this.config["getterParser_" + deviceType]; if (url) { var req = { url: url, async: true, success: function(response) { var data = null; if (parser) { data = (function($$) { return eval(parser); })(response.data); } else { if (typeof(response.data) === "string") { var _data = response.data.trim(); if (deviceType === "switchBinary" || deviceType === "sensorBinary") { if (_data === "1" || _data === "on" || _data === "true") { data = "on"; } else if (_data === "0" || _data === "off" || _data === "false") { data = "off"; } } if (deviceType === "switchMultilevel" || deviceType === "sensorMultilevel") { if (parseFloat(_data) != NaN) { data = parseFloat(_data); } } } } if (data !== null && (self.config.skipEventIfSameValue !== true || data !== vDev.get("metrics:level"))) { vDev.set("metrics:level", data); } }, error: function(response) { self.log("Can not make request: " + response.statusText + " " + url); } }; // 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 if (self.config.login && self.config.password) { req.auth = { login: self.config.login, password: self.config.password }; } http.request(req); } }; HTTPDevice.prototype.act = function (vDev, action, subst, selfValue) { var self = this, deviceType = vDev.get("deviceType"), url = this.config["setter" + action + "_" + deviceType], langFile = self.loadModuleLang(); if (!!url) { if (subst !== null) { url = url.replace(/\$\$/g, subst); } var req = { url: url, method: this.config.method, async: true, error: function(response) { self.addNotification("error", langFile.err_req + response.statusText + " (" + url + ")", "module"); } }; 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) { req.auth = { login: self.config.login, password: self.config.password }; } http.request(req); } if ((!url || this.config.updateOnAction === true) && selfValue !== null) { vDev.set("metrics:level", selfValue); } }; /* Log helper functions */ HTTPDevice.prototype.log = function(message) { if (undefined === message) return; console.log("["+this.constructor.name+"-"+this.id+"] "+message); }; ================================================ FILE: modules/HTTPDevice/lang/de.json ================================================ { "m_title":"HTTP-Gerät", "m_descr":"Die Anwendung dieser App erfordert solide Kenntnisse in der JavaScript Programmierung. Diese App ermöglicht die Erstellung eines Elements basierend auf Informationen, die Sie durch eine HTTP Abfrage erhalten können. In Abhängigkeit von der Information können Sie einen Binärsensor oder einen mehrstufigen Sensor auswählen. Es ist ebenso möglich, ein Element zu erstellen, welches einige Funktionen via HTTP steuert: In diesem Fall müssen Sie zur Befehlsausführung URLs hinzufügen; bei Binärschaltern ein Link auf „An“ und „Aus“, bei mehrstufigen Schaltern eine URL zur Einstellung des erforderlichen Wertes. Daraus resultiert ein neues Element, das wie ein Element aus dem Smart Home Netzwerk erscheint.

          Einstellungen:
          • Wählen Sie zu allererst den Elementtyp, der erstellt werden soll. Weitere Einstellungen sind vom Elementtyp abhängig.
          • Alle Geräte benötigen eine URL, um den Wert aus dem Internet abzurufen.
          • Es gibt eine Möglichkeit, die Ansprache mit mathematischer Logik zu entwickeln. Dafür werden fundierte Kenntnisse im JavaScript benötigt.
          • Bestimmen Sie die Abrufzeit.
          • Definieren Sie, ob „POST“ oder „GET“ verwendet wird.
          • Bei mehrstufigen Sensoren können Sie den zu verwendenden Maßstab einstellen.
          • Bei Schaltern können die URLs für Schaltungen bestimmt werden.

          Beispiel: Eine App soll den aktuellen Wechselkurs zwischen USD und EUR anzeigen. Die verwendete URL kann http://www.floatrates.com/daily/usd.xml sein. Mit dieser XML-Datei ist es möglich, den wirklichen Wechselkurs durch Verwendung des String “parseFloat($$$$.findOne('/channel/item/exchangeRate/text()'))” herauszufiltern.
          Anwendung: Die App erstellt ein neues Element. In Hinblick auf das oben aufgeführte Beispiel zeigt das Element den Wechselkurs an.", "l_schema":"Wählen Sie einen Gerätetyp", "l_icon":"Icon", "l_setterOn":"Schalte das Gerät über die URL an (ON)", "p_setterOn":"http://192.168.0.100/action/on", "l_setterOff":"Schalte das Gerät über die URL aus (OFF)", "p_setterOff":"http://192.168.0.100/action/off", "l_getter":"Rufe einen Wert über die URL ab", "p_getter":"http://192.168.0.100/get/value", "l_getterParser_Binary":"Inline Javascript für das Einlesen der hereinkommenden Daten als 'on'/'off' Strings", "h_getterParser_Binary":"Bei keiner Eingabe wird der ankommende String verwendet. Bsp.: $$$$.split(':')[1] oder $$$$.data.metrics.level oder $$$$ === 'ON' ? 'on' : 'off'", "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()\\\"))", "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", "l_scale":"Sensor Skala", "l_method":"HTTP Methoden", "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":"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", "rl_skipEventIfSameValue": "Don't send update event if value has not changed", "h_skipEventIfSameValue": "If same value is reported, do not send update event. In this case no update listeners will be called.", "l_login":"Login", "h_login":"If needed, set login", "l_password":"Pasword", "h_password":"If needed, set password" } ================================================ FILE: modules/HTTPDevice/lang/en.json ================================================ { "m_title":"HTTP Device", "m_descr":"The use of this app requires solid knowledge of JavaScript programming. This app allows you creating an element based on information you can get from a HTTP request. Depending on the information you may choose a sensor binary or a sensor multilevel. It is also possible to create an element controlling some functions via HTTP: In this case you need to add URLs for executing the commands; for binary switches one link of “on” and “off”, for multilevel switch an URL to set a value is required. The result is a new element that appears like an element from the Smart Home network.

          Settings:
          • First of all you need to choose what type of element shall be created. Depending on the type other settings are required.
          • All devices need an URL to call the value from the Internet
          • There is a chance to form the response using formal logic. In order to do this some detailed JavaScript knowledge is needed.
          • Define the polling time.
          • Define if POST or GET is used.
          • For Multilevel sensor you can define the scale to be used.
          • For Switches the URLS for switching can be defined.

          Example: An app shall show the current exchange rate between USD and EUR. The URL used can be http://www.floatrates.com/daily/usd.xml. This is an XML file, so it is possible to filter out the real exchange rate using the string “parseFloat($$$$.findOne('/channel/item/exchangeRate/text()'))”
          Usage: The app creates a new element. In case of the example the element will show the exchange rate.", "l_schema":"Select device type", "l_icon":"Icon", "l_setterOn":"URL for action On", "p_setterOn":"http://192.168.0.100/action/on", "l_setterOff":"URL for action Off", "p_setterOff":"http://192.168.0.100/action/off", "l_getter":"URL to get value", "p_getter":"http://192.168.0.100/get/value", "l_getterParser_Binary":"Inline Javascript to parse incoming data to 'on'/'off' strings", "h_getterParser_Binary":"Can be empty to use string as is. Example: $$$$.split(':')[1] or $$$$.data.metrics.level or $$$$ === 'ON' ? 'on' : 'off'", "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()\\\"))", "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_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", "rl_skipEventIfSameValue": "Don't send update event if value has not changed", "h_skipEventIfSameValue": "If same value is reported, do not send update event. In this case no update listeners will be called.", "l_login":"Login", "h_login":"If needed, set login", "l_password":"Pasword", "h_password":"If needed, set password" } ================================================ FILE: modules/HTTPDevice/lang/ru.json ================================================ { "m_title":"HTTP устройство", "m_descr":"Создание устройства основанного на HTTP запросах.", "l_schema":"Выберите тип устройства", "l_icon":"Иконка", "l_setterOn":"URL для действия Включить", "p_setterOn":"http://192.168.0.100/action/on", "l_setterOff":"URL для действия Выключить", "p_setterOff":"http://192.168.0.100/action/off", "l_getter":"URL для получения значения", "p_getter":"http://192.168.0.100/get/value", "l_getterParser_Binary":"JavaScript код для преобразования полученного значения в строки 'on' или 'off'", "h_getterParser_Binary":"Поле может быть пустым, если использовать полученную строку без изменений. Пример преобразования: $$$$.split(':')[1] или $$$$.data.metrics.level или $$$$ === 'ON' ? 'on' : 'off'", "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()\\\"))", "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_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": "При действиях с устройством (включение/выключение/диммирование), менять состояние на соответствующее действию, не дожидаясь получения опроса значения.", "rl_skipEventIfSameValue": "Не отправлять событие обновления, если значение не поменялось", "h_skipEventIfSameValue": "Если получено значение, совпадающее с предыдущим, не обновлять значение. В этом случае не будут вызваны подписчики на события обновления.", "l_login":"Логин", "h_login":"Если требуется, задайте логин", "l_password":"Пароль", "h_password":"Если требуется, задайте пароль" } ================================================ FILE: modules/HTTPDevice/module.json ================================================ { "singleton": false, "dependencies": [], "category": "developers_stuff", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "HTTPDevice", "version": "2.2.1", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "deviceType": "toggleButton", "iconSensorBinary": "", "iconSensorMultilevel": "", "setterOn_toggleButton": "", "getter_sensorBinary": "", "getterParser_sensorBinary": "", "getterPollInterval_sensorBinary": 0, "getter_sensorMultilevel": "", "getterParser_sensorMultilevel": "", "getterPollInterval_sensorMultilevel": 0, "scale_sensorMultilevel": "", "setterOn_switchBinary": "", "setterOff_switchBinary": "", "getter_switchBinary": "", "getterParser_switchBinary": "", "getterPollInterval_switchBinary": 0, "setterLevel_switchMultilevel": "", "getter_switchMultilevel": "", "getterParser_switchMultilevel": "", "getterPollInterval_switchMultilevel": 0, "method": "GET", "contentType": "application/x-www-form-urlencoded", "data":"", "methodForGetValue": "GET", "contentTypeForGetValue": "application/x-www-form-urlencoded", "dataForGetValue":"", "updateOnAction": false, "skipEventIfSameValue": false, "login":"", "password":"" }, "schema": { "type": "object", "properties": { "deviceType": { "type": "string", "enum": ["toggleButton", "sensorBinary", "sensorMultilevel", "switchBinary", "switchMultilevel"], "required": true }, "iconSensorBinary": { "type": "string", "enum": ["alarm", "motion", "smoke", "co", "flood","cooling","tamper","door"], "default": "alarm", "required": true, "dependencies": "deviceType" }, "iconSensorMultilevel": { "type": "string", "enum": ["temperature", "luminosity", "energy", "humidity", "barometer","seismic","ultraviolet","acceleration_x","acceleration_y","acceleration_z"], "default": "temperature", "required": true, "dependencies": "deviceType" }, "setterOn_toggleButton": { "type": "string", "required": true, "dependencies": "deviceType" }, "getter_sensorBinary": { "type": "string", "required": true, "dependencies": "deviceType" }, "getterParser_sensorBinary": { "type": "string", "required": false, "dependencies": "deviceType" }, "getterPollInterval_sensorBinary": { "type": "integer", "required": false, "dependencies": "deviceType" }, "getter_sensorMultilevel": { "type": "string", "required": true, "dependencies": "deviceType" }, "getterParser_sensorMultilevel": { "type": "string", "required": false, "dependencies": "deviceType" }, "getterPollInterval_sensorMultilevel": { "type": "integer", "required": false, "dependencies": "deviceType" }, "scale_sensorMultilevel": { "type": "string", "required": false, "dependencies": "deviceType" }, "setterOn_switchBinary": { "type": "string", "required": true, "dependencies": "deviceType" }, "setterOff_switchBinary": { "type": "string", "required": true, "dependencies": "deviceType" }, "getter_switchBinary": { "type": "string", "required": false, "dependencies": "deviceType" }, "getterParser_switchBinary": { "type": "string", "required": false, "dependencies": "deviceType" }, "getterPollInterval_switchBinary": { "type": "integer", "required": false, "dependencies": "deviceType" }, "setterLevel_switchMultilevel": { "type": "string", "required": true, "dependencies": "deviceType" }, "getter_switchMultilevel": { "type": "string", "required": false, "dependencies": "deviceType" }, "getterParser_switchMultilevel": { "type": "string", "required": false, "dependencies": "deviceType" }, "getterPollInterval_switchMultilevel": { "type": "integer", "required": false, "dependencies": "deviceType" }, "method": { "type": "string", "required": true, "enum": ["GET", "POST"] }, "contentType": { "type": "string", "required": true, "dependencies": "method" }, "data":{ "type": "string", "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", "required": true, "dependencies": "deviceType" }, "skipEventIfSameValue": { "type": "boolean", "required": true }, "login": { "type": "string", "required": false }, "password": { "format": "password", "type": "string", "required": false } } }, "options": { "fields": { "deviceType": { "type": "select" }, "iconSensorBinary": { "type": "select", "label": "__l_icon__", "dependencies": { "deviceType": "sensorBinary" } }, "iconSensorMultilevel": { "type": "select", "label": "__l_icon__", "dependencies": { "deviceType": "sensorMultilevel" } }, "setterOn_toggleButton": { "label": "__l_setterOn__", "placeholder": "__p_setterOn__", "dependencies": { "deviceType": "toggleButton" } }, "getter_sensorBinary": { "label": "__l_getter__", "placeholder": "__p_getter__", "dependencies": { "deviceType": "sensorBinary" } }, "getterParser_sensorBinary": { "label": "__l_getterParser_Binary__", "helper": "__h_getterParser_Binary__", "dependencies": { "deviceType": "sensorBinary" } }, "getterPollInterval_sensorBinary": { "label": "__l_getterPollInterval__", "helper": "__h_getterPollInterval__", "dependencies": { "deviceType": "sensorBinary" } }, "getter_sensorMultilevel": { "label": "__l_getter__", "placeholder": "__p_getter__", "dependencies": { "deviceType": "sensorMultilevel" } }, "getterParser_sensorMultilevel": { "label": "__l_getterParser_Multilevel__", "helper": "__h_getterParser_Multilevel__", "dependencies": { "deviceType": "sensorMultilevel" } }, "getterPollInterval_sensorMultilevel": { "label": "__l_getterPollInterval__", "helper": "__h_getterPollInterval__", "dependencies": { "deviceType": "sensorMultilevel" } }, "scale_sensorMultilevel": { "label": "__l_scale__", "dependencies": { "deviceType": "sensorMultilevel" } }, "setterOn_switchBinary": { "label": "__l_setterOn__", "placeholder": "__p_setterOn__", "dependencies": { "deviceType": "switchBinary" } }, "setterOff_switchBinary": { "label": "__l_setterOff__", "placeholder": "__p_setterOff__", "dependencies": { "deviceType": "switchBinary" } }, "getter_switchBinary": { "label": "__l_getter__", "placeholder": "__p_getter__", "dependencies": { "deviceType": "switchBinary" } }, "getterParser_switchBinary": { "label": "__l_getterParser_Binary__", "helper": "__h_getterParser_Binary__", "dependencies": { "deviceType": "switchBinary" } }, "getterPollInterval_switchBinary": { "label": "__l_getterPollInterval__", "helper": "__h_getterPollInterval__", "dependencies": { "deviceType": "switchBinary" } }, "setterLevel_switchMultilevel": { "label": "__l_setterLevel__", "placeholder": "__p_setterLevel__", "helper": "__h_setterLevel__", "dependencies": { "deviceType": "switchMultilevel" } }, "getter_switchMultilevel": { "label": "__l_getter__", "placeholder": "__p_getter__", "dependencies": { "deviceType": "switchMultilevel" } }, "getterParser_switchMultilevel": { "label": "__l_getterParser_Multilevel__", "helper": "__h_getterParser_Multilevel__", "dependencies": { "deviceType": "switchMultilevel" } }, "getterPollInterval_switchMultilevel": { "label": "__l_getterPollInterval__", "helper": "__h_getterPollInterval__", "dependencies": { "deviceType": "switchMultilevel" } }, "method": { "label": "__l_method__", "type": "select" }, "contentType": { "label": "__l_contentType__", "helper": "__h_contentType__", "dependencies": { "method": "POST" } }, "data":{ "label": "__l_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", "rightLabel": "__rl_updateOnAction__", "helper": "__h_updateOnAction__", "dependencies": { "deviceType": [ "switchBinary", "switchMultilevel"] } }, "skipEventIfSameValue": { "type": "checkbox", "rightLabel": "__rl_skipEventIfSameValue__", "helper": "__h_skipEventIfSameValue__" }, "login": { "label": "__l_login__", "helper": "__h_login__" }, "password": { "label": "__l_password__", "helper": "__h_password__" } } } } ================================================ FILE: modules/HazardNotification/index.js ================================================ /*** 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); 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(); 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 !== '') { self.controller.notificationChannelSend(notification.target, notification.message ? notification.message : self.getInstanceTitle()); } } }); }; 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); } 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); } } };*/ ================================================ FILE: modules/HazardNotification/lang/de.json ================================================ { "m_title": "Gefahrenbenachrichtigung", "m_descr": "" } ================================================ FILE: modules/HazardNotification/lang/en.json ================================================ { "m_title": "Hazard Notification", "m_descr": "" } ================================================ FILE: modules/HazardNotification/module.json ================================================ { "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": {} } ================================================ FILE: modules/Heating/htdocs/js/postRender.js ================================================ 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); } } }); }; ================================================ FILE: modules/Heating/index.js ================================================ /*** Heating Z-Way HA module ******************************************* Version: 1.3 stable (c) Z-Wave.Me, 2021 ----------------------------------------------------------------------------- Author: Niels Roche , Martin Petzold , Michael Hensche , Karsten Reichel , Vitaliy Yurkin 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.RoomID == roomID && entry.Starttime == s.stime && entry.Endtime == s.etime && entry.Temperature == s.temp; }); if (!_.isEmpty(sched)) { sched[0].Weekday.push(day); } else { // add schedule schedule.push({ "RoomID": roomID, "Room": "", "Weekday": [day], "Starttime": s.stime, "Endtime": s.etime, "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, "frostProtection": sc.frostProtectionTemp, "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.pollReset = function() { var now = Date.now(); //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 state = self.config.roomSettings[roomId].state, stateName = state + "Temp"; if (state === "schedule") { var now = new Date(), today = (now.getDay()).toString(), minutesToday = (now.getHours() * 60) + now.getMinutes(), scheduleFound = false; _.forEach(self.schedule, function(entry) { if (parseInt(entry.RoomID) === roomId) { var Starttime = entry.Starttime, Endtime = entry.Endtime, sHours = parseInt(Starttime.substr(0, 2), 10), sMinutes = parseInt(Starttime.substr(3, 2), 10), eHours = parseInt(Endtime.substr(0, 2), 10), eMinutes = parseInt(Endtime.substr(3, 2), 10), startMinutesToday = (sHours * 60) + sMinutes, endMinutesToday = (eHours * 60) + eMinutes === 0 ? 24*60 : (eHours * 60) + eMinutes, weekday = entry.Weekday; // If time in schedule range set schedule temperature if (weekday.indexOf(today) >= 0 && minutesToday >= startMinutesToday && minutesToday < endMinutesToday) { // Set temperature from schedule scheduleFound = true; self.performChangesOnThermostats(vdev, parseFloat(entry.Temperature)); } } }); // If schedule is not found set energySave if (scheduleFound == false) { if (self.config.roomSettings[roomId].energySaveTemp !== undefined) self.performChangesOnThermostats(vdev, self.config.roomSettings[roomId].energySaveTemp); } } else { // Set temperature from state frostProtection/energySave/comfort if (self.config.roomSettings[roomId][stateName] !== undefined) self.performChangesOnThermostats(vdev, self.config.roomSettings[roomId][stateName]); } 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.updateRoomsList = function(id) { delete self.config.roomSettings[id]; self.saveConfig(); var newRooms = self.vDev.get('metrics:rooms').filter(function(el) { return parseInt(el.room) != id }); self.vDev.set('metrics:rooms', newRooms); } // update the list of rooms after deleting a room this.controller.on('location.removed', this.updateRoomsList); }; 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); if (key === 'start') { self.controller.off(pollEntry, self.pollByStart); } else { self.controller.off(pollEntry, self.pollByEnd); } }); // clean registry self.registerdSchedules[key] = []; } this.controller.emit("cron.removeTask", "HeatingReset_" + this.id + ".poll"); this.controller.off("HeatingReset_" + this.id + ".poll", this.pollReset); this.controller.devices.off('created', this.triggerControl); this.controller.off('location.removed', this.updateRoomsList); 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.getThermostats(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 = parseFloat(room.frostProtection);; break; case 'E': temp = parseFloat(room.energySave); break; case 'C': temp = parseFloat(room.comfort); break; default: temp = parseFloat(schedulePreset); } } room.targetTemp = 't ~ ' + temp; } }); self.vDev.set('metrics:rooms', metrRooms); thermostats.forEach(function(thermostat) { self.performChangesOnThermostats(thermostat, temp); }); } }; this.pollByEnd = function() { var pollIdentifier = this.event, identifierArr = pollIdentifier.split('.'), locId = parseInt(identifierArr[1], 10), thermostats = [], metrRooms = []; // If the end time is equal to the start time do not run the end command var isEndTimeEqualStartTime = false; _.forEach(self.registerdSchedules['start'], function(scheduleStart) { var scheduleStartArray = scheduleStart.split('.'); var scheduleEndArray = pollIdentifier.split('.'); if (scheduleStartArray[3] === scheduleEndArray[3] && scheduleStartArray[5] === scheduleEndArray[5] && scheduleStartArray[6] === scheduleEndArray[6]) { isEndTimeEqualStartTime = true; } }); if (isEndTimeEqualStartTime) return; /* * identifierArr[1] ... room name * identifierArr[7] ... '-' separeated temperature * */ //schedulePreset = identifierArr[7] !== 'poll'? identifierArr[7].replace('-', '.') : null; //get thermostats thermostats = self.getThermostats(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' ? parseFloat(room.frostProtection) : (room.fallback == 'C' ? parseFloat(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') { // Create listeners for each schedule of the each rooms self.schedule.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); }); } }); } 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); // At first remove, so that there are no duplicates self.controller.emit("cron.removeTask", scheduleEntry); 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(24, 0), startI = (new Date()).setHours(h, m), compareString = scheduleItems[0] + '.' + scheduleItems[1] + '.' + scheduleItems[2] + '.' + d, endI = getTime(scheduleFilter, compareString + '.end'); // check if end is next day if ((!endI && startI) || (startI && endI && endI < startI)) { nextDay = d === 6 ? 0 : d + 1; newCS = scheduleItems[0] + '.' + scheduleItems[1] + '.' + scheduleItems[2] + '.' + nextDay; endI = getTime(scheduleFilter, newCS + '.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), roomCmd = command; // do commands for each room entry self.newRooms.forEach(function(room, index) { var currTemp, roomId = parseInt(room.room, 10); if (argRoom === null || argRoom === roomId) { // set custom configs if configured if (argRoom === null && command === 'custom') { roomCmd = room.state; } // Set state for room if (command !== 'custom') { self.config.roomSettings[roomId].state = command; } var thermostats = self.getThermostats(roomId); // set current temperature switch (roomCmd) { case 'comfort': currTemp = parseFloat(room.comfort); break; case 'energySave': currTemp = parseFloat(room.energySave); break; case 'frostProtection': currTemp = parseFloat(room.frostProtection); break; case 'schedule': default: currTemp = null; } // 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') || (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) { // search in registry for all regsitered schedules that should be removed from Cron var 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; } }); self.saveConfig(); 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: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 = self.config.roomSettings[roomId].state; 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.toString() }) ? 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": 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] = Date.now() + 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 { console.log("--- Heating_" + self.id,self.langFile.err_wrong_date_format); } }); } 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; console.log("--- Heating_" + self.id,self.langFile.err_no_thermostats + roomName); } else { console.log("--- Heating_" + self.id,self.langFile.err_parsing_schedule_data); } }; /* * 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; }, 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 = (parseInt(day) + 1) % 7, 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)) { console.log("--- Heating_" + self.id,self.langFile.err_wrong_time_format); } else { console.log("--- Heating_" + self.id,self.langFile.err_something_went_wrong); } }; /* * 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('##################################'); } }; ================================================ FILE: modules/Heating/lang/de.json ================================================ { "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" } ================================================ FILE: modules/Heating/lang/en.json ================================================ { "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" } ================================================ FILE: modules/Heating/module.json ================================================ { "dependencies": [ "Cron" ], "singleton": false, "category": "system", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "Heating", "version": "1.2.1", "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": {} } ================================================ FILE: modules/HomeKitGate/htdocs/js/postRender.js ================================================ function modulePostRender() { if ($("div[data-alpaca-field-name='pin']").find('input').val() === "") { $("div[data-alpaca-field-name='pin']").hide(); } else { $("div[data-alpaca-field-name='pin']").show(); } }; ================================================ FILE: modules/HomeKitGate/index.js ================================================ /*** HomeKitGate Z-Way HA module ******************************************* Version: 2.4 (c) Z-Wave.Me, 2023 ----------------------------------------------------------------------------- Author: Poltorak Serguei , Yurkin Vitaliy Description: This module announces Z-Way HA devices to HomeKit ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function HomeKitGate (id, controller) { // Call superconstructor first (AutomationModule) HomeKitGate.super_.call(this, id, controller); }; inherits(HomeKitGate, AutomationModule); _module = HomeKitGate; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- HomeKitGate.ControllerId = "__RaZberry_Controller"; HomeKitGate.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) HomeKitGate.super_.prototype.init.call(this, config); var self = this; var rgbDevices = []; var thermostats = []; var lastSupportedThermostatMode = 1; // Default Heat if (!this.config.hkDevices) { this.config.hkDevices = {}; } // If hkDevicesArray doesn't contain an vDevId, then remove it from the hkDevices Object.keys(this.config.hkDevices).forEach(function(vDevId) { if (self.config.hkDevicesArray.indexOf(vDevId) == -1) { delete self.config.hkDevices[vDevId]; } }); this.saveConfig(); updateSkippedDevicesList(); // define functions and helpers function onDeviceAddedCore(vDevT) { var controller = this.controller; // defaults vDevT.title = vDevT.title || vDevT.id; vDevT.manufacturer = vDevT.manufacturer || "Z-Wave.Me"; vDevT.product = vDevT.product || vDevT.deviceType; vDevT.firmwareRevision = vDevT.firmwareRevision || zwayVersion.release; vDevT.serialNumber = vDevT.serialNumber || vDevT.id; // Skip widgets if (vDevT.permanently_hidden || !(vDevT.visibility) || (vDevT.probeType === "alarmSensor_general_purpose") || (vDevT.probeType === "thermostat_mode") || vDevT.tags.indexOf("homekit-skip") !== -1 || (vDevT.deviceType === "thermostat" && vDevT.id.indexOf("ZWayVDev") === -1)) return; // Supported widgets if (!((vDevT.deviceType === "switchBinary") || (vDevT.deviceType === "switchMultilevel") || (vDevT.deviceType === "sensorMultilevel") || (vDevT.deviceType === "sensorBinary") || (vDevT.deviceType === "doorlock") || (vDevT.deviceType === "switchRGBW") || (vDevT.deviceType === "thermostat"))) return; var m = self.addDevice(vDevT); var accessory = m.$accessory; if (vDevT.deviceType === "switchBinary") { // skip thermostat mode if (vDevT.id.slice(-2) === "64") return; var type = HomeKit.Services.Switch; if (vDevT.tags.indexOf("type-light") !== -1) type = HomeKit.Services.Lightbulb; if (vDevT.tags.indexOf("type-fan") !== -1) type = HomeKit.Services.Fan; var service = accessory.addService(type, "Binary Switch"); service.addCharacteristic(HomeKit.Characteristics.Name, "string", vDevT.title); m.level = service.addCharacteristic(HomeKit.Characteristics.On, "bool", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && d.get("metrics:level") === "on"; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (!!d) d.performCommand(value ? "on" : "off"); } }); } else if (vDevT.deviceType === "switchMultilevel" && (vDevT.probeType !== "motor") ) { // skip if related to switchRGB var nodeId = self.controller.devices.get(vDevT.id).get("nodeId") var match = rgbDevices.filter(function(el) { return el.id === nodeId; })[0]; if (match) return; var service = accessory.addService(HomeKit.Services.Lightbulb, "Multilevel Switch"); service.addCharacteristic(HomeKit.Characteristics.Name, "string", vDevT.title); // necessary m.binaryLevel = service.addCharacteristic(HomeKit.Characteristics.On, "bool", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && d.get("metrics:level") ? true : false; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (!!d) d.performCommand(value ? "on" : "off"); } }); // optional m.level = service.addCharacteristic(HomeKit.Characteristics.Brightness, "int", { get: function() { var d = controller.devices.get(vDevT.id); if (!d) return 0; var value = parseInt(d.get("metrics:level")); return value > 99 ? 99 : value || 0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (d) d.performCommand("exact", { level: value }); }}, undefined, {"unit":"percentage", "maxValue":99, "minValue":0, "minStep":1} ); } else if (vDevT.deviceType === "sensorMultilevel") { var service; if (vDevT.probeType === "temperature" || vDevT.scaleTitle === "°C"){ service = accessory.addService(HomeKit.Services.TemperatureSensor, "Temperature"); m.level = service.addCharacteristic(HomeKit.Characteristics.CurrentTemperature, "float", { get: function() { var d = controller.devices.get(vDevT.id); if (!d) return 0; var temperature = parseFloat(d.get("metrics:level")); if (temperature >= -999 && temperature <= 999) { return temperature; } else { return 0; } }}, undefined, {"unit":"celsius", "maxValue":999, "minValue":-999, "minStep":0.0001} ); } else if (vDevT.probeType === "humidity" || vDevT.scaleTitle === "%") { service = accessory.addService(HomeKit.Services.HumiditySensor, "Humidity"); m.level = service.addCharacteristic(HomeKit.Characteristics.CurrentRelativeHumidity, "float", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && parseFloat(d.get("metrics:level")) || 0.0; }}, undefined, {"unit":"percentage", "maxValue":100, "minValue":0, "minStep":0.0001} ); } else { service = accessory.addService(HomeKit.Services.LightSensor, "LightSensor"); m.level = service.addCharacteristic(HomeKit.Characteristics.CurrentAmbientLightLevel, "float", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && parseFloat(d.get("metrics:level")) || 0.0; }}, undefined, {"maxValue":100000, "minValue":-100000, "minStep":0.0001} ); } service.addCharacteristic(HomeKit.Characteristics.Name, "string", vDevT.title); } else if (vDevT.deviceType === "sensorBinary") { var service; if (vDevT.probeTitle === "Motion") { service = accessory.addService(HomeKit.Services.MotionSensor, "Motion Sensor"); // Contact Sensor Characteristic m.level = service.addCharacteristic(HomeKit.Characteristics.MotionDetected, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && d.get("metrics:level") === "on"; } }); } else { service = accessory.addService(HomeKit.Services.ContactSensor, "Binary Sensor"); // Contact Sensor Characteristic m.level = service.addCharacteristic(HomeKit.Characteristics.ContactSensorState, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && d.get("metrics:level") === "on"; } }); } // Add Name service.addCharacteristic(HomeKit.Characteristics.Name, "string", vDevT.title); //serviceContactSensor.addCharacteristic(HomeKit.Characteristics.StatusLowBattery, ["pr", "ev"], "uint8", "Status Low Battery", 0); } else if (vDevT.deviceType === "doorlock") { var service = accessory.addService(HomeKit.Services.LockMechanism, "Door Lock"); service.addCharacteristic(HomeKit.Characteristics.Name, "string", vDevT.title); m.StateLevel = service.addCharacteristic(HomeKit.Characteristics.LockCurrentState, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); return (!!d && d.get("metrics:level") === "close") ? 1 : 0; } }); m.level = service.addCharacteristic(HomeKit.Characteristics.LockTargetState, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); return (!!d && d.get("metrics:level") === "close") ? 1 : 0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (d) d.performCommand(value ? "close" : "open"); } }); } else if (vDevT.deviceType === "switchRGBW") { // Don't create rgbw widgets without brightness var brightnessDev = null; var brightnessDevId; if (vDevT.id.indexOf("RGB") === 0) { // RGB module var redDevice = self.controller.instances.filter(function(instance){return instance.id == vDevT.id.substring(4)})[0].params.red; var before = redDevice.indexOf("-")+1; var after = redDevice.indexOf("-",redDevice.indexOf("-")+1); brightnessDevId = redDevice.substring(0, before) + "1" + redDevice.substring(after); brightnessDev = self.controller.devices.get(brightnessDevId); } else { // Z-Wave Device brightnessDevId = vDevT.id.substring(0, vDevT.id.indexOf("-",vDevT.id.indexOf("-")+1)+1) + "38"; brightnessDev = self.controller.devices.get(brightnessDevId); } if (!brightnessDev) return; var service = accessory.addService(HomeKit.Services.Lightbulb, "Multilevel Switch"); service.addCharacteristic(HomeKit.Characteristics.Name, "string", vDevT.title); self.onDeviceWipedOut(brightnessDevId); m.level = service.addCharacteristic(HomeKit.Characteristics.On, "bool", { get: function() { var d = controller.devices.get(brightnessDevId); return (!!d && d.get("metrics:level")) ? true : false; }, set: function(value) { var d = controller.devices.get(brightnessDevId); if (!!d) d.performCommand(value ? "on" : "off"); } }); m.hue = service.addCharacteristic(HomeKit.Characteristics.Hue, "float", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && parseFloat(hsv(d).h) || 0.0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (!!d) exactColor(d, "Hue", value); }}, undefined, {"unit":"arcdegrees", "maxValue":360, "minValue":0, "minStep":1} ); m.saturation = service.addCharacteristic(HomeKit.Characteristics.Saturation, "float", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && parseFloat(hsv(d).s) || 0.0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (!!d) exactColor(d, "Saturation", value); }}, undefined, {"unit":"percentage", "maxValue":100, "minValue":0, "minStep":1} ); m.brightness = service.addCharacteristic(HomeKit.Characteristics.Brightness, "int", { get: function() { var d = controller.devices.get(brightnessDevId); if (!d) return 0; var value = d.get("metrics:level"); return value > 99 ? 99 : value || 0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (!!d) { exactColor(d, "Brightness", value); var b = controller.devices.get(brightnessDevId); if (!!b) b.performCommand("exact", { level: value }); }}}, undefined, {"unit":"percentage", "maxValue":99, "minValue":0, "minStep":1} ); // dirty hack to handle in OnLevelChange, but not to create a HK accessory if (brightnessDev) { self.mapping[brightnessDev.id] = { $accessory: m.$accessory, level: m.level, brightness: m.brightness }; } } else if (vDevT.deviceType === "thermostat") { var service = accessory.addService(HomeKit.Services.Thermostat, "Thermostat"); service.addCharacteristic(HomeKit.Characteristics.Name, "string", vDevT.title); var nodeId = self.controller.devices.get(vDevT.id).get("nodeId") // If HK thermostat already generated, exit. var match = thermostats.filter(function(el) { return el === nodeId; })[0]; if (match) { self.onDeviceWipedOut(vDevT.id); return; } thermostats.push(nodeId); // Get all thermostat modes var thermostatModes = [1]; // For danfoss LC and Secure without ThermostatMode CC if (zway.devices[nodeId].ThermostatMode && zway.devices[nodeId].ThermostatMode.data.supported.value) { thermostatModes = Object.keys(zway.devices[nodeId].ThermostatMode.data).filter(function(mode) {return !isNaN(parseInt(mode))}); } // Use in HK only Off, Cool, Heat, Auto var supportedHKModes = [0,1,2,3]; var modes = []; supportedHKModes.forEach(function(validMode) { thermostatModes.forEach(function(mode) { if (validMode == mode) modes.push(validMode); if (validMode == 3 && mode == 10) modes.push(validMode); }); }); // Remove mode 3 (Auto) if exist var modesWithOutAuto = modes.filter(function(mode) {return mode != 3}); var currentMaxMode = Math.max.apply(null, modesWithOutAuto); var currentMinMode = Math.min.apply(null, modesWithOutAuto); var targetMaxMode = Math.max.apply(null, modes); var targetMinMode = Math.min.apply(null, modes); m.currentThermostatMode = service.addCharacteristic(HomeKit.Characteristics.CurrentHeatingCoolingState, "uint8", { get: function() { return modesWithOutAuto.length > 1 ? getCurrentMode(nodeId) : modesWithOutAuto[0]; }}, undefined, {"valid-values": modesWithOutAuto, "maxValue": currentMaxMode, "minValue": currentMinMode} ); m.targetThermostatMode = service.addCharacteristic(HomeKit.Characteristics.TargetHeatingCoolingState, "uint8", { get: function() { return modesWithOutAuto.length > 1 ? getTargetMode(nodeId) : modesWithOutAuto[0];; }, set: function(mode) { zway.devices[nodeId].ThermostatMode.Set(mode == 3 ? 10 : mode); }}, undefined, {"valid-values": modes, "maxValue":targetMaxMode, "minValue":targetMinMode} ); m.currentTemperature = service.addCharacteristic(HomeKit.Characteristics.CurrentTemperature, "float", { get: function() { return getCurrentTemperature(nodeId); }}, undefined, {"unit":"celsius", "maxValue":100, "minValue":0, "minStep":0.1} ); m.targetTemperature = service.addCharacteristic(HomeKit.Characteristics.TargetTemperature, "float", { get: function() { return getTargetTemperature(nodeId); }, set: function(temperature) { if (modesWithOutAuto.length > 1) zway.devices[nodeId].ThermostatMode.Set(lastSupportedThermostatMode == 3 ? 10 : lastSupportedThermostatMode); else lastSupportedThermostatMode = modesWithOutAuto[0]; zway.devices[nodeId].ThermostatSetPoint.Set(lastSupportedThermostatMode, temperature); }}, undefined, {"unit":"celsius", "maxValue":40, "minValue":5, "minStep":0.5} ); // { 0:"Celsius", 1:"Fahrenheit" } service.addCharacteristic(HomeKit.Characteristics.TemperatureDisplayUnits, "uint8", 0, ["pr", "pw", "ev"], {"valid-values":[0, 1], "maxValue":1, "minValue":0} ); } else if (vDevT.probeType === "motor") { var service = accessory.addService(HomeKit.Services.WindowCovering, "Blind Control"); service.addCharacteristic(HomeKit.Characteristics.Name, "string", vDevT.title); m.targetPosition = service.addCharacteristic(HomeKit.Characteristics.TargetPosition, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); if (!d) return 0; var value = parseInt(d.get("metrics:level")); return value > 99 ? 99 : value || 0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (d) d.performCommand("exact", { level: value }); }}, undefined, {"unit":"percentage", "maxValue":99, "minValue":0, "minStep":1} ); m.currentPosition = service.addCharacteristic(HomeKit.Characteristics.CurrentPosition, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); if (!d) return 0; var value = parseInt(d.get("metrics:level")); return value > 99 ? 99 : value || 0; }}, undefined, {"unit":"percentage", "maxValue":99, "minValue":0, "minStep":1} ); m.positionState = service.addCharacteristic(HomeKit.Characteristics.PositionState, "uint8", { get: function() { return 2; /* Stoped */ }}, undefined, {"valid-values":[0, 1, 2], "maxValue":2, "minValue":0} ); } // Battery Service add to all widgets If device is a battery powered var nodeId = self.controller.devices.get(vDevT.id).get("nodeId") var technology = self.controller.devices.get(vDevT.id).get("technology") if (technology === "Z-Wave" && zway.devices[nodeId].data.isListening.value === false) { var batteryDeviceId = vDevT.id.substring(0, vDevT.id.lastIndexOf("_")+1) + nodeId + "-0-128"; var batteryService = accessory.addService(HomeKit.Services.Battery, "Battery"); m.batteryLevel = batteryService.addCharacteristic(HomeKit.Characteristics.BatteryLevel, "uint8", { get: function() { var d = controller.devices.get(batteryDeviceId); return !!d && parseInt(d.get("metrics:level")) || 0; }}, undefined, {"maxValue":100, "minValue":0, "minStep":1} ); m.chargingState = batteryService.addCharacteristic(HomeKit.Characteristics.ChargingState, "uint8", { get: function() { return 0; }}, undefined, {"maxValue":2, "minValue":0, "minStep":1} ); m.statusLowBattery = batteryService.addCharacteristic(HomeKit.Characteristics.StatusLowBattery, "uint8", { get: function() { var d = controller.devices.get(batteryDeviceId); return !!d && parseInt(d.get("metrics:level")) > 50 ? 0 : 1 || 0; }}, undefined, {"maxValue":1, "minValue":0, "minStep":1} ); } }; /***************** THERMOSTAT HELPERS *****************/ function getCurrentTemperature(nodeId) { // Find first CC SensorMultilevel in all instances var ccSensorMultilevel; var insts_keys = Object.keys(zway.devices[nodeId].instances); for (var i = 0; i < insts_keys.length; i++) { if (zway.devices[nodeId].instances[parseInt(insts_keys[i])].SensorMultilevel) { ccSensorMultilevel = zway.devices[nodeId].instances[parseInt(insts_keys[i])].SensorMultilevel; break; } } if (ccSensorMultilevel && ccSensorMultilevel.data[1] && ccSensorMultilevel.data[1].val && ccSensorMultilevel.data[1].val.value) { return ccSensorMultilevel.data[1].val.value; } else { if (lastSupportedThermostatMode == 3){ lastSupportedThermostatMode = 10; } else if (lastSupportedThermostatMode == 0) { lastSupportedThermostatMode = 1; } return zway.devices[nodeId].ThermostatSetPoint.data[lastSupportedThermostatMode].val.value; } }; function getTargetTemperature(nodeId) { var targetTemperature; if (zway.devices[nodeId].ThermostatMode) { var mode = zway.devices[nodeId].ThermostatMode.data.mode.value; if (mode == 1 || mode == 2 || mode == 3) { targetTemperature = zway.devices[nodeId].ThermostatSetPoint.data[mode].val.value; } else { if (lastSupportedThermostatMode == 0) { targetTemperature = 10; } else { targetTemperature = zway.devices[nodeId].ThermostatSetPoint.data[lastSupportedThermostatMode == 3 ? 10 : lastSupportedThermostatMode].val.value; } } if (targetTemperature < 5) targetTemperature = 5; if (targetTemperature > 40) targetTemperature = 40; } else { targetTemperature = zway.devices[nodeId].ThermostatSetPoint.data[1].val.value; // Danfoss LC } return targetTemperature; }; function getTargetMode(nodeId) { var mode = zway.devices[nodeId].ThermostatMode.data.mode.value; if (mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 10) { lastSupportedThermostatMode = mode == 10 ? 3 : mode; // Transform ZW Auto(10) mode to HK Auto(3) mode } return lastSupportedThermostatMode; } function getCurrentMode(nodeId) { var mode = zway.devices[nodeId].ThermostatMode.data.mode.value; if (mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 10) { if (mode == 3 || mode == 10){ lastSupportedThermostatMode = 1; // CurrentHeatingCoolingState not support Auto(10) mode, change it to Heat(1) } else { lastSupportedThermostatMode = mode; } } return lastSupportedThermostatMode; } /***************** RGB HELPERS *****************/ function exactColor(vDev, valueName, value) { var match = rgbDevices.filter(function(el) { return el.id === vDev.id; })[0]; // If vDev already in rgbDevices array, set propertie if (match) { if (valueName === "Hue") { match.hue = value; match.valueCount += 1; } else if (valueName === "Saturation") { match.saturation = value; match.valueCount += 1; } else if (valueName === "Brightness") { match.brightness = value; } // Wait 2 Characteristics: "Hue, Saturation" to exact color if (valueName !== "Brightness" && match.valueCount == 2) { match.valueCount = 0; var colors = hsv2rgb({h:match.hue, s:match.saturation, v:match.brightness}); vDev.performCommand("exact", {red: colors.r, green: colors.g, blue: colors.b}); } } // If vDev not in rgbDevices array, add vDev and set propertie else { var last = (rgbDevices.push({"id":vDev.id, hue: 0, saturation: 0, brightness: 100, valueCount:0}) - 1); if (valueName === "Hue") { rgbDevices[last].hue = value; rgbDevices[last].valueCount += 1; } else if (valueName === "Saturation") { rgbDevices[last].saturation = value; rgbDevices[last].valueCount += 1; } else if (valueName === "Brightness") { rgbDevices[last].brightness = value; } } } function hsv(vDev){ return rgb2hsv({r:vDev.get("metrics:color:r"), g:vDev.get("metrics:color:g"), b:vDev.get("metrics:color:b")}); } function hsv2rgb(obj) { // H: 0-360; S,V: 0-100; RGB: 0-255 var r, g, b; var sfrac = obj.s / 100; var vfrac = obj.v / 100; if(sfrac === 0){ var vbyte = Math.round(vfrac*255); return { r: vbyte, g: vbyte, b: vbyte }; } var hdb60 = (obj.h % 360) / 60; var sector = Math.floor(hdb60); var fpart = hdb60 - sector; var c = vfrac * (1 - sfrac); var x1 = vfrac * (1 - sfrac * fpart); var x2 = vfrac * (1 - sfrac * (1 - fpart)); switch(sector){ case 0: r = vfrac; g = x2; b = c; break; case 1: r = x1; g = vfrac; b = c; break; case 2: r = c; g = vfrac; b = x2; break; case 3: r = c; g = x1; b = vfrac; break; case 4: r = x2; g = c; b = vfrac; break; case 5: default: r = vfrac; g = c; b = x1; break; } return { "r": Math.round(255 * r), "g": Math.round(255 * g), "b": Math.round(255 * b) }; } function rgb2hsv(obj) { // RGB: 0-255; H: 0-360, S,V: 0-100 var r = obj.r/255, g = obj.g/255, b = obj.b/255; var max, min, d, h, s, v; min = Math.min(r, Math.min(g, b)); max = Math.max(r, Math.max(g, b)); if (min === max) { // shade of gray return {h: 0, s: 0, v: r * 100}; } var d = (r === min) ? g - b : ((b === min) ? r - g : b - r); h = (r === min) ? 3 : ((b === min) ? 1 : 5); h = 60 * (h - d/(max - min)); s = (max - min) / max; v = max; return {"h": h, "s": s * 100, "v": v * 100}; } function updateSkippedDevicesList() { // Add tag "homekit-skip" for all skipped devices from config self.config.skippedDevices.forEach(function(vDevId) { delete self.config.hkDevices[vDevId]; removeFromHkDevicesArray(vDevId); var vDev = self.controller.devices.get(vDevId); if (vDev !== null && vDev.get("tags").indexOf("homekit-skip") === -1 ) { vDev.addTag("homekit-skip"); } }); self.saveConfig(); // Remove tag "homekit-skip" if device not in skipped list self.controller.devices.forEach(function(vDev) { if (vDev !== null && vDev.get("tags").indexOf("homekit-skip") !== -1 && self.config.skippedDevices.indexOf(vDev.id) === -1) { vDev.removeTag("homekit-skip"); } }); } function removeFromHkDevicesArray(vDevid) { var index = self.config.hkDevicesArray.indexOf(vDevid); if (index !== -1) { self.config.hkDevicesArray.splice(index, 1); } } this.hkDeviceRemove = function (vDevId) { var m = self.mapping[vDevId]; if (m) { var accessory = m.$accessory; if (accessory) accessory.remove(); delete self.mapping[vDevId]; } } this.onDeviceAdded = function (vDev) { console.log("HK: added", vDev.id); onDeviceAddedCore(self.vDevToTemplate(vDev)); // update device tree self.hk.update(); } this.onDeviceRemoved = function (vDev) { console.log("HK: removed", vDev.id); var m = self.mapping[vDev.id]; if (m) { var accessory = m.$accessory; self.hk.update(accessory.aid, m["reachable"].iid); } } this.onDeviceWipedOut = function (vDevId) { console.log("HK: wipe out", vDevId); self.hkDeviceRemove(vDevId); // update device tree self.hk.update(); } this.onLevelChanged = function (vDev) { console.log("HK: updated", vDev.id); var m = self.mapping[vDev.id]; if (!m) return; var accessory = m.$accessory; if (!accessory) return; for (var characteristicName in m) { // skip special names if (characteristicName[0] === "$") continue; self.hk.update(accessory.aid, m[characteristicName].iid); } } this.onTagsChanged = function (vDev) { // Add tag "homekit-skip" to skipped Devices list in config and remove device from Homekit if (vDev.get("tags").indexOf("homekit-skip") !== -1 && self.config.skippedDevices.indexOf(vDev.id) === -1) { self.config.skippedDevices.push(vDev.id); delete self.config.hkDevices[vDev.id]; removeFromHkDevicesArray(vDev.id); self.saveConfig(); self.onDeviceWipedOut(vDev.id); } // Remove tag "homekit-skip" from skipped Devices list in config and add device to Homekit if (vDev.get("tags").indexOf("homekit-skip") === -1 && self.config.skippedDevices.indexOf(vDev.id) !== -1) { var index = self.config.skippedDevices.indexOf(vDev.id); self.config.skippedDevices.splice(index, 1); self.saveConfig(); self.onDeviceAdded(vDev); } } this.onPermanentlyHiddenChanged = function (vDev) { // Remove device from Homekit if (vDev.get("permanently_hidden") === true) { delete self.config.hkDevices[vDev.id]; removeFromHkDevicesArray(vDev.id); self.saveConfig(); self.onDeviceWipedOut(vDev.id); } // Add device to Homekit if (vDev.get("permanently_hidden") === false) { self.saveConfig(); self.onDeviceAdded(vDev); } } var pin = this.config.pin; // if undefined or empty, will be autogenerated this.hk = new HomeKit(this.config.name, function(r) { if (r.method == "GET" && r.path == "/accessories") { return this.accessories.serialize(r); } else if (r.method == "PUT" && r.path == "/characteristics" && r.data && r.data.characteristics) { r.data.characteristics.forEach(function (c) { if (typeof c.value !== "undefined") { // use c.aid, c.iid and c.value here var characteristic = this.accessories.find(c.aid, c.iid); if (characteristic) characteristic.value = c.value; // update subscribers this.update(c.aid, c.iid); } if (typeof c.ev === "boolean") { // set event subscription state r.events(c.aid, c.iid, c.ev); } }, this); return null; // 204 } else if (r.method == "GET" && r.path.substring(0, 20) == "/characteristics?id=") { var self = this; // Array of characteristics to update var accessoriesCharacteristics = r.path.substring(20).split(','); var characteristics = []; accessoriesCharacteristics.forEach(function(item, i, arr) { characteristicsItem = item.split('.').map(function(x) { return parseInt(x) }); var characteristic = self.accessories.find(characteristicsItem[0], characteristicsItem[1]); if (characteristic) { characteristics.push({ aid: characteristicsItem[0], iid: characteristicsItem[1], value: characteristic.value}); } }); return {"characteristics": characteristics}; } else if (r.path == "/identify") { console.log(this.name, "PIN:", this.pin); if (self.config.pin != this.pin) { self.config.pin = this.pin; self.saveConfig(); } self.controller.addNotification("notification", "HomeKit PIN: " + this.pin, "module", "HomeKitGate"); } }, pin); // prepare the structure holders this.hk.accessories = new HKAccessoryCollection(this.hk); this.mapping = {} // load saved devices in case vDevs are not yet loaded to make sure HK always receives the full list of device this.preloadDevices(); // add main accessory var razberryAccessory = this.addDevice({ id: HomeKitGate.ControllerId, title: "Z-Way", manufacturer: "Z-Wave.Me", product: "RaZberry", serialNumber: "12345678", firmwareRevision: zwayVersion.release }).$accessory; var razberryService = razberryAccessory.addService(HomeKit.Services.HAPProtocolInformation, "RaZberry Service"); razberryService.addCharacteristic(HomeKit.Characteristics.State, "uint8" , 0, ["pr", "ev"], {"maxValue":1, "minValue":0, "minStep":1}); razberryService.addCharacteristic(HomeKit.Characteristics.Version, "string", "1.0", ["pr", "ev"]); razberryService.addCharacteristic(HomeKit.Characteristics.ControlPoint, "data", "", ["pr", "pw", "ev"]); // add existing devices this.controller.devices.each(function(d) { onDeviceAddedCore(self.vDevToTemplate(d)); }); // listen for future device collection changes this.controller.devices.on("created", this.onDeviceAdded); this.controller.devices.on("removed", this.onDeviceRemoved); this.controller.devices.on("wipedOut", this.onDeviceWipedOut); this.controller.devices.on("change:metrics:level", this.onLevelChanged); this.controller.devices.on("change:metrics:isFailed", this.onLevelChanged); this.controller.devices.on("change:tags", this.onTagsChanged); this.controller.devices.on("change:permanently_hidden", this.onPermanentlyHiddenChanged); // update device tree this.hk.update(); console.log("HomeKit PIN:", this.hk.pin); self.config.pin = this.hk.pin; this.controller.addNotification("notification", "HomeKit PIN: " + this.hk.pin, "module", "HomeKitGate"); }; HomeKitGate.prototype.stop = function () { HomeKitGate.super_.prototype.stop.call(this); this.controller.devices.off("created", this.onDeviceAdded); this.controller.devices.off("removed", this.onDeviceRemoved); this.controller.devices.off("wipedOut", this.onDeviceWipedOut); this.controller.devices.off("change:metrics:level", this.onLevelChanged); this.controller.devices.off("change:metrics:isFailed", this.onLevelChanged); this.controller.devices.off("change:tags", this.onTagsChanged); this.controller.devices.off("change:permanently_hidden", this.onPermanentlyHiddenChanged); if (this.hk) { this.hk.stop(); } delete this.mapping; delete this.onDeviceAdded; delete this.onDeviceRemoved; delete this.onDeviceWipedOut; delete this.onLevelChanged; delete this.onTagsChanged; delete this.onPermanentlyHiddenChanged; }; HomeKitGate.prototype.addDevice = function(vDevT) { var self = this; // clean old structure if any this.hkDeviceRemove(vDevT.id); var m = this.mapping[vDevT.id] = {}; if (!_.isEqual(_.omit(this.config.hkDevices[vDevT.id], "aid"), _.omit(vDevT, "aid"))) { var aid = (this.config.hkDevices[vDevT.id] && this.config.hkDevices[vDevT.id].aid) || (1 + Math.max(0, Math.max.apply(null, Object.keys(this.config.hkDevices).map(function(k) { return self.config.hkDevices[k].aid; })))); this.config.hkDevices[vDevT.id] = vDevT; if (this.config.hkDevicesArray.indexOf(vDevT.id) == -1) this.config.hkDevicesArray.push(vDevT.id); this.config.hkDevices[vDevT.id].aid = aid; this.saveConfig(); } var aid = this.config.hkDevices[vDevT.id].aid; var accessory = m.$accessory = this.hk.accessories.addAccessory(vDevT.title, vDevT.manufacturer, vDevT.product, vDevT.serialNumber, vDevT.firmwareRevision, aid); // add Bridging State Service to all Accessories var service = accessory.addService(HomeKit.Services.BridgingState, "BridgingState"); service.addCharacteristic(HomeKit.Characteristics.LinkQuality, "uint8", 1, ["pr", "ev"], {"maxValue":4, "minValue":1, "minStep":1}); service.addCharacteristic(HomeKit.Characteristics.AccessoryIdentifier, "string", HomeKitGate.generateUUID()); service.addCharacteristic(HomeKit.Characteristics.Category, "uint16", 1, ["pr", "ev"], {"maxValue":16, "minValue":1, "minStep":1}); m.reachable = service.addCharacteristic(HomeKit.Characteristics.Reachable, "bool", vDevT.id === HomeKitGate.ControllerId ? true : { get: function() { var d = self.controller.devices.get(vDevT.id); return !!d && !d.get("metrics:isFailed"); } }, ["pr", "ev"]); return m; }; HomeKitGate.prototype.preloadDevices = function() { var self = this; Object.keys(this.config.hkDevices).forEach(function(vDevId) { self.addDevice(self.config.hkDevices[vDevId]); }); }; HomeKitGate.prototype.vDevToTemplate = function(vDev) { return { id: vDev.id, deviceType: vDev.get("deviceType"), probeType: vDev.get("probeType"), tags: vDev.get("tags"), permanently_hidden: vDev.get('permanently_hidden'), visibility: vDev.get('visibility'), title: vDev.get("metrics:title"), scaleTitle: vDev.get("metrics:scaleTitle"), probeTitle: vDev.get("metrics:probeTitle"), manufacturer: vDev.get("manufacturer"), product: vDev.get("product"), firmwareRevision: vDev.get("firmware"), serialNumber: undefined }; }; // Static methods HomeKitGate.generateUUID = function () { return 'xxxxxxxx-xxxx-xxxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); return v.toString(16); }); } ================================================ FILE: modules/HomeKitGate/lang/de.json ================================================ { "m_title":"Apple HomeKit Gateway", "m_descr":"Fügt alle im Z-Way verfügbaren Widgets zum Apple HomeKit hinzu.

          If you don't want to export some device in HomeKit, add homekit-skip tag to that device. Make a switch look like a bulb: add tag type-light, for fun: type-fun.

          NB! Works only with Z-Way v3.0.0 and upper.", "l_options":"Name, wie er in iOS Geräten angezeigt werden soll:", "l_pin":"Apple Homekit Pin", "l_skipped_devices":"Devices that don't need to be displayed in HomeKit", "h_skipped_devices":"homekit-skip tag will be added to each selected device.", "l_hkDevicesArray": "Devices exported to Apple HomeKit (autogenerated)" } ================================================ FILE: modules/HomeKitGate/lang/en.json ================================================ { "m_title":"Apple HomeKit Gate", "m_descr":"The Apple HomeKit protocol allows controlling HomeKit compatible devices from Apple iOS devices (iPhone, iPad). Activating this option your Z-Way gateway will be recognized as a HomeKit Bridge. All Z-Wave switches, dimmer etc. are mapped into the HomeKit world and can be controlled using third party HomeKit apps on the given platforms as well as in the native iOS Home app.

          If you don't want to export some device in HomeKit, add homekit-skip tag to that device. Make a switch look like a bulb: add tag type-light, for fun: type-fun.

          NB! Works only with Z-Way v3.0.0 and upper.", "l_options":"Name as it appears in iOS devices", "l_pin":"Apple Homekit Pin", "l_skipped_devices":"Devices that don't need to be displayed in HomeKit", "h_skipped_devices":"homekit-skip tag will be added to each selected device.", "l_hkDevicesArray": "Devices exported to Apple HomeKit (autogenerated)" } ================================================ FILE: modules/HomeKitGate/lang/ru.json ================================================ { "m_title":"Apple HomeKit Шлюз", "m_descr":"Интеграция с приложением Дом от Apple (HomeKit) позволяет управлять устройвами с устройств iOS (iPhone и iPad).

          Чтобы не выгружать устройство в HomeKit добавьте ему тег homekit-skip. Чтобы реле отображалось как лампа, добавьте тег type-light, вентилятор: type-fun.

          Важно! Работает только с Z-Way v3.0.0 и выше.", "l_options":"Имя которое будет отображаться при поиске на iOS устройстве", "l_pin":"Пин-код HomeKit", "l_skipped_devices":"Устройства, которые не нужно отображать в HomeKit", "h_skipped_devices":"homekit-skip тэг будет добавлен каждому выбранному устройству.", "l_hkDevicesArray": "Устройства, экспортированные в HomeKit (заполняется автоматически)" } ================================================ FILE: modules/HomeKitGate/module.json ================================================ { "singleton": true, "dependencies": [], "category": "support_external_ui", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "HomeKitGate", "version": "2.4", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "name": "RaZberry", "pin": "", "hkDevicesArray": [], "skippedDevices": [] }, "schema": { "type": "object", "properties": { "name": { "type": "string", "required": true }, "pin": { "type": "string", "readonly": true }, "skippedDevices": { "type": "array", "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_all:deviceId", "required": false }, "hkDevicesArray" : { "type": "array", "items": { "type": "string", "required": false } } }, "required": false }, "options": { "fields": { "name": { "label": "__l_options__" }, "pin": { "label": "__l_pin__" }, "skippedDevices": { "label": "__l_skipped_devices__", "helper": "__h_skipped_devices__", "type": "checkbox", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_all:deviceName" }, "hkDevicesArray": { "label": "__l_hkDevicesArray__", "fields": { "item": { "type": "text", "readonly": true } } } } }, "postRender": "loadFunction:postRender.js" } ================================================ FILE: modules/IfThen/index.js ================================================ /*** IfThen Z-Way HA module ******************************************* Version: 2.6.0 (c) Z-Wave.Me, 2020 ----------------------------------------------------------------------------- Author: Niels Roche Author: Hans-Christian Göckeritz Author: Karsten Reichel Author: Serguei Poltorak Description: Bind actions on one device to other devices or scenes ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function IfThen (id, controller) { // Call superconstructor first (AutomationModule) IfThen.super_.call(this, id, controller); } inherits(IfThen, AutomationModule); _module = IfThen; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- IfThen.prototype.init = function (config) { IfThen.super_.prototype.init.call(this, config); var self = this, ifElement = self.config.sourceDevice[self.config.sourceDevice.filterIf]; this.handlerLevel = function (sDev) { var that = self, operator = ifElement.operator, ifLevel = (ifElement.status === 'level' && ifElement.level) || (!ifElement.status && ifElement.level) ? ifElement.level : ifElement.status, check = false, value = sDev.get("metrics:level"); if (operator && ifLevel) { switch (operator) { case '>': check = value > ifLevel; break; case '=': check = value === ifLevel; break; case '<': check = value < ifLevel; 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) { var type = el.filterThen; var id = el[type].target, lvl = el[type].status === 'level' && typeof el[type].level === 'number' ? el[type].level : (el[type].status === 'color' && el[type].color? el[type].color: el[type].status), vDev = that.controller.devices.get(id), // compare old and new level to avoid unneccessary updates compareValues = function(valOld,valNew){ var vO = _.isNaN(parseFloat(valOld))? valOld : parseFloat(valOld), vN = _.isNaN(parseFloat(valNew))? valNew : parseFloat(valNew); return vO !== vN; }, set = compareValues(vDev.get("metrics:level"), lvl); //if (vDev && set) { if (vDev && (!el[type].sendAction || (el[type].sendAction && set))) { if (vDev.get("deviceType") === type && (type === "switchMultilevel" || type === "thermostat" || type === "switchRGBW")) { if (lvl === 'on' || lvl === 'off'){ vDev.performCommand(lvl); } else if (typeof lvl === 'object') { vDev.performCommand("exact", lvl); } else { vDev.performCommand("exact", { level: lvl }); } } else if (vDev.get("deviceType") === "toggleButton" && type === "scene") { vDev.performCommand("on"); } else if (vDev.get("deviceType") === type) { vDev.performCommand(lvl); } } }); } }; // Setup metric update event listener if(ifElement && ifElement.device){ self.controller.devices.on(ifElement.device, 'change:metrics:level', self.handlerLevel); } }; IfThen.prototype.stop = function () { var self = this; // remove event listener self.controller.devices.off(self.config.sourceDevice[self.config.sourceDevice.filterIf].device,'change:metrics:level', self.handlerLevel); IfThen.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/IfThen/lang/de.json ================================================ { "m_title":"Wenn -> Dann", "m_descr":"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", "on":"An", "off":"Aus", "l_level":"Level", "level": "Setze Level", "open":"Öffnen", "close":"Schließen", "l_scene":"Szene order Benachrichtigung", "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_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." } ================================================ FILE: modules/IfThen/lang/en.json ================================================ { "m_title":"If -> Then", "m_descr":"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", "on":"On", "off":"Off", "l_level":"Level", "level": "Set Level", "open":"Open", "close":"Close", "l_scene":"Scene or notification", "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_sendAction":"Don't send On command, if device is already turned On, similarly for Off", "h_sendAction":"Need to avoid flooding the network." } ================================================ FILE: modules/IfThen/lang/ru.json ================================================ { "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":"Нужно чтобы не забивать сеть.", "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." } ================================================ FILE: modules/IfThen/module.json ================================================ { "singleton": false, "dependencies": [], "category": "automation_basic", "author": "Z-Wave.Me", "homepage": "https://z-wave.me", "icon": "icon.png", "moduleName": "IfThen", "version": "2.6.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "sourceDevice": {}, "targets": [], "mail_helper": "__h_nomail__", "mail_button": "__b_createmail__", "mail_confirm": "__m_createmail__" }, "schema": { "type": "object", "properties": { "sourceDevice": { "type": "object", "properties": { "filterIf": { "type": "string", "enum": [ "scene", "switchControl", "switchBinary", "switchMultilevel", "sensorBinary", "sensorMultilevel", "sensorDiscrete" ], "required": true }, "sensorMultilevel": { "type": "object", "dependencies": "filterIf", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorMultilevel:deviceId", "required": true }, "operator": { "type": "string", "enum": [ "=", ">", "<" ], "required": true }, "status": { "type": "integer", "required": true } } }, "switchBinary": { "type": "object", "dependencies": "filterIf", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchBinary:deviceId", "required": true }, "status": { "type": "integer", "enum": [ "off", "on" ], "required": true } } }, "scene": { "type": "object", "dependencies": "filterIf", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true } } }, "sensorDiscrete": { "type": "object", "dependencies": "filterIf", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorDiscrete:deviceId", "required": true }, "level": { "type": "string", "required": true } } }, "switchControl": { "type": "object", "dependencies": "filterIf", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchControl:deviceId", "required": true }, "status": { "type": "integer", "enum": [ "off", "on", "level" ], "required": true }, "operator": { "dependencies": "status", "type": "string", "enum": [ "=", ">", "<" ], "required": true }, "level": { "dependencies": "status", "type": "integer", "minimum": 0, "maximum": 99, "required": true } } }, "switchMultilevel": { "type": "object", "dependencies": "filterIf", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchMultilevel:deviceId", "required": true }, "status": { "type": "integer", "enum": [ "off", "on", "level" ], "required": true }, "operator": { "dependencies": "status", "type": "string", "enum": [ "=", ">", "<" ], "required": true }, "level": { "dependencies": "status", "type": "integer", "minimum": 0, "maximum": 99, "required": true } } }, "sensorBinary": { "type": "object", "dependencies": "filterIf", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorBinary:deviceId", "required": true }, "status": { "type": "integer", "enum": [ "off", "on" ], "required": true } } } } }, "targets": { "type": "array", "items": { "type": "object", "properties": { "filterThen": { "type": "string", "enum": [ "switchBinary", "switchMultilevel", "doorlock", "scene", "thermostat", "switchRGBW" ], "required": true }, "switchBinary": { "type": "object", "dependencies": "filterThen", "properties": { "target": { "type": "string", "datasource": "namespaces", "enum": "namespaces:devices_switchBinary:deviceId", "required": true }, "status": { "type": "integer", "enum": [ "off", "on" ], "required": true }, "sendAction": { "type": "boolean", "required": true, "default": false } } }, "switchMultilevel": { "type": "object", "dependencies": "filterThen", "properties": { "target": { "type": "string", "datasource": "namespaces", "enum": "namespaces:devices_switchMultilevel:deviceId", "required": true }, "status": { "type": "integer", "enum": [ "off", "on", "level" ], "required": true }, "level": { "dependencies": "status", "type": "integer", "minimum": 0, "maximum": 99, "required": true }, "sendAction": { "type": "boolean", "required": true, "default": false } } }, "doorlock": { "type": "object", "dependencies": "filterThen", "properties": { "target": { "type": "string", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId", "required": true }, "status": { "type": "integer", "enum": [ "close", "open" ], "required": true }, "sendAction": { "type": "boolean", "required": true, "default": false } } }, "scene": { "type": "object", "dependencies": "filterThen", "properties": { "target": { "type": "string", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true } } }, "thermostat": { "type": "object", "dependencies": "filterThen", "properties": { "target": { "type": "string", "datasource": "namespaces", "enum": "namespaces:devices_thermostat:deviceId", "required": true }, "status": { "enum": [ 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15, 15.5, 16, 16.5, 17, 17.5, 18, 18.5, 19, 19.5, 20, 20.5, 21, 21.5, 22, 22.5, 23, 23.5, 24, 24.5, 25, 25.5, 26, 26.5, 27 ] }, "sendAction": { "type": "boolean", "required": true, "default": false } } }, "switchRGBW": { "type": "object", "dependencies": "filterThen", "properties": { "target": { "type": "string", "datasource": "namespaces", "enum": "namespaces:devices_switchRGBW:deviceId", "required": true }, "status": { "type": "integer", "enum": [ "off", "on", "color" ], "required": true, "default": "on" }, "color": { "type": "object", "dependencies": "status", "properties": { "red": { "type": "integer", "minimum": 0, "maximum": 255, "required": true }, "green": { "type": "integer", "minimum": 0, "maximum": 255, "required": true }, "blue": { "type": "integer", "minimum": 0, "maximum": 255, "required": true } } }, "sendAction": { "type": "boolean", "required": true, "default": false } } } } } } } }, "options": { "fields": { "sourceDevice": { "label": "__l_event_sources__", "fields": { "filterIf": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose__", "label": "__l_filter__", "optionLabels": [ "__toggleButton__", "__switchControl__", "__switchBinary__", "__switchMultilevel__", "__sensorBinary__", "__sensorMultilevel__", "__sensorDiscrete__" ] }, "sensorMultilevel": { "dependencies": { "filterIf": "sensorMultilevel" }, "fields": { "device": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorMultilevel:deviceName" }, "operator": { "type": "select" }, "status": { "label": "__l_level__" } } }, "switchBinary": { "dependencies": { "filterIf": "switchBinary" }, "fields": { "device": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchBinary:deviceName" }, "status": { "optionLabels": [ "__off__", "__on__" ] } } }, "scene": { "dependencies": { "filterIf": "scene" }, "fields": { "device": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" } } }, "sensorDiscrete": { "dependencies": { "filterIf": "sensorDiscrete" }, "fields": { "device": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorDiscrete:deviceName" }, "level": { "label": "__l_controller_action__", "helper": "__h_controller_action__" } } }, "switchControl": { "dependencies": { "filterIf": "switchControl" }, "fields": { "device": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchControl:deviceName" }, "status": { "type": "select", "label": "__l_level__", "optionLabels": [ "__off__", "__on__", "__level__" ] }, "operator": { "dependencies": { "status": [ "level" ] }, "type": "select" }, "level": { "dependencies": { "status": [ "level" ] } } } }, "switchMultilevel": { "dependencies": { "filterIf": "switchMultilevel" }, "fields": { "device": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchMultilevel:deviceName" }, "status": { "type": "select", "label": "__l_level__", "optionLabels": [ "__off__", "__on__", "__level__" ] }, "operator": { "dependencies": { "status": [ "level" ] }, "type": "select" }, "level": { "dependencies": { "status": [ "level" ] } } } }, "sensorBinary": { "dependencies": { "filterIf": "sensorBinary" }, "fields": { "device": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorBinary:deviceName" }, "status": { "optionLabels": [ "__off__", "__on__" ] } } } } }, "targets": { "label": "__l_actors__", "fields": { "item": { "fields": { "filterThen": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose__", "label": "__l_filter__", "optionLabels": [ "__switchBinary__", "__switchMultilevel__", "__doorlock__", "__l_scene__", "__thermostat__", "__switchRGBW__" ] }, "switchBinary": { "dependencies": { "filterThen": "switchBinary" }, "fields": { "target": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "label": "__l_target_device__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchBinary:deviceName" }, "status": { "label": "__l_action__", "optionLabels": [ "__off__", "__on__" ] }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } }, "switchMultilevel": { "dependencies": { "filterThen": "switchMultilevel" }, "fields": { "target": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "label": "__l_target_device__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchMultilevel:deviceName" }, "status": { "type": "select", "label": "__l_level__", "optionLabels": [ "__off__", "__on__", "__level__" ] }, "level": { "dependencies": { "status": [ "level" ] } }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } }, "doorlock": { "dependencies": { "filterThen": "doorlock" }, "fields": { "target": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "label": "__l_target_device__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName" }, "status": { "label": "__l_action__", "optionLabels": [ "__close__", "__open__" ] }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } }, "scene": { "dependencies": { "filterThen": "scene" }, "fields": { "target": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "label": "__l_scene__", "label": "__l_scene__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" } } }, "thermostat": { "dependencies": { "filterThen": "thermostat" }, "fields": { "target": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "label": "__l_target_device__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_thermostat:deviceName" }, "status": { "type": "select", "label": "__l_level__" }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } }, "switchRGBW": { "dependencies": { "filterThen": "switchRGBW" }, "fields": { "target": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "label": "__l_target_device__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchRGBW:deviceName" }, "status": { "type": "select", "label": "__l_level__", "optionLabels": [ "__off__", "__on__", "__colors__" ] }, "color": { "dependencies": { "status": [ "color" ] }, "label": "__colors__", "fields": { "red": { "label": "__l_red__", "helper": "__h_color__" }, "green": { "label": "__l_green__", "helper": "__h_color__" }, "blue": { "label": "__l_blue__", "helper": "__h_color__" } } }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } } } } } } } } } ================================================ FILE: modules/IfThen/patchnotes.txt ================================================ v2.6.0 - removed notifications in favor of new notification system v2.5.1 - allow negativ values for multilevel sensors v2.5.0 - notification as part of If/Then v2.4.5 - allow push and mail with same notification v2.4.4 - adding push and/or mail notification v2.4.1 - adding support for sensorDiscrete - adding support for switchRGBW - bugfix ON/OFF if sensorMultilevel is trigger action v2.1.1 - bugfix missing change of levels switch multilevel and refactoring - avoid update of vDev with same level v2.1.0 - add Thermostat and Multilevel Sensor support - add set Level to switch control and switch multilevel v2.0.5 - bugfix: wrong adressing of 'if'-device if app is stopped, so that conditions are still active ================================================ FILE: modules/ImportRemoteHA/index.js ================================================ /*** ImportRemoteHA Z-Way HA module ******************************************* Version: 2.0.4 (c) Z-Wave.Me, 2022 ----------------------------------------------------------------------------- Author: Poltorak Serguei , Niels Roche Description: Imports devices from remote HA engine ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function ImportRemoteHA (id, controller) { // Call superconstructor first (AutomationModule) ImportRemoteHA.super_.call(this, id, controller); } inherits(ImportRemoteHA, AutomationModule); _module = ImportRemoteHA; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- ImportRemoteHA.prototype.init = function (config) { ImportRemoteHA.super_.prototype.init.call(this, config); var self = this; var config_url = this.config.url.indexOf('http://') > -1? this.config.url : 'http://' + this.config.url + ':8083'; console.log('config_url',config_url); this.urlPrefix = config_url + "/ZAutomation/api/v1/devices"; this.dT = Math.max(this.config.dT, 500); // 500 ms minimal delay between requests this.timestamp = 0; this.lastRequest = 0; this.timer = null; this.requestUpdate(); }; ImportRemoteHA.prototype.stop = function () { var self = this; if (this.timer) { clearTimeout(this.timer); } this.controller.devices.filter(function(xDev) { return (xDev.id.indexOf("RemoteHA_" + self.id + "_") !== -1) }).map(function(yDev) { return yDev.id }).forEach(function(item) { self.controller.devices.remove(item); }); ImportRemoteHA.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ImportRemoteHA.prototype.requestUpdate = function () { var self = this; this.lastRequest = Date.now(); var url = this.urlPrefix + "?since=" + this.timestamp.toString(); try { http.request({ url: url, method: "GET", async: true, auth: { login: self.config.login, password: self.config.password }, success: function(response) { self.parseResponse(response); }, error: function(response) { console.log("Can not make request: " + response.statusText + " " + url); // don't add it to notifications, since it will fill all the notifcations on error }, complete: function() { var dt = self.lastRequest + self.dT - Date.now(); if (dt < 0) { dt = 1; // in 1 ms not to make recursion } if (self.timer) { clearTimeout(self.timer); } self.timer = setTimeout(function() { self.requestUpdate(); }, dt); } }); } catch (e) { self.timer = setTimeout(function() { self.requestUpdate(); }, self.dT); } }; ImportRemoteHA.prototype.parseResponse = function (response) { var self = this; if (response.status === 200, response.contentType === "application/json") { var data = response.data.data; this.timestamp = data.updateTime; data.devices.forEach(function(item) { var localId = "RemoteHA_" + self.id + "_" + item.id, vDev = self.controller.devices.get(localId); if (vDev) { for (var m in item.metrics) { if (vDev.get("metrics:" + m) !== item.metrics[m]) { vDev.set("metrics:" + m, item.metrics[m]); } } } else { if (self.skipDevice(localId)) { return; } var dev = { deviceId: localId, defaults: { nodeId: item.nodeId === undefined ? undefined : ('R-' + self.id + (item.bindingName === undefined ? "" : ("_" + item.bindingName)) + "_" + item.nodeId), bindingName: item.bindingName === undefined ? undefined : ('R-' + item.bindingName), technology: item.technology, deviceType: item.deviceType, probeType: item.probeType, metrics: item.metrics, visibility: item.visibility || true, permanently_hidden: item.permanently_hidden }, handler: function(command, args) { self.handleCommand(this, command, args); }, overlay: {}, moduleId: this.id } // add tag if activated if (self.config.addTag) { dev.overlay.tags = self.config.tagName && self.config.tagName !== ''? [self.config.tagName] : ["RemoteHA_" + self.id]; } self.controller.devices.create(dev); self.renderDevice({deviceId: localId, deviceType: item.deviceType}); } }); if (data.structureChanged) { var removeList = this.controller.devices.filter(function(xDev) { var found = false; if (xDev.id.indexOf("RemoteHA_" + self.id + "_") === -1) { return false; // not to remove devices created by other modules } data.devices.forEach(function(item) { if (("RemoteHA_" + self.id + "_" + item.id) === xDev.id) { found |= true; return false; // break } }); return !found; }).map(function(yDev) { return yDev.id }); removeList.forEach(function(item) { self.controller.devices.remove(item); }); } } }; ImportRemoteHA.prototype.handleCommand = function(vDev, command, args) { var self = this; var argsFlat = ""; if (args) { for (var key in args) { argsFlat = argsFlat + (argsFlat ? "&" : "?") + key.toString() + "=" + args[key].toString(); } } var remoteId = vDev.id.slice(("RemoteHA_" + this.id + "_").length); var url = this.urlPrefix + "/" + remoteId + "/command/" + command + argsFlat; http.request({ url: url, method: "GET", async: true, auth: { login: self.config.login, password: self.config.password }, error: function(response) { console.log("Can not make request: " + response.statusText + " " + url); // don't add it to notifications, since it will fill all the notifcations on error } }); }; ImportRemoteHA.prototype.skipDevice = function(id) { var skip = false; this.config.skipDevices.forEach(function(skipItem) { if (skipItem === id) { skip |= true; return false; // break } }); return skip; }; // check if deviceId is already added to list ImportRemoteHA.prototype.renderDevice = function (obj) { var skip = false; this.config.renderDevices.forEach(function (deviceObj) { if (deviceObj.deviceId === obj.deviceId) { skip |= true; return false; // break } }); if (!skip) { this.config.renderDevices.push(obj); this.saveConfig(); } }; ================================================ FILE: modules/ImportRemoteHA/lang/de.json ================================================ { "m_title":"Verbinde anderen Z-Way-Controller", "m_descr":"Z-Way steuert ein Z-Wave Netzwerk mit bis zu 232 Geräten. Diese App führt mehrere Z-Way Einheiten in einem User Interface zusammen. Im Grunde bildet die App Daten eines anderen Z-Way betriebenen Gateways in Ihrem Gateway ab. Die Elemente werden dargestellt und können wie das vorliegende bedient werden. Bitte beachten Sie, dass die Funktion des zweiten Gateways unberührt bleibt. Das heißt, das zweite Gateway nimmt die Elemente Ihres ersten Gateways nicht wahr, kann sie aber steuern und dessen Elemente ohne weitere Einschränkungen abrufen:

          Einstellungen:
          • Stellen Sie die IP Adresse der Remote-Box einschließlich der Port-Nummer ein.
          • Definieren Sie das Abrufintervall. Empfehlenswert wäre einmal pro Sekunde.
          • Inkludieren Sie das fernzusteuernde Gerät in Ihre UI.
          ", "l_url":"URL Z-Wave Homegateway", "l_login":"Login", "l_password":"Password", "l_dT":"Updateperiode (in Millisekunden)", "l_skipDevices":"Geräte die nicht aus dem Z-Wave Homegateway importiert werden sollen:", "l_renderDevices":"Aus dem Z-Wave Homegateway importierte Geräte (wird automatisch geladen):", "rl_addTag":"Füge einen Tag zu allen Remote Geräten hinzu", "l_tagName":"Tag", "ph_tagName": "Remote Box", "h_tagName": "RemoteHA_ID wird benutzt, wenn kein Tag angegeben ist" } ================================================ FILE: modules/ImportRemoteHA/lang/en.json ================================================ { "m_title":"Link other Z-Way controller", "m_descr":"Z-Way controls one Z-Wave network with up to 232 devices. This app will combine multiple Z-Way entities into one User Interface. Essentially the app is mapping the data of another Z-Way powered Gateway into your gateway. The elements are displayed and can be used like the local ones. Please note that the function of the second gateway remains untouched. This means that this second gateway will not see the elements for your first gateway but can control and access their own elements without any further restrictions:

          Settings:
          • You need to set the IP address of the Remote Box including the port number.
          • You need to define the polling interval. Once a second is a quite good choice. You need to manage the remoted device to be included into your UI.
          ", "l_url":"URL to remote side", "l_login":"Login", "l_password":"Password", "l_dT":"Update period (in milliseconds)", "l_skipDevices":"List of devices not to import from remote side", "l_renderDevices":"List of devices imported from remote side (filled automatically)", "rl_addTag":"Add tag to all remote widgets", "l_tagName":"Tag", "ph_tagName": "Remote Box", "h_tagName": "RemoteHA_ID is used if no tag is added" } ================================================ FILE: modules/ImportRemoteHA/lang/ru.json ================================================ { "m_title":"Импорт устройств из другой RaZberry", "m_descr":"Устройства с разных контроллеров RaZberry можно объединить в одном контроллере.", "l_url":"URL другой RaZberry", "l_login":"Логин", "l_password":"Пароль", "l_dT":"Частота опроса другой RaZberry (в миллисекундах)", "l_skipDevices":"Список устройств, которые не нужно импортировать с другой RaZberry", "l_renderDevices":"Список устройств для импорта с другой RaZberry (заполняется автоматически)", "rl_addTag":"Add tag to all remote widgets", "l_tagName":"Tag", "ph_tagName": "Remote Box", "h_tagName": "RemoteHA_ID is used if no tag is added" } ================================================ FILE: modules/ImportRemoteHA/module.json ================================================ { "dependencies": [], "singleton": false, "category": "support_external_dev", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "ImportRemoteHA", "version": "2.0.4", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "addTag":false, "tagName":"", "url": "", "login":"admin", "password":"admin", "dT": 1000, "skipDevices": [], "renderDevices": [] }, "schema": { "type": "object", "properties": { "addTag":{ "type" : "boolean", "required" : false }, "tagName":{ "type" : "string", "dependencies": "addTag", "required" : false }, "url": { "type": "string", "required": true }, "login": { "type": "string", "required": true }, "password": { "format": "password", "type": "string", "required": true }, "dT": { "type": "number", "required": true }, "skipDevices": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_all:deviceId", "required": true } }, "renderDevices": { "type": "array", "items": { "type": "object", "properties": { "deviceId": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_all:deviceId", "required": true }, "deviceType": { "type": "string", "required": true } } } } } }, "options": { "fields": { "addTag":{ "type": "checkbox", "rightLabel":"__rl_addTag__" }, "tagName":{ "label": "__l_tagName__", "placeholder": "__ph_tagName__", "helper":"__h_tagName__" }, "url": { "label": "__l_url__", "placeholder": "192.168.0.100" }, "login": { "label": "__l_login__" }, "password": { "type": "password", "label": "__l_password__" }, "dT": { "label": "__l_dT__" }, "skipDevices": { "label": "__l_skipDevices__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_all:deviceName" } } }, "renderDevices": { "label": "__l_renderDevices__", "fields": { "item": { "fields": { "deviceId": { "type": "select", "readonly": true, "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_all:deviceName" }, "deviceType": { "readonly": true } } } } } } } } ================================================ FILE: modules/ImportRemoteHA/patchnotes.txt ================================================ #v2.0.3 - fixed: hidden and deactivated devices of remote controller were also shown #v2.0.2 (pull request #393 by xibrix) - fixed: RenderDevices array in storage are doubleing up when module config is edited. #v2.0.1 - add functionality to tag all remote widgets - enhance url input to add ip adress only (with backward compatibility) ================================================ FILE: modules/InbandNotifications/index.js ================================================ /*** InbandNotifications Z-Way HA module ******************************************* Version: 1.1.2 (c) Z-Wave.Me, 2020 ----------------------------------------------------------------------------- Author: Niels Roche , Serguei Poltorak Description: Listens to the statuses of all devices and emits notifications on changed. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function InbandNotifications (id, controller) { // Call superconstructor first (AutomationModule) InbandNotifications.super_.call(this, id, controller); } inherits(InbandNotifications, AutomationModule); _module = InbandNotifications; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- InbandNotifications.prototype.init = function (config) { InbandNotifications.super_.prototype.init.call(this, config); var self = this, lastChanges = []; this.writeNotification = function (vDev) { if (!vDev.get('permanently_hidden')) { var devId = vDev.get('id'), devType = vDev.get('deviceType'), devProbeType = vDev.get('probeType'), devName = vDev.get('metrics:title'), scaleUnit = vDev.get('metrics:scaleTitle'), level = vDev.get('metrics:level'); function getCustomIcon() { var customIcons = vDev.get('customIcons'); if (!customIcons || Object.keys(customIcons).length === 0) return undefined; return customIcons.level ? customIcons.level[level] : customIcons.default; }; function eventType(){ var probeTitle = vDev.get('metrics:probeTitle'); return probeTitle ? probeTitle.toLowerCase() : 'status'; }; var lastEvent = lastChanges.filter(function(e) { return e.id === devId; })[0]; if (!lastEvent) { lastEvent = { id: devId, l: null }; lastChanges.push(lastEvent); } var msg = { dev: devName, l: level, // will be extended below location: vDev.get('location'), customIcon: getCustomIcon() }; var msgType = ""; if (lastEvent.l === level && ["battery", "sensorBinary", "sensorDiscrete", "toggleButton", "switchControl"].indexOf(devType) === - 1) return; // emit only for new values (not same as previous) or sensorBinary/sensorDiscrete/toggleButton/switchControl events // depending on device type choose the correct notification switch(devType) { case 'switchBinary': case 'switchControl': case 'sensorBinary': case 'fan': case 'doorlock': case 'toggleButton': msgType = 'device-OnOff'; break; case 'switchMultilevel': msg.l += '%'; msgType = 'device-status'; break; case 'sensorDiscrete': msgType = 'device-status'; break; case 'battery': case 'sensorMultilevel': case 'sensorMultiline': case 'thermostat': msg.l += (scaleUnit ? ' ' + scaleUnit : ''); msgType = 'device-' + eventType(); break; case 'switchRGBW': msg.color = vDev.get('metrics:color'); msgType = 'device-' + eventType(); break; default: return; // don't add the notification } lastEvent.l = level; self.controller.addNotification('device-info', msg , msgType, devId); } }; // Setup metric update event listener self.controller.devices.on('change:metrics:level', self.writeNotification); }; InbandNotifications.prototype.stop = function () { var self = this; self.controller.devices.off('change:metrics:level', self.writeNotification); InbandNotifications.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/InbandNotifications/lang/de.json ================================================ { "m_title":"Ereignisprotokoll ", "m_descr":"Der Inband Notifier ordnet die Darstellung der Systemereignisse im Zeitrahmen Ihres User Interface an. Es sollte die ganze Zeit aktiviert sein. In der Verwendung mit diesem Modul kann sehr viel falsch gemacht werden, daher lassen Sie es unberührt, solange es nicht wirklich benötigt wird.", "l_dev":"Geräte die überwacht werden sollen", "l_devT":"Gerätetypen die überwacht werden sollen", "all":"alle Gerätetypen", "battery":"Batterie", "fan":"Ventilator", "doorlock":"Türschloss", "switchBinary":"An/Aus Schalter ", "switchMultilevel":"Dimmer/Motor Steuerung ", "switchControl":"Schalter Steuerung ", "sensorBinary":"An/Aus Sensor ", "sensorMultilevel":"Analogsensor ", "sensorMultiline":"Informations Sensor", "thermostat":"Thermostat Regler " } ================================================ FILE: modules/InbandNotifications/lang/en.json ================================================ { "m_title":"Inband Notifier", "m_descr":"The inband notifier organizes to display the events of the system into the time line of your User Interface. It should be active all the time. There is nothing to be done right with the module but a lot of things that can be done wrong. This means, don’t touch it unless its really needed. ", "l_dev":"Devices to be monitored", "l_devT":"Device types to be monitored", "all":"all device types ", "battery":"Battery Sensor ", "fan":"Fan Switch ", "doorlock":"Doorlock Switch ", "switchBinary":"On/Off Switch ", "switchMultilevel":"Dimmer/Motor Control ", "switchControl":"Switch Control ", "sensorBinary":"On/Off Sensor ", "sensorMultilevel":"Analog Sensor ", "sensorMultiline":"Informer ", "thermostat":"Thermostat Switch " } ================================================ FILE: modules/InbandNotifications/module.json ================================================ { "singleton" : true, "dependencies" : [], "category" : "basic_gateway_modules", "author" : "Z-Wave.Me", "homepage" : "https://razberry.z-wave.me", "icon": "icon.png", "moduleName":"InbandNotifications", "version" : "1.1.2", "maturity" : "stable", "repository" : { "type" : "git", "source" : "https://github.com/Z-Wave-Me/home-automation" }, "defaults" : { "title" : "__m_title__", "description" : "__m_descr__" }, "schema": {}, "options": {} } ================================================ FILE: modules/InfoWidget/index.js ================================================ /*** InfoWidget Z-Way HA module ******************************************* Version: 1.0.1 (c) Z-Wave.Me, 2018 ----------------------------------------------------------------------------- Author: Niels Roche Changed: Karsten Reichel Description: Creates a text/information widget as devicetype 'text'. It is possible to internationalize one widget with EN,DE,RU or CN translation. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function InfoWidget (id, controller) { // Call superconstructor first (AutomationModule) InfoWidget.super_.call(this, id, controller); } inherits(InfoWidget, AutomationModule); _module = InfoWidget; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- InfoWidget.prototype.init = function (config) { InfoWidget.super_.prototype.init.call(this, config); var self = this, vDev; this.vDev = []; this.createTextWidgets = function(lang) { var lng = lang ? lang : self.controller.defaultLang; if (self.config.widgets && self.config.widgets.length > 0 && self.config.internationalize === false) { self.config.widgets.forEach(function (widget, indx) { var devId = "InfoWidget_" + self.id + '_' + indx; if (self.vDev.indexOf(devId) === -1) { vDev = this.controller.devices.create({ deviceId: devId, defaults: { metrics: { title: widget.headline, text: widget.text, icon: widget.imgURI } }, overlay: { deviceType: "text", metrics: { title: widget.headline, text: widget.text, icon: widget.imgURI } }, moduleId: self.id }); self.vDev.push(vDev); } }); } if (self.config.widgetsInt && self.config.widgetsInt.length > 0 && self.config.internationalize === true) { self.config.widgetsInt.forEach(function (widget) { if (widget.lang === lng) { var devId = "InfoWidget_" + self.id + "_Int"; if (self.vDev.indexOf(devId) === -1) { vDev = this.controller.devices.create({ deviceId: devId, defaults: { metrics: { title: widget.headline, text: widget.text, icon: widget.imgURI } }, overlay: { deviceType: "text", metrics: { title: widget.headline, text: widget.text, icon: widget.imgURI } }, moduleId: self.id }); self.vDev.push(vDev); } } }); this.controller.on('language.changed',self.updateIntWidgets); } }; this.updateIntWidgets = function (lang) { var dev = self.config.widgetsInt.filter(function(widget) { return widget.lang === lang; }); if (dev.length > 0 && self.vDev[0]) { self.vDev[0].set('metrics:title',dev[0].headline); self.vDev[0].set('metrics:text',dev[0].text); self.vDev[0].set('metrics:icon',dev[0].imgURI); } }; this.createTextWidgets(self.controller.defaultLang); }; InfoWidget.prototype.stop = function () { var self = this; this.controller.off('language.changed',self.updateIntWidgets); if (self.vDev) { self.vDev.forEach(function (dev) { this.controller.devices.remove(dev.id); }); self.vDev = null; } InfoWidget.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/InfoWidget/lang/de.json ================================================ { "m_title":"Informations Widget", "m_descr":"Diese App wird zur Darstellung einiger Textblöcke in der UI verwendet. Sie dient hauptsächlich zur einleitenden Information sowie zur Begrüßung. Sie können sie z. B. auch als Linkliste verwenden, die es gestattet, den reinen HTML Code als Textblock umzusetzen.

          Einstellungen:
          • Erstellen Sie Ihren Text für die Darstellung in verschiedenen Sprachen. Bietet Ihre UI keine Sprachanweisungen, wird standardmäßig der Text in Englisch angezeigt.
          ", "l_widgets":"Ein oder mehrere Informations Widgets erzeugen", "h_widgets":"Klicken sie um ein Widget hinzuzufügen", "l_widget": "Widget", "l_headline":"Eingabe der Überschrift:", "p_headline":"Überschrift", "l_text":"Eingabe des Informationstextes", "p_text":"Text...", "l_imgURI":"URL des Bildes (optional):", "p_imgURI":"z.B. 'http://www.images.com/nature/tree_xy.jpg'", "l_translate":"Internationalisierung", "rl_translate":"Internationalisierung aktivieren", "p_translate":"Bei Aktivierung lassen sich mehrere Texte für die entsprechend eingestellte Benutzeroberflächensprache erstellen.", "l_lang":"Textsprache", "h_lang":"Bitte wählen Sie eine der Sprachen in der Sie Ihren Text verfassen möchten aus.", "en":"Englisch", "de":"Deutsch", "ru":"Russisch", "cn":"Chinesisch" } ================================================ FILE: modules/InfoWidget/lang/en.json ================================================ { "m_title":"Information Widget", "m_descr":"This app is used to display some text block in the UI. The main reason that this app exists is to give you some introductory information and a welcome greeting. However you can use this e.g. as a link list since it allows defining pure HTML code as text block

          Settings:
          • Define your text to be shown in different languages. If there is no statement for the language of your UI, the English text will be used as default.
          ", "l_widgets":"Create one or nore Information Widgets", "h_widgets":"Click to create a widget", "l_widget": "Widget", "l_headline":"Enter your headline:", "p_headline":"Headline", "l_text":"Enter your text:", "p_text":"Text...", "l_imgURI":"URL of your image (optional):", "p_imgURI":"e.g. 'http://www.images.com/nature/tree_xy.jpg'", "l_translate":"Internationalization", "rl_translate":"Activate Internationalization", "p_translate":"'If activated you could add different texts for each language known in user interface.", "l_lang":"Text Language", "h_lang":"Please choose one language in that you will write your text message.", "en":"English", "de":"German", "ru":"Russian", "cn":"Chinese" } ================================================ FILE: modules/InfoWidget/module.json ================================================ { "singleton" : false, "dependencies": [], "category": "device_enhancements", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"InfoWidget", "version": "1.0.1", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults" : { "title" : "__m_title__", "description" : "__m_descr__", "widgets":[] }, "schema" : { "type":"object", "properties" : { "internationalize" : { "type" : "boolean", "required" : false }, "widgetsInt": { "type": "array", "items": { "$ref": "#/definitions/widgetInt" }, "dependencies": "internationalize" }, "widgets": { "type": "array", "items": { "$ref": "#/definitions/widget" }, "dependencies": "internationalize" } }, "definitions":{ "widgetInt": { "type" : "object", "properties":{ "lang" : { "enum" : ["en","de","ru","cn"], "type": "string", "default": "en", "required" : true }, "headline" : { "type" : "string", "required" : true }, "text" : { "type" : "string", "required" : true }, "imgURI" : { "type" : "string", "format" : "uri", "required" : false } } }, "widget": { "type" : "object", "properties":{ "headline" : { "type" : "string", "required" : true }, "text" : { "type" : "string", "required" : true }, "imgURI" : { "type" : "string", "required" : false } } } } }, "options" : { "fields":{ "internationalize": { "type": "checkbox", "label":"__l_translate__", "rightLabel":"__rl_translate__", "default": false }, "widgets":{ "label":"__l_widgets__", "helper":"__h_widgets__", "dependencies": { "internationalize": false } }, "widgetsInt":{ "label":"__l_widgets__", "helper":"__h_widgets__", "dependencies": { "internationalize": true } } }, "definitions":{ "fields":{ "widgetInt":{ "fields": { "lang" : { "label" : "__l_lang__", "placeholder": "__p_lang__", "optionLabels":["__en__","__de__","__ru__","__cn__"], "type" : "select" }, "headline" : { "label" : "__l_headline__", "placeholder": "__p_headline__", "type" : "text" }, "text" : { "label" : "__l_text__", "placeholder": "__p_text__", "type" : "textarea" }, "imgURI" : { "label" : "__l_imgURI__", "placeholder": "__p_imgURI__", "type" : "text" } }, "dependencies": { "internationalize": true } }, "widget":{ "fields": { "headline" : { "label" : "__l_headline__", "placeholder": "__p_headline__", "type" : "text" }, "text" : { "label" : "__l_text__", "placeholder": "__p_text__", "type" : "textarea" }, "imgURI" : { "label" : "__l_imgURI__", "placeholder": "__p_imgURI__", "type" : "text" } }, "dependencies": { "internationalize": false } } } } } } ================================================ FILE: modules/LightMotionRockerAutocontrol/index.js ================================================ /*** LightMotionRockerAutocontrol Z-Way Home Automation module ************************************* Version: 1.0.1 (c) Z-Wave.Me, 2014 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: This module controls light via rocker, several motion sensors and light sensor. Light can be turned on and off by rocker (always) or by motion sensors (only if light level is high enough and manual control was not performed). A timer turns light off. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function LightMotionRockerAutocontrol (id, controller) { // Call superconstructor first (AutomationModule) LightMotionRockerAutocontrol.super_.call(this, id, controller); // Light state this.lightIs = "unknown"; // Timers this.rockerRelaxTimer = null; this.motionOffTimer = null; this.rescueOffTimer = null; // Prevent motion to trigger light after rocker operation this.rockerRelax = false; // States of all motion sensors this.motionsStates = {}; // State of luminance sensor (true to allow trigger by motion) this.luminanceState = true; }; inherits(LightMotionRockerAutocontrol, AutomationModule); _module = LightMotionRockerAutocontrol; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- LightMotionRockerAutocontrol.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) LightMotionRockerAutocontrol.super_.prototype.init.call(this, config); var self = this; this.rockerHandler = function(vDev) { var event = vDev.get("metrics:level"); if (event === "on") { self.turnLightsOn(); // Turn on light self.rockerRelaxTimerStart(); // Don't react on motion events for some period self.motionTimerStop(); // Stop motion timer if any self.rescueTimerStart(); // Run timer to turn off light if user forgot about it } else if (event === "off") { self.turnLightsOff(); // Turn off light self.rockerRelaxTimerStart(); // Don't react on motion events for some period self.motionTimerStop(); // Stop motion timer if any self.rescueTimerStop(); // Stop rescue timer } }; this.motionHandler = function(vDev) { var level = vDev.get("metrics:level"); if (level === "on") { self.motionsStates[vDev.id] = true; self.rescueTimerStart(); // Start rescue } else if (level === "off") { self.motionsStates[vDev.id] = false; } else { return; // Don't check motion states, unknown event } self.checkMotions(); // Check if all motion sensors are off }; this.luminanceHandler = function(vDev) { if (self.config.luminanceThreshold > 0) { var _tmp = vDev.get("metrics:level") < self.config.luminanceThreshold; if (_tmp !== self.luminanceState) { self.luminanceState = _tmp; self.checkMotions(); } } }; this.config.rockers.forEach(function(rocker) { self.controller.devices.on(rocker, "change:metrics:level", self.rockerHandler); }); this.config.motions.forEach(function(motion) { self.controller.devices.on(motion, "change:metrics:level", self.motionHandler); }); if (this.config.luminance) { self.controller.devices.on(this.config.luminance, "change:metrics:level", self.luminanceHandler); } var vDevLumi = this.controller.devices.get(this.config.luminance); if (this.config.luminanceThreshold > 0 && vDevLumi) { this.luminanceState = vDevLumi.get("metrics:level") < this.config.luminanceThreshold; } else { this.luminanceState = true; // If no device found or luminanceThreshold = 0, always work on motion } }; LightMotionRockerAutocontrol.prototype.stop = function () { LightMotionRockerAutocontrol.super_.prototype.stop.call(this); var self = this; this.config.rockers.forEach(function(rocker) { self.controller.devices.off(rocker, "change:metrics:level", self.rockerHandler); }); this.config.motions.forEach(function(motion) { self.controller.devices.off(motion, "change:metrics:level", self.motionHandler); }); if (this.config.luminance) { self.controller.devices.off(this.config.luminance, "change:metrics:level", self.luminanceHandler); } if (this.rockerRelaxTimer) { clearTimeout(this.rockerRelaxTimer); } if (this.motionOffTimer) { clearTimeout(this.motionOffTimer); } if (this.rescueOffTimer) { clearTimeout(this.rescueOffTimer); } }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- LightMotionRockerAutocontrol.prototype.checkMotions = function() { if (this.rockerRelax) { return; } var oneMotionIsOn = false; for (var vDevId in this.motionsStates) { oneMotionIsOn |= this.motionsStates[vDevId]; }; if (oneMotionIsOn && this.luminanceState) { this.turnLightsOn(); this.rescueTimerStart(); this.motionTimerStop(); } if (!oneMotionIsOn || !this.luminanceState) { this.motionTimerStart(); } }; LightMotionRockerAutocontrol.prototype.turnLights = function(action) { var self = this; self.config.lights.forEach(function(device) { var vDev = self.controller.devices.get(device); if (vDev) { vDev.performCommand(action); } }); }; LightMotionRockerAutocontrol.prototype.turnLightsOn = function() { if (this.lightIs !== "on") { this.lightIs = "on"; this.turnLights("on"); } }; LightMotionRockerAutocontrol.prototype.turnLightsOff = function() { if (this.lightIs !== "off") { this.lightIs = "off"; this.turnLights("off"); } }; LightMotionRockerAutocontrol.prototype.rockerRelaxTimerStart = function() { var self = this; if (this.rockerRelaxTimer) { clearTimeout(this.rockerRelaxTimer); this.rockerRelaxTimer = null; } this.rockerRelaxTimer = setTimeout(function() { self.rockerRelax = false; self.rockerRelaxTimer = null; }, this.config.rockerRelaxTime * 1000); this.rockerRelax = true; }; LightMotionRockerAutocontrol.prototype.rescueTimerStart = function() { var self = this; if (this.rescueOffTimer) { clearTimeout(this.rescueOffTimer); this.rescueOffTimer = null; } this.rescueOffTimer = setTimeout(function() { self.turnLightsOff(); self.rescueOffTimer = null; }, this.config.rescueOffTimeout * 1000); }; LightMotionRockerAutocontrol.prototype.motionTimerStart = function() { var self = this; if (this.motionOffTimer) { clearTimeout(this.motionOffTimer); this.motionOffTimer = null; } this.motionOffTimer = setTimeout(function() { self.turnLightsOff(); self.rescueTimerStop(); self.motionOffTimer = null; }, this.config.motionOffTimeout * 1000); }; LightMotionRockerAutocontrol.prototype.rescueTimerStop = function() { if (this.rescueOffTimer) { clearTimeout(this.rescueOffTimer); this.rescueOffTimer = null; } }; LightMotionRockerAutocontrol.prototype.motionTimerStop = function() { if (this.motionOffTimer) { clearTimeout(this.motionOffTimer); this.motionOffTimer = null; } }; ================================================ FILE: modules/LightMotionRockerAutocontrol/lang/en.json ================================================ { "m_title": "Light control by motion sensors and rockers", "m_descr": "Controls ligth switches via one or many motion sensors and any number of rockers. If rocker operation happened, motion does not turn on for a short time (rocker relax time). Once all motion sensors reported no motion, wait for a defined time (motion off period) and turn off light. Motion events are ignored if luminance is lower than a defined threshold. After some long time after last motion event or rocket on event turn off light (in case of low battery in one of the motion sensors).", "l_lights": "Light switch to turn on/off", "l_rockers": "Rockers and paddles for manual opration", "l_rockerRelaxTime": "Rocker relax time, seconds", "l_motions": "Motion sensors to track", "l_motionOffTimeout": "Time to wait before turning light off after all motion sensors are off, seconds", "l_luminance": "Light sensor", "l_luminanceThreshold": "Light threshold (in same units as light sensor reports)", "l_rescueOffTimeout": "Timeout to turn light off in case of low battery in one of motion sensors, seconds" } ================================================ FILE: modules/LightMotionRockerAutocontrol/module.json ================================================ { "singleton": false, "dependencies": [], "category": "complex_applications", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "LightMotionRockerAutocontrol", "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__", "lights": [], "rockers": [], "rockerRelaxTime": 60, "motions": [], "motionOffTimeout": 300, "luminance": null, "luminanceThreshold": 0, "rescueOffTimeout": 3600 }, "schema": { "type": "object", "properties": { "lights": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchBinary:deviceId,namespaces:devices_switchMultilevel:deviceId", "required": true } }, "rockers": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchBinary:deviceId,namespaces:devices_toggleButton:deviceId,namespaces:devices_switchControl:deviceId", "required": true } }, "rockerRelaxTime": { "type": "number", "required": true }, "motions": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorBinary:deviceId,namespaces:devices_switchControl:deviceId", "required": true } }, "motionOffTimeout": { "type": "number", "required": true }, "luminance": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorMultilevel:deviceId", "required": false }, "luminanceThreshold": { "type": "number", "required": true }, "rescueOffTimeout": { "type": "number", "required": true } }, "required": false }, "options": { "fields": { "lights": { "label": "__l_lights__", "fields": { "item": { "label": "", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchBinary:deviceName,namespaces:devices_switchMultilevel:deviceName" } } }, "rockers": { "label": "__l_rockers__", "fields": { "item": { "label": "", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchBinary:deviceName,namespaces:devices_toggleButton:deviceName,namespaces:devices_switchControl:deviceName" } } }, "rockerRelaxTime": { "label": "__l_rockerRelaxTime__" }, "motions": { "label": "__l_motions__", "fields": { "item": { "label": "", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorBinary:deviceName,namespaces:devices_switchControl:deviceName" } } }, "motionOffTimeout": { "label": "__l_motionOffTimeout__" }, "luminance": { "label": "__l_luminance__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorMultilevel:deviceName" }, "luminanceThreshold": { "label": "__l_luminanceThreshold__" }, "rescueOffTimeout": { "label": "__l_rescueOffTimeout__" } } } } ================================================ FILE: modules/LightScene/index.js ================================================ /*** LightScene Z-Way HA module ******************************************* Version: 1.1.1 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Poltorak Serguei Changed: Michael Hensche Description: Implements light scene based on virtual devices of type dimmer, switch or anothe scene ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function LightScene (id, controller) { // Call superconstructor first (AutomationModule) LightScene.super_.call(this, id, controller); } inherits(LightScene, AutomationModule); _module = LightScene; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- LightScene.prototype.init = function (config) { LightScene.super_.prototype.init.call(this, config); var self = this; this.vDev = this.controller.devices.create({ deviceId: "LightScene_" + this.id, defaults: { deviceType: "toggleButton", metrics: { level: "on", // it is always on, but usefull to allow bind icon: "scene", title: self.getInstanceTitle() } }, overlay: {}, handler: function (command) { if (command !== 'on') return; self.config.switches.forEach(function(devState) { var vDev = self.controller.devices.get(devState.device); if (vDev) { if (!devState.sendAction || (devState.sendAction && vDev.get("metrics:level") != devState.status)) { vDev.performCommand(devState.status); } } }); self.config.thermostats.forEach(function(devState) { var vDev = self.controller.devices.get(devState.device); if (vDev) { if (!devState.sendAction || (devState.sendAction && vDev.get("metrics:level") != devState.status)) { vDev.performCommand("exact", { level: devState.status }); } } }); self.config.dimmers.forEach(function(devState) { var vDev = self.controller.devices.get(devState.device); if (vDev) { if (!devState.sendAction || (devState.sendAction && vDev.get("metrics:level") != devState.status)) { vDev.performCommand("exact", { level: devState.status }); } } }); self.config.locks.forEach(function(devState) { var vDev = self.controller.devices.get(devState.device); if (vDev) { if (!devState.sendAction || (devState.sendAction && vDev.get("metrics:level") != devState.status)) { vDev.performCommand(devState.status); } } }); self.config.scenes.forEach(function(scene) { var vDev = self.controller.devices.get(scene); if (vDev) { vDev.performCommand("on"); } }); self.vDev.set("metrics:level", "on"); // update on ourself to allow catch this event }, moduleId: this.id }); }; LightScene.prototype.stop = function () { if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } LightScene.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/LightScene/lang/de.json ================================================ { "m_title":"Szene", "m_descr":"Die Lichtszene ist eine Basisfunktion des Smart Home. Mit nur einem Klick werden mehrere Geräte in einen individuellen Status geschalten. In Verwendung weiterer Apps wie “Delayed Scene” oder “Scheduled Scene” kann diese Szene automatisch in Abhängigkeit von generierten Ereignissen im Smart Home aktiviert werden. Die App erstellt ein neues Element zur Szenenauslösung. Der Einstelldialog ermöglicht die Auswahl von mehreren Schaltern und Dimmern sowie die Definition des gewünschten Schaltzustands eines jeden ausgewählten Gerätes.Desweiteren ist es möglich, andere Szenen als Teil dieser Szene auszulösen.Bitte beachten Sie, dass Dimmer einen Wert zwischen ‘0‘ und ‘100‘ bedürfen, während dessen bei Schaltungen zwischen ‘an‘ und ‘aus‘ gewählt wird.

          Einstellungen:
          • Wählen Sie alle Schalter, die zur Szene gehören sollen und definierten Sie den Schaltstatus als ‘an’ oder ‘aus’.
          • Wählen Sie alle Dimmer, die zur Szene gehören sollen und definieren Sie den Dimmpegel als einen Wert zwischen 0 und 100.
          • Auf Wunsch wählen Sie weitere Szenen, die mit dieser Szene ausgelöst werden sollen.

          Anwendung: Der Button auf dem Szenenelement aktiviert die definierte Szene. ", "l_switches":"Schalter:", "on":"An", "off":"Aus", "l_dimmers":"Dimmer/Motorsteuerungen:", "l_level":"Level", "l_scenes":"Aktionen/Szenen die aktiviert werden sollen:", "l_thermostats":"Thermostate", "l_temperature":"Temperatur", "l_locks":"Türschlösser", "close":"Verriegelt", "open":"Entriegelt", "l_sendAction":"Don't send On command, if device is already turned On, similarly for Off", "h_sendAction":"Need to avoid flooding the network." } ================================================ FILE: modules/LightScene/lang/en.json ================================================ { "m_title":"Scene", "m_descr":"The scene is a basic function of a smart home. Multiple devices are switched into individual status by just one click. Using other apps like “Delayed Scene” or “Scheduled Scene” this scene can also be activated automatically depending on events generated in the Smart Home. The app will create a new element to triggering the scene. The setup dialog allows selecting a series of switches and dimmers and defining the desired switching state of each individual device selected. Additionally it is possible to trigger other scenes as part of the scene. Please note that dimmers require a value between 0 and 100, while the switches can be selected between on and off.

          Setup:
          • Select all switches that shall belong to the scene and define the switching state as ‘on’ or ‘off’
          • Select all dimmers that shall belong to the scene and define the dimming level as a value between 0 und 100
          • Optionally select other scenes that shall be trigger with this scene.

          Usage: The toggle button on the scene element will activate the defined scene", "l_switches":"List of switches:", "on":"On", "off":"Off", "l_dimmers":"List of dimmers:", "l_level":"Level", "l_scenes":"List of scenes to activate:", "l_thermostats":"List of thermostats", "l_temperature":"Temperature", "l_locks":"List of locks:", "close":"Close", "open":"Open", "l_sendAction":"Don't send On command, if device is already turned On, similarly for Off", "h_sendAction":"Need to avoid flooding the network." } ================================================ FILE: modules/LightScene/lang/ru.json ================================================ { "m_title":"Сцена", "m_descr":"В сцене можно задать уровень освещения, включить или выключить лампы, открыть или закрыть жалюзи, задать температуру для термостатов.", "l_switches":"Список выключателей", "on":"Включить", "off":"Выключить", "l_dimmers":"Список диммеров", "l_level":"Уровень", "l_scenes":"Список сцен", "l_thermostats":"Список термостатов", "l_temperature":"Температура", "l_locks":"Список замков", "close":"Закрыть", "open":"Открыть", "l_sendAction":"Не отправлять команду ВКЛ, если устройство уже включено, аналогично для ВЫКЛ", "h_sendAction":"Нужно чтобы не забивать сеть." } ================================================ FILE: modules/LightScene/module.json ================================================ { "dependencies": [], "singleton": false, "category": "automation_basic", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"LightScene", "version": "1.1.1", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "switches": [], "dimmers": [], "thermostats": [], "locks":[], "scenes": [] }, "schema": { "type": "object", "properties": { "switches": { "type": "array", "items": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchBinary:deviceId", "required": true }, "status": { "type": "integer", "required": true, "enum": ["off", "on"] }, "sendAction": { "type": "boolean", "required": true, "default" : false } } } }, "dimmers": { "type": "array", "items": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchMultilevel:deviceId", "required": true }, "status": { "type": "integer", "minimum": 0, "maximum": 99, "required": true }, "sendAction": { "type": "boolean", "required": true, "default" : false } } } }, "thermostats": { "type": "array", "items": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_thermostat:deviceId", "required": true }, "status": { "type": "integer", "minimum": 0, "maximum": 99, "required": true }, "sendAction": { "type": "boolean", "required": true, "default" : false } } } }, "locks": { "type": "array", "items": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId", "required": true }, "status": { "type": "string", "required": true, "enum": ["close", "open"], "default" : "open" }, "sendAction": { "type": "boolean", "required": true, "default" : false } } } }, "scenes": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true } } }, "required": false }, "options": { "fields": { "switches": { "label": "__l_switches__", "fields": { "item": { "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchBinary:deviceName" }, "status": { "label": "", "optionLabels": ["__off__", "__on__"] }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } } } }, "dimmers": { "label": "__l_dimmers__", "fields": { "item": { "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchMultilevel:deviceName" }, "status": { "label": "__l_level__" }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } } } }, "thermostats": { "label": "__l_thermostats__", "fields": { "item": { "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_thermostat:deviceName" }, "status": { "label": "__l_temperature__" }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } } } }, "locks": { "label": "__l_locks__", "fields": { "item": { "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName" }, "status": { "label": "", "optionLabels": ["__close__", "__open__"] }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } } } }, "scenes": { "label": "__l_scenes__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" } } } } } } ================================================ FILE: modules/LightScene/patchnotes.txt ================================================ v1.1.1 - change icon type form gesture to new icon type scene ================================================ FILE: modules/LogicalRules/index.js ================================================ /*** LogicalRules Z-Way HA module ******************************************* Version: 1.5 (c) Z-Wave.Me, 2021 ----------------------------------------------------------------------------- Author: Poltorak Serguei , Niels Roche , Yurkin Vitaliy Description: Implements logical rules and activates scene on rule match. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function LogicalRules (id, controller) { // Call superconstructor first (AutomationModule) LogicalRules.super_.call(this, id, controller); var self = this; this.attachedList = []; this._testRule = function () { // wrapper to correct this and parameters in testRule self.testRule.call(self, null); }; } inherits(LogicalRules, AutomationModule); _module = LogicalRules; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- LogicalRules.prototype.init = function (config) { LogicalRules.super_.prototype.init.call(this, config); var self = this; this.config.tests.forEach(function(test) { if (test.testType === "binary") { self.attachDetach(test.testBinary, true); } else if (test.testType === "multilevel") { self.attachDetach(test.testMultilevel, true); } else if (test.testType === "remote") { self.attachDetach(test.testRemote, true); } else if (test.testType === "sensorDiscrete") { self.attachDetach(test.testSensorDiscrete, true); } else if (test.testType === "nested") { test.testNested.tests.forEach(function(xtest) { if (xtest.testType === "binary") { self.attachDetach(xtest.testBinary, true); } else if (xtest.testType === "multilevel") { self.attachDetach(xtest.testMultilevel, true); } else if (xtest.testType === "remote") { self.attachDetach(xtest.testRemote, true); } else if (xtest.testType === "sensorDiscrete") { self.attachDetach(xtest.testSensorDiscrete, true); } }); } }); if (this.config.eventSource) { this.config.eventSource.forEach(function(scene) { self.controller.devices.on(scene, "change:metrics:level", self._testRule); }); } }; LogicalRules.prototype.stop = function () { var self = this; if (this.config.eventSource) { this.config.eventSource.forEach(function(scene) { self.controller.devices.off(scene, "change:metrics:level", self._testRule); }); } this.config.tests.forEach(function(test) { if (test.testType === "binary") { self.attachDetach(test.testBinary, false); } else if (test.testType === "multilevel") { self.attachDetach(test.testMultilevel, false); } else if (test.testType === "remote") { self.attachDetach(test.testRemote, false); } else if (test.testType === "sensorDiscrete") { self.attachDetach(test.testSensorDiscrete, false); } else if (test.testType === "nested") { test.testNested.tests.forEach(function(xtest) { if (xtest.testType === "binary") { self.attachDetach(xtest.testBinary, false); } else if (xtest.testType === "multilevel") { self.attachDetach(xtest.testMultilevel, false); } else if (xtest.testType === "remote") { self.attachDetach(xtest.testRemote, false); } else if (xtest.testType === "sensorDiscrete") { self.attachDetach(xtest.testSensorDiscrete, false); } }); } }); this.attachedList = []; LogicalRules.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- LogicalRules.prototype.attachDetach = function (test, attachOrDetach) { if (this.config.triggerOnDevicesChange === false) { // this condition is used to allow empty triggerOnDevicesChange if old LogicalRules is used return; } if (attachOrDetach) { if (this.attachedList.indexOf(test.device) === -1) { this.attachedList.push(test.device); this.controller.devices.on(test.device, "change:metrics:level", this._testRule); this.controller.devices.on(test.device, "change:metrics:change", this._testRule); } } else { this.controller.devices.off(test.device, "change:metrics:level", this._testRule); this.controller.devices.off(test.device, "change:metrics:change", this._testRule); } }; LogicalRules.prototype.testRule = function (tree) { var res = null, topLevel = !tree, self = this, langFile = self.loadModuleLang(); if (!tree) { tree = this.config; } if (tree.logicalOperator === "and") { res = true; tree.tests.forEach(function(test) { if (test.testType === "multilevel") { res = res && self.op(self.controller.devices.get(test.testMultilevel.device).get("metrics:level"), test.testMultilevel.testOperator, test.testMultilevel.testValue); } else if (test.testType === "binary") { res = res && (self.controller.devices.get(test.testBinary.device).get("metrics:level") === test.testBinary.testValue); } else if (test.testType === "remote") { var dev = self.controller.devices.get(test.testRemote.device); res = res && ((_.contains(["on", "off"], test.testRemote.testValue) && dev.get("metrics:level") === test.testRemote.testValue) || (_.contains(["upstart", "upstop", "downstart", "downstop"], test.testRemote.testValue) && dev.get("metrics:change") === test.testRemote.testValue)); } else if (test.testType === "sensorDiscrete") { res = res && (self.controller.devices.get(test.testSensorDiscrete.device).get("metrics:level") === test.testSensorDiscrete.testValue); } else if (test.testType === "time") { var curTime = new Date(), time_arr = test.testTime.testValue.split(":").map(function(x) { return parseInt(x, 10); }); res = res && self.op(curTime.getHours() * 60 + curTime.getMinutes(), test.testTime.testOperator, time_arr[0] * 60 + time_arr[1]); } else if (test.testType === "nested") { res = res && self.testRule(test.testNested); } }); } else if (tree.logicalOperator === "or") { res = false; tree.tests.forEach(function(test) { if (test.testType === "multilevel") { res = res || self.op(self.controller.devices.get(test.testMultilevel.device).get("metrics:level"), test.testMultilevel.testOperator, test.testMultilevel.testValue); } else if (test.testType === "binary") { res = res || (self.controller.devices.get(test.testBinary.device).get("metrics:level") === test.testBinary.testValue); } else if (test.testType === "remote") { var dev = self.controller.devices.get(test.testRemote.device); res = res || ((_.contains(["on", "off"], test.testRemote.testValue) && dev.get("metrics:level") === test.testRemote.testValue) || (_.contains(["upstart", "upstop", "downstart", "downstop"], test.testRemote.testValue) && dev.get("metrics:change") === test.testRemote.testValue)); } else if (test.testType === "sensorDiscrete") { res = res || (self.controller.devices.get(test.testSensorDiscrete.device).get("metrics:level") === test.testSensorDiscrete.testValue); } else if (test.testType === "time") { var curTime = new Date(), time_arr = test.testTime.testValue.split(":").map(function(x) { return parseInt(x, 10); }); res = res || self.op(curTime.getHours() * 60 + curTime.getMinutes(), test.testTime.testOperator, time_arr[0] * 60 + time_arr[1]); } else if (test.testType === "nested") { res = res || self.testRule(test.testNested); } }); } if (topLevel && res) { tree.action.switches && tree.action.switches.forEach(function(devState) { var vDev = self.controller.devices.get(devState.device); if (vDev) { if (!devState.sendAction || (devState.sendAction && vDev.get("metrics:level") !== devState.status)) { vDev.performCommand(devState.status); } } }); tree.action.dimmers && tree.action.dimmers.forEach(function(devState) { var vDev = self.controller.devices.get(devState.device); if (vDev) { if (!devState.sendAction || (devState.sendAction && vDev.get("metrics:level") !== devState.status)) { vDev.performCommand("exact", { level: devState.status }); } } }); tree.action.thermostats && tree.action.thermostats.forEach(function(devState) { var vDev = self.controller.devices.get(devState.device); if (vDev) { if (!devState.sendAction || (devState.sendAction && vDev.get("metrics:level") !== devState.status)) { vDev.performCommand("exact", { level: devState.status }); } } }); tree.action.locks && tree.action.locks.forEach(function(devState) { var vDev = self.controller.devices.get(devState.device); if (vDev) { if (!devState.sendAction || (devState.sendAction && vDev.get("metrics:level") !== devState.status)) { vDev.performCommand(devState.status); } } }); tree.action.scenes && tree.action.scenes.forEach(function(scene) { var vDev = self.controller.devices.get(scene); if (vDev) { vDev.performCommand("on"); } }); } return res; }; LogicalRules.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!! }; ================================================ FILE: modules/LogicalRules/lang/de.json ================================================ { "m_title":"Logische Regel", "m_descr":"Diese App ist die bei Weitestem komplexeste aber auch leistungsfähigste App, die in Ihrem Gateway vorinstalliert ist.Sie erlaubt die logische Kombination mehrerer Zustände und Ereignisse zur Verwendung der Booleschen Befehle UND, ODER und NICHT. Zur Verwendung dieses Widgets benötigen Sie das Grundwissen über Boolesche Logik und/ oder Softwareentwicklung.Bitte beachten Sie, dass die Booleschen Logik letztendlich auf die Auslösung einer Aktion hinaus läuft. Das heißt, Sie benötigen zumindest ein (dynamisches) Ursprungsereignis, welches wesentlich mehr (statische) Statusinformation auslöst, von denen Sie die Aktion abhängig machen können. Es ist nicht angebracht, zwei Ereignisse zu kombinieren, da die finale Aktion nur eintritt, wenn beide Ereignisse zur gleichen Zeit auftreten. Das ist eher unwahrscheinlich. Der Unterschied zwischen einem dynamischen Ereignis und einem statischen Zustand kann mit der Verwendung eines einfachen Bewegungssensors erklärt werden. Das dynamische Ereignis ist der Moment, wenn der Bewegungssensor auslöst. Der statische Zustand ist die Information, dass der Bewegungssensor sich aktuell im auslösenden Status befindet (eine Bewegung erkannt hat und einige Sekunden wartet, bevor er wieder in den inaktiven Modus zurückkehrt).

          Einstellungen: Wählen Sie Statusinformation und Ereignis, welche Sie sinnvoll mit einander kombinieren wollen und wählen Sie den Booleschen Befehl, diese zu verbinden.", "rl_options":"Führe eine Regelprüfung bei jeder Änderung eines Zustandes, der unter 'Bedingungen' gelisteten Geräte durch.", "l_eventSource":"Führe eine Regelprüfung bei der Aktivierung folgender Szenen durch:", "h_eventSource":"When you start any scene from the list below, this logical rule will be checked. If necessary, you can run a logical rule by time: 1) Create a scene 2) Run the scene on a schedule 3) Select the created scene from the list below.", "l_logicalOperator":"Boolescher Operator", "h_logicalOperator":"Diese Option verknüpft alle folgenden Bedingungen mit einer logischen OR (ODER) oder AND (UND) Regel. Bei OR wird die Aktion ausgeführt, wenn mindestens eine Bedingung zutrifft. Bei AND wird die Aktion nur ausgeführt, wenn alle Bedingung 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_action":"Aktionen:", "l_switches":"Binäre Schalter:", "l_thermostats":"Thermostats", "off":"Aus", "on":"An", "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", "close":"Verriegelt", "open":"Entriegelt", "l_scenes":"Zu aktivierende Szenen/Aktionen:", "or": "OR", "and": "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_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_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", "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.", "l_sensorDiscrete":"Scene Controller", "l_choose_dev":"--- Choose a device ---", "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)" } ================================================ FILE: modules/LogicalRules/lang/en.json ================================================ { "m_title":"Logical Rule", "m_descr":"This app is the by far most complex but also most powerful app preloaded to your gateway. It allows to combine multiple status and events in a logical way using the Boolean operators AND, OR and NOT. You need to have basic knowledge about Boolean logic and/or software development to properly use this widget. Please note that the final target of the Boolean logic is to trigger an action. This mean you need at least one (dynamic) event source that essentially triggers but many more (static) status information that you can make the action depending on. It is not suitable to combine two events because the final action will only happen if both events would occur at the very same moment. This is quite unlikely. The difference between a dynamic event and a static status can be explained using a simple motion sensor. The dynamic event is the moment when the motion sensor trips. The static status is the information if the motion sensor is actually in tripped state (has just detected a motion and wait for a few seconds before turning back into inactivity mode).

          Settings: Select the status information and the events you want to logically combine and select the Boolean operators to combine them.", "rl_options":"Trigger the rule check on change of any device mentioned in conditions.", "l_eventSource":"Trigger the rule check on activation of the following scenes:", "h_eventSource":"When you start any scene from the list below, this logical rule will be checked. If necessary, you can run a logical rule by time: 1) Create a scene 2) Run the scene on a schedule 3) Select the created scene from the list below.", "l_logicalOperator":"Boolean operator", "h_logicalOperator":"This option links all following conditions with a logical 'OR' or 'AND' rule. 'OR' will trigger an action if at least one condition is true. 'AND' will only trigger an action if all conditions are true.", "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_action":"Actions", "l_switches":"List of switches:", "l_thermostats":"List of thermostats", "off":"Off", "on":"On", "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:", "close":"Close", "open":"Open", "l_scenes":"List of scenes to activate:", "or": "OR", "and": "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_sendAction":"Don't send On command, if device is already turned On, similarly for Off. Need to avoid flooding the network.", "h_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", "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.", "l_sensorDiscrete":"Scene Controller", "l_choose_dev":"--- Choose a device ---", "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)" } ================================================ FILE: modules/LogicalRules/lang/ru.json ================================================ { "m_title":"Логические правила", "m_descr":"Используя логические правила можно составлять сценарии следующих типов:
          • Если сработал датчик, то выполнить действие
          • Если сработал датчик 1 И датчик 2, то выполнить действие
          • Если сработал датчик 1 ИЛИ датчик 2, то выполнить действие
          С помощью вложенных условий возможно создавать правила с более сложной логикой.", "rl_options":"Выполнить проверку при срабатывании любого устройства из условий", "l_eventSource":"Выполнить проверку при запуске любой из сцен:", "h_eventSource":"При запуске любой сцены из списка ниже будет запускаться проверка этого логического правила. Если необходимо, то можно выполнять логическое правило по времени: 1) Создайте сцену 2) Запускайте сцену по расписанию 3) Выберите созданную сцену из списка ниже.", "l_logicalOperator":"Логический оператор", "h_logicalOperator":"Все условия связываются одним из логических операторов И/ИЛИ.
          ИЛИ вызовет действие, если хотя бы одно условие истинно.
          И вызовет действие только в том случае, если все условия истинны.", "l_tests":"Условия", "l_testBinary":"Условие бинарных устройств", "l_testMultilevel":"Условие многоуровневых датчиков", "l_testRemote":"Условие кнопок пульта", "l_testTime":"Условие по времени", "timeFormat":"чч:мм", "l_testNested":"Вложенные условия", "l_action":"Действия", "l_switches":"Список выключателей", "l_thermostats":"Список термостатов", "off":"Выключить", "on":"Включить", "upstart":"Вверх старт", "upstop":"Вверх стоп", "downstart":"Вниз старт", "downstop":"Вниз стоп", "l_dimmers":"Список диммеров", "l_status":"Уровень", "l_locks":"Список замков", "close":"Закрыть", "open":"Открыть", "l_scenes":"Список сцен", "or": "ИЛИ", "and": "И", "l_testType": "Тип условия:", "h_testNested": "Добавьте новое вложенное условие, по аналогии с логической нотацией: (A 'И' B) 'И' (C 'ИЛИ' D). Дополнительным вложенным условием является (C 'ИЛИ' D). Действие запускается только в том случае, если условия (A 'И' B) и условия (C 'ИЛИ' D) выполняются одновременно. Пример: если (датчик освещенности A > 80%) 'И' (датчик температуры B > 26°C) 'И' (время < 12:00 'ИЛИ' время > 14:00), то жалюзи в гостиной выставляются на 50%.", "l_sendAction":"Не отправлять команду ВКЛ, если устройство уже включено, аналогично для ВЫКЛ", "h_sendAction":"Уменьшает нагрузку на Z-Wave сеть для лучшего быстродействия.", "h_testTime":"Это условие проверяет, является ли текущее время 'меньше или равно' или 'больше или равно' введенному времени. Например, если текущее время: 09:00, а введенное время >= 10:00, то условие не выполнится. Формат 24ч - чч:мм.", "h_triggerOnDevicesChange": "Если отключить эту функцию, то проверка правила будет выполняться только при срабатывании сцен из списка ниже. Срабатывание устройств из условий не будет запускать проверку.", "l_sensorDiscrete":"Условие контроллера сцен", "l_choose_dev":"--- Выберите устройство ---", "l_controller_action":"Событие от контроллера сцен", "h_controller_action":"Событие описывается двузначным значением: первая цифра - номер кнопки, вторая цифра - действие кнопки (0=одинарное нажатие, 1=отпускание, 2=удержание, 3=двойное нажатие, 4=тройное нажатие и т. д. Примеры: Кнопка 2 + Двойное нажатие => 23, Кнопка 1 + Одинарное нажатие => 10)" } ================================================ FILE: modules/LogicalRules/module.json ================================================ { "dependencies": [], "singleton": false, "category": "automation_basic", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "LogicalRules", "version": "1.5", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "triggerOnDevicesChange": true, "eventSource": [], "logicalOperator": "and", "tests": [], "action": {} }, "schema": { "type": "object", "properties": { "logicalOperator": { "type": "string", "enum": ["and", "or"], "default": "and", "required": true }, "tests": { "type": "array", "items": { "type": "object", "properties": { "testType": { "type": "string", "enum": [ "binary", "multilevel", "remote", "sensorDiscrete", "time", "nested" ], "required": true, "default": "binary" }, "testBinary": { "type": "object", "dependencies": "testType", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorBinary:deviceId,namespaces:devices_switchBinary:deviceId", "required": true }, "testValue": { "type": "string", "enum": ["off", "on"], "required": true, "default": "on" } } }, "testMultilevel": { "type": "object", "dependencies": "testType", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorMultilevel:deviceId,namespaces:devices_switchMultilevel:deviceId,namespaces:devices_battery:deviceId", "required": true }, "testOperator": { "type": "string", "enum": ["=", "!=", ">", ">=", "<", "<="], "required": true }, "testValue": { "type": "number", "required": true } } }, "testRemote": { "type": "object", "dependencies": "testType", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchControl:deviceId,namespaces:devices_toggleButton:deviceId", "required": true }, "testValue": { "type": "string", "enum": ["off", "on", "upstart", "upstop", "downstart", "downstop"], "required": true, "default": "on" } } }, "testSensorDiscrete": { "type": "object", "dependencies": "testType", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorDiscrete:deviceId", "required": true }, "testValue": { "type": "string", "required": true } } }, "testTime": { "type": "object", "dependencies": "testType", "properties": { "testOperator": { "type": "string", "enum": [">=", "<="], "required": true }, "testValue": { "format": "time", "type": "string", "required": true } } }, "testNested": { "type": "object", "dependencies": "testType", "properties": { "logicalOperator": { "type": "string", "enum": ["and", "or"], "default": "or", "required": true }, "tests": { "type": "array", "items": { "type": "object", "properties": { "testType": { "type": "string", "enum": [ "binary", "multilevel", "remote", "sensorDiscrete", "time" ], "required": true, "default": "binary" }, "testBinary": { "type": "object", "dependencies": "testType", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorBinary:deviceId,namespaces:devices_switchBinary:deviceId", "required": true }, "testValue": { "type": "string", "enum": ["off", "on"], "required": true } } }, "testMultilevel": { "type": "object", "dependencies": "testType", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorMultilevel:deviceId,namespaces:devices_switchMultilevel:deviceId,namespaces:devices_battery:deviceId", "required": true }, "testOperator": { "type": "string", "enum": ["=", "!=", ">", ">=", "<", "<="], "required": true }, "testValue": { "type": "number", "required": true } } }, "testRemote": { "type": "object", "dependencies": "testType", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchControl:deviceId,namespaces:devices_toggleButton:deviceId", "required": true }, "testValue": { "type": "string", "enum": ["off", "on", "upstart", "upstop", "downstart", "downstop"], "required": true } } }, "testSensorDiscrete": { "type": "object", "dependencies": "testType", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorDiscrete:deviceId", "required": true }, "testValue": { "type": "string", "required": true } } }, "testTime": { "type": "object", "dependencies": "testType", "properties": { "testOperator": { "type": "string", "enum": [">=", "<="], "required": true }, "testValue": { "format": "time", "type": "string", "required": true } } } } } } } } } } }, "action": { "type": "object", "properties": { "switches": { "type": "array", "items": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchBinary:deviceId", "required": true }, "status": { "type": "string", "required": true, "enum": ["off", "on"], "default": "on" }, "sendAction": { "type": "boolean", "required": true, "default": false } } } }, "dimmers": { "type": "array", "items": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchMultilevel:deviceId", "required": true }, "status": { "required": true, "minimum": 0, "maximum": 99 }, "sendAction": { "type": "boolean", "required": true, "default": false } } } }, "thermostats": { "type": "array", "items": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_thermostat:deviceId", "required": true }, "status": { "required": true, "minimum": 0, "maximum": 99 }, "sendAction": { "type": "boolean", "required": true, "default": false } } } }, "locks": { "type": "array", "items": { "type": "object", "properties": { "device": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId", "required": true }, "status": { "type": "string", "required": true, "enum": ["close", "open"], "default": "open" }, "sendAction": { "type": "boolean", "required": true, "default": false } } } }, "scenes": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true } } }, "required": false }, "triggerOnDevicesChange": { "type": "boolean", "default": true }, "eventSource": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true } } } }, "options": { "renderform": "true", "fields": { "logicalOperator": { "label": "__l_logicalOperator__", "optionLabels": ["__and__", "__or__"], "helper": "__h_logicalOperator__", "type": "select" }, "tests": { "label": "__l_tests__", "fields": { "item": { "fields": { "testType": { "type": "select", "label": "__l_testType__", "optionLabels": [ "__l_testBinary__", "__l_testMultilevel__", "__l_testRemote__", "__l_sensorDiscrete__", "__l_testTime__", "__l_testNested__" ], "default": "" }, "testBinary": { "dependencies": { "testType": "binary" }, "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorBinary:deviceName,namespaces:devices_switchBinary:deviceName" }, "testValue": { "optionLabels": ["__off__", "__on__"] } } }, "testMultilevel": { "dependencies": { "testType": "multilevel" }, "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorMultilevel:deviceName,namespaces:devices_switchMultilevel:deviceName,namespaces:devices_battery:deviceName" }, "testOperator": { "type": "select", "optionLabels": ["=", "≠", ">", "≥", "<", "≤"] } } }, "testRemote": { "dependencies": { "testType": "remote" }, "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchControl:deviceName,namespaces:devices_toggleButton:deviceName" }, "testValue": { "optionLabels": ["__off__", "__on__", "__upstart__", "__upstop__", "__downstart__", "__downstop__"] } } }, "testSensorDiscrete": { "dependencies": { "testType": "sensorDiscrete" }, "fields": { "device": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorDiscrete:deviceName" }, "testValue": { "label": "__l_controller_action__", "helper": "__h_controller_action__" } } }, "testTime": { "dependencies": { "testType": "time" }, "helper": "__h_testTime__", "fields": { "testOperator": { "type": "select", "optionLabels": ["≥", "≤"] }, "testValue": { "type": "time", "maskString": "99:99", "dateFormat": "HH:mm" } } }, "testNested": { "dependencies": { "testType": "nested" }, "helper": "__h_testNested__", "fields": { "logicalOperator": { "label": "__l_logicalOperator__", "optionLabels": ["__and__", "__or__"], "helper": "__h_logicalOperator__", "type": "select" }, "tests": { "fields": { "item": { "fields": { "testType": { "label": "__l_testType__", "optionLabels": [ "__l_testBinary__", "__l_testMultilevel__", "__l_testRemote__", "__l_sensorDiscrete__", "__l_testTime__" ], "type": "select", "default": "" }, "testBinary": { "dependencies": { "testType": "binary" }, "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorBinary:deviceName,namespaces:devices_switchBinary:deviceName" }, "testValue": { "optionLabels": ["__off__", "__on__"] } } }, "testMultilevel": { "dependencies": { "testType": "multilevel" }, "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorMultilevel:deviceName,namespaces:devices_switchMultilevel:deviceName,namespaces:devices_battery:deviceName" }, "testOperator": { "type": "select", "optionLabels": ["=", "≠", ">", "≥", "<", "≤"] } } }, "testRemote": { "dependencies": { "testType": "remote" }, "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchControl:deviceName,namespaces:devices_toggleButton:deviceName" }, "testValue": { "optionLabels": ["__off__", "__on__", "__upstart__", "__upstop__", "__downstart__", "__downstop__"] } } }, "testSensorDiscrete": { "dependencies": { "testType": "sensorDiscrete" }, "fields": { "device": { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorDiscrete:deviceName" }, "testValue": { "label": "__l_controller_action__", "helper": "__h_controller_action__" } } }, "testTime": { "dependencies": { "testType": "time" }, "helper": "__h_testTime__", "fields": { "testValue": { "type": "time", "maskString": "99:99", "dateFormat": "HH:mm" }, "testOperator": { "type": "select", "optionLabels": ["≥", "≤"] } } } } } } } } } } } } }, "action": { "label": "__l_action__", "fields": { "switches": { "label": "__l_switches__", "fields": { "item": { "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchBinary:deviceName" }, "status": { "label": "", "optionLabels": ["__off__", "__on__"] }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } } } }, "dimmers": { "label": "__l_dimmers__", "fields": { "item": { "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchMultilevel:deviceName" }, "status": { "label": "__l_status__", "type": "integer" }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } } } }, "thermostats": { "label": "__l_thermostats__", "fields": { "item": { "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_thermostat:deviceName" }, "status": { "label": "__l_status__", "type": "integer" }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } } } }, "locks": { "label": "__l_locks__", "fields": { "item": { "fields": { "device": { "label": "", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName" }, "status": { "label": "", "optionLabels": ["__close__", "__open__"] }, "sendAction": { "type": "checkbox", "rightLabel": "__l_sendAction__", "helper": "__h_sendAction__" } } } } }, "scenes": { "label": "__l_scenes__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" } } } } }, "triggerOnDevicesChange": { "type": "checkbox", "rightLabel": "__rl_options__", "helper": "__h_triggerOnDevicesChange__" }, "eventSource": { "label": "__l_eventSource__", "helper": "__h_eventSource__", "datasource": "namespaces", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" } } } } } } ================================================ FILE: modules/LogicalRules/patchnotes.txt ================================================ v1.2.2 - german translations added - helper label for time condition added v1.2.1 - Added option to LogicalRules - don't send On command if device already turned on,similarly for Off v1.2.0 - ui form structure refactored to reduce overhead - translations added (ru translations in english) - descriptions and examples added for a better usability v1.1.1 - format of time input field changed (HH:mm 24h) - version number corrected now 1.1.1 ================================================ FILE: modules/MQTTClient/index.js ================================================ /*** MQTT Client Z-Way HA module **************************************************** Version: 1.3 (c) Z-Wave.Me, 2023 ----------------------------------------------------------------------------- Author: Yurkin Vitaliy Description: Publishes the devices status to a MQTT topics and receives device control commands. *****************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function MQTTClient(id, controller) { MQTTClient.super_.call(this, id, controller); // Create instance variables this.reconnectInterval = null; }; inherits(MQTTClient, AutomationModule); _module = MQTTClient; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- MQTTClient.prototype.init = function(config) { // Call superclass' init (this will process config argument and so on) MQTTClient.super_.prototype.init.call(this, config); var self = this; var mqttConnected = false; var mqttConnectionAttempts = 0; var vDev = this.controller.devices.create({ deviceId: "MQTTClient" + this.id, defaults: { metrics: { title: "MQTT Client Connection Status " + this.config.host } }, overlay: { deviceType: "text", metrics: { text: "Not connected", icon: "/ZAutomation/api/v1/load/modulemedia/MQTTClient/icon.png" } }, moduleId: this.id }); this.updateSkippedDevicesList(); var clientId = this.config.clientId; if (this.config.clientIdRandomize) clientId += "-" + Math.random().toString(16).substr(2, 6); // 1 - Open MQTT Socket if (this.config.user && this.config.password) this.m = new mqtt(this.config.host, this.config.port, this.config.user, this.config.password, clientId); else this.m = new mqtt(this.config.host, this.config.port, clientId); if (this.config.security) { try { this.m.tlsset(); } catch (e) { console.log("--- MQTTClient security error:", e); } } // 2 - Init MQTT handlers this.m.onconnect = function () { console.log("--- MQTTClient connected to", self.config.host); vDev.set("metrics:text", "Connected to " + self.config.host); clearInterval(self.reconnectInterval); self.reconnectInterval = null; mqttConnected = true; mqttConnectionAttempts = 0; this.subscribe(self.config.topicPrefix + "/" + self.config.topicPostfixSet + "/#"); self.controller.devices.on("change:metrics:level", self.onLevelChanged); }; this.m.ondisconnect = function () { console.log("--- MQTTClient disconnected"); vDev.set("metrics:text", "Disconnected. Trying to connect every 1 second"); mqttConnected = false; mqttConnectionAttempts = 0; self.controller.devices.off("change:metrics:level", self.onLevelChanged); }; this.m.onmessage = function (topic, byteArrayMsg) { var msg = byteArrayToString(byteArrayMsg); var channels = topic.split("/"), vDevId = channels[channels.length - 1], vDevFound = false; self.controller.devices.forEach(function(vDev) { if (vDev.id === vDevId) { vDevFound = true; var deviceType = vDev.get("deviceType"); if (deviceType === "switchMultilevel") { vDev.performCommand("exact", {level: parseInt(msg)}); } else if (deviceType === "thermostat") { vDev.performCommand("exact", {level: parseFloat(msg)}); } else if (deviceType === "switchRGBW") { rgb = msg.split(",") vDev.performCommand("exact", {red: rgb[0], green: rgb[1], blue: rgb[2]}); } else { // on, off, stop, open, close and custom command vDev.performCommand(msg); } } }); if (!vDevFound) { console.log("--- MQTTClient: Device " + vDevId + " Not Found"); } }; this.onLevelChanged = function(vDev) { // if Not mqtt-skip, then publish device status and full data if (vDev.get("tags").indexOf("mqtt-skip") === -1) { if (self.config.topicPostfixStatus) { self.m.publish(self.config.topicPrefix + "/" + self.config.topicPostfixStatus + "/" + vDev.id, vDev.get("metrics:level").toString()); } if (self.config.topicPostfixData) { self.m.publish(self.config.topicPrefix + "/" + self.config.topicPostfixData + "/" + vDev.id, JSON.stringify(vDev)); } } }; this.onTagsChanged = function(vDev) { // Add tag "mqtt-skip" to skipped Devices list in config if (vDev.get("tags").indexOf("mqtt-skip") !== -1 && self.config.skippedDevices.indexOf(vDev.id) === -1) { self.config.skippedDevices.push(vDev.id); self.saveConfig(); } // Remove tag "mqtt-skip" from skipped Devices list in config if (vDev.get("tags").indexOf("mqtt-skip") === -1 && self.config.skippedDevices.indexOf(vDev.id) !== -1) { var index = self.config.skippedDevices.indexOf(vDev.id); self.config.skippedDevices.splice(index, 1); self.saveConfig(); } }; this.controller.devices.on("change:tags", this.onTagsChanged); // 3 - Connect try { mqttConnectionAttempts = mqttConnectionAttempts + 1; console.log("--- MQTTClient attempt to connect first time..."); vDev.set("metrics:text", "Attempt to connect first time..."); self.m.connect(); } catch (err) { console.log("--- MQTTClient connection error", self.config.host, err, "Reconnect after 5 seconds..."); vDev.set("metrics:text", "--- MQTTClient connection error: " + err); } // Check connection after 5 seconds self.reconnectInterval = setInterval(function() { if (!mqttConnected) { try { mqttConnectionAttempts = mqttConnectionAttempts + 1; console.log("--- MQTTClient attempts to connect " + mqttConnectionAttempts + " ..."); vDev.set("metrics:text", "Attempts to connect " + mqttConnectionAttempts + " ..."); self.m.connect(); } catch (err) { console.log("--- MQTTClient connection error", self.config.host, err, "Reconnect after 5 seconds..."); vDev.set("metrics:text", "--- MQTTClient connection error: " + err); } } }, 5000); }; MQTTClient.prototype.stop = function () { var self = this; this.controller.devices.off("change:metrics:level", this.onLevelChanged); this.controller.devices.off("change:tags", this.onTagsChanged); clearInterval(this.reconnectInterval); this.reconnectInterval = null; this.controller.devices.remove("MQTTClient" + this.id); try { self.m.disconnect(); console.log("--- MQTTClient disconnected from", self.config.host); } catch (err) { console.log("--- MQTTClient disconnect error from", self.config.host, err); } MQTTClient.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- MQTTClient.prototype.updateSkippedDevicesList = function () { var self = this // Add tag "mqtt-skip" for all skipped devices from config this.config.skippedDevices.forEach(function(vDevId) { var vDev = self.controller.devices.get(vDevId); if (vDev !== null && vDev.get("tags").indexOf("mqtt-skip") === -1 ) { vDev.addTag("mqtt-skip"); } }); // Remove tag "mqtt-skip" if device not in skipped list this.controller.devices.forEach(function(vDev) { if (vDev !== null && vDev.get("tags").indexOf("mqtt-skip") !== -1 && self.config.skippedDevices.indexOf(vDev.id) === -1) { vDev.removeTag("mqtt-skip"); } }); }; ================================================ FILE: modules/MQTTClient/lang/en.json ================================================ { "m_title" : "MQTT Client", "m_descr" : "Publishes the devices data to a MQTT topics and receives device control commands.

          Topics for publishes devices data:
          1. Only device status: zway/status/DEVICE_ID
          2. Full device data: zway/data/DEVICE_ID
          3. Full data for all devices: zway/data/#

          To control devices publish a message with command to the topic zway/set/DEVICE_ID:
          1. 'on', 'off' - relays
          2. 'open', 'close' - door locks
          3. 'on', 'off', level (25, 80, etc) - dimmers
          4. 'on', 'off', 'stop', level (25, 80, etc) - motors
          5. level (25, 80, etc) - thermostats
          6. rgb '0,127,255' (Red=0, Green=127, Blue=255) - Color Lamp/Strips

          Topics prefix (zway) and postfix (data, status, set) can be changed in the app settings. If you don't want to publish some devices to a MQTT add mqtt-skip tag to that device.", "client_id_label": "Client ID", "client_id_randomize_label": "Add random number to the client ID", "client_id_randomize_helper": "Needed for some MQTT brokers.", "host_label": "Hostname or IP address", "port_label": "Port", "security": "Security", "user_label": "Username", "user_helper": "If empty, the username is not used.", "password_label": "Password", "password_helper": "If empty, the password is not used.", "topic_prefix_label": "Topic prefix", "topic_postfix_status_label": "Topic postfix for publish device status", "topic_postfix_status_helper": "Publish only device status, like: 'on', 'off', '25.5', etc.", "topic_postfix_data_label":"Topic postfix for publish device data", "topic_postfix_data_helper":"Publish all device data, like: name, status, id, room, update time, etc.", "topic_postfix_set_label": "Topic postfix for setting the value", "topic_postfix_set_helper": "Publish a message with command to the set-topic to control device.", "skipped_devices_label":"Devices that should not be published in MQTT", "skipped_devices_helper":"mqtt-skip tag will be added to each selected device." } ================================================ FILE: modules/MQTTClient/module.json ================================================ { "singleton": true, "dependencies": [], "category": "support_external_ui", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "MQTTClient", "version": "1.3", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "clientId": "zway", "clientIdRandomize": false, "host": "127.0.0.1", "port": 1883, "security": false, "user": "", "password": "", "topicPrefix": "zway", "topicPostfixStatus": "status", "topicPostfixData": "data", "topicPostfixSet": "set", "skippedDevices": [] }, "schema": { "type": "object", "properties": { "clientId": { "type": "string", "required": true }, "clientIdRandomize": { "type": "boolean", "required": true }, "host": { "type": "string", "required": true }, "port": { "type": "integer", "required": true }, "security": { "type": "boolean", "required": true }, "user": { "type": "string", "required": false }, "password": { "type": "string", "required": false }, "topicPrefix": { "type": "string", "required": true }, "topicPostfixStatus": { "type": "string", "required": true }, "topicPostfixData": { "type": "string", "required": true }, "topicPostfixSet": { "type": "string", "required": true }, "skippedDevices": { "type": "array", "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_all:deviceId", "required": false } }, "required": false }, "options": { "fields": { "clientId": { "label": "__client_id_label__" }, "clientIdRandomize": { "label": "__client_id_randomize_label__", "helper": "__client_id_randomize_helper__" }, "host": { "label": "__host_label__" }, "port": { "label": "__port_label__" }, "security": { "label": "__security__" }, "user": { "label": "__user_label__", "helper": "__user_helper__" }, "password": { "label": "__password_label__", "helper": "__password_helper__" }, "topicPrefix": { "label": "__topic_prefix_label__" }, "topicPostfixStatus": { "label": "__topic_postfix_status_label__", "helper": "__topic_postfix_status_helper__" }, "topicPostfixData": { "label": "__topic_postfix_data_label__", "helper": "__topic_postfix_data_helper__" }, "topicPostfixSet": { "label": "__topic_postfix_set_label__", "helper": "__topic_postfix_set_helper__" }, "skippedDevices": { "label": "__skipped_devices_label__", "helper": "__skipped_devices_helper__", "type": "checkbox", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_all:deviceName" } } } } ================================================ FILE: modules/MatterGate/htdocs/js/postRender.js ================================================ function modulePostRender() { if ($("div[data-alpaca-field-name='pin']").find('input').val() === "") { $("div[data-alpaca-field-name='pin']").hide(); } else { $("div[data-alpaca-field-name='pin']").show(); } }; ================================================ FILE: modules/MatterGate/index.js ================================================ /*** MatterGate Z-Way HA module ******************************************* Version: 1.0 (c) Z-Wave.Me, 2023 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: This module announces Z-Way HA devices to Matter ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function MatterGate (id, controller) { // Call superconstructor first (AutomationModule) MatterGate.super_.call(this, id, controller); }; inherits(MatterGate, AutomationModule); _module = MatterGate; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- MatterGate.prototype.init = function (config) { // Call superclass' init (this will process config argument and so on) MatterGate.super_.prototype.init.call(this, config); var self = this; this.endpoints = {}; if (!this.config.matterDevices) { this.config.matterDevices = {}; } // If matterDevicesArray doesn't contain an vDevId, then remove it from the matterDevices Object.keys(this.config.matterDevices).forEach(function(vDevId) { if (self.config.matterDevicesArray.indexOf(vDevId) == -1) { delete self.config.matterDevices[vDevId]; } }); this.saveConfig(); updateSkippedDevicesList(); // define functions and helpers function onDeviceAddedCore(vDevT) { var controller = this.controller; // defaults vDevT.title = vDevT.title || vDevT.id; vDevT.manufacturer = vDevT.manufacturer || "Z-Wave.Me"; vDevT.product = vDevT.product || vDevT.deviceType; vDevT.firmwareRevision = vDevT.firmwareRevision || zwayVersion.release; vDevT.serialNumber = vDevT.serialNumber || vDevT.id; // Skip widgets if (vDevT.permanently_hidden || !(vDevT.visibility) || (vDevT.probeType === "alarmSensor_general_purpose") || (vDevT.probeType === "thermostat_mode") || vDevT.tags.indexOf("matter-skip") !== -1 || (vDevT.deviceType === "thermostat" && vDevT.id.indexOf("ZWayVDev") === -1)) return; // Supported widgets if (!((vDevT.deviceType === "switchBinary") || (vDevT.deviceType === "switchMultilevel") || (vDevT.deviceType === "sensorMultilevel") || (vDevT.deviceType === "sensorBinary") || (vDevT.deviceType === "doorlock") || (vDevT.deviceType === "switchRGBW") || (vDevT.deviceType === "thermostat"))) return; var ep = self.addDevice(vDevT); if (vDevT.deviceType === "switchBinary") { // skip thermostat mode if (vDevT.id.slice(-2) === "64") return; /* TODO handle appearance var type = Matter.Services.Switch; if (vDevT.tags.indexOf("type-light") !== -1) type = Matter.Services.Lightbulb; if (vDevT.tags.indexOf("type-fan") !== -1) type = Matter.Services.Fan; */ self.matter.addEndPointSwitchBinary(ep); self.addEndpoint(ep, vDevT.id, self.binarySwitchGet, self.binarySwitchSet); } else if (vDevT.deviceType === "switchMultilevel" && (vDevT.probeType !== "motor") ) { // skip if related to switchRGB var rgbDevId; if (vDevT.id.slice(-2) === "38"){ rgbDevId = vDevT.id.substring(0, vDevT.id.indexOf("-",vDevT.id.indexOf("-")+1)+1) + "51-rgb"; } var match = rgbDevices.filter(function(el) { return el.id === rgbDevId; })[0]; if (match) return; self.matter.addEndPointSwitchMultiLevel(ep); self.addEndpoint(ep, vDevT.id, self.multilevelSwitchGet, self.multilevelSwitchSet); } else if (vDevT.deviceType === "sensorMultilevel") { if (vDevT.probeType === "temperature" && (vDevT.scaleTitle === "°C" || vDevT.scaleTitle === "°F")) { self.matter.addEndPointSensorTemperature(ep, -99*100, 99*100); self.addEndpoint(ep, vDevT.id, self.multilevelSensor100Get); } else if (vDevT.probeType === "humidity" && vDevT.scaleTitle === "%") { self.matter.addEndPointSensorHumidity(ep, 0*100, 100*100); self.addEndpoint(ep, vDevT.id, self.multilevelSensor100Get); } else if (vDevT.probeType === "luminosity") { self.matter.addEndPointSensorLight(ep, 1, 10000); self.addEndpoint(ep, vDevT.id, self.multilevelSensorGet); } } else if (vDevT.deviceType === "sensorBinary") { if (vDevT.probeType === "motion") { } else if (vDevT.probeType === "door-window") { self.matter.addEndPointSensorContact(ep); self.addEndpoint(ep, vDevT.id, self.binarySensorGet, undefined, self.binarySensorPush); } } /* else if (vDevT.deviceType === "doorlock") { var service = accessory.addService(Matter.Services.LockMechanism, "Door Lock"); service.addCharacteristic(Matter.Characteristics.Name, "string", vDevT.title); m.StateLevel = service.addCharacteristic(Matter.Characteristics.LockCurrentState, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); return (!!d && d.get("metrics:level") === "close") ? 1 : 0; } }); m.level = service.addCharacteristic(Matter.Characteristics.LockTargetState, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); return (!!d && d.get("metrics:level") === "close") ? 1 : 0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (d) d.performCommand(value ? "close" : "open"); } }); } else if (vDevT.deviceType === "switchRGBW") { // Don't create rgbw widgets without brightness var brightnessDev = null; var brightnessDevId; if (vDevT.id.indexOf("RGB") === 0) { // RGB module var redDevice = self.controller.instances.filter(function(instance){return instance.id == vDevT.id.substring(4)})[0].params.red; var before = redDevice.indexOf("-")+1; var after = redDevice.indexOf("-",redDevice.indexOf("-")+1); brightnessDevId = redDevice.substring(0, before) + "1" + redDevice.substring(after); brightnessDev = self.controller.devices.get(brightnessDevId); } else { // Z-Wave Device brightnessDevId = vDevT.id.substring(0, vDevT.id.indexOf("-",vDevT.id.indexOf("-")+1)+1) + "38"; brightnessDev = self.controller.devices.get(brightnessDevId); } if (!brightnessDev) return; var service = accessory.addService(Matter.Services.Lightbulb, "Multilevel Switch"); service.addCharacteristic(Matter.Characteristics.Name, "string", vDevT.title); self.onDeviceWipedOut(brightnessDevId); m.level = service.addCharacteristic(Matter.Characteristics.On, "bool", { get: function() { var d = controller.devices.get(brightnessDevId); return (!!d && d.get("metrics:level")) ? true : false; }, set: function(value) { var d = controller.devices.get(brightnessDevId); if (!!d) d.performCommand(value ? "on" : "off"); } }); m.hue = service.addCharacteristic(Matter.Characteristics.Hue, "float", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && parseFloat(hsv(d).h) || 0.0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (!!d) exactColor(d, "Hue", value); }}, undefined, {"unit":"arcdegrees", "maxValue":360, "minValue":0, "minStep":1} ); m.saturation = service.addCharacteristic(Matter.Characteristics.Saturation, "float", { get: function() { var d = controller.devices.get(vDevT.id); return !!d && parseFloat(hsv(d).s) || 0.0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (!!d) exactColor(d, "Saturation", value); }}, undefined, {"unit":"percentage", "maxValue":100, "minValue":0, "minStep":1} ); m.brightness = service.addCharacteristic(Matter.Characteristics.Brightness, "int", { get: function() { var d = controller.devices.get(brightnessDevId); if (!d) return 0; var value = d.get("metrics:level"); return value > 99 ? 99 : value || 0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (!!d) { exactColor(d, "Brightness", value); var b = controller.devices.get(brightnessDevId); if (!!b) b.performCommand("exact", { level: value }); }}}, undefined, {"unit":"percentage", "maxValue":99, "minValue":0, "minStep":1} ); // dirty hack to handle in OnLevelChange, but not to create a Matter accessory if (brightnessDev) { self.mapping[brightnessDev.id] = { $accessory: m.$accessory, level: m.level, brightness: m.brightness }; } } else if (vDevT.deviceType === "thermostat") { var service = accessory.addService(Matter.Services.Thermostat, "Thermostat"); service.addCharacteristic(Matter.Characteristics.Name, "string", vDevT.title); var deviceID = vDevT.id.substring(vDevT.id.lastIndexOf("_")+1,vDevT.id.indexOf("-")); // If Matter thermostat already generated, exit. var match = thermostats.filter(function(el) { return el === deviceID; })[0]; if (match) { self.onDeviceWipedOut(vDevT.id); return; } thermostats.push(deviceID); // Get all thermostat modes var thermostatModes = [1]; // For danfoss LC and Secure without ThermostatMode CC if (zway.devices[deviceID].ThermostatMode && zway.devices[deviceID].ThermostatMode.data.supported.value) { thermostatModes = Object.keys(zway.devices[deviceID].ThermostatMode.data).filter(function(mode) {return !isNaN(parseInt(mode))}); } // Use in Matter only Off, Cool, Heat, Auto var supportedMatterModes = [0,1,2,3]; var modes = []; supportedMatterModes.forEach(function(validMode) { thermostatModes.forEach(function(mode) { if (validMode == mode) modes.push(validMode); if (validMode == 3 && mode == 10) modes.push(validMode); }); }); // Remove mode 3 (Auto) if exist var modesWithOutAuto = modes.filter(function(mode) {return mode != 3}); var currentMaxMode = Math.max.apply(null, modesWithOutAuto); var currentMinMode = Math.min.apply(null, modesWithOutAuto); var targetMaxMode = Math.max.apply(null, modes); var targetMinMode = Math.min.apply(null, modes); m.currentThermostatMode = service.addCharacteristic(Matter.Characteristics.CurrentHeatingCoolingState, "uint8", { get: function() { return modesWithOutAuto.length > 1 ? getCurrentMode(deviceID) : modesWithOutAuto[0]; }}, undefined, {"valid-values": modesWithOutAuto, "maxValue": currentMaxMode, "minValue": currentMinMode} ); m.targetThermostatMode = service.addCharacteristic(Matter.Characteristics.TargetHeatingCoolingState, "uint8", { get: function() { return modesWithOutAuto.length > 1 ? getTargetMode(deviceID) : modesWithOutAuto[0];; }, set: function(mode) { zway.devices[deviceID].ThermostatMode.Set(mode == 3 ? 10 : mode); }}, undefined, {"valid-values": modes, "maxValue":targetMaxMode, "minValue":targetMinMode} ); m.currentTemperature = service.addCharacteristic(Matter.Characteristics.CurrentTemperature, "float", { get: function() { return getCurrentTemperature(deviceID); }}, undefined, {"unit":"celsius", "maxValue":999, "minValue":-999, "minStep":0.1} ); m.targetTemperature = service.addCharacteristic(Matter.Characteristics.TargetTemperature, "float", { get: function() { return getTargetTemperature(deviceID); }, set: function(temperature) { if (modesWithOutAuto.length > 1) zway.devices[deviceID].ThermostatMode.Set(lastSupportedThermostatMode == 3 ? 10 : lastSupportedThermostatMode); else lastSupportedThermostatMode = modesWithOutAuto[0]; zway.devices[deviceID].ThermostatSetPoint.Set(lastSupportedThermostatMode, temperature); }}, undefined, {"unit":"celsius", "maxValue":40, "minValue":5, "minStep":0.5} ); // { 0:"Celsius", 1:"Fahrenheit" } service.addCharacteristic(Matter.Characteristics.TemperatureDisplayUnits, "uint8", 0, ["pr", "pw", "ev"], {"valid-values":[0, 1], "maxValue":1, "minValue":0} ); } else if (vDevT.probeType === "motor") { var service = accessory.addService(Matter.Services.WindowCovering, "Blind Control"); service.addCharacteristic(Matter.Characteristics.Name, "string", vDevT.title); m.targetPosition = service.addCharacteristic(Matter.Characteristics.TargetPosition, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); if (!d) return 0; var value = parseInt(d.get("metrics:level")); return value > 99 ? 99 : value || 0; }, set: function(value) { var d = controller.devices.get(vDevT.id); if (d) d.performCommand("exact", { level: value }); }}, undefined, {"unit":"percentage", "maxValue":99, "minValue":0, "minStep":1} ); m.currentPosition = service.addCharacteristic(Matter.Characteristics.CurrentPosition, "uint8", { get: function() { var d = controller.devices.get(vDevT.id); if (!d) return 0; var value = parseInt(d.get("metrics:level")); return value > 99 ? 99 : value || 0; }}, undefined, {"unit":"percentage", "maxValue":99, "minValue":0, "minStep":1} ); m.positionState = service.addCharacteristic(Matter.Characteristics.PositionState, "uint8", { get: function() { return 2; }}, // Stopped undefined, {"valid-values":[0, 1, 2], "maxValue":2, "minValue":0} ); } */ }; /***************** THERMOSTAT HELPERS *****************/ /* function getCurrentTemperature(deviceID) { var ccSensorMultilevel = zway.devices[deviceID].SensorMultilevel; if (ccSensorMultilevel && ccSensorMultilevel.data[1] && ccSensorMultilevel.data[1].val && ccSensorMultilevel.data[1].val.value) { var temperature = ccSensorMultilevel.data[1].val.value; if (temperature >= -999 && temperature <= 999) { return temperature; } else { return 0; } } else { if (lastSupportedThermostatMode == 3){ lastSupportedThermostatMode = 10; } else if (lastSupportedThermostatMode == 0) { lastSupportedThermostatMode = 1; } return zway.devices[deviceID].ThermostatSetPoint.data[lastSupportedThermostatMode].val.value; } }; function getTargetTemperature(deviceID) { var targetTemperature; if (zway.devices[deviceID].ThermostatMode) { var mode = zway.devices[deviceID].ThermostatMode.data.mode.value; if (mode == 1 || mode == 2 || mode == 3) { targetTemperature = zway.devices[deviceID].ThermostatSetPoint.data[mode].val.value; } else { if (lastSupportedThermostatMode == 0) { targetTemperature = 10; } else { targetTemperature = zway.devices[deviceID].ThermostatSetPoint.data[lastSupportedThermostatMode == 3 ? 10 : lastSupportedThermostatMode].val.value; } } if (targetTemperature < 5) targetTemperature = 5; if (targetTemperature > 40) targetTemperature = 40; } else { targetTemperature = zway.devices[deviceID].ThermostatSetPoint.data[1].val.value; // Danfoss LC } return targetTemperature; }; function getTargetMode(deviceID) { var mode = zway.devices[deviceID].ThermostatMode.data.mode.value; if (mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 10) { lastSupportedThermostatMode = mode == 10 ? 3 : mode; // Transform ZW Auto(10) mode to Matter Auto(3) mode } return lastSupportedThermostatMode; } function getCurrentMode(deviceID) { var mode = zway.devices[deviceID].ThermostatMode.data.mode.value; if (mode == 0 || mode == 1 || mode == 2 || mode == 3 || mode == 10) { if (mode == 3 || mode == 10){ lastSupportedThermostatMode = 1; // CurrentHeatingCoolingState not support Auto(10) mode, change it to Heat(1) } else { lastSupportedThermostatMode = mode; } } return lastSupportedThermostatMode; } */ /***************** RGB HELPERS *****************/ /* function exactColor(vDev, valueName, value) { var match = rgbDevices.filter(function(el) { return el.id === vDev.id; })[0]; // If vDev already in rgbDevices array, set propertie if (match) { if (valueName === "Hue") { match.hue = value; match.valueCount += 1; } else if (valueName === "Saturation") { match.saturation = value; match.valueCount += 1; } else if (valueName === "Brightness") { match.brightness = value; } // Wait 2 Characteristics: "Hue, Saturation" to exact color if (valueName !== "Brightness" && match.valueCount == 2) { match.valueCount = 0; var colors = hsv2rgb({h:match.hue, s:match.saturation, v:match.brightness}); vDev.performCommand("exact", {red: colors.r, green: colors.g, blue: colors.b}); } } // If vDev not in rgbDevices array, add vDev and set propertie else { var last = (rgbDevices.push({"id":vDev.id, hue: 0, saturation: 0, brightness: 100, valueCount:0}) - 1); if (valueName === "Hue") { rgbDevices[last].hue = value; rgbDevices[last].valueCount += 1; } else if (valueName === "Saturation") { rgbDevices[last].saturation = value; rgbDevices[last].valueCount += 1; } else if (valueName === "Brightness") { rgbDevices[last].brightness = value; } } } function hsv(vDev){ return rgb2hsv({r:vDev.get("metrics:color:r"), g:vDev.get("metrics:color:g"), b:vDev.get("metrics:color:b")}); } function hsv2rgb(obj) { // H: 0-360; S,V: 0-100; RGB: 0-255 var r, g, b; var sfrac = obj.s / 100; var vfrac = obj.v / 100; if(sfrac === 0){ var vbyte = Math.round(vfrac*255); return { r: vbyte, g: vbyte, b: vbyte }; } var hdb60 = (obj.h % 360) / 60; var sector = Math.floor(hdb60); var fpart = hdb60 - sector; var c = vfrac * (1 - sfrac); var x1 = vfrac * (1 - sfrac * fpart); var x2 = vfrac * (1 - sfrac * (1 - fpart)); switch(sector){ case 0: r = vfrac; g = x2; b = c; break; case 1: r = x1; g = vfrac; b = c; break; case 2: r = c; g = vfrac; b = x2; break; case 3: r = c; g = x1; b = vfrac; break; case 4: r = x2; g = c; b = vfrac; break; case 5: default: r = vfrac; g = c; b = x1; break; } return { "r": Math.round(255 * r), "g": Math.round(255 * g), "b": Math.round(255 * b) }; } function rgb2hsv(obj) { // RGB: 0-255; H: 0-360, S,V: 0-100 var r = obj.r/255, g = obj.g/255, b = obj.b/255; var max, min, d, h, s, v; min = Math.min(r, Math.min(g, b)); max = Math.max(r, Math.max(g, b)); if (min === max) { // shade of gray return {h: 0, s: 0, v: r * 100}; } var d = (r === min) ? g - b : ((b === min) ? r - g : b - r); h = (r === min) ? 3 : ((b === min) ? 1 : 5); h = 60 * (h - d/(max - min)); s = (max - min) / max; v = max; return {"h": h, "s": s * 100, "v": v * 100}; } */ function updateSkippedDevicesList() { // Add tag "matter-skip" for all skipped devices from config self.config.skippedDevices.forEach(function(vDevId) { delete self.config.matterDevices[vDevId]; removeFromMatterDevicesArray(vDevId); var vDev = self.controller.devices.get(vDevId); if (vDev !== null && vDev.get("tags").indexOf("matter-skip") === -1 ) { vDev.addTag("matter-skip"); } }); self.saveConfig(); // Remove tag "matter-skip" if device not in skipped list self.controller.devices.forEach(function(vDev) { if (vDev !== null && vDev.get("tags").indexOf("matter-skip") !== -1 && self.config.skippedDevices.indexOf(vDev.id) === -1) { vDev.removeTag("matter-skip"); } }); } function removeFromMatterDevicesArray(vDevid) { var index = self.config.matterDevicesArray.indexOf(vDevid); if (index !== -1) { self.config.matterDevicesArray.splice(index, 1); } } this.matterDeviceRemove = function (vDevId) { // TODO call matter.remove } this.onDeviceAdded = function (vDev) { console.log("Matter: added", vDev.id); onDeviceAddedCore(self.vDevToTemplate(vDev)); } this.onDeviceRemoved = function (vDev) { console.log("Matter: removed", vDev.id); // TODO self.matter.setReachable(ep, m["reachable"]); } this.onDeviceWipedOut = function (vDevId) { console.log("Matter: wipe out", vDevId); self.matterDeviceRemove(vDevId); // update device tree // TODO self.matter.update(); } this.onLevelChanged = function (vDev) { console.log("Matter: updated", vDev.id); self.devicePush(vDev.id); } this.onTagsChanged = function (vDev) { // Add tag "matter-skip" to skipped Devices list in config and remove device from Homekit if (vDev.get("tags").indexOf("matter-skip") !== -1 && self.config.skippedDevices.indexOf(vDev.id) === -1) { self.config.skippedDevices.push(vDev.id); delete self.config.matterDevices[vDev.id]; removeFromMatterDevicesArray(vDev.id); self.saveConfig(); self.onDeviceWipedOut(vDev.id); } // Remove tag "matter-skip" from skipped Devices list in config and add device to Homekit if (vDev.get("tags").indexOf("matter-skip") === -1 && self.config.skippedDevices.indexOf(vDev.id) !== -1) { var index = self.config.skippedDevices.indexOf(vDev.id); self.config.skippedDevices.splice(index, 1); self.saveConfig(); self.onDeviceAdded(vDev); } } this.onPermanentlyHiddenChanged = function (vDev) { // Remove device from Homekit if (vDev.get("permanently_hidden") === true) { delete self.config.matterDevices[vDev.id]; removeFromMatterDevicesArray(vDev.id); self.saveConfig(); self.onDeviceWipedOut(vDev.id); } // Add device to Homekit if (vDev.get("permanently_hidden") === false) { self.saveConfig(); self.onDeviceAdded(vDev); } } var rgbDevices = []; /* var thermostats = []; var lastSupportedThermostatMode = 1; // Default Heat */ var pin = this.config.pin; // if undefined or empty, will be autogenerated this.matter = new Matter( this.config.name, function(arg) { debugPrint("Getter " + JSON.stringify(arg)); var ep = parseInt(arg["end_point_id"], 10); var epObj = self.endpoints[ep]; return !!epObj && epObj.getter.call(self, epObj.id); }, function(arg) { debugPrint("Setter " + JSON.stringify(arg)); var ep = parseInt(arg["end_point_id"], 10); var val = parseInt(arg["value_0"], 10); var epObj = self.endpoints[ep]; return !!epObj && epObj.setter.call(self, epObj.id, val); } //,, //pin ); // prepare the structure holders this.mapping = {} // load saved devices in case vDevs are not yet loaded to make sure Matter always receives the full list of device this.preloadDevices(); // add existing devices this.controller.devices.each(function(d) { onDeviceAddedCore(self.vDevToTemplate(d)); }); // listen for future device collection changes this.controller.devices.on("created", this.onDeviceAdded); this.controller.devices.on("removed", this.onDeviceRemoved); this.controller.devices.on("wipedOut", this.onDeviceWipedOut); this.controller.devices.on("change:metrics:level", this.onLevelChanged); this.controller.devices.on("change:metrics:isFailed", this.onLevelChanged); this.controller.devices.on("change:tags", this.onTagsChanged); this.controller.devices.on("change:permanently_hidden", this.onPermanentlyHiddenChanged); // update device tree //TODO//this.matter.update(); console.log("Matter PIN:", this.matter.pin); self.config.pin = this.matter.pin; this.controller.addNotification("notification", "Matter PIN: " + this.matter.pin, "module", "MatterGate"); }; MatterGate.prototype.stop = function () { MatterGate.super_.prototype.stop.call(this); this.controller.devices.off("created", this.onDeviceAdded); this.controller.devices.off("removed", this.onDeviceRemoved); this.controller.devices.off("wipedOut", this.onDeviceWipedOut); this.controller.devices.off("change:metrics:level", this.onLevelChanged); this.controller.devices.off("change:metrics:isFailed", this.onLevelChanged); this.controller.devices.off("change:tags", this.onTagsChanged); this.controller.devices.off("change:permanently_hidden", this.onPermanentlyHiddenChanged); if (this.matter) { this.matter.stop(); } delete this.endpoints; delete this.onDeviceAdded; delete this.onDeviceRemoved; delete this.onDeviceWipedOut; delete this.onLevelChanged; delete this.onTagsChanged; delete this.onPermanentlyHiddenChanged; }; MatterGate.prototype.addDevice = function(vDevT) { var self = this; // clean old structure if any this.matterDeviceRemove(vDevT.id); if (!_.isEqual(_.omit(this.config.matterDevices[vDevT.id], "ep"), _.omit(vDevT, "ep"))) { var ep = (this.config.matterDevices[vDevT.id] && this.config.matterDevices[vDevT.id].ep) || (1 + Math.max(2, Math.max.apply(null, Object.keys(this.config.matterDevices).map(function(k) { return self.config.matterDevices[k].ep; })))); this.config.matterDevices[vDevT.id] = vDevT; if (this.config.matterDevicesArray.indexOf(vDevT.id) == -1) this.config.matterDevicesArray.push(vDevT.id); this.config.matterDevices[vDevT.id].ep = ep; this.saveConfig(); } var ep = this.config.matterDevices[vDevT.id].ep; return ep; }; MatterGate.prototype.preloadDevices = function() { var self = this; Object.keys(this.config.matterDevices).forEach(function(vDevId) { self.addDevice(self.config.matterDevices[vDevId]); }); }; MatterGate.prototype.vDevToTemplate = function(vDev) { return { id: vDev.id, deviceType: vDev.get("deviceType"), probeType: vDev.get("probeType"), tags: vDev.get("tags"), permanently_hidden: vDev.get('permanently_hidden'), visibility: vDev.get('visibility'), title: vDev.get("metrics:title"), scaleTitle: vDev.get("metrics:scaleTitle"), probeTitle: vDev.get("metrics:probeTitle"), manufacturer: vDev.get("manufacturer"), product: vDev.get("product"), firmwareRevision: vDev.get("firmware"), serialNumber: undefined }; }; MatterGate.prototype.addEndpoint = function(ep, id, getter, setter, pusher) { this.endpoints[ep] = { id: id, getter: getter, setter: setter, pusher: pusher }; }; MatterGate.prototype.epById = function(id) { for (var ep in this.endpoints) { if (this.endpoints[ep].id === id) { return parseInt(ep, 10); } }; return null; }; MatterGate.prototype.devicePush = function(id) { var ep = this.epById(id); if (!ep) return; var pusher = this.endpoints[ep].pusher; if (!pusher) return; pusher.call(this, id, ep); }; // Getters and setters MatterGate.prototype.binarySwitchGet = function(id) { var dev = this.controller.devices.get(id); if (!dev) return false; return dev.get("metrics:level") === "on" ? 0xFF : 0x00; }; MatterGate.prototype.binarySwitchSet = function(id, value) { var dev = this.controller.devices.get(id); if (!dev) return false; dev.performCommand(value ? "on" : "off"); return true; }; MatterGate.prototype.multilevelSwitchGet = function(id) { var dev = this.controller.devices.get(id); if (!dev) return false; var level = dev.get("metrics:level") ; if (level === "on") level === 99; if (level === "off") level = 0; if (level < 0) level = 0; if (level >= 99) level = 100; return level; }; MatterGate.prototype.multilevelSwitchSet = function(id, value) { var dev = this.controller.devices.get(id); if (!dev) return false; dev.performCommand("exact", { level: value }); return true; }; MatterGate.prototype.multilevelSensorGet = function(id) { var dev = this.controller.devices.get(id); if (!dev) return false; return Math.round(dev.get("metrics:level")); }; MatterGate.prototype.multilevelSensor100Get = function(id) { var dev = this.controller.devices.get(id); if (!dev) return false; return Math.round(dev.get("metrics:level") * 100); }; MatterGate.prototype.binarySensorGet = function(id) { var dev = this.controller.devices.get(id); if (!dev) return false; return dev.get("metrics:level") === "on" ? 0x01 : 0x00; }; MatterGate.prototype.binarySensorPush = function(id, ep) { var dev = this.controller.devices.get(id); if (!dev) return; this.matter.setEndPointSensorContactState(ep, dev.get("metrics:level") === "on" ? 0x01 : 0x00); }; ================================================ FILE: modules/MatterGate/lang/en.json ================================================ { "m_title":"Matter Bridge", "m_descr":"The Matter protocol allows to control Z-Way from Apple Home, Google Home, Amazon Alexa and many other Matter-compatible controllers", "l_options":"Name the bridge will appears with in Matter", "l_pin":"Pin code", "l_skipped_devices":"Devices that don't need to be displayed in Matter", "h_skipped_devices":"matter-skip tag will be added to each selected device.", "l_hkDevicesArray": "Devices exported to Matter (autogenerated)" } ================================================ FILE: modules/MatterGate/lang/ru.json ================================================ { "m_title":"Matter Шлюз", "m_descr":"Интеграция с контроллерами, поддерживающими Matter.", "l_options":"Имя под которым шлюз будет отображаться в контроллерах Matter", "l_pin":"Пин-код", "l_skipped_devices":"Устройства, которые не нужно отображать в Matter", "h_skipped_devices":"matter-skip тэг будет добавлен каждому выбранному устройству.", "l_hkDevicesArray": "Устройства, экспортированные в Matter (заполняется автоматически)" } ================================================ FILE: modules/MatterGate/module.json ================================================ { "singleton": true, "dependencies": [], "category": "support_external_ui", "author": "Z-Wave.Me", "homepage": "http://z-wave.me", "icon": "icon.svg", "moduleName": "MatterGate", "version": "1.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "name": "RaZberry", "pin": "", "matterDevicesArray": [], "skippedDevices": [] }, "schema": { "type": "object", "properties": { "name": { "type": "string", "required": true }, "pin": { "type": "string", "readonly": true }, "skippedDevices": { "type": "array", "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_all:deviceId", "required": false }, "matterDevicesArray" : { "type": "array", "items": { "type": "string", "required": false } } }, "required": false }, "options": { "fields": { "name": { "label": "__l_options__" }, "pin": { "label": "__l_pin__" }, "skippedDevices": { "label": "__l_skipped_devices__", "helper": "__h_skipped_devices__", "type": "checkbox", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_all:deviceName" }, "matterDevicesArray": { "label": "__l_matterDevicesArray__", "fields": { "item": { "type": "text", "readonly": true } } } } }, "postRender": "loadFunction:postRender.js" } ================================================ FILE: modules/MobileAppSupport/index.js ================================================ /* Mobile App Support * * Z-Wave.Me, 2022 * Version 3.0.0 * * Author: Poltorak Serguei */ /* Module configuration format: config apps[] list of phones token str phone FCM token title str phone name profileName str name of the profile in the phone app os str target OS: ios/android app_profile str UUID of the profile in the app in case of multiple profiles user int user Id userName str user name auth_token str first digits of auth token to revoke it on delete last_seen int last seen date created int creation date */ function MobileAppSupport(id, controller) { MobileAppSupport.super_.call(this, id, controller); this.PNS = "https://pns.z-wave.me:5010/v1.0/push"; this.URL = "/smarthome/#/events"; // TODO make URL specific to the event } inherits(MobileAppSupport, AutomationModule); _module = MobileAppSupport; MobileAppSupport.prototype.init = function(config) { MobileAppSupport.super_.prototype.init.call(this, config); var self = this; this.defineHandlers(); this.externalAPIAllow("MobileAppSupportAPI"); this.config.apps.forEach(this.announceApp, this); } MobileAppSupport.prototype.stop = function() { this.config.apps.forEach(this.unannounceApp, this); this.externalAPIRevoke("MobileAppSupportAPI"); MobileAppSupport.super_.prototype.stop.call(this); }; MobileAppSupport.prototype.channelID = function(token, app_profile) { return this.getName() + "_" + this.id + "_" + token + "_" + app_profile; }; MobileAppSupport.prototype.announceApp = function(app) { var self = this; this.controller.registerNotificationChannel(this.channelID(app.token, app.app_profile), app.user, app.title + "(" + app.profileName + ")", function(message) { self.sendNotification(app.token, app.app_profile, message); }); }; MobileAppSupport.prototype.unannounceApp = function(app) { this.controller.unregisterNotificationChannel(this.channelID(app.token, app.app_profile)); }; MobileAppSupport.prototype.getAppByToken = function(token) { return _.findWhere(this.config.apps, { token: token }); }; MobileAppSupport.prototype.getAppByTokenAppProfile = function(token, app_profile) { return _.findWhere(this.config.apps, { token: token, app_profile: app_profile }); }; MobileAppSupport.prototype.getApps = function(user) { return this.config.apps.filter(function(app) { return user === undefined || app.user === user; }); }; MobileAppSupport.prototype.registerApp = function(token, title, profileName, os, app_profile, user, authToken) { var found_app = _.findWhere(this.config.apps, { token: token, app_profile: app_profile }); if (!found_app) { // new app var app = this.generateApp(token, title, profileName, os, app_profile, user, authToken); this.config.apps.push(app); this.announceApp(app); var profile = this.controller.getProfile(user) || {}; var lang = this.loadModuleLang(); this.addNotification("notification", lang.m_welcome + ": " + app.title + " (" + profile.name + " / " + profile.login + ", " + profileName + ")", "module"); this.permanentAuthToken(user, authToken); // optimization - already done in permanentAuthToken // this.saveConfig(); } else { // existing if (found_app.user === user) { if (found_app.auth_token != authToken) { // just a re-login - no need to re-announce the channel - only update the auth token this.removeAuthToken(user, found_app.auth_token); found_app.auth_token = authToken; this.permanentAuthToken(user, found_app.auth_token); } found_app.title = title; found_app.profileName = profileName; found_app.last_seen = Date.now(); } else { // re-register under new token var lang = this.loadModuleLang(); this.unregisterApp(token, app_profile); this.registerApp(token, title, profileName, os, app_profile, user); } } return true; }; MobileAppSupport.prototype.unregisterApp = function(token, app_profile) { var app = _.findWhere(this.config.apps, { token: token, app_profile: app_profile }); if (app) { var profile = this.controller.getProfile(app.user) || {}; var lang = this.loadModuleLang(); this.addNotification("notification", lang.m_goodby + ": " + app.title + " (" + profile.name + " / " + profile.login + ", " + profile.profileName + ")", "module"); this.config.apps = _.without(this.config.apps, _.findWhere(this.config.apps, app)); this.saveConfig(); this.unannounceApp(app); this.removeAuthToken(app.user, app.auth_token); return true; } return false; }; MobileAppSupport.prototype.generateApp = function(token, title, profileName, os, app_profile, user, authToken) { return { token: token, title: title, profileName: profileName, os: os, app_profile: app_profile, user: user, auth_token: authToken, last_seen: Date.now(), created: Date.now() } }; MobileAppSupport.prototype.permanentAuthToken = function(user, authToken) { var profile = this.controller.getProfile(user); if (profile) { this.controller.permanentToken(profile, authToken); } }; MobileAppSupport.prototype.removeAuthToken = function(user, authToken) { var profile = this.controller.getProfile(user); if (profile) { this.controller.removeToken(profile, authToken); } }; MobileAppSupport.prototype.sendNotification = function(token, app_profile, notification) { var app = this.config.apps.filter(function(app) { return app.token === token && app.app_profile === app_profile; })[0]; if (app && app.os) { var message = { to: token, os: app.os, profileId: app.app_profile, url: this.URL, title: (app.profileName ? app.profileName : "Smart Home") + " " + this.controller.getRemoteId(), body: notification }; console.log("(Mobile App Support) send notification: " + JSON.stringify(notification)); this.sendPushMessage(message); }; }; MobileAppSupport.prototype.sendPushMessage = function(message) { var req = { url: this.PNS, method: "POST", async: true, data: JSON.stringify(message), success: function(response) { console.log("(Mobile App Support) Notification sent"); }, error: function(response) { console.log("(Mobile App Support) Notification send failed: " + JSON.stringify(response)); } }; try { http.request(req); } catch (e) { console.log("(Mobile App Support) Exception during notify listener."); } }; // --------------- Public HTTP API ------------------- MobileAppSupport.prototype.externalAPIAllow = function (_name) { ws.allowExternalAccess(_name, this.controller.auth.ROLE.USER); ws.allowExternalAccess(_name + ".app", this.controller.auth.ROLE.USER); global[_name] = this[_name]; }; MobileAppSupport.prototype.externalAPIRevoke = function (_name) { delete global[_name]; ws.revokeExternalAccess(_name); ws.revokeExternalAccess(_name + ".app"); }; MobileAppSupport.prototype.defineHandlers = function () { var self = this; this.MobileAppSupportAPI = function () { return {status: 400, body: "Bad MobileAppSupportAPI request "}; }; this.MobileAppSupportAPI.app = function(url, request) { if (request.method == 'GET') { return { status: 200, body: _.clone(self.getApps(request.role === self.controller.auth.ROLE.ADMIN ? undefined : request.user)).map(function(app) { var profile = self.controller.getProfile(app.user); app.userName = profile ? profile.name : ''; return app; }) }; } else if (request.method == 'POST') { var reqObj; try { reqObj = parseToObject(request.body); } catch (e) { return { status: 400, body: e.toString() }; } if ( reqObj && reqObj.hasOwnProperty('os') && reqObj.os != '' && reqObj.hasOwnProperty('profileId') && reqObj.profileId != '' && reqObj.hasOwnProperty('token') && reqObj.token != '' && reqObj.hasOwnProperty('title') && reqObj.title != '' ) { if (self.registerApp(reqObj.token, reqObj.title, reqObj.profileName, reqObj.os, reqObj.profileId, request.user, request.authToken)) { return { status: 200, body: 'Registrated' }; } else { return { status: 400, body: 'Already registrated' }; } } else { return { status: 400, body: 'Missing arguments os, token, title' }; } } else if (request.method == 'DELETE') { var reqObj; try { reqObj = parseToObject(request.body); } catch (e) { return { status: 400, body: e.toString() }; } if ( reqObj && reqObj.hasOwnProperty('token') && reqObj.token != '' && reqObj.hasOwnProperty('profileId') && reqObj.profileId != '' ) { var app = self.getAppByTokenAppProfile(reqObj.token, reqObj.profileId); if ( app && (request.role === self.controller.auth.ROLE.ADMIN || request.user == app.user) && self.unregisterApp(reqObj.token, reqObj.profileId) ) { return { status: 200, body: 'Deleted' } } else { return { status: 400, body: 'Phone not found' }; } } else { return { status: 400, body: 'Missing argument token or profileId' }; } } else { return { status: 405, body: 'Method Not Allowed' } } }; this.MobileAppSupportAPI.registerApp = this.MobileAppSupportAPI.app; // alias }; ================================================ FILE: modules/MobileAppSupport/lang/de.json ================================================ { "m_title":"Handy App Unterstützung", "m_descr":"Dieses Modul wird von der Z-Way Handy App (Android und iOS) benötigt fur Push Benachrichtigungen", "m_welcome": "Phone connected to Z-Way", "m_goodby": "Phone disconnected from Z-Way" } ================================================ FILE: modules/MobileAppSupport/lang/en.json ================================================ { "m_title":"Mobile App Support", "m_descr":"This module is required for Z-Way mobile app (Android and iOS) for Push Notification.", "m_welcome": "Phone connected to Z-Way", "m_goodby": "Phone disconnected from Z-Way", "l_title": "Phone name", "l_user": "User", "l_os": "Type" } ================================================ FILE: modules/MobileAppSupport/module.json ================================================ { "singleton": true, "dependencies": [], "category": "support_external_ui", "author": "Z-Wave.Me", "homepage": "http://z-wave.me", "icon": "icon.png", "moduleName": "MobileAppSupport", "version": "2.0.0", "maturity": "beta", "defaults": { "title": "__m_title__", "description": "__m_descr__", "apps": [] }, "schema" : { "type": "object", "properties": { "apps": { "type": "array", "items": { "type": "object", "properties": { "token": { "type": "string", "readonly": true }, "title": { "type": "string", "readonly": true }, "os": { "type": "string", "readonly": true }, "app_profile": { "type": "string", "readonly": true }, "user": { "type": "integer", "field": "enum", "datasource": "namespaces", "enum": "users:id", "readonly": true }, "userName": { "type": "string", "readonly": true }, "auth_token": { "type": "string", "readonly": true }, "last_seen": { "type": "integer", "readonly": true }, "created": { "type": "integer", "readonly": true } } } } } }, "options" : { "fields": { "apps": { "items": { "fields": { "token": { "type": "hidden" }, "title": { "type": "text", "label": "__l_title__" }, "os": { "type": "text", "label": "__l_os__" }, "app_profile": { "type": "hidden" }, "user": { "type": "select", "datasource": "namespaces", "optionLabels": "users:title", "label": "__l_user__" }, "userName": { "type": "hidden" }, "auth_token": { "type": "hidden" }, "last_seen": { "type": "hidden" }, "created": { "type": "hidden" } } } } } } } ================================================ FILE: modules/MultilineSensor/index.js ================================================ /*** MultilineSensor Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Niels Roche Description: Choose different sensors to merge them into one virtual device. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function MultilineSensor (id, controller) { // Call superconstructor first (AutomationModule) MultilineSensor.super_.call(this, id, controller); } inherits(MultilineSensor, AutomationModule); _module = MultilineSensor; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- MultilineSensor.prototype.init = function (config) { MultilineSensor.super_.prototype.init.call(this, config); var self = this, devices = [], deviceMetrics = [], item = {}, firstDevice = {}, allSensors = []; this.vDev = null; this.updateAttributes = function(dev) { var sensors = [], sensor = [], indx = null, uT = 0; sensors = self.vDev.get('metrics:sensors'); sensor = sensors.filter(function(sensor){ return sensor.id === dev.get('id'); }); if(sensor[0]){ uT = dev.get('updateTime'); // update sensor metrics sensor[0].metrics = dev.get('metrics'); if (uT > 0) { sensor[0].updateTime = uT; } // get first sensor firstDevice = sensors[0]; } // update vDev metrics with values of first sensor self.vDev.set('metrics:icon', self.getIcon(firstDevice)); self.vDev.set('metrics:level', self.getLevel(firstDevice)); self.vDev.set('metrics:scaleTitle', self.getScaleTitle(firstDevice)); if (uT > 0) { self.vDev.set('updateTime', uT); } }; this.createVDevIfSensorsAreCreated = function(dev){ var indx = self.config.devices.map(function(e) { return e.selectedDevice; }).indexOf(dev.id); if(indx > -1 && deviceMetrics.map(function(e) { return e.selectedDevice; }).indexOf(dev.id) === -1){ item = { id: dev.id, deviceType: dev.get('deviceType'), metrics: dev.get('metrics'), hasHistory: dev.get('hasHistory'), updateTime: dev.get('updateTime') }; deviceMetrics.push(item); if(self.config.devices[indx].hide === true) { dev.set({'visibility': false}); }else{ dev.set({'visibility': true}); } if(deviceMetrics.length > 0){ firstDevice = deviceMetrics[0]; } // update vDev metrics self.vDev.set('metrics:icon', self.getIcon(firstDevice)); self.vDev.set('metrics:level', self.getLevel(firstDevice)); self.vDev.set('metrics:scaleTitle', self.getScaleTitle(firstDevice)); // listen to sensor changes self.controller.devices.on(dev.id, 'change:metrics:level', self.updateAttributes); self.controller.devices.on(dev.id, 'change:[object Object]', self.updateAttributes); self.controller.devices.on(dev.id, 'change:updateTime', self.updateAttributes); } }; self.controller.devices.filter(function (dev){ return self.config.devices.map(function(e) { return e.selectedDevice; }).indexOf(dev.id) > -1; }).forEach(function (dev){ var indx = self.config.devices.map(function(e) { return e.selectedDevice; }).indexOf(dev.id); item = { id: dev.id, deviceType: dev.get('deviceType'), metrics: dev.get('metrics'), hasHistory: dev.get('hasHistory'), updateTime: dev.get('updateTime') }; deviceMetrics.push(item); if(self.config.devices[indx].hide === true) { dev.set({'visibility': false}); }else{ dev.set({'visibility': true}); } // listen to sensor changes self.controller.devices.on(dev.id, 'change:metrics:level', self.updateAttributes); self.controller.devices.on(dev.id, 'change:[object Object]', self.updateAttributes); self.controller.devices.on(dev.id, 'change:updateTime', self.updateAttributes); }); this.vDev = this.controller.devices.create({ deviceId: "Multiline_" + this.id, defaults: { metrics: { multilineType: 'multilineSensor', title: self.getInstanceTitle(), icon: self.getIcon(deviceMetrics[0]), level: self.getLevel(deviceMetrics[0]), scaleTitle: self.getScaleTitle(deviceMetrics[0]) } }, overlay: { deviceType: 'sensorMultiline', metrics: { title: self.getInstanceTitle(), sensors: deviceMetrics, icon: self.getIcon(deviceMetrics[0]), level: self.getLevel(deviceMetrics[0]), scaleTitle: self.getScaleTitle(deviceMetrics[0]) } }, handler: function(command){ if(command === 'update' && deviceMetrics.length > 0){ deviceMetrics.forEach(function(sensor){ getDev = self.controller.devices.filter(function(vDev){ return sensor.id === vDev.id; }); try{ getDev[0].performCommand('update'); } catch(e) { self.controller.addNotification('device-info', 'Update has failed. Error:' + e , 'device-status', getDev[0].id); } }); } }, moduleId: this.id }); // refresh/create virtual device if sensors are created (after restart) self.controller.devices.on('created', self.createVDevIfSensorsAreCreated); }; MultilineSensor.prototype.stop = function () { var self = this; if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } self.controller.devices.filter(function (dev){ return self.config.devices.map(function(e) { return e.selectedDevice; }).indexOf(dev.id) > -1; }).forEach(function (dev){ if(dev.get('visibility') === false) { dev.set({'visibility': true}); } self.controller.devices.off(dev.id, 'change:metrics:level', self.updateAttributes); self.controller.devices.off(dev.id, 'change:[object Object]', self.updateAttributes); self.controller.devices.off(dev.id, 'change:updateTime', self.updateAttributes); self.controller.devices.off('created', self.createVDevIfSensorsAreCreated); }); MultilineSensor.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- MultilineSensor.prototype.getIcon = function (device) { return device && device.metrics.icon? device.metrics.icon : ''; }; MultilineSensor.prototype.getScaleTitle = function (device) { return device && device.metrics.scaleTitle? device.metrics.scaleTitle : ''; }; MultilineSensor.prototype.getLevel = function (device) { return device && device.metrics.level? device.metrics.level : ''; }; MultilineSensor.prototype.getTitle = function (device) { return device && device.metrics.title? device.metrics.title : ''; }; ================================================ FILE: modules/MultilineSensor/lang/de.json ================================================ { "m_title":"Multi-Sensor", "m_descr":"(Führt mehrere Sensoren in einem Element zusammen.)
          Diese App bündelt bis zu 10 Sensorwerte in einem UI Element. Der erste ausgewählte Sensor wird als Elementwert angezeigt. Jeder weitere ausgewählte Sensorwert erscheint in einem Splash-Screen, der bei Klick auf das Element sichtbar wird. Außerdem kann das ursprünglich für die erwählten Sensoren erstellte Element ausgeblendet werden. Diese App hilft zur Optimierung der Benutzeroberfläche durch Entfernung einiger zur Verwendung empfohlener Sensorelemente ohne den Zugang zu deren zur Nutzung zu blockieren. Das gilt zum Beispiel für die Zusammenfassung von Sensoren zu einer logischen Gruppe (Bsp.: ein intelligenter Stromzähler, der sowohl den Stromverbrauch aller Geräte sowie einige individuelle Werte, z. B. für die 3 Phasen im Haus liefert)

          Einstellungen: Bestimmen Sie die Sensoren, welche Sie zusammenführen möchten und entscheiden Sie, ob diese als Einzelelement ausgeblendet werden sollen. ", "l_devices":"Erzeuge ein neues virtuelles Gerät aus den folgenden Sensoren:", "h_devices":"Es lassen sich maximal bis zu 10 Sensoren kombinieren.", "rl_hidden":"Gerät in 'Elementeübersicht' ausblenden", "l_choose_dev":"--- Gerät wählen ---" } ================================================ FILE: modules/MultilineSensor/lang/en.json ================================================ { "m_title":"Multi Sensor", "m_descr":"(Combine multiple sensors into one element)
          This app allows grouping up to 10 sensor values into one element of the UI. The first selected sensor will be shown as the value of the element. Every additional selected sensor value will appear in a splash screen visible when clicking on the element. Additionally the element originally created for the selected sensors may be hidden.This app helps improve the User Interface by removing some sensor elements without blocking access to its recommended to use this e.g. for sensors that form a logical group (like a smart power meter that provides both power draw for all devices plus some individual values e.g. for the three phases in the house)

          Settings: Select the sensors you like to combine and decide if they shall be hidden as individual element.", "l_devices":"Create a new virtual device with the sensors choosen below:", "h_devices":"You can only combine up to 10 sensors with each other.", "rl_hidden":"Do not show device in elements view", "l_choose_dev":"--- Choose a device ---" } ================================================ FILE: modules/MultilineSensor/module.json ================================================ { "singleton" : false, "dependencies": [], "category" : "device_enhancements", "author" : "Z-Wave.Me", "homepage" : "http://razberry.z-wave.me", "icon" : "icon.png", "moduleName":"MultilineSensor", "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__", "devices": [] }, "schema": { "properties": { "devices": { "type": "array", "items": { "maxItems":10, "required": true, "$ref": "#/definitions/device" } } }, "definitions":{ "device": { "type":"object", "properties":{ "selectedDevice":{ "field":"enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorBinary:deviceId,namespaces:devices_sensorMultilevel:deviceId" }, "hide" : { "type" : "boolean", "dependencies": "selectedDevice", "required" : false } } } } }, "options": { "fields": { "devices": { "label": "__l_devices__", "helper": "__h_devices__" } }, "definitions":{ "fields":{ "device":{ "fields": { "selectedDevice" : { "type": "select", "removeDefaultNone": false, "noneLabel": "__l_choose_dev__", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorBinary:deviceName,namespaces:devices_sensorMultilevel:deviceName" }, "hide": { "type": "checkbox", "rightLabel":"__rl_hidden__" } } } } } } } ================================================ FILE: modules/NotificationChannelEmail/htdocs/js/postRender.js ================================================ function modulePostRender() { $.ajax("/ZAutomation/api/v1/profiles") .done(function(profilesResponse) { if (profilesResponse.data && profilesResponse.data.length) { var list = $("
            "); var content = []; profilesResponse.data.forEach(function(p) { if (p.email) { content.push($("
          • " + p.email + " (" + p.name + ")
          • ")); } }); if (content.length) { content.forEach(function(row) { list.append(row); }); $("#alpaca_data legend").first().append(list); } } }); }; ================================================ FILE: modules/NotificationChannelEmail/index.js ================================================ /*** NotificationChannelEmail Z-Way HA module ******************************************* Version: 3.0.0 (c) Z-Wave.Me, 2020 ----------------------------------------------------------------------------- Author: Serguei Poltorak Description: Add notification channel and send notifications via e-mail. ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function NotificationChannelEmail (id, controller) { // Call superconstructor first (AutomationModule) NotificationChannelEmail.super_.call(this, id, controller); this.emailRe = /^(([^<>()\[\]\.,;:\s@\"]+(\.[^<>()\[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; } inherits(NotificationChannelEmail, AutomationModule); _module = NotificationChannelEmail; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- NotificationChannelEmail.prototype.init = function (config) { NotificationChannelEmail.super_.prototype.init.call(this, config); this.remote_id = this.controller.getRemoteId(); this.disabled = false; this.collectedMessages = []; this.subscribe(config); var self = this; this.onProfileUpdate = function(profile) { self.unsubscribe(); self.subscribe(config); } this.controller.on('profile.updated', this.onProfileUpdate); }; NotificationChannelEmail.prototype.stop = function () { if (this.timer) { clearInterval(this.timer); this.timer = undefined; } if (this.onProfileUpdate) { this.controller.off('profile.updated', this.onProfileUpdate); } this.unsubscribe(); NotificationChannelEmail.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- NotificationChannelEmail.prototype.subscribe = function(config) { var self = this; // Loop thru all configured mails config.channels.forEach(function (channel) { if (self.emailRe.test(channel.email)) { var user = channel.user || 0; self.controller.registerNotificationChannel(self.channelID(user, channel.email), user, "E-Mail to " + channel.email, function(message) { self.sender(channel.email, message); }); } else if (channel.email) { self.addNotification('error', 'Invalid e-mail addrress ' + channel.email, 'module'); } }); // Loop thru mails of all users this.controller.profiles.forEach(function (user) { if (self.emailRe.test(user.email)) { self.controller.registerNotificationChannel(self.channelID(user.id, user.email), user.id, "E-Mail to " + user.name + " (" + user.email + ")", function(message) { self.sender(user.email, message); }); } else if (user.email) { self.addNotification('error', 'invalid e-mail addrress ' + user.email, 'module'); } }); }; NotificationChannelEmail.prototype.unsubscribe = function() { var self = this; // uregister all in one shot by prefix Object.keys(this.controller.notificationChannels).forEach(function (id) { var prefix = self.channelIDPrefix(); if (id.lastIndexOf(prefix) === 0) { self.controller.unregisterNotificationChannel(id); } }); }; NotificationChannelEmail.prototype.channelIDPrefix = function() { return this.getName() + "_" + this.id + "_"; }; NotificationChannelEmail.prototype.channelID = function(profileId, email) { return this.channelIDPrefix() + profileId + "_" + email; }; NotificationChannelEmail.prototype.sender = function(to, message) { if (this.disabled) return; // check mail validity if (!this.emailRe.test(to)) { this.addNotification('error', 'invalid e-mail addrress ' + to, 'module'); return; } this.collectedMessages.push({ mail_to: to, message: message }); if (!this.timer) { this.sendSendMessageWithDelay(); } } NotificationChannelEmail.prototype.sendSendMessageWithDelay = function () { var self = this, mailObject = {}; this.timer = setInterval( function() { if (self.collectedMessages.length > 0) { mailObject = self.collectedMessages.shift(); console.log(self.getName() + " Sending a message to " + mailObject.mail_to); http.request({ method: "POST", url: "https://service.z-wave.me/emailnotification/index.php", async: true, data: { remote_id: self.remote_id, mail_to: mailObject.mail_to, subject: mailObject.message, message: mailObject.message + "
            " + (new Date()), language: self.controller.defaultLang }, headers: { "Content-Type": "application/x-www-form-urlencoded" }, error: function(response) { console.log("NotificationChannelEmail error: " + (typeof response !== 'string'? JSON.stringify(response) : response)); self.disabled = true; // disable infinite loop sending e-mail notification about faulure to send e-mail notification self.addNotification('error', 'NotificationChannelEmail error' + (typeof response === 'string'? ': ' + JSON.stringify(response) : ''), 'module'); self.disabled = false; } }); } else { if (self.timer) { clearInterval(self.timer); self.timer = undefined; } } }, 5000); }; ================================================ FILE: modules/NotificationChannelEmail/lang/en.json ================================================ { "m_title": "Notifications by E-mail", "m_descr": "This app sends notifications by e-mail.", "l_emails": "Recepients", "h_emails": "User's E-mails are registered automatically. Add here additional recipients.", "l_email": "E-mail", "l_assignedToUser": "Assign this E-Mail to a user", "h_assignedToUser": "If set, this E-Mail will be also used to send notifications to the user", "l_not_assigned_to_user": "Don't assign to a user", "m_subject": "Z-Way Notification", "l_subject": "Subject", "h_subject": "The subject for E-mail messages" } ================================================ FILE: modules/NotificationChannelEmail/module.json ================================================ { "dependencies": [], "singleton": true, "category": "notifications", "author": "Z-Wave.Me", "homepage": "https://z-wave.me", "icon": "icon.png", "moduleName": "NotificationChannelEmail", "version": "3.0.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "subject": "__m_subject__", "channels": [] }, "schema": { "type": "object", "properties": { "subject": { "type" : "string", "required" : true }, "channels": { "type": "array", "items": { "type": "object", "properties": { "email": { "type" : "string", "required": true }, "user": { "type": "integer", "field": "enum", "datasource": "namespaces", "enum": "users:id", "required": false } } } } } }, "options": { "fields": { "subject": { "label": "__l_subject__", "helper": "__h_subject__" }, "channels": { "label": "__l_emails__", "helper": "__h_emails__", "items": { "fields": { "email": { "label": "__l_email__" }, "user": { "type": "select", "datasource": "namespaces", "optionLabels": "users:title", "noneLabel": "__l_not_assigned_to_user__", "label": "__l_assignedToUser__", "helper": "__h_assignedToUser__" } } } } } }, "postRender": "loadFunction:postRender.js" } ================================================ FILE: modules/NotificationFiltering/index.js ================================================ /*** NotificationFiltering Z-Way HA module ******************************************* Version: 1.0.0 (c) Z-Wave.Me, 2020 ----------------------------------------------------------------------------- Author: Serguei Poltorak ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function NotificationFiltering (id, controller) { // Call superconstructor first (AutomationModule) NotificationFiltering.super_.call(this, id, controller); } inherits(NotificationFiltering, AutomationModule); _module = NotificationFiltering; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- NotificationFiltering.prototype.init = function (config) { NotificationFiltering.super_.prototype.init.call(this, config); var self = this; this.normalizeConfig(); this.readConfig(); this.handler = function(notice) { self.onNotificationHandler(notice); }; this.controller.on('notifications.push', this.handler); // automaitcally delete and add default notification for devices if (this.config.autogenOnDeviceListUpdate) { this.handleDeviceListUpdate = function(params) { self.onDeviceListUpdate(params); }; this.controller.on('profile.deviceListUpdated', this.handleDeviceListUpdate); } this.handlerUserConfigUpdate = function(user, config) { self.userConfigUpdate(user, config); }; this.controller.on('notificationFiltering.userConfigUpdate', this.handlerUserConfigUpdate); }; NotificationFiltering.prototype.stop = function () { NotificationFiltering.super_.prototype.stop.call(this); if (this.config.autogenOnDeviceListUpdate) { this.controller.off('profile.deviceListUpdated', this.handleDeviceListUpdate); } this.controller.off('notifications.push', this.handler); this.controller.off('notificationFiltering.userConfigUpdate', this.handlerUserConfigUpdate); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- NotificationFiltering.prototype.prepareMessage = function(message, devId) { var regex = /<([^:<> ]*):([^<> ]*)>/; // match or <:metrics:level> while ((search = regex.exec(message))) { var vDev = this.controller.devices.get(search[1] ? search[1] : devId); if (vDev) { message = message.replace(search[0], vDev.get(search[2]) || "") } } return message; } NotificationFiltering.prototype.onNotificationHandler = function (notice) { var self = this; if (!notice || !notice.message) return; var sendTo = []; var defaultMessage = typeof notice.message === 'string' ? notice.message : (notice.message.dev + " : " + notice.message.l); defaultMessage = this.prepareMessage(defaultMessage, notice.source); Object.keys(self.logLevels).forEach(function (level) { if ( (level === "errors" && (notice.level === "critical"|| notice.level === "error")) || (level === "notifications" && (notice.level == "notification" || notice.level == "device-info" || notice.level == "info")) || (level === "warnings" && notice.level == "warning") ) { self.logLevels[level].forEach(function(ch) { sendTo.push({ type: ch.type, user: ch.user, channel: ch.channel, message: defaultMessage }); }); } }); self.devices.forEach(function (device) { if (notice.source == device.id) { var send = true; if (device.comparator !== null) { var value = parseFloat(notice.message.l); if (isNaN(value)) { if (eval("'" + notice.message.l + "'" + device.comparator) === false) { send = false; } } else { if (eval(value + device.comparator) === false) { send = false; } } } if (send) { sendTo.push({ type: device.type, user: device.user, channel: device.channel, message: device.message ? self.prepareMessage(device.message, device.id) : defaultMessage }); } } }); sendTo.forEach(function(to) { if (to.type === "user") { self.controller.notificationUserChannelsSend(to.user, to.message); } else if (to.type === "channel") { self.controller.notificationChannelSend(to.channel, to.message); } else { self.controller.notificationAllChannelsSend(to.message); } }); }; NotificationFiltering.prototype.normalizeConfig = function() { var self = this; if (this.config.normalizeRules) { var users = _.compact(_.uniq(this.config.rules.filter(function(rule) { return rule.user && rule.recipient_type === "user"; }).map(function(rule) { return rule.user; }))); var channels = _.compact(_.uniq(this.config.rules.filter(function(rule) { return rule.channel && rule.recipient_type === "channel"; }).map(function(rule) { return rule.channel; }))); var newRules = []; users.forEach(function(user) { var userRules = self.config.rules.filter(function(rule) { return rule.user === user; }); var logLevel = _.uniq(_.compact(_.flatten(userRules.map(function(rule) { return rule.logLevel ? rule.logLevel.split(',') : [""]; })))).join(','); var devices = []; userRules.forEach(function(rule) { rule.devices.forEach(function(dev) { devices = devices.concat(dev); }); }); newRules.push({ recipient_type: "user", user: user, logLevel: logLevel, devices: devices }); }); channels.forEach(function(channel) { var channelRules = self.config.rules.filter(function(rule) { return rule.channel === channel; }); var logLevel = _.uniq(_.compact(_.flatten(channelRules.map(function(rule) { return rule.logLevel ? rule.logLevel.split(',') : [""]; })))).join(','); var devices = []; channelRules.forEach(function(rule) { rule.devices.forEach(function(dev) { devices = devices.concat(dev); }); }); newRules.push({ recipient_type: "channel", channel: channel, logLevel: logLevel, devices: devices }); }); var allRules = this.config.rules.filter(function(rule) { return rule.recipient_type === "all"; }); if (allRules.length) { var logLevel = _.uniq(_.compact(_.flatten(allRules.map(function(rule) { return rule.logLevel ? rule.logLevel.split(',') : [""]; })))).join(','); var devices = []; allRules.forEach(function(rule) { rule.devices.forEach(function(dev) { devices = devices.concat(dev); }); }); newRules.push({ recipient_type: "all", logLevel: logLevel, devices: devices }); } this.config.rules = newRules; } this.saveConfig(); }; NotificationFiltering.prototype.readConfig = function () { var self = this; this.devices = []; this.logLevels = {}; // normalize rules (keep each user/channel/all only once) // TODO // parse rules this.config.rules.forEach(function(rule) { var recipientType = rule.recipient_type, user = rule.user, channel = rule.channel; if (rule.logLevel) { rule.logLevel.split(',').forEach(function(logLevel) { if (!self.logLevels[logLevel]) self.logLevels[logLevel] = []; self.logLevels[logLevel].push({type: recipientType, user: user, channel: rule.channel}); }); } rule.devices.forEach(function(device) { var d, ml = false; if (device.dev_toggleButton) { d = device.dev_toggleButton; } else if (device.dev_switchControl) { d = device.dev_switchControl; ml = true; } else if (device.dev_switchBinary) { d = device.dev_switchBinary; } else if (device.dev_switchMultilevel) { d = device.dev_switchMultilevel; ml = true; } else if (device.dev_sensorBinary) { d = device.dev_sensorBinary; } else if (device.dev_sensorMultilevel) { d = device.dev_sensorMultilevel; ml = true; } else if (device.dev_sensorMultiline) { d = device.dev_sensorMultiline; ml = true; } else if (device.dev_fan) { d = device.dev_fan; ml = true; } else if (device.dev_doorlock) { d = device.dev_doorlock; ml = true; } else if (device.dev_thermostat) { d = device.dev_thermostat; ml = true; } else { return; } var comparator = null; if (ml) { if (d.dev_matchValue && d.dev_matchValue.dev_matchValueOperation && d.dev_matchValue.dev_matchValue !== 'all' && d.dev_matchValue.dev_matchValueOperand) { comparator = d.dev_matchValue.dev_matchValueOperation + d.dev_matchValue.dev_matchValueOperand; } } else { if (d.dev_matchValue && d.dev_matchValue !== 'all') { comparator = "=='" + d.dev_matchValue + "'"; } } // each device can be present multiple times with different channels self.devices.push({ "id": d.dev_select, "message": d.dev_message, "comparator": comparator, "type": recipientType, "user": user, "channel": channel }); }); }); }; NotificationFiltering.prototype.addRule = function(userId, deviceId, deviceType, matchValueOperation, matchValue) { var dev = {}; dev["dev_filter"] = deviceType; dev[deviceType] = {}; dev[deviceType]["dev_select"] = deviceId; if (matchValueOperation) dev[deviceType]["dev_matchValueOperation"] = matchValueOperation; if (matchValue) dev[deviceType]["dev_matchValue"] = matchValue; console.log("Adding rule for user " + userId + " for device " + deviceId); this.config.rules.push({ recipient_type: "user", user: userId, devices: [dev] }); }; NotificationFiltering.prototype.removeUserDeviceRules = function(userId, deviceId) { var self = this; this.config.rules.forEach(function(rule) { if (rule.recipient_type === "user" && rule.user === userId || rule.recipient_type === "channel" && self.controller.getNotificationChannel(rule.channel) && self.controller.getNotificationChannel(rule.channel).user === userId) { console.log("Removing rule for user " + userId + " for device " + deviceId); rule.devices = rule.devices.filter(function(dev) { return dev[dev["dev_filter"]]["dev_select"] !== deviceId }); } }); // clean empty this.config.rules = this.config.rules.filter(function(rule) { return rule.devices.length || rule.logLevel }); }; NotificationFiltering.prototype.onDeviceListUpdate = function (params) { var self = this; var userId = params.profile.id; params.deleted.forEach(function(id) { self.removeUserDeviceRules(userId, id); }); params.added.forEach(function(id) { var dev = self.controller.devices.get(id) if (dev.get("permanently_hidden") || !dev.get("visibility")) return; var dt = dev.get('deviceType'), pt = dev.get('probeType'); // Notify on alarm trigger if ( dt === "sensorBinary" && ["general_purpose", "smoke", "co", "flood", "door-window", "tamper", "motion"].indexOf(pt) > -1 || dt === "sensorBinary" && ["alarmSensor_general_purpose", "alarmSensor_smoke", "alarmSensor_co", "alarmSensor_coo", "alarmSensor_heat", "alarmSensor_flood", "alarmSensor_door", "alarmSensor_burglar", "alarmSensor_power", "alarmSensor_system", "alarmSensor_emergency", "alarmSensor_clock"].indexOf(pt) > -1 || dt === "sensorBinary" && ["alarm_smoke", "alarm_co", "alarm_coo", "alarm_heat", "alarm_flood", "alarm_burglar", "alarm_power", "alarm_system", "alarm_emergency", "alarm_clock", "siren", "gas"].indexOf(pt) > -1 ) { self.addRule(userId, id, "dev_sensorBinary", null, "on"); } // Notify on siren on if (dt === "switchBinary" && pt === "siren") { self.addRule(userId, id, "dev_switchBinary", null, "on"); } // Notify on valve open/close if (dt === "switchBinary" && pt === "valve") { self.addRule(userId, id, "dev_switchBinary", null, "all"); } // Notify on doorlock open/close if (dt === "switchBinary" && pt === "doorlock") { self.addRule(userId, id, "dev_doorlock", null, "all"); } // Notify on low battery if (dt === "sensorMultilevel" && pt === "battery" || dt === "battery") { self.addRule(userId, id, "dev_sensorMultilevel", "<", 10); } }); this.normalizeConfig(); this.readConfig(); }; NotificationFiltering.prototype.userConfigUpdate = function(user, config) { var self = this; var devsStruct = []; // remove all rules for that user and his channels (keep global flags) var toRemove = []; this.config.rules.forEach(function(rule, index) { var chUser; if ( (rule.recipient_type === "user" && rule.user == user) || // non-strict == because might be as string in module params (rule.recipient_type === "channel" && (chUser = self.controller.getNotificationChannel(rule.channel)) && chUser.user == user) // non-strict == because might be as string in module params ) { rule.devices = []; if (!rule.logLevel) { // if no global flags, remove toRemove.unshift(index); // add to remove list in reverse order } } }); toRemove.forEach(function(i) { self.config.rules.splice(i, 1); }); var userDevices = this.controller.devicesByUser(user).map(function(dev) { return dev.id; }); config.forEach(function(rule) { var channel = self.controller.getNotificationChannel(rule.channel); // validity and access checks if (rule.recipient_type === "channel") { if (!rule.channel || !channel) return; // invalid format if (channel.user != user) return; // access violation } if (rule.recipient_type === "user") { if (!rule.user) return; // invalid format if (rule.user != user) return; // access violation } // filter by device validity and access rule.devices = rule.devices.filter(function(d) { if (!d["dev_filter"]) return false; // invalid format if (!d[d["dev_filter"]] || !d[d["dev_filter"]]["dev_select"]) return false; // invalid format return userDevices.indexOf(d[d["dev_filter"]]["dev_select"]) > -1; }); self.config.rules.push(rule); }); this.normalizeConfig(); this.readConfig(); } ================================================ FILE: modules/NotificationFiltering/lang/en.json ================================================ { "m_title": "Notification Filtering", "m_descr": "

            Defines which notifications to send to notification channels (push, e-mail, SMS, ...)

            ", "rl_autogenOnDeviceListUpdate": "Generate and delete rules on user device list update", "h_autogenOnDeviceListUpdate": "Automatically add most important notifications for users when new device is granted to the user. Delete rules for devices with revoked access.", "rl_normalizeRules": "Normalize rules", "h_normalizeRules": "Aggregate all rules for each user/channel in one block. Can be turned on to let system normalize all rules and then can be turned off again.", "unselected": "--- please select ---", "errors": "all error messages", "warnings": "all warnings", "notifications": "all notifications", "none": "nothing", "l_recipient_type": "Recipient", "l_all": "All users", "l_user": "User", "h_user": "Select the user to notify", "l_channel": "A specific communication channel", "h_channel": "Select the notifciation channel to use.", "l_logLevel": "Global notifications", "h_logLevel": "Select, which kind of notifications to inform about. Useful to inform admin or installer about problems", "l_devices": "Devices specific notifications", "h_devices": "Select devices that will generate notifications.", "l_dev_message" : "Device specific message", "h_dev_message" : "Enter a message that should be sent instead of the default notification. Empty to keep the default. Use <devId:property> to refer to a property of a specific device. For example, <DummyDevice_44:metrics:level> or <ZWayVDev_zway5_7-0-38:metrics:title> (see API structure below). If devId is empty, the device on which the event has fired is used. For example, «<:metrics:title> is <:metrics:level>, while <DummyDevice_44:metrics:title> is <DummyDevice_44:metrics:level>» will produce «Light is on, while Door sensor is off»", "l_dev_matchValue": "Condition", "h_dev_matchValue": "Send the message only if the device reports the following state.", "l_dev_matchValueOperation": "Operator", "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.", "dev_toggleButton": "Toggle Button", "dev_switchControl": "Switch Control", "dev_switchBinary": "Switch Binary", "dev_switchMultilevel": "Switch Multilevel", "dev_sensorBinary": "Sensor Binary", "dev_sensorMultilevel": "Sensor Multilevel", "dev_sensorMultiline": "Sensor Multiline", "dev_fan": "Fan", "dev_doorlock": "Doorlock", "dev_thermostat": "Thermostat", "l_ToggleButton": "Toggle Button", "l_switchControl": "Switch Control", "l_switchBinary": "Switch Binary", "l_switchMultilevel": "Switch Multilevel", "l_sensorBinary": "Sensor Binary", "l_sensorMultilevel": "Sensor Multilevel", "l_sensorMultiline": "Sensor Multiline", "l_fan": "Fan", "l_doorlock": "Doorlock", "l_thermostat": "Thermostat" } ================================================ FILE: modules/NotificationFiltering/module.json ================================================ { "dependencies": [], "singleton": true, "category": "notifications", "author": "Z-Wave.Me", "homepage": "https://z-wave.me", "icon": "icon.png", "moduleName": "NotificationFiltering", "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__", "rules": [{"recipient_type": "user", "user": 1, "logLevel": "errors,warnings"}], "autogenOnDeviceListUpdate": true, "normalizeRules": true }, "schema": { "type": "object", "properties": { "rules": { "type": "array", "items": { "type": "object", "properties": { "recipient_type": { "type": "string", "enum": [ "all", "user", "channel" ], "default": "all", "required": true }, "channel": { "type": "string", "dependencies": "recipient_type", "field": "enum", "datasource": "namespaces", "enum": "namespaces:notificationChannels:channelId", "required": true }, "user": { "type": "number", "dependencies": "recipient_type", "field": "enum", "datasource": "namespaces", "enum": "users:id", "required": true }, "logLevel": { "type": "string", "enum": [ "errors", "warnings", "notifications" ] }, "devices": { "type": "array", "items": { "type": "object", "properties": { "dev_filter": { "type": "string", "enum": [ "unselected", "dev_toggleButton", "dev_switchControl", "dev_switchBinary", "dev_switchMultilevel", "dev_sensorBinary", "dev_sensorMultilevel", "dev_sensorMultiline", "dev_doorlock", "dev_thermostat" ], "required": true }, "dev_toggleButton": { "type": "object", "dependencies": "dev_filter", "properties": { "dev_select": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true }, "dev_message": { "required": false } } }, "dev_switchControl": { "type": "object", "dependencies": "dev_filter", "properties": { "dev_select": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchControl:deviceId", "required": true }, "dev_matchValue": { "type": "object", "properties": { "dev_matchValueOperation": { "type": "string", "enum": [ "<", ">", "==", "<=", ">=", "!=" ], "required": false, "title": "__l_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": "dev_matchValueOperation", "pattern": "[0-9]*", "required": false } } }, "dev_message": { "required": false } } }, "dev_switchBinary": { "type": "object", "dependencies": "dev_filter", "properties": { "dev_select": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchBinary:deviceId", "required": true }, "dev_matchValue": { "type": "string", "enum": [ "all", "on", "off" ], "title": "__l_dev_matchValue__", "required": true }, "dev_message": { "required": false } } }, "dev_switchMultilevel": { "type": "object", "dependencies": "dev_filter", "properties": { "dev_select": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchMultilevel:deviceId", "required": true }, "dev_matchValue": { "type": "object", "properties": { "dev_matchValueOperation": { "type": "string", "enum": [ "<", ">", "==", "<=", ">=", "!=" ], "required": false, "title": "__l_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": "dev_matchValueOperation", "pattern": "[0-9]*", "required": false } } }, "dev_message": { "required": false } } }, "dev_sensorBinary": { "type": "object", "dependencies": "dev_filter", "properties": { "dev_select": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorBinary:deviceId", "required": true }, "dev_matchValue": { "type": "string", "enum": [ "all", "on", "off" ], "title": "__l_dev_matchValue__", "required": false }, "dev_message": { "required": false } } }, "dev_sensorMultilevel": { "type": "object", "dependencies": "dev_filter", "properties": { "dev_select": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorMultilevel:deviceId", "required": true }, "dev_matchValue": { "type": "object", "properties": { "dev_matchValueOperation": { "type": "string", "enum": [ "<", ">", "==", "<=", ">=", "!=" ], "required": false, "title": "__l_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": "dev_matchValueOperation", "pattern": "[0-9]*", "required": false } } }, "dev_message": { "required": false } } }, "dev_sensorMultiline": { "type": "object", "dependencies": "dev_filter", "properties": { "dev_select": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_sensorMultiline:deviceId", "required": true }, "dev_matchValue": { "type": "object", "properties": { "dev_matchValueOperation": { "type": "string", "enum": [ "<", ">", "==", "<=", ">=", "!=" ], "required": false, "title": "__l_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": "dev_matchValueOperation", "pattern": "[0-9]*", "required": false } } }, "dev_message": { "required": false } } }, "dev_doorlock": { "type": "object", "dependencies": "dev_filter", "properties": { "dev_select": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId", "required": true }, "dev_matchValue": { "type": "string", "enum": [ "all", "open", "close" ] }, "dev_message": { "required": false } } }, "dev_thermostat": { "type": "object", "dependencies": "dev_filter", "properties": { "dev_select": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_thermostat:deviceId", "required": true }, "dev_matchValue": { "type": "object", "properties": { "dev_matchValueOperation": { "type": "string", "enum": [ "<", ">", "==", "<=", ">=", "!=" ], "required": false, "title": "__l_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": "dev_matchValueOperation", "pattern": "[0-9]*", "required": false } } }, "dev_message": { "required": false } } } } } } } } }, "autogenOnDeviceListUpdate": { "type": "boolean", "default": true }, "normalizeRules": { "type": "boolean", "default": true } }, "required": false }, "options": { "fields": { "rules": { "items": { "fields": { "recipient_type": { "label": "__l_recipient_type__", "type": "select", "optionLabels": [ "__l_all__", "__l_user__", "__l_channel__" ] }, "channel": { "dependencies": { "recipient_type": "channel" }, "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:notificationChannels:channelNameEx", "label": "__l_channel__", "helper": "__h_channel__" }, "user": { "dependencies": { "recipient_type": "user" }, "type": "select", "datasource": "namespaces", "optionLabels": "users:title", "required": true, "label": "__l_user__", "helper": "__h_user__", "title": "__t_user__" }, "logLevel": { "type": "checkbox", "optionLabels": [ "__errors__", "__warnings__", "__notifications__" ], "label": "__l_logLevel__", "helper": "__h_logLevel__" }, "devices": { "label": "__l_devices__", "helper": "__h_devices__", "items": { "fields": { "dev_filter": { "type": "select", "optionLabels": [ "__unselected__", "__dev_toggleButton__", "__dev_switchControl__", "__dev_switchBinary__", "__dev_switchMultilevel__", "__dev_sensorBinary__", "__dev_sensorMultilevel__", "__dev_sensorMultiline__", "__dev_doorlock__", "__dev_thermostat__" ] }, "dev_toggleButton": { "label": "__l_ToggleButton__", "dependencies": { "dev_filter": "dev_toggleButton" }, "fields": { "dev_select": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" }, "dev_message": { "label": "__l_dev_message__", "helper": "__h_dev_message__" } } }, "dev_switchControl": { "label": "__l_switchControl__", "dependencies": { "dev_filter": "dev_switchControl" }, "fields": { "dev_select": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchControl:deviceName" }, "dev_matchValue": { "label": "__l_dev_matchValue__", "fields": { "dev_matchValueOperation": { "type": "select", "optionLabels": [ "<", ">", "=", "≤", "≥", "≠" ], "noneLabel": "All", "label": "__l_dev_matchValueOperation__", "helper": "__h_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": { "dev_matchValueOperation": [ "<", ">", "==", "<=", ">=", "!=" ] }, "label": "__l_dev_matchValueOperand__", "helper": "__h_dev_matchValueOperand__" } } }, "dev_message": { "label": "__l_dev_message__", "helper": "__h_dev_message__" } } }, "dev_switchBinary": { "label": "__l_switchBinary__", "dependencies": { "dev_filter": "dev_switchBinary" }, "fields": { "dev_select": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchBinary:deviceName" }, "dev_matchValue": { "helper": "__h_dev_matchValue__", "type": "select", "optionLabels": [ "All", "On", "Off" ] }, "dev_message": { "label": "__l_dev_message__", "helper": "__h_dev_message__" } } }, "dev_switchMultilevel": { "label": "__l_switchMultilevel__", "dependencies": { "dev_filter": "dev_switchMultilevel" }, "fields": { "dev_select": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchMultilevel:deviceName" }, "dev_matchValue": { "label": "__l_dev_matchValue__", "fields": { "dev_matchValueOperation": { "type": "select", "optionLabels": [ "<", ">", "=", "≤", "≥", "≠" ], "noneLabel": "All", "label": "__l_dev_matchValueOperation__", "helper": "__h_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": { "dev_matchValueOperation": [ "<", ">", "==", "<=", ">=", "!=" ] }, "label": "__l_dev_matchValueOperand__", "helper": "__h_dev_matchValueOperand__" } } }, "dev_message": { "label": "__l_dev_message__", "helper": "__h_dev_message__" } } }, "dev_sensorBinary": { "label": "__l_sensorBinary__", "dependencies": { "dev_filter": "dev_sensorBinary" }, "fields": { "dev_select": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorBinary:deviceName" }, "dev_matchValue": { "helper": "__h_dev_matchValue__", "type": "select", "optionLabels": [ "All", "On", "Off" ] }, "dev_message": { "label": "__l_dev_message__", "helper": "__h_dev_message__" } } }, "dev_sensorMultilevel": { "label": "__l_sensorMultilevel__", "dependencies": { "dev_filter": "dev_sensorMultilevel" }, "fields": { "dev_select": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorMultilevel:deviceName" }, "dev_matchValue": { "label": "__l_dev_matchValue__", "fields": { "dev_matchValueOperation": { "type": "select", "optionLabels": [ "<", ">", "=", "≤", "≥", "≠" ], "noneLabel": "All", "label": "__l_dev_matchValueOperation__", "helper": "__h_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": { "dev_matchValueOperation": [ "<", ">", "==", "<=", ">=", "!=" ] }, "label": "__l_dev_matchValueOperand__", "helper": "__h_dev_matchValueOperand__" } } }, "dev_message": { "label": "__l_dev_message__", "helper": "__h_dev_message__" } } }, "dev_sensorMultiline": { "label": "__l_sensorMultiline__", "dependencies": { "dev_filter": "dev_sensorMultiline" }, "fields": { "dev_select": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_sensorMultiline:deviceName" }, "dev_matchValue": { "label": "__l_dev_matchValue__", "fields": { "dev_matchValueOperation": { "type": "select", "optionLabels": [ "<", ">", "=", "≤", "≥", "≠" ], "noneLabel": "All", "label": "__l_dev_matchValueOperation__", "helper": "__h_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": { "dev_matchValueOperation": [ "<", ">", "==", "<=", ">=", "!=" ] }, "label": "__l_dev_matchValueOperand__", "helper": "__h_dev_matchValueOperand__" } } }, "dev_message": { "label": "__l_dev_message__", "helper": "__h_dev_message__" } } }, "dev_doorlock": { "label": "__l_doorlock__", "dependencies": { "dev_filter": "dev_doorlock" }, "fields": { "dev_select": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName" }, "dev_matchValue": { "label": "__l_dev_matchValue__", "type": "select", "optionLabels": [ "All", "Open", "Close" ] }, "dev_message": { "label": "__l_dev_message__", "helper": "__h_dev_message__" } } }, "dev_thermostat": { "label": "__l_thermostat__", "dependencies": { "dev_filter": "dev_thermostat" }, "fields": { "dev_select": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_thermostat:deviceName" }, "dev_matchValue": { "label": "__l_dev_matchValue__", "fields": { "dev_matchValueOperation": { "type": "select", "optionLabels": [ "<", ">", "=", "≤", "≥", "≠" ], "noneLabel": "All", "label": "__l_dev_matchValueOperation__", "helper": "__h_dev_matchValueOperation__" }, "dev_matchValueOperand": { "dependencies": { "dev_matchValueOperation": [ "<", ">", "==", "<=", ">=", "!=" ] }, "label": "__l_dev_matchValueOperand__", "helper": "__h_dev_matchValueOperand__" } } }, "dev_message": { "label": "__l_dev_message__", "helper": "__h_dev_message__" } } } } } } } } }, "autogenOnDeviceListUpdate": { "type": "checkbox", "rightLabel": "__rl_autogenOnDeviceListUpdate__", "helper": "__h_autogenOnDeviceListUpdate__" }, "normalizeRules": { "type": "checkbox", "rightLabel": "__rl_normalizeRules__", "helper": "__h_normalizeRules__" } } } } ================================================ FILE: modules/NotificationSend/index.js ================================================ /*** NotificationSend Z-Way HA module ******************************************* Version: 1.0.0 (c) Z-Wave.Me, 2020 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: Send a custom notification ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function NotificationSend(id, controller) { // Call superconstructor first (AutomationModule) NotificationSend.super_.call(this, id, controller); } inherits(NotificationSend, AutomationModule); _module = NotificationSend; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- NotificationSend.prototype.init = function(config) { NotificationSend.super_.prototype.init.call(this, config); var self = this; this.vDev = this.controller.devices.create({ deviceId: this.getName() + "_" + this.id, defaults: { deviceType: "toggleButton", metrics: { level: "on", // it is always on, but usefull to allow bind icon: "/ZAutomation/api/v1/load/modulemedia/" + this.getName() + "/icon.png", title: this.getInstanceTitle() } }, overlay: {}, handler: function(command) { if (command !== 'on') return; if (self.config.recipient_type === "user") { self.controller.notificationUserChannelsSend(self.config.user, self.config.message); } else if (self.config.recipient_type === "channel") { self.controller.notificationChannelSend(self.config.channel, self.config.message); } else { self.controller.notificationAllChannelsSend(self.config.message); } // update on ourself to allow catch this event self.vDev.set("metrics:level", "on"); }, moduleId: this.id }); }; NotificationSend.prototype.stop = function() { if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } Scenes.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/NotificationSend/lang/en.json ================================================ { "m_title":"Send a notification", "m_descr":"Send a notification to all, to a user or to a specific channel", "l_recipient_type": "Recipient", "l_all": "All users", "l_user": "User", "h_user": "Select the user to notify", "l_channel": "A specific communication channel", "h_channel": "Select the notifciation channel to use.", "l_message" : "Device specific message", "h_message" : "Enter a message that should be sent instead of the default notification. Empty to keep the default." } ================================================ FILE: modules/NotificationSend/module.json ================================================ { "dependencies": [], "singleton": false, "category": "notifications", "author": "Z-Wave.Me", "homepage": "https://z-wave.me", "icon": "icon.png", "moduleName": "NotificationSend", "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__", "recipient_type": "all", "message": "" }, "schema": { "type": "object", "properties": { "recipient_type": { "type": "string", "enum": [ "all", "user", "channel" ], "default": "all", "required": true }, "channel": { "dependencies": "recipient_type", "field": "enum", "datasource": "namespaces", "enum": "namespaces:notificationChannels:channelId", "required": true }, "user": { "dependencies": "recipient_type", "field": "enum", "datasource": "namespaces", "enum": "users:id", "required": true }, "message": { "required": true } } }, "options": { "fields": { "recipient_type": { "label": "__l_recipient_type__", "type": "select", "optionLabels": [ "__l_all__", "__l_user__", "__l_channel__" ] }, "channel": { "dependencies": { "recipient_type": "channel" }, "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:notificationChannels:channelName", "label": "__l_channel__", "helper": "__h_channel__" }, "user": { "dependencies": { "recipient_type": "user" }, "type": "select", "datasource": "namespaces", "optionLabels": "users:title", "required": true, "label": "__l_user__", "helper": "__h_user__", "title": "__t_user__" }, "message": { "label": "__l_message__", "helper": "__h_message__" } } } } ================================================ FILE: modules/OpenRemoteHelpers/index.js ================================================ /* RaZOR * * OpenRemote, the Home of the Digital Home. * Adopted for Z-Wave.Me Z-Way * Copyright 2008-2013, OpenRemote Inc. * * See the contributors.txt file in the distribution for a * full listing of individual contributors. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ /*** OpenRemoteHelpers Z-Way HA module ******************************************* Description: This module provides HTTP helpers for OpenRemote integration. Author: Pieter E. Zanstra Converted into Z-Way HA module: Poltorak Serguei Version 1.01.02 2015-02-12 (Pieter E. Zanstra) Added a second argument to metrics command, so the user can select other attributes than the default level-attribute (http://192.168.0.85:8083/OpenRemote/metrics/ZWayVDev_zway_26-0-37/icon) Without the second argument the call returns all device attributes as json. Closed the Switch statement with keyword default: to cater for erroneous function calls Version 1.01.01 2014-09-09 (Yurkin Vitaliy aivs@z-wave.me) Added metrics command to get metrics (http://192.168.0.85:8083/OpenRemote/metrics/ZWayVDev_zway_26-0-37) Version 1.01.01 2014-02-28 Testing all functions and bugfixing (Yurkin Vitaliy aivs@z-wave.me) Version 1.01.00 2013-12-11 Converted into Z-Way HA module Version 1.00.009 2013-09-27 Updated installation instruction in this file Version 1.00.008 2013-09-25 Adaptations for RazBerry v1.4 release - .SetWithDuration has been abandoned so DimmerSet has been changed again. Version 1.00.007 2013-08-07 - Added a number of SensorMultilevel variants (Humidity, Luminescence) Version 1.00.006 2013-07-31 - Fixed error with DimmerSet function Version 1.00.005 2013-07-21 - Fixed error in DimmerLevel - Now use .SetWithDuration (0) instead of .Set in DimmerSet function - Added function DimmerStatus (author Velouria) USAGE General parameters used in the calls are: N the number that designates a Z-Wave device I the number that refers to an instance (e.g. a channel in a dual binary switch Use 0 for single binary switches. For dual switches use 1 and 2 for channels 1 and 2 respectively) ${param} This variable is an OpenRemote system parameter that is used e.g. for passing values from a slider. S Scale (e.g. Watt, kWh, etc.) Status functions that return the value "on" or "off" are to be used in OpenRemote with sensors of the type:switch. Apply Regular expression: on|off in the http call. OpenRemote usage: http://IP:8083/OpenRemote//N/I/... SwitchBinaryOn/N/I SwitchBinaryOff/N/I SwitchBinaryStatus/N/I Add Regular Expression on|off in http call specification Do use Sensor type:switch with this command SwitchMultilevelSet/N/I/${param} SwitchMultilevelLevel/N/I Returns exact dimmer level SwitchMultilevelStatus/N/I Add Regular Expression on|off in http call specification Do use Sensor type:switch with this command AlarmStatus/N off means not triggered SensorBinaryStatus/N/I/Type off means not triggered ThermostatLevel/N ThermostatSet/N/${param} ThermostatSetMode/N/Mode ThermostatModeName/N Do use RexExp [\w\s]{1,} in the http-call to get rid of quotes BatteryLevel/N Do use Sensor type:Range with this command for use in slider MeterLevel/N/I/S Use S=0 for kWh; S=2 for Watts; MeterReset/N/I TemperatureLevel/N/I HumidityLevel/N/I SensorMultilevel/N/I/S DoorLock/N DoorUnlock/N TODO: - Add helpers for vDevs ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function OpenRemoteHelpers (id, controller) { // Call superconstructor first (AutomationModule) OpenRemoteHelpers.super_.call(this, id, controller); } inherits(OpenRemoteHelpers, AutomationModule); _module = OpenRemoteHelpers; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- OpenRemoteHelpers.prototype.init = function (config) { OpenRemoteHelpers.super_.prototype.init.call(this, config); // define global handler for HTTP requests OpenRemote = function(url, request) { var params = url.split("/"); params.shift(); // shift empty string before first / var cmd = params.shift(); var N = params.shift(); var I = params.shift(); switch(cmd) { case "SwitchBinaryOn": zway.devices[N].instances[I].SwitchBinary.Set(255); return "on"; case "SwitchBinaryOff": zway.devices[N].instances[I].SwitchBinary.Set(0); return "off"; case "SwitchBinaryStatus": return zway.devices[N].instances[I].SwitchBinary.data.level.value ? "on" : "off"; case "SwitchMultilevelSet": var level = params.shift(); zway.devices[N].instances[I].SwitchMultilevel.Set(level); return level; case "SwitchMultilevelStatus": return zway.devices[N].instances[I].SwitchMultilevel.data.level.value ? "on" : "off"; case "SwitchMultilevelLevel": return zway.devices[N].instances[I].SwitchMultilevel.data.level.value; case "StartLevelChange": var dir = params.shift(); return zway.devices[N].instances[I].SwitchMultilevel.StartLevelChange(dir); case "StopLevelChange": return zway.devices[N].instances[I].SwitchMultilevel.StopLevelChange(); case "AlarmStatus": // there are usually no instances for alarms return (zway.devices[N].AlarmSensor.data.level.value == 0) ? "off" : "on" ; case "SensorBinaryStatus": var T = params.shift(); // sensor type return (zway.devices[N].instances[I].SensorBinary.data[T].level.value == 0) ? "off" : "on" ; case "ThermostatLevel": var temp = I; // there are usually no instances for thermostats var mode = zway.devices[N].ThermostatMode ? zway.devices[N].ThermostatMode.data.mode.value : null; if (mode === null) { // no ThemorstatMode CC - pick up first mode for (var key in zway.devices[N].ThermostatSetPoint.data) { var _modeId = parseInt(key, 10); if (!isNaN(_modeId)) { mode = _modeId; break; } } } if (mode !== null) { return zway.devices[N].ThermostatSetPoint.data[mode].setVal.value; } return 0; case "ThermostatSet": var temp = I; // there are usually no instances for thermostats var mode = zway.devices[N].ThermostatMode ? zway.devices[N].ThermostatMode.data.mode.value : null; if (mode === null) { // no ThemorstatMode CC - pick up first mode for (var key in zway.devices[N].ThermostatSetPoint.data) { var _modeId = parseInt(key, 10); if (!isNaN(_modeId)) { mode = _modeId; break; } } } if (mode !== null) { zway.devices[N].ThermostatSetPoint.Set(mode, temp); return temp; } return 0; case "ThermostatSetMode": var mode = I; // there are usually no instances for thermostats if (! zway.devices[N].ThermostatMode.data[mode]) { for (var m in zway.devices[N].ThermostatMode.data) { if (zway.devices[N].ThermostatMode.data[m] && zway.devices[N].ThermostatMode.data[m].modeName && zway.devices[N].ThermostatMode.data[m].modeName.value.toLowerCase() == mode.toLowerCase()) { mode = m; break; } } } zway.devices[N].ThermostatMode.Set(mode); return mode; case "ThermostatModeName": // there are usually no instances for thermostats if (zway.devices[N].ThermostatMode) { var mode = zway.devices[N].ThermostatMode.data.mode.value; return zway.devices[N].ThermostatMode.data[mode].modeName.value; } else { // no ThemorstatMode CC - pick up first mode for (var key in zway.devices[N].ThermostatSetPoint.data) { var _modeId = parseInt(key, 10); if (!isNaN(_modeId)) { var mode = _modeId; return zway.devices[N].ThermostatSetPoint.data[mode].modeName.value } } } return "?"; case "BatteryLevel": // Battery is never in instances zway.devices[N].Battery.Get(); return zway.devices[N].Battery.data.last.value; case "MeterLevel": var S = params.shift(); zway.devices[N].instances[I].Meter.Get(); return zway.devices[N].instances[I].Meter.data[S].val.value; case "MeterReset": zway.devices[N].instances[I].Meter.Reset(); return 0; // just to return something case "TemperatureLevel": var S = 1; zway.devices[N].instances[I].SensorMultilevel.Get(); return zway.devices[N].instances[I].SensorMultilevel.data[S].val.value; case "HumidityLevel": var S = 5; zway.devices[N].instances[I].SensorMultilevel.Get(); return zway.devices[N].instances[I].SensorMultilevel.data[S].val.value; case "SensorMultilevel": var S = params.shift(); zway.devices[N].instances[I].SensorMultilevel.Get(); return zway.devices[N].instances[I].SensorMultilevel.data[S].val.value; case "DoorLock": // there are usually no instances for door locks zway.devices[N].DoorLock.Set(255); return "on"; case "DoorUnLock": // there are usually no instances for door locks zway.devices[N].DoorLock.Set(0); return "off"; case "DoorStatus": // there are usually no instances for door locks return zway.devices[N].DoorLock.data.level.value ? "on" : "off"; case "DeviceName": return zway.devices[N].instances[I].NodeNaming.data.nodename.value; case "DeviceLocation": return zway.devices[N].instances[I].NodeNaming.data.location.value; case "metrics": // used HA API for all device to get metrics if (I !== "") { attrib = "metrics:" + I; } else { attrib = "metrics"; } return this.controller.devices.get(N).get(attrib); // Your "case" statements may go after this line, but before keyword default: ! default: return "Error: Function " + cmd + " is not defined in OpenRemoteHelpers"; } }; ws.allowExternalAccess("OpenRemote", this.controller.auth.ROLE.USER); // login required }; OpenRemoteHelpers.prototype.stop = function () { OpenRemoteHelpers.super_.prototype.stop.call(this); ws.revokeExternalAccess("OpenRemote"); OpenRemote = null; }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/OpenRemoteHelpers/lang/de.json ================================================ { "m_title":"OpenRemote Assistent", "m_descr":"„Open Remote” ist ein sehr schönes generisches Benutzeroberflächensystem für Smart Home. Weitere Informationen dazu finden Sie auf http://www.openremote.org/. Diese App liefert die Daten Ihres Smart Home in einer Weise, die durch „Open Remote“ inkludiert werden. Weitere Informationen zur Anwendung von „Open Remote“ entnehmen Sie bitte der Open Remote Webseite.

            Einstellungen: Keine weiteren Einstellungen
            Usage: The OpenRemote UI will show the devices included. " } ================================================ FILE: modules/OpenRemoteHelpers/lang/en.json ================================================ { "m_title":"OpenRemote Helper", "m_descr":"Open remote is a very nice generic User Interface System for Smart Homes. You find more information about this service on http://www.openremote.org/. This app provides the data of your smart home into a way to be included by openremote. For more information how to use open remote please refer to the open remote website.

            Settings: No further settings.
            Usage: The OpenRemote UI will show the devices included." } ================================================ FILE: modules/OpenRemoteHelpers/lang/ru.json ================================================ { "m_title":"OpenRemote функции помощники", "m_descr":"Набор функций помощников для сервера OpenRemote." } ================================================ FILE: modules/OpenRemoteHelpers/module.json ================================================ { "dependencies": [], "singleton": true, "category": "support_external_ui", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"OpenRemoteHelpers", "version": "1.01.02", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__" }, "schema": { }, "options": { } } ================================================ FILE: modules/OpenWeather/index.js ================================================ /*** OpenWeather Extended Z-Way HA module ******************************************* Version: 1.2.0 (c) Z-Wave.Me, 2014 ----------------------------------------------------------------------------- Author: Serguei Poltorak , Niels Roche , Michael Pruefer Description: This module creates weather widget that shows you in addition to the temperature also humidity, pressure etc. You can also add a daylight widget that delivers on/off based on sunrise/sunset ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function OpenWeather (id, controller) { // Call superconstructor first (AutomationModule) OpenWeather.super_.call(this, id, controller); } inherits(OpenWeather, AutomationModule); _module = OpenWeather; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- OpenWeather.prototype.init = function (config) { OpenWeather.super_.prototype.init.call(this, config); var self = this; this.vDevWeather = self.controller.devices.create({ deviceId: "OpenWeather_" + this.id, defaults: { deviceType: "sensorMultiline", metrics: { multilineType: 'openWeather', probeTitle: 'Temperature' } }, overlay: { metrics: { scaleTitle: this.config.units === "celsius" ? '°C' : '°F', title: this.config.city } }, moduleId: this.id }); if (config.show_daylight) { this.vDevDayLight = self.controller.devices.create({ deviceId: "OpenWeatherDaylight_" + this.id, defaults: { deviceType: "sensorBinary", metrics: { probeTitle: 'daylight' } }, overlay: { metrics: { title: this.config.city + " Daylight" } }, moduleId: this.id }); } if (config.show_temp_hum) { this.vDevTemp = self.controller.devices.create({ deviceId: "OpenWeatherTemp" + this.id, defaults: { deviceType: "sensorMultilevel", metrics: { probeTitle: 'Temperature' } }, overlay: { metrics: { title: this.config.city + " Temperature", scaleTitle: this.config.units === "celsius" ? '°C' : '°F' } }, moduleId: this.id }); this.vDevHum = self.controller.devices.create({ deviceId: "OpenWeatherHum" + this.id, defaults: { deviceType: "sensorMultilevel", metrics: { probeTitle: 'Humidity' } }, overlay: { metrics: { title: this.config.city + " Humidity", scaleTitle: "%" } }, moduleId: this.id }); } if (config.show_wind) { this.vDevWindSpeed = self.controller.devices.create({ deviceId: "OpenWeatherWindspeed" + this.id, defaults: { deviceType: "sensorMultilevel", metrics: { probeTitle: 'WindSpeed' } }, overlay: { metrics: { title: this.config.city + " wind speed", scaleTitle: this.config.units_wind } }, moduleId: this.id }); this.vDevWindGust = self.controller.devices.create({ deviceId: "OpenWeatherWindGust" + this.id, defaults: { deviceType: "sensorMultilevel", metrics: { probeTitle: 'WindGust' } }, overlay: { metrics: { title: this.config.city + " wind gust", scaleTitle: this.config.units_wind } }, moduleId: this.id }); this.vDevWindDegree = self.controller.devices.create({ deviceId: "OpenWeatherWindDegree" + this.id, defaults: { deviceType: "sensorMultilevel", metrics: { probeTitle: 'WindDegree' } }, overlay: { metrics: { title: this.config.city + " wind degree", scaleTitle: "°" } }, moduleId: this.id }); } refresh_rate = self.config.refresh_rate *60 *1000; this.timer = setInterval(function() { self.fetchExtendedWeather(self); }, refresh_rate); self.fetchExtendedWeather(self); }; OpenWeather.prototype.stop = function () { OpenWeather.super_.prototype.stop.call(this); if (this.timer) clearInterval(this.timer); if (this.vDevWeather) { this.controller.devices.remove(this.vDevWeather.id); this.vDevWeather = null; } if (this.vDevDayLight) { this.controller.devices.remove(this.vDevDayLight.id); this.vDevDayLight = null; } if (this.vDevTemp) { this.controller.devices.remove(this.vDevTemp.id); this.vDevTemp = null; } if (this.vDevHum) { this.controller.devices.remove(this.vDevHum.id); this.vDevHum = null; } if (this.vDevWindSpeed) { this.controller.devices.remove(this.vDevWindSpeed.id); this.vDevWindSpeed = null; } if (this.vDevWindGust) { this.controller.devices.remove(this.vDevWindGust.id); this.vDevWindGust = null; } if (this.vDevWindDegree) { this.controller.devices.remove(this.vDevWindDegree.id); this.vDevWindDegree = null; } }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- OpenWeather.prototype.fetchExtendedWeather = function(instance) { var self = instance, langFile = self.loadModuleLang(), lang = self.controller.defaultLang; http.request({ url: "https://api.openweathermap.org/data/2.5/weather?q=" + encodeURIComponent(self.config.city) + "," + encodeURIComponent(self.config.country) +"&lang=" + lang + "&appid=" + encodeURIComponent(self.config.api), async: true, success: function(res) { try { var main = res.data.main, weather = res.data.weather, wind = res.data.wind, windspeed = wind.speed, windgust = wind.gust, winddeg = wind.deg, clouds = res.data.clouds, country = res.data.sys.country, weatherData = {'main': main,'weather': weather, 'wind': wind, 'clouds': clouds}, temp = Math.round((self.config.units === "celsius" ? main.temp - 273.15 : main.temp * 1.8 - 459.67) * 10) / 10, hum = main.humidity, icon = "https://openweathermap.org/img/w/" + weather[0].icon + ".png", flag = "https://openweathermap.org/images/flags/" + country.toLowerCase() + ".png", sunrise = Math.round(res.data.sys.sunrise) * 1000, icon_sunrise = "https://openweathermap.org/img/w/01d.png", sunset = Math.round(res.data.sys.sunset) * 1000, icon_sunset = "https://openweathermap.org/img/w/01n.png"; if (typeof windgust === 'undefined') windgust = windspeed; if (self.config.units_wind === "km/h") { windspeed = Math.round(windspeed * 3.6); windgust = Math.round(windgust * 3.6); } else if (self.config.units_wind === "mph") { windspeed = Math.round(windspeed * 2.237); windgust = Math.round(windgust * 2.237); } if (self.vDevDayLight) { var now = Date.now(); if (now > sunrise && now < sunset) { self.vDevDayLight.set("metrics:level", "on"); self.vDevDayLight.set("metrics:icon", icon_sunrise); } else { self.vDevDayLight.set("metrics:level", "off"); self.vDevDayLight.set("metrics:icon", icon_sunset); } } if (self.vDevTemp) { self.vDevTemp.set("metrics:level", temp); self.vDevTemp.set("metrics:icon", "temperature"); } if (self.vDevHum) { self.vDevHum.set("metrics:level", hum); self.vDevHum.set("metrics:icon", "humidity"); } if (self.vDevWindSpeed) { self.vDevWindSpeed.set("metrics:level", windspeed); self.vDevWindSpeed.set("metrics:icon", icon); } if (self.vDevWindGust) { self.vDevWindGust.set("metrics:level", windgust); self.vDevWindGust.set("metrics:icon", icon); } if (self.vDevWindDegree) { self.vDevWindDegree.set("metrics:level", winddeg); self.vDevWindDegree.set("metrics:icon", icon); } self.vDevWeather.set("metrics:zwaveOpenWeather", weatherData); self.vDevWeather.set("metrics:level", temp); self.vDevWeather.set("metrics:icon", icon); self.vDevWeather.set("metrics:country", country); self.vDevWeather.set("metrics:flag", flag); } catch (e) { self.addNotification("error", langFile.err_parse, "module"); } }, error: function() { self.addNotification("error", langFile.err_fetch, "module"); } }); }; ================================================ FILE: modules/OpenWeather/lang/de.json ================================================ { "m_title":"Lokales Wetter", "m_descr": "Die App zur Wettervorhersage erstellt ein Element, welches die vom Internetservice https://openweathermap.org/. aufbereiteten lokalen Wetterdaten anzeigt. Das Element zeigt die lokal vorherrschende Temperatur an, aber durch Klick auf das angelegte Element werden Luftfeuchtigkeit, Windgeschwindigkeit und Luftdruck gleicherma�en aufgezeigt.

            Einstellungen:
            • W�hlen Sie Ihr Land und Ihre Stadt. Sie haben die auch M�glichkeit St�dtenamen mit https://openweathermap.org/ abzugleichen.
            • Sie k�nnen sich die Temperatur sowohl in Celsius als auch in Fahrenheit anzeigen lassen.
            ", "l_units": "Einheiten", "l_country": "Land", "p_country": "Deutschland", "l_city": "Stadt", "p_city": "Berlin", "temp":"Temperatur", "err_parse":"Fehler beim Einlesen der Wetterinformationen.", "err_fetch":"Fehler beim Aktualisieren der Wetterinformationen.", "h_api":"Gehen Sie auf die URL: https://openweathermap.org/appid#get und folgen Sie den Anweisungen, um ein OpenWeather-Token zu erstellen.", "rl_show_daylight":"Tageslichtsensor anzeigen", "h_show_daylight":"Erzeugt einen binären Sensor, der sich je nach Sonnenauf- oder Sonnenuntergangszeit ein- (Tageslicht ein) oder ausschaltet (Tageslicht aus).", "rl_show_temp_hum": "Temperatur- und Feuchtigkeitssensor anzeigen", "h_show_temp_hum": "Erzeugt Temperatur- und Feuchtigkeitssensor.", "rl_show_wind": "Wind Sensoren anzeigen", "h_show_wind": "Erzeugt Wind Geschwindigkeits- und Richtungssensor.", "l_refresh_rate": "Refresh Rate (Minuten):", "h_refresh_rate": "Bitte beachten Sie die max. Anzahl der Abfragen Ihres Accounts!" } ================================================ FILE: modules/OpenWeather/lang/en.json ================================================ { "m_title":"Local Weather", "m_descr": "The Local Weather app creates an element showing the local weather data as provided by the Internet service https://openweathermap.org/. The element is showing the local temperature but clicking on the created element will reveal humidity, wind speed, air pressure as well.

            Setting:
            • Pick your country and your city. You may want to check city names with https://openweathermap.org/
            ", "l_units": "Units", "l_country": "Country", "p_country": "England", "l_city": "City", "p_city": "London", "temp":"Temperature", "err_parse":"Can not parse weather information.", "err_fetch":"Can not fetch weather information.", "h_api":"Go to the URL: https://openweathermap.org/appid#get and follow the instructions to create an OpenWeather token.", "rl_show_daylight": "Show daylight sensor", "h_show_daylight": "Generates a binary sensor that registers on (daylight on) or off (daylight off) based on the sunrise or sunset time.", "rl_show_temp_hum": "Show temperature and humidity sensors", "h_show_temp_hum": "Generates temperature and humidity sensor.", "rl_show_wind": "Show wind sensors", "h_show_wind": "Generates a wind speed and direction sensor.", "l_refresh_rate": "Refresh rate (minutes):", "h_refresh_rate": "Please remember about maximum allowed requests for your OpenWeatherMap account!" } ================================================ FILE: modules/OpenWeather/lang/ru.json ================================================ { "m_title":"Погодный информер", "m_descr": "Погодный информер предоставляет данные с https://openweathermap.org/.", "l_units": "Единица измерения", "l_country": "Страна", "p_country": "Россия", "l_city": "Город", "p_city": "Москва", "temp":"Температура", "err_parse":"Не возможно распарсить информацию о погоде.", "err_fetch":"Не возможно извлечь информацию о погоде.", "h_api":"Go to the URL: https://openweathermap.org/appid#get and follow the instructions to create an OpenWeather token.", "rl_show_daylight": "Show daylight sensor", "h_show_daylight": "Generates a binary sensor that registers on (daylight on) or off (daylight off) based on the sunrise or sunset time.", "rl_show_temp_hum": "Show temperature and humidity sensors", "h_show_temp_hum": "Generates temperature and humidity sensor.", "rl_show_wind": "Show wind sensors", "h_show_wind": "Generates a wind speed and direction sensor.", "l_refresh_rate": "Refresh rate (minutes):", "h_refresh_rate": "Please remember about maximum allowed requests for your OpenWeatherMap account!" } ================================================ FILE: modules/OpenWeather/module.json ================================================ { "dependencies": [], "singleton": false, "category": "support_external_dev", "author": "Z-Wave.Me", "homepage": "https://z-wave.me/z-way", "icon": "icon.png", "moduleName":"OpenWeather", "version": "1.3.0", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__", "units": "celsius", "units_wind": "km/h", "refresh_rate": "20", "city": "", "country": "", "show_daylight": false, "show_temp_hum": false, "show_wind": false }, "schema": { "type": "object", "properties": { "city": { "type": "string", "required": true }, "country": { "type": "string", "required": true }, "units": { "type": "string", "enum": ["celsius", "fahrenheit"], "required": true }, "units_wind": { "type": "string", "enum": ["km/h","m/s","mph"], "required": true }, "api": { "type": "string", "required": true }, "refresh_rate": { "type": "string", "enum": ["05","10","20","30","40","50","60"], "required": true }, "show_daylight": { "type": "boolean", "required": false }, "show_temp_hum": { "type": "boolean", "required": false }, "show_wind": { "type": "boolean", "required": false } }, "required": true }, "options": { "fields": { "city": { "label": "__l_city__", "placeholder": "__p_city__" }, "country": { "label": "__l_country__", "placeholder": "__p_country__" }, "units": { "label": "__l_units__", "type": "select" }, "units_wind": { "type": "select" }, "api": { "label": "API-Key", "placeholder": "API-Key", "helper":"__h_api__" }, "refresh_rate": { "label": "__l_refresh_rate__", "type": "select", "helper": "__h_refresh_rate__" }, "show_daylight": { "type": "checkbox", "rightLabel":"__rl_show_daylight__", "helper":"__h_show_daylight__" }, "show_temp_hum": { "type": "checkbox", "rightLabel":"__rl_show_temp_hum__", "helper":"__h_show_temp_hum__" }, "show_wind": { "type": "checkbox", "rightLabel":"__rl_show_wind__", "helper":"__h_show_wind__" } } } } ================================================ FILE: modules/OpenWeather/patchnotes.txt ================================================ v1.2.0 - add checkbox to add an optional daylight widget based on sunrise/sunset information ================================================ FILE: modules/PhilioHW/index.js ================================================ /*** PhilioHW Z-Way HA module ******************************************* Version: 1.0.2 (c) Z-Wave.Me, 2016 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: Support for Philio hardware ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function PhilioHW (id, controller) { // Call superconstructor first (AutomationModule) PhilioHW.super_.call(this, id, controller); } inherits(PhilioHW, AutomationModule); _module = PhilioHW; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- PhilioHW.prototype.init = function (config) { PhilioHW.super_.prototype.init.call(this, config); this.ZWAY_DATA_CHANGE_TYPE = { "Updated": 0x01, // Value updated or child created "Invalidated": 0x02, // Value invalidated "Deleted": 0x03, // Data holder deleted - callback is called last time before being deleted "ChildCreated": 0x04, // New direct child node created // ORed flags "PhantomUpdate": 0x40, // Data holder updated with same value (only updateTime changed) "ChildEvent": 0x80 // Event from child node }; var self = this; this.bindings = []; this.zwayReg = function (zwayName) { var zway = global.ZWave && global.ZWave[zwayName].zway; if (!zway) { return; } if (!zway.ZMEPHISetLED) { return; } self.zwayName = zwayName; self.bindings[zwayName] = []; if (zway.controller.data.philiohw) { self.registerButtons(zwayName); } else { self.controller.emit("ZWave.dataBind", self.bindings[zwayName], zwayName, "", function() { if (zway.controller.data.philiohw) { self.controller.emit("ZWave.dataUnbind", self.bindings[zwayName]); self.registerButtons(zwayName); } }, ""); zway.ZMEPHIGetButton(1); } }; this.zwayUnreg = function(zwayName) { self.controller.devices.remove("PhilioHW_" + self.id + "_" + zwayName + "_Tamper"); if (!self.config.no_battery) { self.controller.devices.remove("PhilioHW_" + self.id + "_" + zwayName + "_PowerFailure"); self.controller.devices.remove("PhilioHW_" + self.id + "_" + zwayName + "_BatteryLevel"); } // detach handlers if (self.bindings[zwayName]) { self.controller.emit("ZWave.dataUnbind", self.bindings[zwayName]); } self.bindings[zwayName] = null; }; this.controller.on("ZWave.register", this.zwayReg); this.controller.on("ZWave.unregister", this.zwayUnreg); // walk through existing ZWave if (global.ZWave) { for (var name in global.ZWave) { this.zwayReg(name); } } this.WPS_OFF = 0; this.WPS_REGISTRAR = 1; this.WPS_ENROLLEE = 2; this.WPS = this.WPS_OFF; // for LED indicator of WPS this.amINervous = false; // export function to show LED status with nervous blinks if (!PhilioHW.nervous) { PhilioHW.nervous = self.nervous; } } PhilioHW.prototype.stop = function () { var self = this; // unsign event handlers this.controller.off("ZWave.register", this.zwayReg); this.controller.off("ZWave.unregister", this.zwayUnreg); // detach handlers for (var name in this.bindings) { this.controller.emit("ZWave.dataUnbind", this.bindings[name]); } this.bindings = []; if (PhilioHW.nervous == self.nervous) { PhilioHW.nervous = undefined; } PhilioHW.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- PhilioHW.prototype.nervous = function(amINervous) { this.amINervous = amINervous; this.roundLED(); }; PhilioHW.prototype.roundLED = function() { var zwayName = this.zwayName; if (this.WPS === this.WPS_REGISTRAR) { global.ZWave[zwayName].zway.ZMEPHISetLED(0x11, 0x04); // LED steady On } else if (this.WPS === this.WPS_ENROLLEE) { global.ZWave[zwayName].zway.ZMEPHISetLED(0x11, 0x08); // Fast blink } else if (!this.config.no_battery && global.ZWave[zwayName].zway.controller.data.philiohw.powerFail.value) { global.ZWave[zwayName].zway.ZMEPHISetLED(0x11, 0x02); // LED off to save battery } else if (global.ZWave[zwayName].zway.controller.data.philiohw.tamper.state.value === 0) { global.ZWave[zwayName].zway.ZMEPHISetLED(0x11, 0x10); // Flashing LED } else if (this.amINervous) { // vice versa to idle (tamper.state = 2) if (!this.config.breath) { global.ZWave[zwayName].zway.ZMEPHISetLED(0x11, 0x20); // Breathing LED } else { global.ZWave[zwayName].zway.ZMEPHISetLED(0x11, 0x02); // LED off } } else if (global.ZWave[zwayName].zway.controller.data.philiohw.tamper.state.value === 2) { if (this.config.breath) { global.ZWave[zwayName].zway.ZMEPHISetLED(0x11, 0x20); // Breathing LED } else { global.ZWave[zwayName].zway.ZMEPHISetLED(0x11, 0x02); // LED off } } } PhilioHW.prototype.registerButtons = function(zwayName) { var self = this, langFile = this.loadModuleLang(); // get current power state and buttons states if (!self.config.no_battery) { global.ZWave[zwayName].zway.ZMEPHIGetPower(); } else { global.ZWave[zwayName].zway.controller.data.philiohw.powerFail.value = false; global.ZWave[zwayName].zway.controller.data.philiohw.batteryFail.value = false; global.ZWave[zwayName].zway.controller.data.philiohw.batteryLevel.value = 0; } global.ZWave[zwayName].zway.ZMEPHIGetButton(0); global.ZWave[zwayName].zway.ZMEPHIGetButton(1); global.ZWave[zwayName].zway.ZMEPHIGetButton(2); // Create vDev var tamperDev = this.controller.devices.create({ deviceId: "PhilioHW_" + this.id + "_" + zwayName + "_Tamper", defaults: { deviceType: "sensorBinary", probeType: "alarm_burglar", metrics: { icon: "alarm", level: global.ZWave[zwayName].zway.controller.data.philiohw.tamper.state.value !== 2 ? "on" : "off", title: 'Controller Tamper' } }, overlay: {}, handler: function(command, args) {}, moduleId: this.id }); if (!self.config.no_battery) { var powerFailureDev = this.controller.devices.create({ deviceId: "PhilioHW_" + this.id + "_" + zwayName + "_PowerFailure", defaults: { deviceType: "sensorBinary", probeType: "alarm_power", metrics: { icon: "alarm", level: global.ZWave[zwayName].zway.controller.data.philiohw.powerFail.value ? "on" : "off", title: 'Controller Power Failure' } }, overlay: {}, handler: function(command, args) {}, moduleId: this.id }); var batteryLevelDev = this.controller.devices.create({ deviceId: "PhilioHW_" + this.id + "_" + zwayName + "_BatteryLevel", defaults: { deviceType: "sensorMultilevel", probeType: "battery", metrics: { scaleTitle: '%', icon: "battery", level: (global.ZWave[zwayName].zway.controller.data.philiohw.batteryLevel.value || 0) * 10, title: 'Controller Backup Battery' } }, overlay: {}, handler: function(command, args) {}, moduleId: this.id }); } // Trap events this.controller.emit("ZWave.dataBind", self.bindings[zwayName], zwayName, "philiohw.tamper.state", function(type) { if (type === self.ZWAY_DATA_CHANGE_TYPE["Updated"]) { switch (this.value) { case 0: self.addNotification("critical", langFile.tamper_triggered, "controller"); tamperDev.set("metrics:level", "on"); break; case 2: self.addNotification("notification", langFile.tamper_idle, "controller"); tamperDev.set("metrics:level", "off"); break; } } self.roundLED(); }, ""); this.controller.emit("ZWave.dataBind", self.bindings[zwayName], zwayName, "philiohw.funcA.state", function(type) { switch (this.value) { case 3: // click self.WPS = self.WPS_REGISTRAR; self.roundLED(); setTimeout(function() { self.WPS = self.WPS_OFF; self.roundLED(); }, 30*1000); system("/lib/wifi-helper.sh WPSRegistrar"); break; case 2: // hold self.WPS = self.WPS_ENROLLEE; self.roundLED(); setTimeout(function() { self.WPS = self.WPS_OFF; self.roundLED(); }, 30*1000); system("/lib/wifi-helper.sh WPS"); break; } }, ""); this.controller.emit("ZWave.dataBind", self.bindings[zwayName], zwayName, "philiohw.funcB.state", function(type) { switch (this.value) { case 3: if (global.ZWave[zwayName].zway.controller.data.controllerState.value === 0) { global.ZWave[zwayName].zway.AddNodeToNetwork(true, true); } else if (global.ZWave[zwayName].zway.controller.data.controllerState.value === 1) { global.ZWave[zwayName].zway.AddNodeToNetwork(false, false); } break; case 2: if (global.ZWave[zwayName].zway.controller.data.controllerState.value === 0) { global.ZWave[zwayName].zway.RemoveNodeFromNetwork(true, true); } else if (global.ZWave[zwayName].zway.controller.data.controllerState.value === 5) { global.ZWave[zwayName].zway.RemoveNodeFromNetwork(false, false); } break; } }, ""); this.controller.emit("ZWave.dataBind", self.bindings[zwayName], zwayName, "controllerState", function(type) { if (this.value == 0) { global.ZWave[zwayName].zway.ZMEPHISetLED(0x10, 0x02); // idle } else if (this.value >= 1 && this.value <= 4) { global.ZWave[zwayName].zway.ZMEPHISetLED(0x10, 0x08); // including } else if (this.value >= 5 && this.value <= 7) { global.ZWave[zwayName].zway.ZMEPHISetLED(0x10, 0x10); // excluding } else { global.ZWave[zwayName].zway.ZMEPHISetLED(0x10, 0x20); // other } }, ""); if (!self.config.no_battery) { this.controller.emit("ZWave.dataBind", self.bindings[zwayName], zwayName, "philiohw.batteryLevel", function(type) { if (type === self.ZWAY_DATA_CHANGE_TYPE["Updated"]) { self.addNotification("notification", langFile.remaining_battery_level + (this.value * 10) + "%", "controller"); batteryLevelDev.set("metrics:level", (this.value * 10)); } }, ""); this.controller.emit("ZWave.dataBind", self.bindings[zwayName], zwayName, "philiohw.powerFail", function(type) { if (type === self.ZWAY_DATA_CHANGE_TYPE["Updated"]) { if (this.value) { self.addNotification("critical", langFile.power_failure, "controller"); powerFailureDev.set("metrics:level", "on"); if (self.batteryTimer) clearInterval(self.batteryTimer); self.batteryTimer = setInterval(function() { global.ZWave[zwayName].zway.ZMEPHIGetPower(); }, 60*1000); } else { self.addNotification("notification", langFile.power_recovery, "controller"); powerFailureDev.set("metrics:level", "off"); if (self.batteryTimer) clearInterval(self.batteryTimer); self.batteryTimer = setInterval(function() { global.ZWave[zwayName].zway.ZMEPHIGetPower(); }, 3600*1000); } } self.roundLED(); }, ""); this.controller.emit("ZWave.dataBind", self.bindings[zwayName], zwayName, "philiohw.batteryFail", function(type) { if (this.value) { self.addNotification("critical", langFile.battery_falure, "controller"); batteryLevelDev.set("metrics:level", 0); } }, ""); } // sync round LED with actual box status on start self.roundLED(); }; ================================================ FILE: modules/PhilioHW/lang/de.json ================================================ { "m_title":"Philio hardware support", "m_descr":"Support for Philio hardware buttons, LEDs, power and battery management", "tamper_triggered":"Controller opened or removed from wall", "tamper_idle":"Controller returned to normal position", "remaining_battery_level":"Remaining controller battery level is ", "battery_failure":"Backup battery failure!", "power_failure":"AC power lost", "power_recovery":"AC power recover", "l_no_battery": "No battery installed", "h_no_battery": "Check if controller have no battery installed to hide battery and power status widgets", "l_breath": "Enable contour LED", "h_breath": "Enable 'breathing' contour LED" } ================================================ FILE: modules/PhilioHW/lang/en.json ================================================ { "m_title":"Philio hardware support", "m_descr":"Support for Philio hardware buttons, LEDs, power and battery management", "tamper_triggered":"Controller opened or removed from wall", "tamper_idle":"Controller returned to normal position", "remaining_battery_level":"Remaining controller battery level is ", "battery_failure":"Backup battery failure!", "power_failure":"AC power lost", "power_recovery":"AC power recover", "l_no_battery": "No battery installed", "h_no_battery": "Check if controller have no battery installed to hide battery and power status widgets", "l_breath": "Enable contour LED", "h_breath": "Enable 'breathing' contour LED" } ================================================ FILE: modules/PhilioHW/lang/ru.json ================================================ { "m_title":"Поддержка контроллера Philio", "m_descr":"Поддержка оборудования Philio: кнопок, светодиода, детекции питания и управления аккумулятором", "tamper_triggered":"Контроллер вскрыли или сняли со стены", "tamper_idle":"Контроллер вернули в нормальное положение", "remaining_battery_level":"Оставшийся зарад аккумулятора контроллера ", "battery_failure":"Сбой в резервном аккумуляторе!", "power_failure":"Пропало питание", "power_recovery":"Питание восстановлено", "l_no_battery": "Аккумулятор не установлен", "h_no_battery": "Установите галочку на контроллерах без аккумулятора, чтобы скрыть виджеты батарейки и статуса питания", "l_breath": "Включить подсветку", "h_breath": "Включить 'дышашую' подсветку" } ================================================ FILE: modules/PhilioHW/module.json ================================================ { "dependencies": [], "singleton": true, "category": "support_external_dev", "author": "Z-Wave.Me", "homepage": "http://www.z-wave.me", "icon": "icon.png", "moduleName":"PhilioHW", "version": "1.0.1", "maturity": "stable", "repository": { "type": "git", "source": "https://github.com/Z-Wave-Me/home-automation" }, "defaults": { "title": "__m_title__", "description": "__m_descr__" }, "schema": { "type": "object", "properties": { "no_battery": { "type": "boolean", "default": false }, "breath": { "type": "boolean", "default": false } } }, "options": { "fields": { "no_battery": { "hidden": true, "type": "checkbox", "label": "__l_no_battery__", "helper": "__h_no_battery__" }, "breath": { "type": "checkbox", "label": "__l_breath__", "helper": "__h_breath__" } } } } ================================================ FILE: modules/PoppCam/index.js ================================================ /*** PoppCam Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Stanislav Morozov Description: This module stores params of PoppCam ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function PoppCam (id, controller) { // Call superconstructor first (AutomationModule) PoppCam.super_.call(this, id, controller); } inherits(PoppCam, AutomationModule); _module = PoppCam; // ---------------------------------------------------------------------------- // --- Module insPoppCam9828tance initialized // ---------------------------------------------------------------------------- _.extend(PoppCam.prototype, { init: function (config) { PoppCam.super_.prototype.init.call(this, config); var self=this; var that = this, vDevId = "CameraDevice_" + this.id; this.proxy_url = "/" + vDevId + "/stream"; var urlup = config.url + "/moveptz.xml?dir=up"; var urldown = config.url + "/moveptz.xml?dir=down"; var urlleft = config.url + "/moveptz.xml?dir=left"; var urlright = config.url + "/moveptz.xml?dir=right"; var urlstop = config.url + "/moveptz.xml?dir=stop"; var opener = function(command) { config.doorDevices.forEach(function(el) { var vDev = that.controller.devices.get(el); if (vDev) { var type = vDev.get("deviceType"); if (type === "switchBinary") { vDev.performCommand(command == "open" ? "on" : "off"); } else if (type === "doorlock") { vDev.performCommand(command); } } }); }; /* setting up the autentications seems not to work anymore this.setup = config.url+ "/login.xml?user=" + config.user + "&password=" + config.password + "&usr=" + config.user + "&pwd=" + config.password; if (this.setup) { http.request({ url: this.setup, async: true, auth: { login: config.user, password: config.password } }); } */ this.url = config.url + "/videostream.cgi?user=" + config.user + "&password=" + config.password; ws.proxify(this.proxy_url, this.url, config.user, config.password); this.vDev = this.controller.devices.create({ deviceId: vDevId, defaults: { deviceType: "camera", metrics: { icon: "camera", title: self.getInstanceTitle() } }, overlay: { metrics: { url: this.proxy_url, hasLeft: !!urlleft, hasRight: !!urlright, hasUp: !!urlup, hasDown: !!urldown, hasClose: !!urlstop || (config.doorDevices && config.doorDevices.length) } }, handler: function(command) { var url = null; console.log(urlstop); if (command == "left") { url = urlleft; } else if (command == "right") { url = urlright } else if (command == "up") { url = urlup; } else if (command == "down") { url = urldown; } else if (command == "open") { url = urlopen; opener(command); } else if (command == "close") { url = urlstop; // opener(command); } if (url) { http.request({ url: url, async: true, auth: { login: config.user, password: config.password } }); } }, moduleId: this.id }); }, stop: function () { PoppCam.super_.prototype.stop.call(this); ws.proxify(this.proxy_url, null); if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } } }); ================================================ FILE: modules/PoppCam/lang/de.json ================================================ { "m_title":"PoppCam", "m_descr":"Ermöglicht PoppCam Ünterstützung", "l_ip":"IP-Adresse der Kamera", "h_ip":"Beispielformat: 'http://IPADDRESS:PORT' - der Default Port ist 81", "l_user":"Benutzername", "h_user":"Sie benötigen den Benutzernamen, den Sie in der Kamerakonfiguration angegeben haben. Default ist 'admin'", "l_pw":"Passwort", "h_pw":"Sie benötigen das Passwort, dass Sie in der Kamerakonfiguration angegeben haben. Default ist 'admin'", "l_door_dev":"Türsensor" } ================================================ FILE: modules/PoppCam/lang/en.json ================================================ { "m_title":"PoppCam", "m_descr":"Support the PoppCam", "l_ip":"Camera IP URL", "h_ip":"in the format 'http://IPADDRESS:PORT'. Default Port is 81", "l_user":"Username", "h_user":"This is the username that was set up during camera configuration. Default is 'admin'", "l_pw":"Password", "h_pw":"This is the password that was set up during camera configuration. Default is 'admin'", "l_door_dev":"Door device" } ================================================ FILE: modules/PoppCam/lang/ru.json ================================================ { "m_title":"PoppCam", "m_descr":"Поддержка Камер PoppCam.", "l_ip":"URL Камеры", "h_ip":"В формате 'http://IPADDRESS:PORT'. По умолчанию порт 81", "l_user":"Имя пользователя", "h_user":"Имя пользователя, которое установлено в настройках камеры. По умолчанию 'admin'", "l_pw":"Пароль", "h_pw":"Пароль, который установлен в настройках камеры. По умолчанию 'admin'", "l_door_dev":"Устройство управления доступом двери" } ================================================ FILE: modules/PoppCam/module.json ================================================ { "singleton": false, "dependencies": [], "category": "surveillance", "author": "Z-Wave.Me", "homepage": "http://www.foscam.com/", "icon": "icon.png", "moduleName":"PoppCam", "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__", "url": "", "doorDevices": [] }, "schema": { "type": "object", "properties": { "url": { "required": true }, "user": { "required": false }, "password": { "format": "password", "required": false }, "doorDevices": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_doorlock:deviceId,namespaces:devices_switchBinary:deviceId", "required": false } } }, "required": false }, "options": { "fields": { "url": { "label": "__l_ip__", "helper": "__h_ip__", "required": true }, "user": { "label": "__l_user__", "helper": "__h_user__", "required": false }, "password": { "label": "__l_pw__", "helper": "__h_pw__", "required": false }, "doorDevices": { "label": "__l_door_dev__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_doorlock:deviceName,namespaces:devices_switchBinary:deviceName" } } } } } } ================================================ FILE: modules/RGB/index.js ================================================ /*** RGB Z-Way HA module ******************************************* Version: 1.0.1 (c) Z-Wave.Me, 2014 ----------------------------------------------------------------------------- Author: Poltorak Serguei -- changed by: Niels Roche Description: Binds several dimmers to make RGB device ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function RGB (id, controller) { // Call superconstructor first (AutomationModule) RGB.super_.call(this, id, controller); } inherits(RGB, AutomationModule); _module = RGB; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- RGB.prototype.init = function (config) { RGB.super_.prototype.init.call(this, config); var self = this; this.lastConfig = { r: 99, g: 99, b: 99 }; function levelToColor(vDev) { var val = vDev.get("metrics:level"); if (val === "on" || val === "255") { return 255; } else if (val === "off" || val === "0") { return 0; } else if (!isNaN(parseInt(val))) { return Math.round(parseInt(val) * 255.0/99.0); } } function colorToLevel(color) { return Math.round(color * 99.0 / 255.0); } this.vDev = this.controller.devices.create({ deviceId: "RGB_" + this.id, defaults: { deviceType: "switchRGBW", metrics: { icon: '', title: 'RGB ' + this.id, color: {r: 0, g: 0, b: 0}, level: 'off' } }, overlay: {}, handler: function (command, args) { switch(command){ case "off": self.controller.devices.get(self.config.red).performCommand(command); self.controller.devices.get(self.config.green).performCommand(command); self.controller.devices.get(self.config.blue).performCommand(command); break; case "exact": // change lastConfig values self.lastConfig.r = colorToLevel(args.red); self.lastConfig.g = colorToLevel(args.green); self.lastConfig.b = colorToLevel(args.blue); // set color self.controller.devices.get(self.config.red).performCommand("exact", { level: colorToLevel(args.red) } ); self.controller.devices.get(self.config.green).performCommand("exact", { level: colorToLevel(args.green) } ); self.controller.devices.get(self.config.blue).performCommand("exact", { level: colorToLevel(args.blue) } ); break; default: self.controller.devices.get(self.config.red).performCommand("exact", { level: self.lastConfig.r } ); self.controller.devices.get(self.config.green).performCommand("exact", { level: self.lastConfig.g } ); self.controller.devices.get(self.config.blue).performCommand("exact", { level: self.lastConfig.b } ); } }, moduleId: this.id }); this.handleLevel = function() { self.vDev.set("metrics:level", ( levelToColor(self.controller.devices.get(this.config.red)) || levelToColor(self.controller.devices.get(this.config.green)) || levelToColor(self.controller.devices.get(this.config.blue)) ) ? 'on' : 'off'); }; this.handleR = function () { self.vDev.set("metrics:color:r", levelToColor(self.controller.devices.get(self.config.red))); self.handleLevel(); }; this.handleG = function () { self.vDev.set("metrics:color:g", levelToColor(self.controller.devices.get(self.config.green))); self.handleLevel(); }; this.handleB = function () { self.vDev.set("metrics:color:b", levelToColor(self.controller.devices.get(self.config.blue))); self.handleLevel(); }; this.controller.devices.on(self.config.red, "change:metrics:level", this.handleR); this.controller.devices.on(self.config.green, "change:metrics:level", this.handleG); this.controller.devices.on(self.config.blue, "change:metrics:level", this.handleB); }; RGB.prototype.stop = function () { this.controller.devices.off(this.config.red, "change:metrics:level", this.handleR); this.controller.devices.off(this.config.green, "change:metrics:level", this.handleG); this.controller.devices.off(this.config.blue, "change:metrics:level", this.handleB); this.handleR = null; this.handleG = null; this.handleB = null; if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } RGB.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/RGB/lang/de.json ================================================ { "m_title":"RGB Steuerung aus 3 Dimmern", "m_descr":"RGB Geräte bieten einen entsprechenden Befehl zur Farbeinstellung. Einige veraltete Geräte unterstützen zwar diese spezielle Z-Wave Farbwahlmethode nicht, aber emulieren ihren Farbwechsel mit drei Dimmern für die Farben Rot, Grün und Blau. Diese App kombiniert diese drei Dimmer und bietet ein praktisches Element zur Farbwahl durch entsprechende Einstellung der drei Dimmerwerte.Beachten Sie bitte: Moderne Z-Wave Geräte sollten durch diese UI unterstützt werden ohne Installation zusätzlicher Apps wie dieser. Infolgedessen wird diese App nur für Altgeräte benötigt.

            Einstellungen:
            • Auswahl freier Dimmer von veralteten RGB Steuergeräten

            Anwendung:Ein neues Element wird zur Einstellung von Farbwerten erstellt. Bitte beachten Sie, dass in der Regel ein weiteres Dimmerelement angelegt wird, dass schließlich die gewählte Farbe dimmt. ", "l_red":"Rot", "l_green":"Grün", "l_blue":"Blau" } ================================================ FILE: modules/RGB/lang/en.json ================================================ { "m_title":"RGB Light from 3 Dimmers", "m_descr":"RGB devices offer a dedicated command to set their color. Some legacy devices dont support this defined Z-Wave Color Choosing Method but emulate their color change with three dimmers for the three colors Red, Green, Blue. This app combines these three dimmers and offers a convenient element to pick the color by setting the three dimmer values accordingly.Note: Modern Z-Wave devices shall be supported by this UI without installing any additional app like this. Hence, this app is only needed for legacy devices

            Settings:
            • Pick the free dimmers of the legacy RGB control device

            Usage:A new element is created allowing setting the color value. Please note that usually another dimmer element is created that will finally dim the device with the selected color.", "l_red":"Red", "l_green":"Green", "l_blue":"Blue" } ================================================ FILE: modules/RGB/lang/ru.json ================================================ { "m_title":"Устройство управления RGB (Цветной) лентой", "m_descr":"Создание устройства для управления RGB (Цветной) лентой основанное на трёх разных диммерах.", "l_red":"Красный", "l_green":"Зеленый", "l_blue":"Синий" } ================================================ FILE: modules/RGB/module.json ================================================ { "dependencies": [], "singleton": false, "category": "legacy_products_workaround", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"RGB", "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__", "red": null, "green": null, "blue": null }, "schema": { "type": "object", "properties": { "red": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchMultilevel:deviceId", "required": true }, "green": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchMultilevel:deviceId", "required": true }, "blue": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_switchMultilevel:deviceId", "required": true } }, "required": true }, "options": { "fields": { "red": { "label": "__l_red__", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchMultilevel:deviceName" }, "green": { "label": "__l_green__", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchMultilevel:deviceName" }, "blue": { "label": "__l_blue__", "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_switchMultilevel:deviceName" } } } } ================================================ FILE: modules/RemoteAccess/index.js ================================================ /*** RemoteAccess Z-Way HA module ******************************************* Version: 1.0.6 (c) Z-Wave.Me, 2015 ----------------------------------------------------------------------------- Author: Niels Roche Description: This module allows to set or get remote access values in admin profile via 'My settings' ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function RemoteAccess (id, controller) { // Call superconstructor first (AutomationModule) RemoteAccess.super_.call(this, id, controller); } inherits(RemoteAccess, AutomationModule); _module = RemoteAccess; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- RemoteAccess.prototype.init = function (config) { RemoteAccess.super_.prototype.init.call(this, config); var self = this, langFile = self.loadModuleLang(); this.path = self.config.path; this.zbw = null; this.checkIfTypeError = true; this.zbwTimerCount = 0; this.serviceTimerCount = 0; this.setZBWService = function() { // look if ZBW Service is available if (typeof ZBWConnect === 'function') { try { self.zbw = self.path? new ZBWConnect(self.path) : new ZBWConnect(); // find zbw by path or use (raspberry) location /etc/zbw as default if(!!self.zbw) { self.checkIfTypeError = self.zbw.getUserId() instanceof TypeError? true : false; self.checkIfTypeError = self.zbw.getActStatus() instanceof TypeError? true : false; self.checkIfTypeError = self.zbw.getSshStatus() instanceof TypeError? true : false; self.checkIfTypeError = self.zbw.getStatus() instanceof TypeError? true : false; } } catch (e) { if (self.serviceTimer) { console.log('Clear self.serviceTimer ... '); clearInterval(self.serviceTimer); } if (!self.zbwTimer) { // set interval (5 sec) to check for service again - max. 5 min self.zbwTimer = setInterval(function() { self.zbwTimerCount ++; console.log('zbwTimerCount:'+ self.zbwTimerCount); self.setZBWService(); }, 5000); } else if (self.zbwTimer && self.zbwTimerCount > 60) { console.log('Clear self.zbwTimer after 5 min'); // clear interval after 5 min clearInterval(self.zbwTimer); self.zbwTimer = undefined; self.addNotification("warning", langFile.zbw_service_timeout, "module"); self.addNotification("error", langFile.load_zbw_error, "module"); console.log(langFile.load_zbw_error,'Error:', e.message); } } if (!!self.zbw && !self.checkIfTypeError) { // start RemoteAccess functions console.log('success! start zbw ... '); self.startRemoteAccess(config, self.zbw, langFile); // clear interval if (self.zbwTimer) { console.log('Clear self.zbwTimer ... '); clearInterval(self.zbwTimer); self.zbwTimer = undefined; } if (self.serviceTimer) { console.log('Clear self.serviceTimer ... '); clearInterval(self.serviceTimer); self.serviceTimer = undefined; } } } else if (!self.serviceTimer) { // set interval (5 sec) to check for service again - max. 5 min self.serviceTimer = setInterval(function() { self.serviceTimerCount ++; console.log('serviceTimerCount:', self.serviceTimerCount); self.setZBWService(); }, 5000); } else if (self.serviceTimer && self.serviceTimerCount > 60) { // clear interval after 5 min console.log('Clear self.serviceTimer after 5 min'); clearInterval(self.serviceTimer); self.serviceTimer = undefined; self.addNotification("warning", langFile.zbw_service_timeout, "module"); } }; // initialize service self.setZBWService(); }; RemoteAccess.prototype.stop = function () { if (this.serviceTimer) { clearInterval(this.serviceTimer); this.serviceTimer = undefined; } if (this.zbwTimer) { clearInterval(this.zbwTimer); this.zbwTimer = undefined; } RemoteAccess.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- RemoteAccess.prototype.startRemoteAccess = function (config, zbw, langFile) { var self = this; this.updateRemoteData = function () { try { self.config.userId = zbw.getUserId(); self.config.actStatus = zbw.getActStatus(); self.config.sshStatus = zbw.getSshStatus(); self.config.zbwStatus = zbw.getStatus(); } catch(e) { self.addNotification("warning", langFile.setup_config_zbw_error + e.message, "module"); } }; this.setRemoteConfigurations = function () { try{ var raSshStatus = zbw.getSshStatus(), raStatus = zbw.getStatus(), password = self.config.pass? self.config.pass : '', currDate = new Date(); // update userId (after restoring backup userId in the config might not be equal userId in ZBW service) self.config.userId = zbw.getUserId(); // stop and start zbw connect if necessary if(raStatus !== false && raStatus === self.config.zbwStatus && (raSshStatus !== self.config.sshStatus || password)){ zbw.setStatus(false); // stop zbw console.log('--- Stopping ZBW Connect Service'); setTimeout(function() { zbw.setStatus(true); console.log('--- Starting ZBW Connect Service'); }, 7000); // wait 7 sec to start zbw } // set remote ssh status if(raSshStatus !== self.config.sshStatus){ zbw.setSshStatus(self.config.sshStatus); self.config.lastChange.sshStatus = currDate; } // start/stop zbw connect - start/stop RemoteAccess if(raStatus !== self.config.zbwStatus){ zbw.setStatus(self.config.zbwStatus); self.config.zbwStatus === false ? console.log('--- Stopping ZBW Connect Service') : console.log('--- Starting ZBW Connect Service'); self.config.lastChange.zbwStatus = currDate; self.config.actStatus = zbw.getActStatus(); } // set remote password if(password){ zbw.setPass(password); self.config.pass = ''; self.config.lastChange.pass = currDate; } if(raSshStatus !== self.config.sshStatus || raStatus !== self.config.zbwStatus || password) { self.addNotification("notification", langFile.config_changed_successful, "module"); } } catch(e) { self.addNotification("error", langFile.config_changed_error + e.message, "module"); } }; // run first time to get the values from zbw module if(self.config.userId === '' || self.config.actStatus === '' || self.config.sshStatus === '' || self.config.zbwStatus === '') { self.updateRemoteData(); } else { self.setRemoteConfigurations(); } }; ================================================ FILE: modules/RemoteAccess/lang/de.json ================================================ { "m_title":"Remote-Zugriff", "m_descr":"Diese App wird benötig, um über die SmartHome UI den Remote-Zugriff zu konfigurieren.

            Hinweis:

            Ein Entfernen oder Stoppen der App wird den Fernzugriff nicht beenden, wenn dieser nicht vorher explizit unter Einstellungen > Management > Verwaltung des Fernzugriffes deaktiviert wurde.", "l_ra_path":"Absoluter Pfad auf dem Server zu ZBWConnect:", "h_ra_path":"z.B. /etc/zbw oder ./zbw - wird auf dem Raspberry nicht benötigt", "load_zbw_error":"ZBWConnect Modul konnte nicht initialisiert werden. Fehler: ", "setup_config_zbw_error":"Der Remote-Zugriff konnte nicht initialisiert werden. Bitte checken Sie Ihre Internetverbindung und starten Sie den Service unter 'Apps > Active > Remote Access' erneut.", "config_changed_successful":"Konfiguration des Remote-Zugriffs erfolgreich geändert.", "config_changed_error":"Konfigurationsproblem: ", "zbw_service_timeout":"The connection to the ZBW Service has timed out. Please restart the App 'Remote Access' and try it again." } ================================================ FILE: modules/RemoteAccess/lang/en.json ================================================ { "m_title":"Remote Access", "m_descr":"This app is required to configure the remote access via the Smart Home UI.

            Note:

            Removing or stopping the app will not stop the remote access, if it has not been disabled explicitly under Settings > Management > Remote Access in advance.", "l_ra_path":"Absolute path to ZBWConnect on server:", "h_ra_path":"e.g. /etc/zbw or ./zbw - not necessary on raspberry", "load_zbw_error":"Cannot initialize ZBWConnect module. Error: ", "setup_config_zbw_error":"Remote Access is not initialized yet, you probably need to connect to the internet and restart the service in 'Apps > Active > Remote Access'", "config_changed_successful":"Configuration of remote access has changed successfully.", "config_changed_error":"Configuration error: ", "zbw_service_timeout":"The connection to the ZBW Service has timed out. Please restart the App 'Remote Access' and try it again." } ================================================ FILE: modules/RemoteAccess/module.json ================================================ { "singleton" : true, "dependencies" : [], "category" : "security", "author" : "Z-Wave.Me", "homepage" : "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"RemoteAccess", "version" : "1.0.6", "maturity" : "stable", "repository" : { "type" : "git", "source" : "https://github.com/Z-Wave-Me/home-automation" }, "defaults" : { "title" : "__m_title__", "description" : "__m_descr__", "path":"", "userId":"", "actStatus":"", "sshStatus":"", "zbwStatus":"", "pass":"", "lastChange":{} }, "schema": { "type": "object", "properties": { "path": { "required": false } } }, "options": { "fields": { "path": { "label": "__l_ra_path__", "helper": "__h_ra_path__" } } } } ================================================ FILE: modules/RemoteAccess/patchnotes.txt ================================================ v1.0.6 - remove initial restart v1.0.5 - add initial restart to fix zbw service problems - interval added to check if ZBW service is available - check max. 5 minutes ================================================ FILE: modules/RoundRobinScenes/index.js ================================================ /*** RoundRobinScenes Z-Way HA module ******************************************* Version: 1.1.0 (c) Z-Wave.Me, 2017 ----------------------------------------------------------------------------- Author: Poltorak Serguei Description: Switches scenes in round robin policy ******************************************************************************/ // ---------------------------------------------------------------------------- // --- Class definition, inheritance and setup // ---------------------------------------------------------------------------- function RoundRobinScenes (id, controller) { // Call superconstructor first (AutomationModule) RoundRobinScenes.super_.call(this, id, controller); } inherits(RoundRobinScenes, AutomationModule); _module = RoundRobinScenes; // ---------------------------------------------------------------------------- // --- Module instance initialized // ---------------------------------------------------------------------------- RoundRobinScenes.prototype.init = function (config) { RoundRobinScenes.super_.prototype.init.call(this, config); this.currentSceneIndex = -1; var self = this; this.vDev = this.controller.devices.create({ deviceId: "RoundRobinScene_" + this.id, defaults: { deviceType: "toggleButton", metrics: { level: "on", // it is always on, but usefull to allow bind icon: "gesture", title: self.getInstanceTitle() } }, overlay: {}, handler: function () { self.currentSceneIndex++; self.currentSceneIndex %= self.config.scenes.length; var vDev = self.controller.devices.get(self.config.scenes[self.currentSceneIndex]); if (vDev) { vDev.performCommand("on"); } self.vDev.set("metrics:level", "on"); // update on ourself to allow catch this event }, moduleId: this.id }); if (this.config.trackCurrent) { this.tracker = function(vDev) { for (var i in self.config.scenes) { if (self.config.scenes[i] === vDev.id) { self.currentSceneIndex = i; } } }; for (var i in this.config.scenes) { this.controller.devices.on(self.config.scenes[i], "change:metrics:level", this.tracker); } } }; RoundRobinScenes.prototype.stop = function () { if (this.config.trackCurrent) { for (var i in this.config.scenes) { this.controller.devices.off(self.config.scenes[i], "change:metrics:level", this.tracker); } this.tracker = null; } if (this.vDev) { this.controller.devices.remove(this.vDev.id); this.vDev = null; } RoundRobinScenes.super_.prototype.stop.call(this); }; // ---------------------------------------------------------------------------- // --- Module methods // ---------------------------------------------------------------------------- ================================================ FILE: modules/RoundRobinScenes/lang/de.json ================================================ { "m_title":"Sequenzieller Szenenumschalter", "m_descr":"Diese Anwendung erstellt ein simples UI Element zur Schaltung von einer Reihe auserwhlter Szenen, welche immer eine nach der anderen (Round Robin) ausgefhrt werden. Im Falle von lediglich zwei Szenen schaltet dieses Element immer zwischen beiden hin und her. Diese Einstellung ist insbesondere dann sinnvoll, wenn mehrere Szenen innerhalb eines Raumes oder Bereiches immer in einer bestimmten Reihenfolge ausgefhrt werden sollen. Beispiel fr Szenen im Schlafzimmer:
            1 - Beim Betreten des Raumes, Erleuchten des Raumes
            2 - Gang zum Bett, Leichtes Abdimmen des Lichtes
            3 - Schlafen, Alles ist ausgeschalten.
            4 - Aufwachen am Morgen, Licht leicht gedimmt einschalten.

            Einstellungen:
            • Auffhren der Szenen, die nacheinander aktiviert werden sollen.
            ", "l_options":"Zu aktivierende Szenen/Aktionen:", "rl_track_current":"Change index of the next scene to be executed according to explicit manually activated scene" } ================================================ FILE: modules/RoundRobinScenes/lang/en.json ================================================ { "m_title":"Round Robin Scene Switcher", "m_descr":"This application creates a simple UI element allowing the switching a series of selected scenes in round robin (one after each other). In case there are only two scenes defined this element will toggle between these two scenes. This setup is particularly useful if there are multiple scenes for one room or zone that are typically executed in a defined order (Example: Three scenes in sleeping room: 1 enter the room –> light up the room, 2 move to bed -> dim the light but still keep it up, 3 sleep -> everything is off, 4 - > wakeup in the morning -> only dim the light)

            Settings:
            • List of scenes to be activates in round robin
            ", "l_options":"List of scenes to activate", "rl_track_current":"Change index of the next scene to be executed according to explicit manually activated scene" } ================================================ FILE: modules/RoundRobinScenes/lang/ru.json ================================================ { "m_title":"Запуск сцен одна за одной по кругу", "m_descr":"Round-robin алгоритм для циклического запуска сцен. При каждом запуске этой сцены будет запускаться следующая сцена из списка.", "l_options":"Список сцен", "rl_track_current":"Обновлять индекс следующей запускаемой сцены, учитывая явный ручной запуск сцен" } ================================================ FILE: modules/RoundRobinScenes/module.json ================================================ { "dependencies": [], "singleton": false, "category": "automation_basic", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName":"RoundRobinScenes", "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__", "scenes": [], "trackCurrent": false }, "schema": { "type": "object", "properties": { "scenes": { "type": "array", "items": { "field": "enum", "datasource": "namespaces", "enum": "namespaces:devices_toggleButton:deviceId", "required": true } }, "trackCurrent": { "type": "boolean" } }, "required": true }, "options": { "fields": { "scenes": { "label": "__l_options__", "fields": { "item": { "type": "select", "datasource": "namespaces", "field": "optionLabels", "optionLabels": "namespaces:devices_toggleButton:deviceName" } } }, "trackCurrent": { "label": "", "rightLabel": "__rl_track_current__" } } } } ================================================ FILE: modules/Rules/index.js ================================================ /*** Rules Z-Way HA module ******************************************* Version: 1.0.1 (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 'compare': if (test.devices.length == 2) { test.devices.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 'compare': /* compare = { type: 'compare', operator: '>=', devices: [{ deviceId: 'aaa', type: 'aaa' }, { deviceId: 'bbb, type: 'bbb' } ] } */ if (test.devices.length == 2) { test.devices.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 = null, vDev2 = null, level = undefined, level2 = undefined; if (test.type !== 'nested' && test.type !== 'time' && test.type !== 'compare') { vDev = self.controller.devices.get(test.deviceId); level = !!vDev ? vDev.get("metrics:level") : undefined; } else if (test.type == 'compare' && test.devices.length == 2) { vDev = self.controller.devices.get(test.devices[0].deviceId); vDev2 = self.controller.devices.get(test.devices[1].deviceId); level = !!vDev ? vDev.get("metrics:level") : undefined; level2 = !!vDev2 ? vDev2.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; case 'compare': res = res && self.op(level, test.operator, level2); break; default: break; } }); return res; }, "or": function(tree) { var res = false, self = this; tree.tests.forEach(function(test) { var vDev = null, vDev2 = null, level = undefined, level2 = undefined; if (test.type !== 'nested' && test.type !== 'time' && test.type !== 'compare') { vDev = self.controller.devices.get(test.deviceId); level = !!vDev ? vDev.get("metrics:level") : undefined; } else if (test.type == 'compare' && test.devices.length == 2) { vDev = self.controller.devices.get(test.devices[0].deviceId); vDev2 = self.controller.devices.get(test.devices[1].deviceId); level = !!vDev ? vDev.get("metrics:level") : undefined; level2 = !!vDev2 ? vDev2.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; case 'compare': res = res || self.op(level, test.operator, level2); 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 !== '') { this.controller.notificationChannelSend(notification.target, notification.message ? notification.message : this.getInstanceTitle()); } }; Rules.prototype.getConfigTimeout = function(configTimeout) { return !!!configTimeout ? 0 : Math.floor(configTimeout * 1000); // !!! > proof for undefined and null }; ================================================ FILE: modules/Rules/lang/de.json ================================================ { "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." } ================================================ FILE: modules/Rules/lang/en.json ================================================ { "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" } ================================================ FILE: modules/Rules/lang/ru.json ================================================ { "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." } ================================================ FILE: modules/Rules/module.json ================================================ { "singleton": false, "dependencies": [], "category": "system", "author": "Z-Wave.Me", "homepage": "http://razberry.z-wave.me", "icon": "icon.png", "moduleName": "Rules", "version": "1.0.1", "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": {} } ================================================ FILE: modules/Rules/patchnotes.txt ================================================ v1.0.1 - support to compare 2 devices v1.0.0 - delay for trigger - logical rule features as "advanced" feature - reverse trigger - Basis: If-Then (v2.5.1), LogicalRules (v1.4.1) ================================================ FILE: modules/Scenes/htdocs/js/postRender.js ================================================ 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 = $('