├── LICENSE ├── README.md ├── devicetypes └── jjhuff │ └── rheem-econet-water-heater.src │ └── rheem-econet-water-heater.groovy └── smartapps └── jjhuff └── rheem-econet-connect.src └── rheem-econet-connect.groovy /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | SmartThings Rheem EcoNet 2 | =============== 3 | 4 | NOTE: this doesn't work anymore due to changes in Rheem's API. 5 | 6 | This is based on https://github.com/copy-ninja/SmartThings_RheemEcoNet with updates for the new API 7 | 8 | SmartThings installation instructions: 9 | -------------------------------------- 10 | 1) Log in to your the SmartThings IDE. If you don't have a login yet, create one. 11 | 12 | 2) Load contents of [Smart App](smartapps/jjhuff/rheem-econet-connect.src/rheem-econet-connect.groovy) in SmartApps section. From IDE, navigate to My SmartApps > + New SmartApp > From Code. Click Save. Click Publish > "For Me" 13 | 14 | 3) Load contents of [Device Handler](devicetypes/jjhuff/rheem-econet-water-heater.src/rheem-econet-water-heater.groovy) in Device Handlers section. From IDE, navigate to My Device Handler > + New SmartDevice > From Code. Click Save. Click Publish "For Me" 15 | 16 | 4) In your mobile app, tap the "+", go to "My Apps", furnish your log in details and pick your gateway brand, and a list of devices will be available for you to pick 17 | 18 | Donations 19 | --------- 20 | If you like this project, please consider donating to one of these: 21 | * [EFF](https://www.eff.org/) 22 | * [Let's Encrypt](https://letsencrypt.org/) 23 | -------------------------------------------------------------------------------- /devicetypes/jjhuff/rheem-econet-water-heater.src/rheem-econet-water-heater.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Rheem Econet Water Heater 3 | * 4 | * Copyright 2017 Justin Huff 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Last Updated : 2017-01-04 16 | * 17 | * Based on https://github.com/copy-ninja/SmartThings_RheemEcoNet 18 | */ 19 | metadata { 20 | definition (name: "Rheem Econet Water Heater", namespace: "jjhuff", author: "Justin Huff") { 21 | capability "Actuator" 22 | capability "Refresh" 23 | capability "Sensor" 24 | capability "Switch" 25 | capability "Thermostat Heating Setpoint" 26 | 27 | command "heatLevelUp" 28 | command "heatLevelDown" 29 | command "updateDeviceData", ["string"] 30 | } 31 | 32 | simulator { } 33 | 34 | tiles { 35 | valueTile("heatingSetpoint", "device.heatingSetpoint", inactiveLabel: false, width: 2, height: 2) { 36 | state("heatingSetpoint", label:'${currentValue}°', 37 | backgroundColors:[ 38 | [value: 90, color: "#f49b88"], 39 | [value: 100, color: "#f28770"], 40 | [value: 110, color: "#f07358"], 41 | [value: 120, color: "#ee5f40"], 42 | [value: 130, color: "#ec4b28"], 43 | [value: 140, color: "#ea3811"] 44 | ] 45 | ) 46 | } 47 | standardTile("heatLevelUp", "device.switch", canChangeIcon: false, decoration: "flat" ) { 48 | state("heatLevelUp", action:"heatLevelUp", icon:"st.thermostat.thermostat-up", backgroundColor:"#F7C4BA") 49 | } 50 | standardTile("heatLevelDown", "device.switch", canChangeIcon: false, decoration: "flat") { 51 | state("heatLevelDown", action:"heatLevelDown", icon:"st.thermostat.thermostat-down", backgroundColor:"#F7C4BA") 52 | } 53 | 54 | standardTile("switch", "device.switch", canChangeIcon: false, decoration: "flat" ) { 55 | state "on", label: 'On', action: "switch.off", 56 | icon: "st.switches.switch.on", backgroundColor: "#79b821" 57 | state("off", label: 'Off', action: "switch.on", 58 | icon: "st.switches.switch.off", backgroundColor: "#ffffff") 59 | } 60 | 61 | standardTile("refresh", "device.switch", decoration: "flat") { 62 | state("default", action:"refresh.refresh", icon:"st.secondary.refresh") 63 | } 64 | 65 | main "heatingSetpoint" 66 | details(["heatingSetpoint", "heatLevelUp", "heatLevelDown", "switch", "refresh"]) 67 | } 68 | } 69 | 70 | def parse(String description) { } 71 | 72 | def refresh() { 73 | log.debug "refresh" 74 | parent.refresh() 75 | } 76 | 77 | def on() { 78 | parent.setDeviceEnabled(this.device, true) 79 | sendEvent(name: "switch", value: "off") 80 | } 81 | 82 | def off() { 83 | parent.setDeviceEnabled(this.device, false) 84 | sendEvent(name: "switch", value: "off") 85 | } 86 | 87 | def setHeatingSetpoint(Number setPoint) { 88 | /*heatingSetPoint = (heatingSetPoint < deviceData.minTemp)? deviceData.minTemp : heatingSetPoint 89 | heatingSetPoint = (heatingSetPoint > deviceData.maxTemp)? deviceData.maxTemp : heatingSetPoint 90 | */ 91 | sendEvent(name: "heatingSetpoint", value: setPoint, unit: "F") 92 | parent.setDeviceSetPoint(this.device, setPoint) 93 | } 94 | 95 | def heatLevelUp() { 96 | def setPoint = device.currentValue("heatingSetpoint") 97 | setPoint = setPoint + 1 98 | setHeatingSetpoint(setPoint) 99 | } 100 | 101 | def heatLevelDown() { 102 | def setPoint = device.currentValue("heatingSetpoint") 103 | setPoint = setPoint - 1 104 | setHeatingSetpoint(setPoint) 105 | } 106 | 107 | def updateDeviceData(data) { 108 | sendEvent(name: "heatingSetpoint", value: data.setPoint, unit: "F") 109 | sendEvent(name: "switch", value: data.isEnabled ? "on" : "off") 110 | } 111 | -------------------------------------------------------------------------------- /smartapps/jjhuff/rheem-econet-connect.src/rheem-econet-connect.groovy: -------------------------------------------------------------------------------- 1 | /** 2 | * Rheem EcoNet (Connect) 3 | * 4 | * Copyright 2017 Justin Huff 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 7 | * in compliance with the License. You may obtain a copy of the License at: 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed 12 | * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License 13 | * for the specific language governing permissions and limitations under the License. 14 | * 15 | * Last Updated : 1/1/17 16 | * 17 | * Based on https://github.com/copy-ninja/SmartThings_RheemEcoNet 18 | * 19 | */ 20 | definition( 21 | name: "Rheem EcoNet (Connect)", 22 | namespace: "jjhuff", 23 | author: "Justin Huff", 24 | description: "Connect to Rheem EcoNet", 25 | category: "SmartThings Labs", 26 | iconUrl: "http://smartthings.copyninja.net/icons/Rheem_EcoNet@1x.png", 27 | iconX2Url: "http://smartthings.copyninja.net/icons/Rheem_EcoNet@2x.png", 28 | iconX3Url: "http://smartthings.copyninja.net/icons/Rheem_EcoNet@3x.png") 29 | 30 | 31 | preferences { 32 | page(name: "prefLogIn", title: "Rheem EcoNet") 33 | page(name: "prefListDevice", title: "Rheem EcoNet") 34 | } 35 | 36 | /* Preferences */ 37 | def prefLogIn() { 38 | def showUninstall = username != null && password != null 39 | return dynamicPage(name: "prefLogIn", title: "Connect to Rheem EcoNet", nextPage:"prefListDevice", uninstall:showUninstall, install: false) { 40 | section("Login Credentials"){ 41 | input("username", "email", title: "Username", description: "Rheem EcoNet Email") 42 | input("password", "password", title: "Password", description: "Rheem EcoNet password (case sensitive)") 43 | } 44 | section("Advanced Options"){ 45 | input(name: "polling", title: "Server Polling (in Minutes)", type: "int", description: "in minutes", defaultValue: "5" ) 46 | } 47 | } 48 | } 49 | 50 | def prefListDevice() { 51 | if (login()) { 52 | def waterHeaterList = getWaterHeaterList() 53 | if (waterHeaterList) { 54 | return dynamicPage(name: "prefListDevice", title: "Devices", install:true, uninstall:true) { 55 | section("Select which water heater to use"){ 56 | input(name: "waterheater", type: "enum", required:false, multiple:true, metadata:[values:waterHeaterList]) 57 | } 58 | } 59 | } else { 60 | return dynamicPage(name: "prefListDevice", title: "Error!", install:false, uninstall:true) { 61 | section(""){ paragraph "Could not find any devices" } 62 | } 63 | } 64 | } else { 65 | return dynamicPage(name: "prefListDevice", title: "Error!", install:false, uninstall:true) { 66 | section(""){ paragraph "The username or password you entered is incorrect. Try again. " } 67 | } 68 | } 69 | } 70 | 71 | 72 | /* Initialization */ 73 | def installed() { initialize() } 74 | def updated() { 75 | unsubscribe() 76 | initialize() 77 | } 78 | def uninstalled() { 79 | unschedule() 80 | unsubscribe() 81 | getAllChildDevices().each { deleteChildDevice(it) } 82 | } 83 | 84 | def initialize() { 85 | // Set initial states 86 | state.polling = [ last: 0, rescheduler: now() ] 87 | 88 | // Create selected devices 89 | def waterHeaterList = getWaterHeaterList() 90 | def selectedDevices = [] + getSelectedDevices("waterheater") 91 | selectedDevices.each { 92 | def dev = getChildDevice(it) 93 | def name = waterHeaterList[it] 94 | if (dev == null) { 95 | try { 96 | addChildDevice("jjhuff", "Rheem Econet Water Heater", it, null, ["name": "Rheem Econet: " + name]) 97 | } catch (e) { 98 | log.debug "addChildDevice Error: $e" 99 | } 100 | } 101 | } 102 | 103 | // Remove unselected devices 104 | /*def deleteDevices = (selectedDevices) ? (getChildDevices().findAll { !selectedDevices.contains(it.deviceNetworkId) }) : getAllChildDevices() 105 | deleteDevices.each { deleteChildDevice(it.deviceNetworkId) } */ 106 | 107 | //Subscribes to sunrise and sunset event to trigger refreshes 108 | subscribe(location, "sunrise", runRefresh) 109 | subscribe(location, "sunset", runRefresh) 110 | subscribe(location, "mode", runRefresh) 111 | subscribe(location, "sunriseTime", runRefresh) 112 | subscribe(location, "sunsetTime", runRefresh) 113 | 114 | //Refresh devices 115 | runRefresh() 116 | } 117 | 118 | def getSelectedDevices( settingsName ) { 119 | def selectedDevices = [] 120 | (!settings.get(settingsName))?:((settings.get(settingsName)?.getAt(0)?.size() > 1) ? settings.get(settingsName)?.each { selectedDevices.add(it) } : selectedDevices.add(settings.get(settingsName))) 121 | return selectedDevices 122 | } 123 | 124 | 125 | /* Data Management */ 126 | // Listing all the water heaters you have in Rheem EcoNet 127 | private getWaterHeaterList() { 128 | def deviceList = [:] 129 | apiGet("/locations", [] ) { response -> 130 | if (response.status == 200) { 131 | response.data.equipment[0].each { 132 | if (it.type.equals("Water Heater")) { 133 | deviceList["" + it.id]= it.name 134 | } 135 | } 136 | } 137 | } 138 | return deviceList 139 | } 140 | 141 | // Refresh data 142 | def refresh() { 143 | if (!login()) { 144 | return 145 | } 146 | 147 | log.info "Refreshing data..." 148 | // update last refresh 149 | state.polling?.last = now() 150 | 151 | // get all the children and send updates 152 | getAllChildDevices().each { 153 | def id = it.deviceNetworkId 154 | apiGet("/equipment/$id", [] ) { response -> 155 | if (response.status == 200) { 156 | log.debug "Got data: $response.data" 157 | it.updateDeviceData(response.data) 158 | } 159 | } 160 | 161 | } 162 | 163 | //schedule the rescheduler to schedule refresh ;) 164 | if ((state.polling?.rescheduler?:0) + 2400000 < now()) { 165 | log.info "Scheduling Auto Rescheduler.." 166 | runEvery30Minutes(runRefresh) 167 | state.polling?.rescheduler = now() 168 | } 169 | } 170 | 171 | // Schedule refresh 172 | def runRefresh(evt) { 173 | log.info "Last refresh was " + ((now() - state.polling?.last?:0)/60000) + " minutes ago" 174 | // Reschedule if didn't update for more than 5 minutes plus specified polling 175 | if ((((state.polling?.last?:0) + (((settings.polling?.toInteger()?:1>0)?:1) * 60000) + 300000) < now()) && canSchedule()) { 176 | log.info "Scheduling Auto Refresh.." 177 | schedule("* */" + ((settings.polling?.toInteger()?:1>0)?:1) + " * * * ?", refresh) 178 | } 179 | 180 | // Force Refresh NOWWW!!!! 181 | refresh() 182 | 183 | //Update rescheduler's last run 184 | if (!evt) state.polling?.rescheduler = now() 185 | } 186 | 187 | def setDeviceSetPoint(childDevice, setpoint) { 188 | log.info "setDeviceSetPoint: $childDevice.deviceNetworkId $setpoint" 189 | if (login()) { 190 | apiPut("/equipment/$childDevice.deviceNetworkId", [ 191 | body: [ 192 | setPoint: setpoint, 193 | ] 194 | ]) 195 | } 196 | 197 | } 198 | def setDeviceEnabled(childDevice, enabled) { 199 | log.info "setDeviceEnabled: $childDevice.deviceNetworkId $enabled" 200 | if (login()) { 201 | apiPut("/equipment/$childDevice.deviceNetworkId", [ 202 | body: [ 203 | isEnabled: enabled, 204 | ] 205 | ]) 206 | } 207 | } 208 | 209 | private login() { 210 | def apiParams = [ 211 | uri: getApiURL(), 212 | path: "/auth/token", 213 | headers: ["Authorization": "Basic Y29tLnJoZWVtLmVjb25ldF9hcGk6c3RhYmxla2VybmVs"], 214 | requestContentType: "application/x-www-form-urlencoded", 215 | body: [ 216 | username: settings.username, 217 | password: settings.password, 218 | "grant_type": "password" 219 | ], 220 | ] 221 | if (state.session?.expiration < now()) { 222 | try { 223 | httpPost(apiParams) { response -> 224 | if (response.status == 200) { 225 | log.debug "Login good!" 226 | state.session = [ 227 | accessToken: response.data.access_token, 228 | refreshToken: response.data.refresh_token, 229 | expiration: now() + 150000 230 | ] 231 | return true 232 | } else { 233 | return false 234 | } 235 | } 236 | } catch (e) { 237 | log.debug "API Error: $e" 238 | return false 239 | } 240 | } else { 241 | // TODO: do a refresh 242 | return true 243 | } 244 | } 245 | 246 | /* API Management */ 247 | // HTTP GET call 248 | private apiGet(apiPath, apiParams = [], callback = {}) { 249 | // set up parameters 250 | apiParams = [ 251 | uri: getApiURL(), 252 | path: apiPath, 253 | headers: ["Authorization": getApiAuth()], 254 | requestContentType: "application/json", 255 | ] + apiParams 256 | log.debug "GET: $apiParams" 257 | try { 258 | httpGet(apiParams) { response -> 259 | callback(response) 260 | } 261 | } catch (e) { 262 | log.debug "API Error: $e" 263 | } 264 | } 265 | 266 | // HTTP PUT call 267 | private apiPut(apiPath, apiParams = [], callback = {}) { 268 | // set up parameters 269 | apiParams = [ 270 | uri: getApiURL(), 271 | path: apiPath, 272 | headers: ["Authorization": getApiAuth()], 273 | requestContentType: "application/json", 274 | ] + apiParams 275 | 276 | try { 277 | httpPut(apiParams) { response -> 278 | callback(response) 279 | } 280 | } catch (e) { 281 | log.debug "API Error: $e" 282 | } 283 | } 284 | 285 | private getApiURL() { 286 | return "https://econet-api.rheemcert.com" 287 | } 288 | 289 | private getApiAuth() { 290 | return "Bearer " + state.session?.accessToken 291 | } --------------------------------------------------------------------------------