Update speaker-weather-forecast.groovy
[smartapps.git] / official / smart-energy-service.groovy
1 /**
2  *  ProtoType Smart Energy Service
3  *
4  *  Copyright 2015 hyeon seok yang
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  */
16  
17 definition(
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",
26     oauth: true)
27 {
28     appSetting "clientId"
29     appSetting "clientSecret"
30     appSetting "callback"
31 }
32
33
34 preferences {
35         page(name: "checkAccessToken")
36 }
37
38 cards {
39     card(name: "Encored Energy Service", type: "html", action: "getHtml", whitelist: whiteList()) {}
40 }
41
42 /* This list contains, that url need to be allowed in Smart Energy Service.*/
43 def whiteList() {
44         [
45         "code.jquery.com", 
46         "ajax.googleapis.com", 
47         "fonts.googleapis.com",
48         "code.highcharts.com", 
49         "enertalk-card.encoredtech.com", 
50         "s3-ap-northeast-1.amazonaws.com",
51         "s3.amazonaws.com", 
52         "ui-hub.encoredtech.com",
53         "enertalk-auth.encoredtech.com",
54         "api.encoredtech.com",
55         "cdnjs.cloudflare.com",
56         "encoredtech.com",
57         "itunes.apple.com"
58     ]
59 }
60
61 /* url endpoints */
62 mappings {
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"]}
69 }
70
71
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. 
75 */
76 def checkAccessToken() {
77         log.debug "Staring the installation"
78
79     /* Choose the level */    
80     atomicState.env_mode ="prod"
81    
82     def lang = clientLocale?.language
83    
84         /* getting language settings of user's device.  */
85     if ("${lang}" == "ko") {
86         atomicState.language = "ko"
87     } else {
88         atomicState.language = "en"
89     }
90     
91     /* create tanslation for descriptive and informative strings that can be seen by users. */
92     if (!state.languageString) {
93         createLocaleStrings() 
94     }
95     
96         if (!atomicState.encoredAccessToken) { /*check if Encored access token does exist.*/
97         
98         log.debug "Encored Access Token does not exist."
99         
100         if (!state.accessToken) { /*if smartThings' access token does not exitst*/
101                 log.debug "SmartThings Access Token does not exist."
102             
103             createAccessToken() /*request and get access token from smartThings*/
104             
105             /* re-create strings to make sure it's been initialized. */
106             //createLocaleStrings() 
107         }
108
109                 def redirectUrl = buildRedirectUrl("requestCode") /* build a redirect url with endpoint "requestCode"*/
110         
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) {
114             section{
115                 paragraph state.languageString."${atomicState.language}".desc1
116                 href(title: state.languageString."${atomicState.language}".main,
117                      description: state.languageString."${atomicState.language}".desc2,
118                      required: true,
119                      style:"embedded",
120                      url: redirectUrl)
121             }
122         }
123     } else {
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) {
127             
128                 /* A push alarm for this application */
129                 input(
130                     type: "boolean", 
131                     name: "notification", 
132                     title: state.languageString."${atomicState.language}".title1,
133                     required: false,
134                     default: true,
135                     multiple: false
136                 )
137                 
138                 /* A plan that user need to decide */
139                 input(
140                         type: "number", 
141                     name: "energyPlan", 
142                     title: state.languageString."${atomicState.language}".title2,
143                     description : state.languageString."${atomicState.language}".subTitle1,
144                     defaultValue: state.languageString.energyPlan,
145                     range: "1130..*",
146                     submitOnChange: true,
147                     required: true, 
148                     multiple: false
149                 )
150                 
151                 /* A displaying unit that user need to decide */
152                 input(
153                         type: "enum", 
154                     name: "displayUnit", 
155                     title: state.languageString."${atomicState.language}".title3, 
156                     defaultValue : state.languageString."${atomicState.language}".defaultValues.default1,
157                     required: true, 
158                     multiple: false, 
159                     options: state.languageString."${atomicState.language}".displayUnits
160                 )
161                 
162                 /* A metering date that user should know */
163                 input(
164                         type: "enum", 
165                     name: "meteringDate", 
166                     title: state.languageString."${atomicState.language}".title4,
167                     defaultValue: state.languageString."${atomicState.language}".defaultValues.default2,
168                     required: true, 
169                     multiple: false, 
170                     options: state.languageString."${atomicState.language}".meteringDays
171                 )
172                 
173                 /* A contract type that user should know */
174                 input(
175                         type: "enum", 
176                         name: "contractType", 
177                     title: state.languageString."${atomicState.language}".title5,
178                     defaultValue: state.languageString."${atomicState.language}".defaultValues.default3,
179                     required: true, 
180                     multiple: false, 
181                     options: state.languageString."${atomicState.language}".contractTypes)
182             }
183             
184         }
185     }
186 }
187
188 def requestCode(){
189         log.debug "In state of sending a request to Encored for OAuth code.\n"
190     
191         /* Make a parameter to request Encored for a OAuth code. */
192     def oauthParams = 
193     [
194                 response_type: "code",
195                 scope: "remote",
196                 client_id: "${appSettings.clientId}",
197         app_version: "web",
198                 redirect_uri: buildRedirectUrl("receiveToken")
199         ]
200
201     /* Request Encored a code. */
202         redirect location: "https://enertalk-auth.encoredtech.com/authorization?${toQueryString(oauthParams)}"
203 }
204
205 def receiveToken(){
206         log.debug "Request Encored to swap code with Encored Aceess Token"
207         
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]
213         
214     log.debug "Swap code with a token"
215     def encoredTokenParams = makePostParams(uri, header, body)
216     
217     log.debug "API call to Encored to swap code with a token"
218     def encoredTokens = getHttpPostJson(encoredTokenParams)
219         
220     /* make a page to show people if the REST was successful or not. */
221     if (encoredTokens) {
222         log.debug "Got Encored OAuth token\n"
223                 atomicState.encoredRefreshToken = encoredTokens.refresh_token
224                 atomicState.encoredAccessToken = encoredTokens.access_token
225         
226         success()
227         } else {
228         log.debug "Could not get Encored OAuth token\n"
229         fail()
230     }
231     
232 }
233
234
235 def installed() {
236         log.debug "Installed with settings: ${settings}"
237
238     initialize()
239 }
240
241 def updated() {
242         log.debug "Updated with settings: ${settings}"
243         
244     /* Make sure uuid is there. */
245     getUUID()
246     
247         /* Check uuid and if it does not exist then don't update.*/
248     if (!atomicState.notPaired) {  
249         def theDay = 1
250
251         for(def i=1; i < 28; i++) {
252                 
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) {
259
260                     theDay = i
261                     i = 28
262
263                 } else if ("Rest of the month" == settings.meteringDate) {
264                     theDay = 27
265                     i = 28
266                 }
267             } else {
268
269                 if (settings.meteringDate == "매월 ${i}일") {
270
271                     theDay = i
272                     i = 28
273
274                 } else if ("말일" == settings.meteringDate) {
275                     theDay = 27
276                     i = 28
277                 }
278             }
279
280         }
281                 
282         /* Set choosen contract to apropriate variable. */
283         def contract = 1
284         if (settings.contractType == "High voltage" || settings.contractType == "주택용 고압") {
285             contract = 2
286                 
287             if (settings.energyPlan < 460) {
288                 settings.energyPlan = 490
289             }
290         } else {
291                 
292                 if (settings.energyPlan < 1130) {
293                 settings.energyPlan = 1130
294             }
295         }
296                 
297         
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"])
301
302        def energyPlanUsage = getHttpGetJson(changeToUsageParam, 'CheckEnergyPlanUsage')
303         def epUsage = 0
304         if (energyPlanUsage) {
305             epUsage = energyPlanUsage.usage
306         } 
307         
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)
315     }
316         
317 }
318
319 def initialize() {
320         log.debug "Initializing Application"
321     
322     def EATValidation = checkEncoreAccessTokenValidation()
323     
324     /* if token exist get user's device id, uuid */
325     if (EATValidation) {
326         getUUID()
327         if (atomicState.uuid) {
328                 
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)
333                 }
334         
335     } else {
336         log.warning "Ecored Access Token did not get refreshed!"
337     }
338         
339     
340     /* add device Type Handler */
341     atomicState.dni = "EncoredDTH01"
342     def d = getChildDevice(atomicState.dni)
343     if(!d) {
344         log.debug "Creating Device Type Handler."
345         
346         d = addChildDevice("Encored Technologies", "EnerTalk Energy Meter", atomicState.dni, null, [name:"EnerTalk Energy Meter", label:name])
347
348     } else {
349         log.debug "Device already created"
350     }
351     
352     setSummary()    
353 }
354
355 def setSummary() {
356     
357     log.debug "in setSummary"
358     def text = "Successfully installed."
359     sendEvent(linkText:count.toString(), descriptionText: app.label,
360               eventType:"SOLUTION_SUMMARY",
361               name: "summary",
362               value: text,
363               data: [["icon":"indicator-dot-gray","iconColor":"#878787","value":text]],
364               displayed: false)
365 }
366
367 // TODO: implement event handlers
368
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
373 */
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')
380
381     log.debug "verified : ${verified}"
382
383     /* if Encored Access Token need to be renewed. */
384     if (!verified) {
385         try {
386             refreshAuthToken()
387
388             /* Recheck the renewed Encored access token. */
389             verifyParam.headers = [Authorization: "Bearer ${atomicState.encoredAccessToken}"]
390             verified = getHttpGetJson(verifyParam, 'CheckRefresh')
391
392         } catch (groovyx.net.http.HttpResponseException e) {
393             /* If refreshing token raises an error  */
394             log.warn "Refresh Token Error :  ${e}"
395         }
396     }
397     
398     return verified
399 }
400
401 /* Get device UUID, if it does not exist, return false. true otherwise.*/
402 private getUUID() {
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"])
408
409     def deviceUUID = getHttpGetJson(uuidParams, 'UUID')
410     log.debug "device uuid is : ${deviceUUID}"
411     if (!deviceUUID) {
412         return false
413     }
414     log.debug "got here even tho"
415     atomicState.uuid = deviceUUID.uuid
416     atomicState.notPaired = false
417     return true
418 }
419
420 private createLocaleStrings() {
421    state.domains = [
422                 test : "http://api.encoredtech.com",
423         prod : "https://api.encoredtech.com:8082/"
424    ]
425    state.languageString = 
426    [
427                 energyPlan : 30000,
428         en : [
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.",
431                 main : "EnerTalk",
432                 defaultValues : [
433                                 default1 : "kWh",
434                                 default2 : "1st day of the month",
435                                 default3 : "Low voltage"
436                                                 ],
437                 meteringDays : [
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",
464                             "Rest of the month"
465                             ],
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> """,
477                 message3 : [
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"
483                            ],
484                 message4 : [
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."
489                                         ]
490             ],
491         ko :[
492                 desc1 : "스마트 에너지 서비스를 이용하시려면 EnerTalk 서비스 가입과 SmartThings 접근 권한이 필요합니다.",
493                 desc2 : "아래 버튼을 누르면 인증을 시작합니다",
494                 main : "EnerTalk 인증",
495                 defaultValues : [
496                                 default1 : "kWh",
497                                 default2 : "매월 1일",
498                                 default3 : "주택용 저압"
499                                                 ],
500                 meteringDays : [
501                             "매월 1일", 
502                             "매월 2일", 
503                             "매월 3일",
504                             "매월 4일",
505                             "매월 5일",
506                             "매월 6일",
507                             "매월 7일",
508                             "매월 8일",
509                             "매월 9일",
510                             "매월 10일",
511                             "매월 11일",
512                             "매월 12일",
513                             "매월 13일",
514                             "매월 14일",
515                             "매월 15일",
516                             "매월 16일",
517                             "매월 17일",
518                             "매월 18일",
519                             "매월 19일",
520                             "매월 20일",
521                             "매월 21일",
522                             "매월 22일",
523                             "매월 23일",
524                             "매월 24일",
525                             "매월 25일",
526                             "매월 26일",
527                             "말일"
528                             ],
529                 displayUnits : ["원(₩)", "kWh"],
530                 contractTypes : ["주택용 저압", "주택용 고압"],
531                 title1 : "알람 설정",
532                 title2 : "사용 계획 (원)",
533                 subTitle1 : "월간 계획을 금액으로 입력하세요",
534                 title3 : "표시 단위",
535                 title4 : "정기검침일",
536                 title5 : "계약종별",
537                 title6 : "사용자 & 알람 설정",
538                 message1 : """ <p>EnerTalk 계정이 SmartThings와 연결 되었습니다!</p> <p>Done을 눌러 계속 진행해 주세요.</p> """,
539                 message2 : """ <p>계정 연결이 실패했습니다.</p> <p>Done 버튼을 눌러 다시 시도해주세요.</p> """,
540                 message3 : [
541                                 header : "기기 설치가 필요합니다.",
542                         body1 : "가정 내 분전반에 EnerTalk 기기를 먼저 설치하고,",
543                         body2 : "아래 버튼을 눌러 기기등록 및 연결을 진행하세요.",
544                         button1 : "기기 설정",
545                         button2 : "설치필요"
546                            ],
547                 message4 : [
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."
552                                         ]
553                 
554             ]
555     ]
556
557 }
558
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}"
564     url
565 }
566
567 String toQueryString(Map m) {
568         return m.collect { k, v -> "${k}=${URLEncoder.encode(v.toString())}" }.sort().join("&")
569 }
570
571 /* make a success message. */
572 private success() {
573         def lang = clientLocale?.language
574    
575     if ("${lang}" == "ko") {
576                 log.debug "I was here at first."
577         atomicState.language = "ko"
578     } else {
579   
580         atomicState.language = "en"
581     }
582         log.debug atomicState.language
583         def message = atomicState.languageString."${atomicState.language}".message1
584         connectionStatus(message)
585 }
586
587 /* make a failure message. */
588 private fail() {
589         def lang = clientLocale?.language
590    
591     if ("${lang}" == "ko") {
592                 log.debug "I was here at first."
593         atomicState.language = "ko"
594     } else {
595   
596         atomicState.language = "en"
597     }
598     def message = atomicState.languageString."${atomicState.language}".message2
599     connectionStatus(message)
600 }
601
602 private connectionStatus(message) {
603     def html = """
604         <!DOCTYPE html>
605         <html>
606         <head>
607         <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width height=device-height">
608        
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">
612             @font-face {
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');
619                 font-weight: normal;
620                 font-style: normal;
621             }
622             @font-face {
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');
629                 font-weight: normal;
630                 font-style: normal;
631             }
632             body {
633                 margin: 0;
634                 width : 100%;
635             }
636             .container {
637                 width: 100%;
638             
639                 /*background: #eee;*/
640                 text-align: center;
641             }
642             img {
643                 vertical-align: middle;
644                 margin-top:20.3125vw;
645                
646             }
647             
648             .encored{
649                 width: 25vw;
650                 height: 25vw;
651                 margin-right : 8.75vw;
652             }
653             .chain {
654                 width:6.25vw;
655                 height: 6.25vw;
656             }
657             .smartt {
658                 width: 25vw;
659                 height: 25vw;
660                 margin-left: 8.75vw
661             }
662                 
663             p {
664                 font-size: 21px;
665                 font-weight: 300;
666                 font-family: Roboto;
667                 text-align: center;
668                 color: #4c4c4e;
669                 
670                 margin-bottom: 0;
671             }
672         /*
673             p:last-child {
674                 margin-top: 0px;
675             }
676         */
677             span {
678                 font-family: 'Swiss 721 W01 Light';
679             }
680         </style>
681   
682         </head>
683         <body>
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" />
688                 <p>${message}</p>
689                 
690             </div>
691             
692         </body>
693         </html>
694         """
695         render contentType: 'text/html', data: html
696 }
697
698 private refreshAuthToken() {
699         /*Refreshing Encored Access Token*/
700     
701     log.debug "Refreshing Encored Access Token"
702         if(!atomicState.encoredRefreshToken) {
703                 log.error "Encored Refresh Token does not exist!"
704         } else {
705     
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}"])
710         
711         def newAccessToken = getHttpPostJson(refreshParam)
712         
713         if (newAccessToken) {
714                 atomicState.encoredAccessToken = newAccessToken.access_token
715             log.debug "Successfully got new Encored Access Token.\n"
716         } else {
717                 log.error "Was unable to renew Encored Access Token.\n"
718         }
719     }
720 }
721
722 private getHttpPutJson(param) {
723         
724     log.debug "Put URI : ${param.uri}"
725         try {
726        httpPut(param) { resp ->
727                         log.debug "HTTP Put Success"
728        }
729     } catch(groovyx.net.http.HttpResponseException e) {
730         log.warn "HTTP Put Error : ${e}"
731     }
732 }
733
734 private getHttpPostJson(param) {
735         log.debug "Post URI : ${param.uri}"
736    def jsonMap = null
737    try {
738        httpPost(param) { resp ->
739            jsonMap = resp.data
740            log.debug resp.data
741        }
742     } catch(groovyx.net.http.HttpResponseException e) {
743         log.warn "HTTP Post Error : ${e}"
744     }
745     
746     return jsonMap
747 }
748
749 private getHttpGetJson(param, testLog) {
750         log.debug "Get URI : ${param.uri}"
751    def jsonMap = null
752    try {
753        httpGet(param) { resp ->
754            jsonMap = resp.data
755        }
756     } catch(groovyx.net.http.HttpResponseException e) {
757         log.warn "HTTP Get Error : ${e}"
758     }
759     
760     return jsonMap
761
762 }
763
764 private makePostParams(uri, header, body=[]) {
765         return [
766         uri : uri,
767         headers : header,
768         body : body
769     ]
770 }
771
772 private makeGetParams(uri, headers, path="") {
773         return [
774         uri : uri,
775         path : path,
776         headers : headers
777     ]
778 }
779
780 def getInitialData() {
781         def lang = clientLocale?.language
782     if ("${lang}" == "ko") {
783         lang = "ko"
784     } else {
785         lang = "en"
786     }
787     atomicState.solutionModuleSettings.language = lang
788         atomicState.solutionModuleSettings
789 }
790
791 def consoleLog() {
792     log.debug "console log: ${request.JSON.str}"
793 }
794
795 def getHtml() {
796         
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"
803     
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"
810     
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 = ""
815     
816     def htmlBody = ""
817     
818     /* Get the language setting on device. */
819     def lang = clientLocale?.language
820     if ("${lang}" == "ko") {
821         atomicState.language = "ko"
822     } else {
823         atomicState.language = "en"
824     }
825     
826     if (atomicState.language == "ko") {
827         rankingShow = "데이터 수집 중"
828         meteringPeriodBillFalse = "데이터 수집 중" 
829         lastMonthFalse = "정보가 없습니다" 
830         standbyFalse = "데이터 수집 중"
831         planFalse = "계획을 입력하세요"
832         thisMonthTitle = "이번 달" 
833         tierTitle = "누진단계" 
834         planTitle = "사용 계획" 
835         lastMonthTitle = "지난달" 
836         rankingTitle = "랭킹" 
837         standbyTitle = "대기전력" 
838         energyMonitorDeviceTitle = "스마트미터 상태"
839         realtimeTitle = "실시간"
840     }
841     
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) {
847         getUUID()
848     }
849     
850     /* If token has been verified or refreshed and if uuid exist, call other apis */
851     log.debug atomicState.notPaired
852     if (!atomicState.notPaired) {
853
854         if(EATValidation) {
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"])
858
859             /* get device status. */
860             deviceStatusData = getHttpGetJson(deviceStatusParam, 'CheckDeviceStatus')
861             
862
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"])
866             
867             /* get standby value */
868             standbyData = getHttpGetJson(standbyParam, 'CheckStandbyPower')
869             
870             
871
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"])
875                         
876             /* Get user's info */
877             meData = getHttpGetJson(meParam, 'CheckMe')
878                         
879                 
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"])
883             
884             /* Get the value of energy used since metering date. */
885             meteringData = getHttpGetJson(meteringParam, 'CheckMeteringUsage')
886                         
887             
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"])
891                         
892             /* Get user's energy usage rank */
893             rankingData = getHttpGetJson(rankingParam, 'CheckingRanking')
894                         
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. */
896
897             /* parse device status. */
898             if (deviceStatusData) {
899                 if (deviceStatusData.status == "NORMAL") {
900                     deviceStatus = true
901                 }
902             }
903             
904             log.debug "deiceStatusData : ${deviceStatus} || ${deviceStatusData}"
905
906             /* Parse standby power. */
907             if (standbyData) {
908                 if (standbyData.standbyPower) {
909                     standby = (standbyData.standbyPower / 1000)
910                 }
911             }
912
913             /* Parse max limit usage and it's bill from user's info. */
914             if (meData) {
915                 if (meData.maxLimitUsageBill) {
916                     maxLimitUsageBill = meData.maxLimitUsageBill
917                     maxLimitUsage = meData.maxLimitUsage
918                 }
919             }
920
921             /* Parse the values which have been used since metering date.
922             * The list is :
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.
930             */  
931             if (meteringData) {
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)
940                     if(tier > 6) {
941                         tier = 6
942                     }
943
944                 } 
945             }
946
947             /* Get ranking data of a user and the percent */
948             if (rankingData) {
949                 if (rankingData.user.ranking) {
950                     percent = ((int)((rankingData.user.ranking / rankingData.user.population) * 10))
951                     if (percent > 10) {
952                         percent = 10
953                     }
954                 }
955             }
956
957             /* if the start value exist, get last month energy usage. */
958             if (start) {
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"])
961
962                 lastMonth = getHttpGetJson(lastMonthParam, 'ChecklastMonth')
963
964             }
965
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*/
970             def planUsed = 0
971             if ( maxLimitUsage > 0 ) {
972                 planUsed = Math.round((meteringUsage / maxLimitUsage) * 100) /* get the pecent of used amount against max usage */
973             } else {
974                 planUsed = Math.round((meteringUsage/ 1000000) * 100) /* if max was not decided let the used value be percent. e.g. 1kWh = 100% */
975             }
976
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')
981
982             if (!realTimeInfo) {
983                 realTimeInfo = 0
984             } else {
985                 realTimeInfo = Math.round(realTimeInfo.activePower / 1000 )
986             }
987                         
988             
989             
990             /* inserting values to device type handler */
991             
992             d?.sendEvent(name: "view", value : "${kWhMonth}")
993             if (deviceStatus) {
994
995                 d?.sendEvent(name: "month", value : "${thisMonthTitle} \n ${kWhMonth} \n kWh")
996             } else { 
997
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}") 
1002             }
1003             
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}")
1007           
1008             deviceId = d.id
1009
1010         } else {
1011             /* If it finally couldn't get Encored access token. */
1012             log.error "Could not get Encored Access Token. Please try later."
1013         }
1014
1015         /* change the display uinit to bill from kWh if user want. */
1016         if (settings.displayUnit == "WON(₩)" || settings.displayUnit == "원(₩)") {
1017             displayUnit = "bill"
1018         }
1019
1020         if (meteringPeriodBill) {
1021             /* reform the value of the bill with the , separator */
1022             meteringPeriodBillShow = formatMoney("${meteringPeriodBill}")
1023             meteringPeriodBillFalse = ""
1024             thisMonthUnitOne = "&#x20A9;"
1025
1026             def dayPassed = getDayPassed(start, end, meteringDay)
1027             if (atomicState.language == 'ko') {
1028                 thisMonthUnitTwo = "/ ${dayPassed}일"
1029             } else {
1030                 if (dayPassed == 1) {
1031                     thisMonthUnitTwo = "/${dayPassed} day"
1032                 } else {
1033                     thisMonthUnitTwo = "/${dayPassed} days"
1034                 }
1035             }
1036         }
1037
1038         if (plan) {
1039             planShow = plan
1040             if (plan >= 1000) {planShow = formatMoney("${plan}") }
1041             planFalse = ""
1042             planUnitOne = "&#x20A9;"
1043
1044             if (atomicState.language == 'ko') {
1045                 planUnitTwo = "남음"
1046             } else {
1047                 planUnitTwo = "left"
1048             }
1049
1050         }
1051
1052         /*set the showing units for html.*/
1053         log.debug lastMonth
1054         if (lastMonth.usages) {
1055             lastMonthShow = formatMoney("${lastMonth.usages[0].meteringPeriodBill}")
1056             lastMonthFalse = ""
1057             lastMonthUnit = "&#x20A9;"
1058        
1059         }
1060
1061         if (standby) {
1062             standbyShow = standby
1063             standbyFalse = ""
1064             standbyUnit = "W"
1065         }
1066
1067         if (percent) {
1068             rankImage = "<img id=\"image-rank\" src=\"https://s3-ap-northeast-1.amazonaws.com/smartthings-images/ranking_${percent}.svg\" />"
1069             rankingShow = ""
1070         }
1071
1072         if (tier) {
1073             tierImage = "<img id=\"image-tier\" src=\"https://s3-ap-northeast-1.amazonaws.com/smartthings-images/tier_${tier}.svg\" />"
1074             tierShow = ""
1075         }
1076
1077         if (deviceStatus) {
1078             onOff = "ON"
1079         }
1080
1081         atomicState.solutionModuleSettings = [
1082            auth           : atomicState.encoredAccessToken, 
1083            deviceState    : deviceStatus, 
1084            percent        : percent,
1085            displayUnit    : displayUnit,
1086            language               : atomicState.language,
1087            deviceId           : deviceId,
1088            pairing                : true
1089         ]
1090         
1091         htmlBody = """
1092          <div id="real-time">
1093                 
1094             <!-- real-time card -->
1095             <div id="my-card"></div>
1096             
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>
1105               </span> 
1106             </div>
1107             
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>
1114               </span>
1115             </div> 
1116             
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> 
1125               </span>
1126             </div>
1127             
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>
1135               </span>
1136             </div>
1137
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>
1144               </span>
1145             </div> 
1146             
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>
1154               </span>
1155             </div>
1156             
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>
1163               </span>
1164             </div>
1165             
1166           </div>
1167           
1168           <!-- hidden section -->
1169           
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>
1174             </div>
1175             <div class="cards" id="my-card2"></div>
1176             <div class="cards" id="my-card3"></div>
1177           </div>
1178           
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>
1183             </div>
1184             <div class="cards" id="my-card4"></div>
1185           </div>
1186           
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>
1191             </div>
1192             <div class="cards" id="my-card5"></div>
1193           </div>
1194           
1195           <div id="ranking">
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>
1199             </div>
1200             <div class="cards" id="my-card6"></div>
1201           </div>
1202           
1203           <div id="plan">
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>
1207             </div>
1208             <div class="cards" id="my-card7"></div>
1209           </div>
1210           
1211           <div id="standby">
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>
1215             </div>
1216             <div class="cards" id="my-card8"></div>
1217           </div>
1218           <script>
1219                      \$("#this-month").slideUp();
1220                 \$("#last-month").slideUp();
1221                 \$("#progressive-step").slideUp();
1222                 \$("#ranking").slideUp();
1223                 \$("#plan").slideUp();
1224                 \$("#standby").slideUp();
1225
1226                 var UI = new Encored.UI({
1227         
1228                 });
1229                 UI.renderCard({
1230                    'cards': [{
1231                         'id': 'ui:h:strealtime:v1', 
1232                     'params': {
1233                                 'lang': '${atomicState.language}', 
1234                                 'useDemoLabel': 1, 
1235                                 'displayUnit': '${displayUnit}'
1236                                         }
1237                                     }], 
1238                     'accessToken': '${atomicState.encoredAccessToken}', 
1239                     'target': document.getElementById("my-card")
1240                              });
1241           </script>
1242         <script src="${buildResourceUrl('javascript/app.js')}"></script>
1243         
1244         """
1245     } else {
1246                 log.debug "abotu to ask device connection"
1247         def d = getChildDevice(atomicState.dni)
1248         /* inserting values to device type handler */ 
1249
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}")
1251         deviceId = d.id
1252                 
1253         if (state.language == "ko") {
1254                 energyMonitorDeviceTitle = "스마트미터 상태"     
1255         }
1256         /* need device pairing */
1257         atomicState.solutionModuleSettings = [
1258                 dId             : deviceId,
1259             pairing     : false
1260         ]
1261   
1262         htmlBody = """
1263         
1264                 <div id="real-time">
1265         
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>
1271                 </div>
1272
1273
1274
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>
1280                   </span>
1281                 </div>
1282             
1283                 </div>
1284                   
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>
1287             <script>
1288                 var ua = navigator.userAgent.toLowerCase();
1289                 var isAndroid = ua.indexOf("android") > -1; //&& ua.indexOf("mobile");
1290                 if(!isAndroid) { 
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>");
1292                                 } else {
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>");
1294                 };
1295             </script>
1296         """
1297     }
1298
1299     renderHTML() {
1300         head {
1301         """
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>
1308         """
1309         }
1310         body {
1311                 htmlBody
1312         }
1313     }
1314 }
1315
1316
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
1320     def ret = ""
1321     def commas = ((int) Math.floor(i/3))
1322
1323     def j = 0
1324     def counter = 0
1325  
1326     while (i >= 0) {
1327
1328         if (counter > 0 && (counter % 3) == 0) {
1329                 ret = "${money[i]},${ret}"
1330             j++
1331         } else {
1332                 ret = "${money[i]}${ret}"
1333         }
1334         
1335         counter++
1336                 i--
1337         }
1338     
1339     ret
1340 }
1341
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).
1346 */
1347 private getDayPassed(start, end, meteringDay){
1348     
1349     def day = 1
1350     def today = new Date(end)
1351     def tzDifference = 9 * 60 + today.getTimezoneOffset()
1352         today = new Date(today.getTime() + tzDifference * 60 * 1000).getDate();
1353     
1354     if (today > meteringDay) {
1355         day += today - meteringDay;
1356        
1357     }
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();
1363  
1364         if (lastDate == 1) {
1365             day += 30;
1366         } else {
1367             day += 31;
1368         }
1369         
1370         day = day - meteringDay + today;
1371     }
1372     
1373     day
1374 }
1375
1376 /* Get Encored push and send the notification. */
1377 def getEncoredPush() {
1378
1379         byte[] decoded = "${params.msg}".decodeBase64()
1380         def decodedString = new String(decoded)
1381
1382     if (settings.notification == "true") {
1383         sendNotification("${decodedString}", [method: "push"])
1384     } else {
1385         sendNotificationEvent("${decodedString}")
1386     }
1387     
1388 }