Posted to tcl by Napier at Thu Jun 26 11:20:27 GMT 2014view raw

  1. # --------------------------------------------
  2. # Nest & Smoke+CO TCL Integration Library
  3. # Connects to the Nest API for control &
  4. # monitoring of NEST Products.
  5. #
  6. # Author: Automation Apps
  7. # --------------------------------------------
  8. # Main Command Syntax:
  9. # -fanTimer <start,stop> -mode <off, heat, cool, heat-cool> -
  10. #
  11.  
  12. enableLOG 0
  13. set varIfMRXMAC "000"
  14.  
  15. # Include Packages in the includePackages List
  16. set includePackages "http tls json json::write tdom uri"
  17. foreach {pkg} $includePackages {package require $pkg}
  18.  
  19. tls::init -tls1 true
  20. http::register https 443 tls::socket
  21.  
  22. namespace eval API {
  23. # Be Sure to Set your Client Secret & Client ID
  24. variable FB_Auth "auth=c.DWjy5mnOvgHcJ1NdKisooOZEIyO5dpVacU8tC5B9Me6QtkQ7vtHOqwYMdneHz8pnNf3raKOQlVjaIVmyQCZFnAxC5H7vKGP5WK3IKTTX3l8Gd0pBB0B1th2Q5cOWZFNWOJiq8IPHZPv6U5HB"
  25. variable authState $::varIfMRXMAC
  26. variable baseURL "https://developer-api.nest.com"
  27. variable basePort 443
  28. variable client_secret "wCJzrq2VuyWcH70DjENWpjupi"
  29. variable client_id "7c3fe371-5740-48ac-a1b2-74df90145813"
  30. variable authRequestURL "https://home.nest.com/login/oauth2?client_id=${client_id}&state=${authState}"
  31. variable authorizeURL "https://api.home.nest.com/oauth2/access_token?code=|AUTHPIN|&client_id=${client_id}&client_secret=${client_secret}&grant_type=authorization_code"
  32. variable forecast "https://home.nest.com/api/0.1/weather/forecast/${zip},${country}"
  33. }
  34.  
  35. namespace eval Web {
  36.  
  37. variable RequestData [dict create]
  38.  
  39. proc Send {args} {
  40. # Syntax:
  41. # -method $METHOD -url $URL -params $PARAMS -suffix $URL-Suffix -port $port -body $body -request $request
  42. # -request feeds the RequestData Variable to Identify to the Callback
  43. # Web::Send -method PATCH -url https://www.url.com -params "Hi Hello" -suffix "devices/hello" -body $json -port 9553 would PATCH:
  44. # https://www.url.com:9553/devices/hello?auth={AUTH CODE}&hi=hello with the value of $json as the body
  45. LOG "Beginning Web::Send $args"
  46. set tempDict $args
  47. if {$::API::FB_Auth == 0} {LOG "OAuth Required Before Continuing"; #TODO: Build Re-Auth Service}
  48. if {![dict exists $tempDict -suffix]} {LOG "No Suffix Detected"; dict set tempDict -suffix ""}
  49. if {![dict exists $tempDict -url]} {LOG "No URL using Default"; dict set tempDict -url $::API::baseURL}
  50. if {![dict exists $tempDict -method]} {LOG "No Method Defined, Using GET"; dict set tempDict -method "GET"}
  51. if {[dict exists $tempDict -params]} {LOG "Formatting Query"; dict set tempDict -params "&[::http::formatQuery {*}[dict get $tempDict -params]]"
  52. } else {LOG "No Params Detected"; dict set tempDict -params ""}
  53. if {[dict exists $tempDict -port]} {LOG "Custom Port Detected: [dict get $tempDict -port]"; http::register https [dict get $tempDict -port] tls::socket
  54. } else {http::register https $::API::basePort tls::socket}
  55. if {![dict exists $tempDict -body]} {LOG "No Body Detected"; dict set tempDict -body ""}
  56. if {![dict exists $tempDict -request]} {dict set tempDict -request "None"}
  57. set ::Web::RequestData $tempDict
  58. if {[dict get $tempDict -method] == "GET"} {set token [http::geturl [dict get $tempDict -url]/[dict get $tempDict -suffix]?${::API::FB_Auth}[dict get $tempDict -params] -command ::Web::httpCallback]
  59. } else {set token [http::geturl [dict get $tempDict -url]/[dict get $tempDict -suffix]?${::API::FB_Auth}[dict get $tempDict -params] \
  60. -method [dict get $tempDict -method] -query [dict get $tempDict -body] -command ::Web::httpCallback]
  61. }
  62. }
  63.  
  64. proc httpCallback {token} {
  65. set data $::Web::RequestData
  66. set response [dict create data [http::data $token] code [http::code $token]]
  67. LOG "----- RECEIVING DATA -----"
  68. LOG "$response"
  69. LOG "--------------------------"
  70. puts $token
  71. if {[string match {30[1237]} [::http::ncode $token]]} {LOG "Redirect Required!"; ::Web::startRedirect $token
  72. } elseif {[::http::ncode $token] == 200} {LOG "Successfull Call Detected"
  73. switch -- [dict get $data -request] {
  74. Query {::Nest::SetStatus [::json::json2dict [dict get $response data]]}
  75. default {LOG "Unknown or Undefined Request, Saving Value to ::Nest::StatusUndefined"; set ::Nest::StatusUndefined $data}
  76. }
  77. } elseif {[::http::ncode $token] == 429} {LOG "Too Many Requests Error Received"}
  78. }
  79.  
  80. proc startRedirect {token} {
  81. set data $::Web::RequestData
  82. array set meta [set ${token}(meta)]
  83. LOG "[parray {*}$token]"
  84. http::cleanup $token
  85. if {![info exist meta(Location)]} {LOG "No Redirection Indicator?"; return}
  86. array set uri [::uri::split $meta(Location)]
  87. LOG "URI:"; LOG "[parray uri]"
  88. unset meta
  89. if {$uri(host) == ""} {set uri(host) $uri(host)}
  90. set url [eval ::uri::join [array get uri]]
  91. LOG "URL: $url"
  92. if {[dict get $data -method] == "GET"} {set token [http::geturl $url -command ::Web::httpCallback]
  93. } else {set token [http::geturl $url -method [dict get $data -method] -query [dict get $data -body] -command ::Web::httpCallback]}
  94. }
  95.  
  96. }
  97.  
  98.  
  99. namespace eval Nest {
  100. variable Status ""
  101. variable StatusUndefined 0
  102. variable thermostats [dict create]
  103. variable devices [dict create thermostats "" protects ""]
  104.  
  105. proc Authorize {authPIN} {
  106. # Sends PIN to Nest to retrieve authentication string
  107. set authURL [string map "|AUTHPIN| $authPIN" $::API::authorizeURL]
  108.  
  109. }
  110.  
  111. proc Query {args} {
  112. # Queries The Nest API Based on Your Parameters
  113. # If blank (::Nest::Query) it will query all parameters into the main dictionary
  114. # -thermostat <therm ID or name> -smoke <smokeCO ID or name>
  115. Web::Send -request Query
  116. }
  117.  
  118. proc Set {args} {
  119.  
  120. }
  121.  
  122. proc SetStatus {newStatus args} {
  123. LOG "---- New Nest Status Received!\n$newStatus"
  124. if {$::Nest::Status != "" && [dict exists $::Nest::Status current]} {dict set ::Nest::Status old [dict get $::Nest::Status current]}
  125. dict set ::Nest::Status current $newStatus
  126. getDevices thermostats
  127. getDevices protects
  128. }
  129.  
  130. proc getDevices {kind} {
  131. # Parses the Data from the Queries and returns "kind" to the proper
  132. # Dictionary with name/id as the key/value pairs
  133. # Options for Kind are: thermostats, smoke
  134. set tempDict [dict create]
  135. if {$::Nest::Status == ""} {LOG "Query Never Performed? Attempting Query..."; Nest::Query}
  136. switch -regexp -nocase -- $kind {
  137. {^thermostats?$} {
  138. if {[dict exists $::Nest::Status current devices thermostats]} {set thermIDs [dict keys [dict get $::Nest::Status current devices thermostats]]
  139. } else {LOG "No Thermostats Detected - Attempting Query"y}
  140. foreach id $thermIDs {
  141. set name [dict get $::Nest::Status current devices thermostats $id name]
  142. dict set tempDict $name $id
  143. dict set ::Nest::Devices thermostats $tempDict
  144. }
  145. }
  146. {^protects?} {
  147. if {[dict exists $::Nest::Status current devices smoke_co_alarms]} {set protectIDs [dict keys [dict get $::Nest::Status current devices smoke_co_alarms]]
  148. } else {LOG "No Protects Were Detected"}
  149. foreach id $protectIDs {
  150. set name [dict get $::Nest::Status current devices smoke_co_alarms $id name]
  151. dict set tempDict $name $id
  152. dict set ::Nest::Devices protects $tempDict
  153. }
  154. }
  155. {^all} {}
  156. default {LOG "Kind was not Recognized - should be thermostats, protects, or all"}
  157. }
  158. }
  159.  
  160. proc nameFromID {id {kind "thermostats"}} {
  161. # Used to Return the Room Name of a given Device ID
  162. dict for {k v} [dict filter [dict get $::Nest::Devices $kind] value $id] {
  163. lappend keys $k
  164. }
  165. return $keys
  166. }
  167. }
  168.  
  169. namespace eval URC {
  170. proc interfaceServer {} {
  171. # Interfaces with URC Interfaces
  172. }
  173. }
  174.  
  175. Nest::Query;after 3000 {set i 0};vwait i