2 * ProtoType Smart Energy Service
4 * Copyright 2015 hyeon seok yang
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:
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 name: "Smart Energy Service",
19 namespace: "Encored Technologies",
20 author: "hyeon seok yang",
21 description: "With visible realtime energy usage status, have good energy habits and enrich your life\r\n",
22 category: "SmartThings Labs",
23 iconUrl: "https://s3-ap-northeast-1.amazonaws.com/smartthings-images/appicon_enertalk%401.png",
24 iconX2Url: "https://s3-ap-northeast-1.amazonaws.com/smartthings-images/appicon_enertalk%402x",
25 iconX3Url: "https://s3-ap-northeast-1.amazonaws.com/smartthings-images/appicon_enertalk%403x",
29 appSetting "clientSecret"
35 page(name: "checkAccessToken")
39 card(name: "Encored Energy Service", type: "html", action: "getHtml", whitelist: whiteList()) {}
42 /* This list contains, that url need to be allowed in Smart Energy Service.*/
46 "ajax.googleapis.com",
47 "fonts.googleapis.com",
48 "code.highcharts.com",
49 "enertalk-card.encoredtech.com",
50 "s3-ap-northeast-1.amazonaws.com",
52 "ui-hub.encoredtech.com",
53 "enertalk-auth.encoredtech.com",
54 "api.encoredtech.com",
55 "cdnjs.cloudflare.com",
63 path("/requestCode") { action: [ GET: "requestCode" ] }
64 path("/receiveToken") { action: [ GET: "receiveToken"] }
65 path("/getHtml") { action: [GET: "getHtml"] }
66 path("/consoleLog") { action: [POST: "consoleLog"]}
67 path("/getInitialData") { action: [GET: "getInitialData"]}
68 path("/getEncoredPush") { action: [POST: "getEncoredPush"]}
72 /* This method does two things depends on the existence of Encored access token. :
73 * 1. If Encored access token does not exits, it starts the process of getting access token.
74 * 2. If Encored access token does exist, it will show a list of configurations, that user need to define values.
76 def checkAccessToken() {
77 log.debug "Staring the installation"
79 /* Choose the level */
80 atomicState.env_mode ="prod"
82 def lang = clientLocale?.language
84 /* getting language settings of user's device. */
85 if ("${lang}" == "ko") {
86 atomicState.language = "ko"
88 atomicState.language = "en"
91 /* create tanslation for descriptive and informative strings that can be seen by users. */
92 if (!state.languageString) {
96 if (!atomicState.encoredAccessToken) { /*check if Encored access token does exist.*/
98 log.debug "Encored Access Token does not exist."
100 if (!state.accessToken) { /*if smartThings' access token does not exitst*/
101 log.debug "SmartThings Access Token does not exist."
103 createAccessToken() /*request and get access token from smartThings*/
105 /* re-create strings to make sure it's been initialized. */
106 //createLocaleStrings()
109 def redirectUrl = buildRedirectUrl("requestCode") /* build a redirect url with endpoint "requestCode"*/
111 /* These lines will start the OAuth process.\n*/
112 log.debug "Start OAuth request."
113 return dynamicPage(name: "checkAccessToken", nextPage:null, uninstall: true, install:false) {
115 paragraph state.languageString."${atomicState.language}".desc1
116 href(title: state.languageString."${atomicState.language}".main,
117 description: state.languageString."${atomicState.language}".desc2,
124 /* This part will load the configuration for this application */
125 return dynamicPage(name:"checkAccessToken",install:true, uninstall : true) {
126 section(title:state.languageString."${atomicState.language}".title6) {
128 /* A push alarm for this application */
131 name: "notification",
132 title: state.languageString."${atomicState.language}".title1,
138 /* A plan that user need to decide */
142 title: state.languageString."${atomicState.language}".title2,
143 description : state.languageString."${atomicState.language}".subTitle1,
144 defaultValue: state.languageString.energyPlan,
146 submitOnChange: true,
151 /* A displaying unit that user need to decide */
155 title: state.languageString."${atomicState.language}".title3,
156 defaultValue : state.languageString."${atomicState.language}".defaultValues.default1,
159 options: state.languageString."${atomicState.language}".displayUnits
162 /* A metering date that user should know */
165 name: "meteringDate",
166 title: state.languageString."${atomicState.language}".title4,
167 defaultValue: state.languageString."${atomicState.language}".defaultValues.default2,
170 options: state.languageString."${atomicState.language}".meteringDays
173 /* A contract type that user should know */
176 name: "contractType",
177 title: state.languageString."${atomicState.language}".title5,
178 defaultValue: state.languageString."${atomicState.language}".defaultValues.default3,
181 options: state.languageString."${atomicState.language}".contractTypes)
189 log.debug "In state of sending a request to Encored for OAuth code.\n"
191 /* Make a parameter to request Encored for a OAuth code. */
194 response_type: "code",
196 client_id: "${appSettings.clientId}",
198 redirect_uri: buildRedirectUrl("receiveToken")
201 /* Request Encored a code. */
202 redirect location: "https://enertalk-auth.encoredtech.com/authorization?${toQueryString(oauthParams)}"
206 log.debug "Request Encored to swap code with Encored Aceess Token"
208 /* Making a parameter to swap code with a token */
209 def authorization = "Basic " + "${appSettings.clientId}:${appSettings.clientSecret}".bytes.encodeBase64()
210 def uri = "https://enertalk-auth.encoredtech.com/token"
211 def header = [Authorization: authorization, contentType: "application/json"]
212 def body = [grant_type: "authorization_code", code: params.code]
214 log.debug "Swap code with a token"
215 def encoredTokenParams = makePostParams(uri, header, body)
217 log.debug "API call to Encored to swap code with a token"
218 def encoredTokens = getHttpPostJson(encoredTokenParams)
220 /* make a page to show people if the REST was successful or not. */
222 log.debug "Got Encored OAuth token\n"
223 atomicState.encoredRefreshToken = encoredTokens.refresh_token
224 atomicState.encoredAccessToken = encoredTokens.access_token
228 log.debug "Could not get Encored OAuth token\n"
236 log.debug "Installed with settings: ${settings}"
242 log.debug "Updated with settings: ${settings}"
244 /* Make sure uuid is there. */
247 /* Check uuid and if it does not exist then don't update.*/
248 if (!atomicState.notPaired) {
251 for(def i=1; i < 28; i++) {
253 /* set user choosen option to apropriate value. */
254 if (atomicState.language == "en") {
255 if ("${i}st day of the month" == settings.meteringDate ||
256 "${i}nd day of the month" == settings.meteringDate ||
257 "${i}rd day of the month" == settings.meteringDate ||
258 "${i}th day of the month" == settings.meteringDate) {
263 } else if ("Rest of the month" == settings.meteringDate) {
269 if (settings.meteringDate == "매월 ${i}일") {
274 } else if ("말일" == settings.meteringDate) {
282 /* Set choosen contract to apropriate variable. */
284 if (settings.contractType == "High voltage" || settings.contractType == "주택용 고압") {
287 if (settings.energyPlan < 460) {
288 settings.energyPlan = 490
292 if (settings.energyPlan < 1130) {
293 settings.energyPlan = 1130
298 /* convert bill to milliwatts */
299 def changeToUsageParam = makeGetParams("${state.domains."${atomicState.env_mode}"}/1.2/devices/${atomicState.uuid}/bill/expectedUsage?bill=${settings.energyPlan}",
300 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"])
302 def energyPlanUsage = getHttpGetJson(changeToUsageParam, 'CheckEnergyPlanUsage')
304 if (energyPlanUsage) {
305 epUsage = energyPlanUsage.usage
308 /* update the the information depends on the option choosen */
309 def configurationParam = makePostParams("${state.domains."${atomicState.env_mode}"}/1.2/me",
310 [Authorization : "Bearer ${atomicState.encoredAccessToken}"],
311 [contractType : contract,
312 meteringDay : theDay,
313 maxLimitUsage : epUsage])
314 getHttpPutJson(configurationParam)
320 log.debug "Initializing Application"
322 def EATValidation = checkEncoreAccessTokenValidation()
324 /* if token exist get user's device id, uuid */
327 if (atomicState.uuid) {
329 def pushParams = makePostParams("${state.domains."${atomicState.env_mode}"}/1.2/devices/${atomicState.uuid}/events/push",
330 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"],
331 [type: "REST", regId:"${state.accessToken}__${app.id}"])
332 getHttpPostJson(pushParams)
336 log.warning "Ecored Access Token did not get refreshed!"
340 /* add device Type Handler */
341 atomicState.dni = "EncoredDTH01"
342 def d = getChildDevice(atomicState.dni)
344 log.debug "Creating Device Type Handler."
346 d = addChildDevice("Encored Technologies", "EnerTalk Energy Meter", atomicState.dni, null, [name:"EnerTalk Energy Meter", label:name])
349 log.debug "Device already created"
357 log.debug "in setSummary"
358 def text = "Successfully installed."
359 sendEvent(linkText:count.toString(), descriptionText: app.label,
360 eventType:"SOLUTION_SUMMARY",
363 data: [["icon":"indicator-dot-gray","iconColor":"#878787","value":text]],
367 // TODO: implement event handlers
369 /* Check the validation of Encored Access Token (EAT)
370 * If it's not valid try refresh Access Token.
371 * If the token gets refreshed, it will refresh the value of Encored Access Token
372 * If it doesn't get refreshed, then it returns null
374 private checkEncoreAccessTokenValidation() {
375 /* make a parameter to check the validation of Encored access token */
376 def verifyParam = makeGetParams("https://enertalk-auth.encoredtech.com/verify",
377 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"])
378 /* check the validation */
379 def verified = getHttpGetJson(verifyParam, 'verifyToken')
381 log.debug "verified : ${verified}"
383 /* if Encored Access Token need to be renewed. */
388 /* Recheck the renewed Encored access token. */
389 verifyParam.headers = [Authorization: "Bearer ${atomicState.encoredAccessToken}"]
390 verified = getHttpGetJson(verifyParam, 'CheckRefresh')
392 } catch (groovyx.net.http.HttpResponseException e) {
393 /* If refreshing token raises an error */
394 log.warn "Refresh Token Error : ${e}"
401 /* Get device UUID, if it does not exist, return false. true otherwise.*/
403 atomicState.uuid = null
404 atomicState.notPaired = true
405 /* Make a parameter to get device id (uuid)*/
406 def uuidParams = makeGetParams( "https://enertalk-auth.encoredtech.com/uuid",
407 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"])
409 def deviceUUID = getHttpGetJson(uuidParams, 'UUID')
410 log.debug "device uuid is : ${deviceUUID}"
414 log.debug "got here even tho"
415 atomicState.uuid = deviceUUID.uuid
416 atomicState.notPaired = false
420 private createLocaleStrings() {
422 test : "http://api.encoredtech.com",
423 prod : "https://api.encoredtech.com:8082/"
425 state.languageString =
429 desc1 : "Tab below to sign in or sign up to Encored EnerTalk smart energy service and authorize SmartThings access.",
430 desc2 : "Click to proceed authorization.",
434 default2 : "1st day of the month",
435 default3 : "Low voltage"
438 "1st day of the month",
439 "2nd day of the month",
440 "3rd day of the month",
441 "4th day of the month",
442 "5th day of the month",
443 "6th day of the month",
444 "7th day of the month",
445 "8th day of the month",
446 "9th day of the month",
447 "10th day of the month",
448 "11th day of the month",
449 "12th day of the month",
450 "13th day of the month",
451 "14th day of the month",
452 "15th day of the month",
453 "16th day of the month",
454 "17th day of the month",
455 "18th day of the month",
456 "19th day of the month",
457 "20st day of the month",
458 "21st day of the month",
459 "22nd day of the month",
460 "23rd day of the month",
461 "24th day of the month",
462 "25th day of the month",
463 "26th day of the month",
466 displayUnits : ["WON(₩)", "kWh"],
467 contractTypes : ["Low voltage", "High voltage"],
468 title1 : "Send push notification",
469 title2 : "Energy Plan",
470 subTitle1 : "Setup your energy plan by won",
471 title3 : "Display Unit",
472 title4 : "Metering Date",
473 title5 : "Contract Type",
474 title6 : "User & Notifications",
475 message1 : """ <p>Your Encored Account is now connected to SmartThings!</p> <p>Click 'Done' to finish setup.</p> """,
476 message2 : """ <p>The connection could not be established!</p> <p>Click 'Done' to return to the menu.</p> """,
478 header : "Device is not installed",
479 body1 : "You need to install EnerTalk device at first,",
480 body2 : "and proceed setup and register device.",
481 button1 : "Setup device",
482 button2 : "Not Installed"
485 header : "Device is not connected.",
486 body1 : "Please check the Wi-Fi network connection",
487 body2 : "and EnerTalk device status.",
488 body3 : "Select ‘Setup Device’ to reset the device."
492 desc1 : "스마트 에너지 서비스를 이용하시려면 EnerTalk 서비스 가입과 SmartThings 접근 권한이 필요합니다.",
493 desc2 : "아래 버튼을 누르면 인증을 시작합니다",
494 main : "EnerTalk 인증",
529 displayUnits : ["원(₩)", "kWh"],
530 contractTypes : ["주택용 저압", "주택용 고압"],
532 title2 : "사용 계획 (원)",
533 subTitle1 : "월간 계획을 금액으로 입력하세요",
537 title6 : "사용자 & 알람 설정",
538 message1 : """ <p>EnerTalk 계정이 SmartThings와 연결 되었습니다!</p> <p>Done을 눌러 계속 진행해 주세요.</p> """,
539 message2 : """ <p>계정 연결이 실패했습니다.</p> <p>Done 버튼을 눌러 다시 시도해주세요.</p> """,
541 header : "기기 설치가 필요합니다.",
542 body1 : "가정 내 분전반에 EnerTalk 기기를 먼저 설치하고,",
543 body2 : "아래 버튼을 눌러 기기등록 및 연결을 진행하세요.",
548 header : "Device is not connected.",
549 body1 : "Please check the Wi-Fi network connection",
550 body2 : "and EnerTalk device status.",
551 body3 : "Select ‘Setup Device’ to reset the device."
559 /* This method makes a redirect url with a given endpoint */
560 private buildRedirectUrl(mappingPath) {
561 log.debug "Start : Starting to making a redirect URL with endpoint : /${mappingPath}"
562 def url = "https://graph.api.smartthings.com/api/token/${state.accessToken}/smartapps/installations/${app.id}/${mappingPath}"
563 log.debug "Done : Finished to make a URL : ${url}"
567 String toQueryString(Map m) {
568 return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
571 /* make a success message. */
573 def lang = clientLocale?.language
575 if ("${lang}" == "ko") {
576 log.debug "I was here at first."
577 atomicState.language = "ko"
580 atomicState.language = "en"
582 log.debug atomicState.language
583 def message = atomicState.languageString."${atomicState.language}".message1
584 connectionStatus(message)
587 /* make a failure message. */
589 def lang = clientLocale?.language
591 if ("${lang}" == "ko") {
592 log.debug "I was here at first."
593 atomicState.language = "ko"
596 atomicState.language = "en"
598 def message = atomicState.languageString."${atomicState.language}".message2
599 connectionStatus(message)
602 private connectionStatus(message) {
607 <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width height=device-height">
609 <link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
610 <title>SmartThings Connection</title>
611 <style type="text/css">
613 font-family: 'Swiss 721 W01 Thin';
614 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot');
615 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.eot?#iefix') format('embedded-opentype'),
616 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.woff') format('woff'),
617 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.ttf') format('truetype'),
618 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-thin-webfont.svg#swis721_th_btthin') format('svg');
623 font-family: 'Swiss 721 W01 Light';
624 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot');
625 src: url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.eot?#iefix') format('embedded-opentype'),
626 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.woff') format('woff'),
627 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.ttf') format('truetype'),
628 url('https://s3.amazonaws.com/smartapp-icons/Partner/fonts/swiss-721-light-webfont.svg#swis721_lt_btlight') format('svg');
639 /*background: #eee;*/
643 vertical-align: middle;
644 margin-top:20.3125vw;
651 margin-right : 8.75vw;
678 font-family: 'Swiss 721 W01 Light';
684 <div class="container">
685 <img class="encored" src="https://s3-ap-northeast-1.amazonaws.com/smartthings-images/appicon_enertalk.png" alt="Encored icon" />
686 <img class="chain" src="https://s3-ap-northeast-1.amazonaws.com/smartthings-images/icon_link.svg" alt="connected device icon" />
687 <img class="smartt" src="https://s3.amazonaws.com/smartapp-icons/Partner/support/st-logo%402x.png" alt="SmartThings logo" />
695 render contentType: 'text/html', data: html
698 private refreshAuthToken() {
699 /*Refreshing Encored Access Token*/
701 log.debug "Refreshing Encored Access Token"
702 if(!atomicState.encoredRefreshToken) {
703 log.error "Encored Refresh Token does not exist!"
706 def authorization = "Basic " + "${appSettings.clientId}:${appSettings.clientSecret}".bytes.encodeBase64()
707 def refreshParam = makePostParams("https://enertalk-auth.encoredtech.com/token",
708 [Authorization: authorization],
709 [grant_type: 'refresh_token', refresh_token: "${atomicState.encoredRefreshToken}"])
711 def newAccessToken = getHttpPostJson(refreshParam)
713 if (newAccessToken) {
714 atomicState.encoredAccessToken = newAccessToken.access_token
715 log.debug "Successfully got new Encored Access Token.\n"
717 log.error "Was unable to renew Encored Access Token.\n"
722 private getHttpPutJson(param) {
724 log.debug "Put URI : ${param.uri}"
726 httpPut(param) { resp ->
727 log.debug "HTTP Put Success"
729 } catch(groovyx.net.http.HttpResponseException e) {
730 log.warn "HTTP Put Error : ${e}"
734 private getHttpPostJson(param) {
735 log.debug "Post URI : ${param.uri}"
738 httpPost(param) { resp ->
742 } catch(groovyx.net.http.HttpResponseException e) {
743 log.warn "HTTP Post Error : ${e}"
749 private getHttpGetJson(param, testLog) {
750 log.debug "Get URI : ${param.uri}"
753 httpGet(param) { resp ->
756 } catch(groovyx.net.http.HttpResponseException e) {
757 log.warn "HTTP Get Error : ${e}"
764 private makePostParams(uri, header, body=[]) {
772 private makeGetParams(uri, headers, path="") {
780 def getInitialData() {
781 def lang = clientLocale?.language
782 if ("${lang}" == "ko") {
787 atomicState.solutionModuleSettings.language = lang
788 atomicState.solutionModuleSettings
792 log.debug "console log: ${request.JSON.str}"
797 /* initializing variables */
798 def deviceStatusData = "", standbyData = "", meData = "", meteringData = "", rankingData = "", lastMonth = "", deviceId = ""
799 def standby = "", plan = "", start = "", end = "", meteringDay = "", meteringUsage = "", percent = "", tier = "", meteringPeriodBill = ""
800 def maxLimitUsageBill, maxLimitUsage = 0
801 def deviceStatus = false
802 def displayUnit = "watt"
804 def meteringPeriodBillShow = "", meteringPeriodBillFalse = "collecting data"
805 def standbyShow = "", standbyFalse = "collecting data"
806 def rankingShow = "collecting data"
807 def tierShow = "collecting data"
808 def lastMonthShow = "", lastMonthFalse = "no records"
809 def planShow = "", planFalse = "set up plan"
811 def thisMonthUnitOne ="", thisMonthUnitTwo = "", planUnitOne = "", planUnitTwo = "", lastMonthUnit = "", standbyUnit = ""
812 def thisMonthTitle = "This Month", tierTitle = "Billing Tier", planTitle = "Energy Goal",
813 lastMonthTitle = "Last Month", rankingTitle = "Ranking", standbyTitle = "Always on", energyMonitorDeviceTitle = "EnerTalk Device" , realtimeTitle = "Realtime"
814 def onOff = "OFF", rankImage = "", tierImage = ""
818 /* Get the language setting on device. */
819 def lang = clientLocale?.language
820 if ("${lang}" == "ko") {
821 atomicState.language = "ko"
823 atomicState.language = "en"
826 if (atomicState.language == "ko") {
827 rankingShow = "데이터 수집 중"
828 meteringPeriodBillFalse = "데이터 수집 중"
829 lastMonthFalse = "정보가 없습니다"
830 standbyFalse = "데이터 수집 중"
831 planFalse = "계획을 입력하세요"
832 thisMonthTitle = "이번 달"
835 lastMonthTitle = "지난달"
837 standbyTitle = "대기전력"
838 energyMonitorDeviceTitle = "스마트미터 상태"
839 realtimeTitle = "실시간"
842 /* check Encored Access Token */
843 def EATValidation = checkEncoreAccessTokenValidation()
844 log.debug EATValidation
845 /* check if uuid already exist or not.*/
846 if (EATValidation && atomicState.notPaired) {
850 /* If token has been verified or refreshed and if uuid exist, call other apis */
851 log.debug atomicState.notPaired
852 if (!atomicState.notPaired) {
855 /* make a parameter to get device status */
856 def deviceStatusParam = makeGetParams( "${state.domains."${atomicState.env_mode}"}/1.2/devices/${atomicState.uuid}/status",
857 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"])
859 /* get device status. */
860 deviceStatusData = getHttpGetJson(deviceStatusParam, 'CheckDeviceStatus')
863 /* make a parameter to get standby value.*/
864 def standbyParam = makeGetParams( "${state.domains."${atomicState.env_mode}"}/1.2/devices/${atomicState.uuid}/standbyPower",
865 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"])
867 /* get standby value */
868 standbyData = getHttpGetJson(standbyParam, 'CheckStandbyPower')
872 /* make a parameter to get user's info. */
873 def meParam = makeGetParams( "${state.domains."${atomicState.env_mode}"}/1.2/me",
874 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"])
876 /* Get user's info */
877 meData = getHttpGetJson(meParam, 'CheckMe')
880 /* make a parameter to get energy used since metering date */
881 def meteringParam = makeGetParams( "${state.domains."${atomicState.env_mode}"}/1.2/devices/${atomicState.uuid}/meteringUsage",
882 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"])
884 /* Get the value of energy used since metering date. */
885 meteringData = getHttpGetJson(meteringParam, 'CheckMeteringUsage')
888 /* make a parameter to get the energy usage ranking of a user. */
889 def rankingParam = makeGetParams( "${state.domains."${atomicState.env_mode}"}/1.2/ranking/usages/${atomicState.uuid}?state=current&period=monthly",
890 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"])
892 /* Get user's energy usage rank */
893 rankingData = getHttpGetJson(rankingParam, 'CheckingRanking')
895 /* Parse the values from the returned value of api calls. Then use these values to inform user how much they have used or will use. */
897 /* parse device status. */
898 if (deviceStatusData) {
899 if (deviceStatusData.status == "NORMAL") {
904 log.debug "deiceStatusData : ${deviceStatus} || ${deviceStatusData}"
906 /* Parse standby power. */
908 if (standbyData.standbyPower) {
909 standby = (standbyData.standbyPower / 1000)
913 /* Parse max limit usage and it's bill from user's info. */
915 if (meData.maxLimitUsageBill) {
916 maxLimitUsageBill = meData.maxLimitUsageBill
917 maxLimitUsage = meData.maxLimitUsage
921 /* Parse the values which have been used since metering date.
923 * meteringPeriodBill : A bill for energy usage.
924 * plan : The left amount of bill until it reaches limit.
925 * start : metering date in millisecond e.g. if the metering started on june and 1st, 2015,06,01
926 * end : Today's date in millisecond
927 * meteringDay : The day of the metering date. e.g. if the metering date is June 1st, then it will return 1.
928 * meteringUSage : The amount of energy that user has used.
929 * tier : the level of energy use, tier exits from 1 to 6.
932 if (meteringData.meteringPeriodBill) {
933 meteringPeriodBill = meteringData.meteringPeriodBill
934 plan = maxLimitUsageBill - meteringData.meteringPeriodBill
935 start = meteringData.meteringStart
936 end = meteringData.meteringEnd
937 meteringDay = meteringData.meteringDay
938 meteringUsage = meteringData.meteringPeriodUsage
939 tier = ((int) (meteringData.meteringPeriodUsage / 100000000) + 1)
947 /* Get ranking data of a user and the percent */
949 if (rankingData.user.ranking) {
950 percent = ((int)((rankingData.user.ranking / rankingData.user.population) * 10))
957 /* if the start value exist, get last month energy usage. */
959 def lastMonthParam = makeGetParams( "${state.domains."${atomicState.env_mode}"}/1.2/devices/${atomicState.uuid}/meteringUsages?period=monthly&start=${start}&end=${end}",
960 [Authorization: "Bearer ${atomicState.encoredAccessToken}", ContentType: "application/json"])
962 lastMonth = getHttpGetJson(lastMonthParam, 'ChecklastMonth')
966 /* I decided to set values to device type handler, on loading solution module.
967 So, users may need to go back to solution module to update their device type handler. */
968 def d = getChildDevice(atomicState.dni)
969 def kWhMonth = Math.round(meteringUsage / 10000) / 100 /* milliwatt to kilowatt*/
971 if ( maxLimitUsage > 0 ) {
972 planUsed = Math.round((meteringUsage / maxLimitUsage) * 100) /* get the pecent of used amount against max usage */
974 planUsed = Math.round((meteringUsage/ 1000000) * 100) /* if max was not decided let the used value be percent. e.g. 1kWh = 100% */
977 /* get realtime usage of user's device.*/
978 def realTimeParam = makeGetParams("${state.domains."${atomicState.env_mode}"}/1.2/devices/${atomicState.uuid}/realtimeUsage",
979 [Authorization: "Bearer ${atomicState.encoredAccessToken}"])
980 def realTimeInfo = getHttpGetJson(realTimeParam, 'CheckRealtimeinfo')
985 realTimeInfo = Math.round(realTimeInfo.activePower / 1000 )
990 /* inserting values to device type handler */
992 d?.sendEvent(name: "view", value : "${kWhMonth}")
995 d?.sendEvent(name: "month", value : "${thisMonthTitle} \n ${kWhMonth} \n kWh")
998 d?.sendEvent(name: "month", value : "\n ${state.languageString."${atomicState.language}".message4.header} \n\n " +
999 "${state.languageString."${atomicState.language}".message4.body1} \n " +
1000 "${state.languageString."${atomicState.language}".message4.body2} \n " +
1001 "${state.languageString."${atomicState.language}".message4.body3}")
1004 d?.sendEvent(name: "real", value : "${realTimeInfo}w \n\n ${realtimeTitle}")
1005 d?.sendEvent(name: "tier", value : "${tier} \n\n ${tierTitle}")
1006 d?.sendEvent(name: "plan", value : "${planUsed}% \n\n ${planTitle}")
1011 /* If it finally couldn't get Encored access token. */
1012 log.error "Could not get Encored Access Token. Please try later."
1015 /* change the display uinit to bill from kWh if user want. */
1016 if (settings.displayUnit == "WON(₩)" || settings.displayUnit == "원(₩)") {
1017 displayUnit = "bill"
1020 if (meteringPeriodBill) {
1021 /* reform the value of the bill with the , separator */
1022 meteringPeriodBillShow = formatMoney("${meteringPeriodBill}")
1023 meteringPeriodBillFalse = ""
1024 thisMonthUnitOne = "₩"
1026 def dayPassed = getDayPassed(start, end, meteringDay)
1027 if (atomicState.language == 'ko') {
1028 thisMonthUnitTwo = "/ ${dayPassed}일"
1030 if (dayPassed == 1) {
1031 thisMonthUnitTwo = "/${dayPassed} day"
1033 thisMonthUnitTwo = "/${dayPassed} days"
1040 if (plan >= 1000) {planShow = formatMoney("${plan}") }
1042 planUnitOne = "₩"
1044 if (atomicState.language == 'ko') {
1047 planUnitTwo = "left"
1052 /*set the showing units for html.*/
1054 if (lastMonth.usages) {
1055 lastMonthShow = formatMoney("${lastMonth.usages[0].meteringPeriodBill}")
1057 lastMonthUnit = "₩"
1062 standbyShow = standby
1068 rankImage = "<img id=\"image-rank\" src=\"https://s3-ap-northeast-1.amazonaws.com/smartthings-images/ranking_${percent}.svg\" />"
1073 tierImage = "<img id=\"image-tier\" src=\"https://s3-ap-northeast-1.amazonaws.com/smartthings-images/tier_${tier}.svg\" />"
1081 atomicState.solutionModuleSettings = [
1082 auth : atomicState.encoredAccessToken,
1083 deviceState : deviceStatus,
1085 displayUnit : displayUnit,
1086 language : atomicState.language,
1087 deviceId : deviceId,
1092 <div id="real-time">
1094 <!-- real-time card -->
1095 <div id="my-card"></div>
1097 <!-- this month section -->
1098 <div class="contents head" id="content1">
1099 <p class="key" id="korean-this">${thisMonthTitle}</p>
1100 <span class="value-block">
1101 <p class="unit first" id="unit-first-this">${thisMonthUnitOne}</p>
1102 <p class="value" id="value-this">${meteringPeriodBillShow}</p>
1103 <p class="value" id="value-fail">${meteringPeriodBillFalse}</p>
1104 <p class="unit second" id="unit-second-this">${thisMonthUnitTwo}</p>
1108 <!-- Billing Tier section -->
1109 <div class="contents tail" id="content2">
1110 <p class="key" id="korean-tier">${tierTitle}</p>
1111 <span class="value-block">
1112 <div id="value-block-tier">${tierImage}</div>
1113 <p class="value" id="value-fail">${tierShow}</p>
1117 <!-- Plan section -->
1118 <div class="contents tail" id="content3">
1119 <p class="key" id="korean-plan">${planTitle}</p>
1120 <span class="value-block">
1121 <p class="unit first" id="unit-first-plan">${planUnitOne}</p>
1122 <p class="value" id="value-plan">${planShow}</p>
1123 <p class="value" id="value-fail">${planFalse}</p>
1124 <p class="unit second" id="unit-second-plan"> ${planUnitTwo}</p>
1128 <!-- Last Month section -->
1129 <div class="contents tail" id="content4">
1130 <p class="key" id="korean-last">${lastMonthTitle}</p>
1131 <span class="value-block">
1132 <p class="unit first" id="unit-first-last">${lastMonthUnit}</p>
1133 <p class="value" id="value-last">${lastMonthShow}</p>
1134 <p class="value" id="value-fail">${lastMonthFalse}</p>
1138 <!-- Ranking section -->
1139 <div class="contents tail" id="content5">
1140 <p class="key" id="korean-ranking">${rankingTitle}</p>
1141 <span class="value-block">
1142 <div id="value-block-rank">${rankImage}</div>
1143 <p class="value" id="value-fail">${rankingShow}</p>
1147 <!-- Standby section -->
1148 <div class="contents tail" id="content6">
1149 <p class="key" id="korean-standby">${standbyTitle}</p>
1150 <span class="value-block">
1151 <p class="value" id="value-standby">${standbyShow}</p>
1152 <p class="value" id="value-fail">${standbyFalse}</p>
1153 <p class="unit third" id="unit-third-standby">${standbyUnit}<p>
1157 <!-- Device status section -->
1158 <div class="contents tail" id="content7">
1159 <p class="key" id="korean-device">${energyMonitorDeviceTitle}</p>
1160 <span class="value-block">
1161 <div class="circle"></div>
1162 <p class="value last" id="value-ON-OFF">${onOff}</p>
1168 <!-- hidden section -->
1170 <div id="this-month">
1171 <div class="card-header">
1172 <p class="st-title" id="korean-title-this">${thisMonthTitle}</p>
1173 <button class="st-show" id="show">X</button>
1175 <div class="cards" id="my-card2"></div>
1176 <div class="cards" id="my-card3"></div>
1179 <div id="last-month">
1180 <div class="card-header">
1181 <p class="st-title" id="korean-title-last">${lastMonthTitle}</p>
1182 <button class="st-show" id="show2">X</button>
1184 <div class="cards" id="my-card4"></div>
1187 <div id="progressive-step">
1188 <div class="card-header">
1189 <p class="st-title" id="korean-title-tier">${tierTitle}</p>
1190 <button class="st-show" id="show3">X</button>
1192 <div class="cards" id="my-card5"></div>
1196 <div class="card-header">
1197 <p class="st-title" id="korean-title-ranking">${rankingTitle}</p>
1198 <button class="st-show" id="show4">X</button>
1200 <div class="cards" id="my-card6"></div>
1204 <div class="card-header">
1205 <p class="st-title" id="korean-title-plan">${planTitle}</p>
1206 <button class="st-show" id="show5">X</button>
1208 <div class="cards" id="my-card7"></div>
1212 <div class="card-header">
1213 <p class="st-title" id="korean-title-standby">${standbyTitle}</p>
1214 <button class="st-show" id="show6">X</button>
1216 <div class="cards" id="my-card8"></div>
1219 \$("#this-month").slideUp();
1220 \$("#last-month").slideUp();
1221 \$("#progressive-step").slideUp();
1222 \$("#ranking").slideUp();
1223 \$("#plan").slideUp();
1224 \$("#standby").slideUp();
1226 var UI = new Encored.UI({
1231 'id': 'ui:h:strealtime:v1',
1233 'lang': '${atomicState.language}',
1235 'displayUnit': '${displayUnit}'
1238 'accessToken': '${atomicState.encoredAccessToken}',
1239 'target': document.getElementById("my-card")
1242 <script src="${buildResourceUrl('javascript/app.js')}"></script>
1246 log.debug "abotu to ask device connection"
1247 def d = getChildDevice(atomicState.dni)
1248 /* inserting values to device type handler */
1250 d?.sendEvent(name: "month", value : "\n ${state.languageString."${atomicState.language}".message3.header} \n\n ${state.languageString."${atomicState.language}".message3.body1} \n ${state.languageString."${atomicState.language}".message3.body2}")
1253 if (state.language == "ko") {
1254 energyMonitorDeviceTitle = "스마트미터 상태"
1256 /* need device pairing */
1257 atomicState.solutionModuleSettings = [
1264 <div id="real-time">
1266 <!-- real-time card -->
1267 <div id="st-pairing-card">
1268 <p class="st-pairing-card-header" align="center">${state.languageString."${atomicState.language}".message3.header}</p>
1269 <p class="st-pairing-card-body" align="center"> ${state.languageString."${atomicState.language}".message3.body1} <br/> ${state.languageString."${atomicState.language}".message3.body2}</p>
1270 <div id="st-deep-link-container"></div>
1275 <!-- Device status section -->
1276 <div class="contents tail" id="content7">
1277 <p class="key">${energyMonitorDeviceTitle}</p>
1278 <span class="value-block">
1279 <p class="value last">${state.languageString."${atomicState.language}".message3.button2}</p>
1285 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
1286 <script src="${buildResourceUrl('javascript/app.js')}"></script>
1288 var ua = navigator.userAgent.toLowerCase();
1289 var isAndroid = ua.indexOf("android") > -1; //&& ua.indexOf("mobile");
1291 \$("#st-deep-link-container").html("<a id=\'st-deep-link\' href=\'https://itunes.apple.com/kr/app/enertalk-for-home/id1024660780?mt=8\'><p class=\'st-deep-text\'>${state.languageString."${state.language}".message3.button1}</p></a>");
1293 \$("#st-deep-link-container").html("<a id=\'st-deep-link\' href=\'market://details?id=com.ionicframework.enertalkhome874425\'><p class=\'st-deep-text\'>${state.languageString."${state.language}".message3.button1}</p></a>");
1302 <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width, height=device-height">
1303 <link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
1304 <link rel="stylesheet" href="${buildResourceUrl('css/app.css')}" type="text/css">
1305 <script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.18/webcomponents-lite.min.js"></script>
1306 <script src="https://enertalk-card.encoredtech.com/sdk.js"></script>
1307 <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
1317 /* put commas for money or if there are things that need to have a comma separator.*/
1318 private formatMoney(money) {
1319 def i = money.length()-1
1321 def commas = ((int) Math.floor(i/3))
1328 if (counter > 0 && (counter % 3) == 0) {
1329 ret = "${money[i]},${ret}"
1332 ret = "${money[i]}${ret}"
1342 /* Count how many days have been passed since metering day:
1343 * if metering day < today, it returns today - metering day
1344 * else if metering day > today, it calcualtes how many days have been passed since meterin day and return calculated value.
1345 * else return 1 (today).
1347 private getDayPassed(start, end, meteringDay){
1350 def today = new Date(end)
1351 def tzDifference = 9 * 60 + today.getTimezoneOffset()
1352 today = new Date(today.getTime() + tzDifference * 60 * 1000).getDate();
1354 if (today > meteringDay) {
1355 day += today - meteringDay;
1358 if (today < meteringDay) {
1359 def startDate = new Date(start);
1360 def month = startDate.getMonth();
1361 def year = startDate.getYear();
1362 def lastDate = new Date(year, month, 31).getDate();
1364 if (lastDate == 1) {
1370 day = day - meteringDay + today;
1376 /* Get Encored push and send the notification. */
1377 def getEncoredPush() {
1379 byte[] decoded = "${params.msg}".decodeBase64()
1380 def decodedString = new String(decoded)
1382 if (settings.notification == "true") {
1383 sendNotification("${decodedString}", [method: "push"])
1385 sendNotificationEvent("${decodedString}")