Posted to tcl by Napier at Thu Jul 03 22:25:43 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. proc enableLOG { status } {
  16. global LOGENABLE
  17. set LOGENABLE $status
  18. }
  19.  
  20. proc LOG {data} {
  21. puts $data
  22. }
  23.  
  24. # Include Packages in the includePackages List
  25. set includePackages "http tls json json::write tdom uri struct::list"
  26. foreach {pkg} $includePackages {package require $pkg}
  27.  
  28. tls::init -tls1 true
  29. http::register https 443 tls::socket
  30.  
  31. namespace eval API {
  32. # Be Sure to Set your Client Secret & Client ID
  33. variable FB_Auth "auth=c.DWjy5mnOvgHcJ1NdKisooOZEIyO5dpVacU8tC5B9Me6QtkQ7vtHOqwYMdneHz8pnNf3raKOQlVjaIVmyQCZFnAxC5H7vKGP5WK3IKTTX3l8Gd0pBB0B1th2Q5cOWZFNWOJiq8IPHZPv6U5HB"
  34. variable authState $::varIfMRXMAC
  35. variable baseURL "https://developer-api.nest.com"
  36. variable basePort 443
  37. variable client_secret "wCJzrq2VuyWcH70DjENWpjupi"
  38. variable client_id "7c3fe371-5740-48ac-a1b2-74df90145813"
  39. variable ThermBaseURL "devices/thermostats/"
  40. variable authRequestURL "https://home.nest.com/login/oauth2?client_id=7c3fe371-5740-48ac-a1b2-74df90145813&state=ip"
  41. variable authorizeURL "https://api.home.nest.com/oauth2/access_token?code=|AUTHPIN|&client_id=${client_id}&client_secret=${client_secret}&grant_type=authorization_code"
  42. variable maxNestEvents 25
  43. variable maxUIEvents 10
  44. # variable forecast "https://home.nest.com/api/0.1/weather/forecast/${zip},${country}"
  45.  
  46. namespace eval CMDS {
  47. variable FanTimer "fan_timer_active" ; # Boolean - Sets 15 Minute Fan Timer {"fan_timer_active": true }
  48. # JSON Example: { "fan_timer_active": true }
  49. variable Temp_f "target_temperature_f" ; # Integer - Sets Fahrenheit {"target_temperature_f": 70} (1 Accuracy)
  50. # JSON Example: { "target_temperature_f": 70 }
  51. variable Temp_c "target_temperature_c" ; # Integer - Sets Celcius (0.5 Accuracy)
  52. # JSON Example: { "target_temperature_c": 30.5 }
  53. variable HighTemp_f "target_temperature_high_f" ; # Integer - Set High Temp in Fahrenheit (1 Accuracy)
  54. # JSON Example: { "target_temperature_high_f": 75 }
  55. variable HighTemp_c "target_temperature_high_c" ; # Integer - Set High Temp in Celcius (0.5 Accuracy)
  56. # JSON Example: { "target_temperature_high_c": 40.5 }
  57. variable LowTemp_f "target_temperature_low_f" ; # Integer - Set Low Temp in Fahrenheit (1 Accuracy)
  58. # JSON Example: { "target_temperature_low_f": 65 }
  59. variable LowTemp_c "target_temperature_low_c" ; # Integer - Set Low Temp in Celcius (0.5 Accuracy)
  60. # JSON Example: { "target_temperature_low_c": 20.5 }
  61. variable mode "hvac_mode" ; # String - Set HVAC Mode <heat, cool, heat-cool, off>
  62. # JSON Example: {"hvac_mode": "heat-cool"}
  63.  
  64. variable Type 0
  65.  
  66. trace add variable ::API::CMDS::FanTimer read {::System::traceCallback i}
  67. trace add variable ::API::CMDS::Temp_f read {::System::traceCallback i}
  68. trace add variable ::API::CMDS::Temp_c read {::System::traceCallback i}
  69. trace add variable ::API::CMDS::HighTemp_f read {::System::traceCallback i}
  70. trace add variable ::API::CMDS::HighTemp_c read {::System::traceCallback i}
  71. trace add variable ::API::CMDS::LowTemp_f read {::System::traceCallback i}
  72. trace add variable ::API::CMDS::LowTemp_c read {::System::traceCallback i}
  73. trace add variable ::API::CMDS::mode read {::System::traceCallback s}
  74. }
  75. }
  76.  
  77. namespace eval Web {
  78.  
  79. variable RequestData [dict create]
  80. variable streamToken ""
  81.  
  82. proc Send {args} {
  83. # Syntax:
  84. # -method $METHOD -url $URL -params $PARAMS -suffix $URL-Suffix -port $port -body $body -request $request
  85. # -request feeds the RequestData Variable to Identify to the Callback
  86. # Web::Send -method PATCH -url https://www.url.com -params "Hi Hello" -suffix "devices/hello" -body $json -port 9553 would PATCH:
  87. # https://www.url.com:9553/devices/hello?auth={AUTH CODE}&hi=hello with the value of $json as the body
  88. LOG "Beginning Web::Send $args"
  89. http::cleanup $::Web::streamToken
  90. set tempDict $args
  91. if {$::API::FB_Auth == 0} {LOG "OAuth Required Before Continuing"; #TODO: Build Re-Auth Service}
  92. if {![dict exists $tempDict -suffix]} {LOG "No Suffix Detected"; dict set tempDict -suffix ""}
  93. if {![dict exists $tempDict -url]} {LOG "No URL using Default"; dict set tempDict -url $::API::baseURL}
  94. if {![dict exists $tempDict -method]} {LOG "No Method Defined, Using GET"; dict set tempDict -method "GET"}
  95. if {[dict exists $tempDict -params]} {LOG "Formatting Query"; dict set tempDict -params "&[::http::formatQuery {*}[dict get $tempDict -params]]"
  96. } else {LOG "No Params Detected"; dict set tempDict -params ""}
  97. if {[dict exists $tempDict -port]} {LOG "Custom Port Detected: [dict get $tempDict -port]"; http::register https [dict get $tempDict -port] tls::socket
  98. } else {http::register https $::API::basePort tls::socket}
  99. if {![dict exists $tempDict -body]} {LOG "No Body Detected"; dict set tempDict -body ""}
  100. if {![dict exists $tempDict -request]} {LOG "No Request Detected"; dict set tempDict -request "None"
  101. } elseif {[dict get $tempDict -request] == "Query"} {LOG "Set Event Stream Header"; http::config -accept "text/event-stream"
  102. } else {LOG "Set Accept All Header"; http::config -accept "/*"}
  103. set ::Web::RequestData $tempDict
  104. 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]
  105. } else {set token [http::geturl [dict get $tempDict -url]/[dict get $tempDict -suffix]?${::API::FB_Auth}[dict get $tempDict -params] \
  106. -method [dict get $tempDict -method] -query [dict get $tempDict -body] -command ::Web::httpCallback]
  107. }
  108. }
  109.  
  110. proc httpCallback {token} {
  111. set data $::Web::RequestData
  112. set response [dict create data [http::data $token] code [http::code $token]]
  113. LOG "----- RECEIVING DATA -----"
  114. LOG "$response"
  115. LOG "--------------------------"
  116. LOG "Token: $token"
  117. if {[string match {30[1237]} [::http::ncode $token]]} {LOG "Redirect Required!"; ::Web::startRedirect $token
  118. } elseif {[::http::ncode $token] == 200} {LOG "Successfull Call Detected"
  119. ::Nest::Query
  120. #http::cleanup $token
  121. #switch -- [dict get $data -request] {
  122. # Authorize {LOG "Authorize Data Assumed"; set response [::json::json2dict [dict get $response data]]}
  123. # default {LOG "Unknown or Undefined Request, Saving Value to ::Nest::StatusUndefined"; set ::Nest::StatusUndefined $data}
  124. #}
  125. # Query {LOG "Query Data Assumed"; ::Nest::setStatus [::json::json2dict [dict get $response data]]}
  126. } elseif {[::http::ncode $token] == 429} {LOG "!!!! ---- |||| Too Many Requests Error Received |||| ----- !!!!"}
  127. }
  128.  
  129. proc httpProgress {token total current} {
  130. LOG "Start Progress"; LOG "Total: $total"; LOG "Current: $current"
  131. # The Following if Statement is used to parse out the random "e" received
  132. # at the start of the Event Stream Data.
  133. if {$current == 1} {LOG "END"; return}
  134. upvar #0 $token state
  135. set body "$state(body)"
  136. set occurrences [regsub -all {\s*\n\s*\n(\s*\n)*} $body "\n" body]
  137. set body [split $body \n]
  138. if {[dict get [lindex $body 0] "event:"] == "auth_revoked"} {::Nest::AuthRevoked}
  139. # The occurrences variable actively tracks how many events are currently saved
  140. # in the $state(body) variable. It is likely a good idea to flush this out at
  141. # a set interval by closing (http::cleanup) and re-initiating the event.
  142. LOG "Event Occurences: $occurrences"
  143. if {$occurrences >= $::API::maxNestEvents} {LOG "Flushing Events"
  144. # Process to Flush Events & Re-Initiate the Event Stream
  145. # should go here.
  146. }
  147. if {$::Nest::Status == ""} {
  148. set startData [::System::convertJSON [lindex $body 1]]
  149. LOG "Start Data:\n$startData"
  150. ::Nest::setStatus [dict get $startData data]
  151. } else {
  152. set newData [::System::convertJSON [lindex $body end-1]]
  153. if {$newData != ""} {
  154. LOG "New Data:\n$newData"
  155. if {[dict exists $::Nest::Status current] && [dict get $::Nest::Status current] != [dict get $newData data]} {
  156. LOG "Data is Different, Setting Nest Status Variable"; ::Nest::setStatus [dict get $newData data]
  157. } else {LOG "New Data has Not Changed, Do Nothing"}
  158. }
  159. }
  160. }
  161.  
  162. proc startRedirect {token} {
  163. set data $::Web::RequestData
  164. array set meta [set ${token}(meta)]
  165. LOG "[parray {*}$token]"
  166. http::cleanup $token
  167. if {![info exist meta(Location)]} {LOG "No Redirection Indicator?"; return}
  168. array set uri [::uri::split $meta(Location)]
  169. LOG "URI:"; LOG "[parray uri]"
  170. unset meta
  171. if {$uri(host) == ""} {set uri(host) $uri(host)}
  172. set url [eval ::uri::join [array get uri]]
  173. LOG "URL: $url"
  174. if {[dict get $data -method] == "GET"} {set ::Web::streamToken [http::geturl $url -keepalive 1 -progress ::Web::httpProgress]
  175. } else {set token [http::geturl $url -method [dict get $data -method] -query [dict get $data -body] -command ::Web::httpCallback]}
  176. }
  177.  
  178. proc sendEmail {to msg args} {
  179. # This Procedure will Prepare and Send an E-Mail to the address(es) provided in
  180. # the "to" argument. It will include the content in the "msg" argument.
  181. # Additionally the following arguments can be defined if needed:
  182. # -footer $txt
  183.  
  184. }
  185. }
  186.  
  187. namespace eval Nest {
  188. variable Status ""
  189. variable StatusUndefined 0
  190. variable thermostats [dict create]
  191. variable devices [dict create thermostats "" protects "" structures ""]
  192. variable presence [dict create]
  193.  
  194.  
  195. proc Authorize {authPIN} {
  196. # Sends PIN to Nest to retrieve authentication string
  197. set authURL [string map "|AUTHPIN| $authPIN" $::API::authorizeURL]
  198. LOG "Authorization URL:\n $authURL"
  199. Web::Send -method POST -url $authURL -request Authorize
  200. #TODO: Send the Authorization as POST with Parameters
  201. }
  202.  
  203. proc AuthRevoked {args} {
  204. # This Procedure is called when the "Auth_Revoked" Event
  205. # is Received from Nest. You will need to re-authenticate
  206. # and get a new "Auth Token" when this occurs.
  207. }
  208.  
  209. proc Query {args} {
  210. # Queries The Nest API Based on Your Parameters
  211. # If blank (::Nest::Query) it will query all parameters into the main dictionary
  212. # Do NOT do this Often as Nest limits the amount of calls you can do per
  213. # month per token.
  214. # All data will be kept up-to-date using the Rest Streaming Feature of the API.
  215. # -thermostat <therm ID or name> -smoke <smokeCO ID or name>
  216. Web::Send -request "Query"
  217. }
  218.  
  219. proc Set {args} {
  220. # This Procedure allows you to Control Nest Products using
  221. # their Official API.
  222. # -command - Specify the command. Command options and examples can be found in the
  223. # ::API::CMDS Namespace and should be referenced as that variable.
  224. # -value - Specify the Value you would like to feed to the Process. This should follow
  225. # the examples in the "::API::CMDS" namespace.
  226. # -device {deviceID} - This argument specifies the device's ID which
  227. # should be manipulated
  228. # -kind {kind} - Specify what kind of device will be controlled.
  229. # this is an optional value but specifying it will increase overall speed.
  230. # Syntax Example:
  231. # ::Nest::Set -command $::API::CMDS::Temp_f -value 75 -device $thermID -kind thermostat
  232. LOG "Beginning Set Procedure"
  233. if {[dict exists $args -kind]} {
  234. LOG "Kind was Specified: [dict get $args -kind]"
  235. switch -regexp -- [dict get $args -kind] {
  236. {^thermostats?$|^therms?$} {::Nest::setThermostat [dict get $args -command] [dict get $args -value] [dict get $args -device]}
  237. {^protects?$|^smokecos?$|^detectors?$} {LOG "Nest Protect Specified\nThere are currently No Official Set Options for Nest Protect"}
  238. default {puts "No"}
  239. }
  240. } else {
  241. LOG "Kind was Not Specified - Starting ID Trace"
  242. }
  243. }
  244.  
  245. proc setThermostat {cmd value id} {
  246. LOG "Setting the Thermostat with ID: $id"
  247. # This Procedure should generally not be called directly, use ::Nest::Set $cmd $value -kind (optional)
  248. # Currently Adds JSON and sends one Command at a Time - another procedure will be written to send multiple
  249. # values with a single call.
  250.  
  251. if {$::API::CMDS::Type == "i"} { set json [::json::write object $cmd $value]
  252. } elseif {$::API::CMDS::Type == "s"} { set json [::json::write object $cmd [::json::write string $value]]
  253. }
  254. LOG "|--- Sending in Patch Body:\n$json"
  255. LOG "|--- Sending to Suffix:\n${::API::baseURL}/${::API::ThermBaseURL}$id"
  256. ::Web::Send -method PATCH -suffix ${::API::ThermBaseURL}$id -body $json
  257. }
  258.  
  259. proc Experimental {args} {
  260. # This procedure will house non-official API procedures that
  261. # may not be supported in the long-term. Generally this will
  262. # emulate features used on the Official Nest App & Web Control
  263. # app.
  264.  
  265. }
  266.  
  267. proc setStatus {newStatus args} {
  268. # This Procedure handles Status Setting when a Query is Made. If Status was already present, it will save the
  269. # "current" Status into the "old" Status Dictionary and the newly received status into the "current" dictionary.
  270. # This can then be used to evaluate events as needed for Macro Integration
  271.  
  272. LOG "---- New Nest Status Received!\n$newStatus"
  273. if {$::Nest::Status != "" && [dict exists $::Nest::Status current]} {
  274. dict set ::Nest::Status old [dict get $::Nest::Status current]
  275. }
  276. dict set ::Nest::Status current $newStatus
  277. getDevices all
  278. }
  279.  
  280. proc getDevices {kind} {
  281. # Parses the Data from the Queries and returns "kind" to the proper
  282. # Dictionary with name/id as the key/value pairs
  283. # Options for Kind are: thermostats, smoke
  284. set tempDict [dict create]
  285. if {$::Nest::Status == ""} {LOG "Query Never Performed? Attempting Query..."; Nest::Query}
  286. switch -regexp -nocase -- $kind {
  287. {^thermostats?$} {
  288. LOG "Getting Thermostats"
  289. if {[dict exists $::Nest::Status current devices thermostats]} {set thermIDs [dict keys [dict get $::Nest::Status current devices thermostats]]
  290. } else {LOG "No Thermostats Detected"; return -1}
  291. LOG "Therm IDs: $thermIDs"
  292. foreach id $thermIDs {
  293. set name [dict get $::Nest::Status current devices thermostats $id name]
  294. dict set tempDict $name $id
  295. }
  296. if {[dict exists $::Nest::devices thermostats] && [dict get $::Nest::devices thermostats] == $tempDict} {LOG "No Change to Thermostats, Do Nothing"; return}
  297. LOG "Change Detected to Thermostats, Setting Devices Dictionary"
  298. dict set ::Nest::devices thermostats $tempDict
  299. return
  300. }
  301. {^protects?$} {
  302. LOG "Getting Protects"
  303. if {[dict exists $::Nest::Status current devices smoke_co_alarms]} {set protectIDs [dict keys [dict get $::Nest::Status current devices smoke_co_alarms]]
  304. } else {LOG "No Protects Were Detected"; return -1}
  305. foreach id $protectIDs {
  306. set name [dict get $::Nest::Status current devices smoke_co_alarms $id name]
  307. dict set tempDict $name $id
  308. }
  309. if {[dict exists $::Nest::devices protects] && [dict get $::Nest::devices protects] == $tempDict} {LOG "No Change to Protects, Do Nothing"; return}
  310. LOG "Change Detected to Protects, Setting Devices Dictionary"
  311. dict set ::Nest::devices protects $tempDict
  312. return
  313. }
  314. {^structures?$|^homes?$|^buildings?$} {
  315. LOG "Getting Structures"
  316. if {[dict exists $::Nest::Status current structures]} {set structIDs [dict keys [dict get $::Nest::Status current structures]]
  317. } else {LOG "No Structured Were Detected"; return -1}
  318. foreach id $structIDs {
  319. set name [dict get $::Nest::Status current structures $id name]
  320. if {[dict exists $::Nest::Status current structures $id thermostats]} {set therms [dict get $::Nest::Status current structures $id thermostats]
  321. } else {LOG "No Thermostats for Structure $name"; set therms ""}
  322. if {[dict exists $::Nest::Status current structures $id smoke_co_alarms]} {set protects [dict get $::Nest::Status current structures $id smoke_co_alarms]
  323. } else {LOG "No Protects for Structure: $name"; set protects ""}
  324. # Check Presence Status for Structures and Report to the System if it has changed
  325. dict set tempDict $name [dict create id $id thermostats $therms protects $protects]
  326. set presence [dict get $::Nest::Status current structures $id away]
  327. if {![dict exists $::Nest::presence $id]} {LOG "Creating Presence for First Time"; dict set ::Nest::presence $id [dict create status $presence startup 1]
  328. } elseif {[dict exists $::Nest::presence $id] && [dict get $::Nest::presence $id status] != $presence} {LOG "Presence Changed"; dict with ::Nest::presence $id {set status $presence; set startup 0}}
  329. }
  330. if {[dict exists $::Nest::devices structures] && [dict get $::Nest::devices structures] == $tempDict} {LOG "No Change to Structures, Do Nothing"; return}
  331. LOG "Change Detected to Structures, Setting Devices Dictionary"
  332. dict set ::Nest::devices structures $tempDict
  333. return
  334. }
  335. {^all} {LOG "Getting All Devices & Structures"
  336. getDevices thermostats; getDevices protects; getDevices structures
  337. }
  338. default {LOG "Kind was not Recognized - should be thermostats, protects, or all"}
  339. }
  340. }
  341.  
  342. proc nameFromID {id {kind "thermostats"}} {
  343. # Used to Return the "Room Name" or "Structure Name" of a given "ID"
  344.  
  345. if {$kind != "structures"} {
  346. LOG "Parsing ID"
  347. dict for {k v} [dict filter [dict get $::Nest::devices $kind] value $id] {
  348. set keys $k
  349. }
  350. } else {
  351. LOG "Getting Structure Name"
  352. set structNames [dict keys [dict get $::Nest::devices structures]]
  353. foreach struct $structNames {
  354. if {$id == [dict get $::Nest::devices structures $struct id]} {
  355. set keys $struct
  356. } else {continue}
  357. }
  358. }
  359. LOG "Returning: $keys"
  360. return $keys
  361. }
  362.  
  363. proc deviceNamesByStructure {struct {kind "thermostats"} args} {
  364. # Will return a list with the "kind" names for a given Structure.
  365. # Structure can be the Structure ID or Structure Name.
  366. # If -unique 1 is specified as an argument, it will erase any duplications
  367. # that would occur should a Thermostat and Detector share the same name
  368. set names ""
  369. if {![dict exists $::Nest::devices structures $struct $kind]} {
  370. LOG "Getting Name from Structure ID"; set struct [::Nest::nameFromID $struct structures]
  371. }
  372. foreach deviceID [dict get $::Nest::devices structures $struct $kind] {lappend names [::Nest::nameFromID $deviceID $kind]}
  373. LOG "$kind in Room: $names"
  374. if {[dict exists $args -unique] && [dict get $args -unique] == 1} {set names [lsort -unique $names]}
  375. return $names
  376. }
  377.  
  378. proc listRooms {{struct "all"}} {
  379. # This procedure will return the rooms within the project which includes both
  380. # the Thermostats & the Nest Protects. Returns into List which should be useable
  381. # for URC's TCL List Objects. Automatically handles duplicate values and only returns
  382. # unique values.
  383. # By default listRooms will list all rooms in the System regardless of what Nest
  384. # structure they are within. Providing a Structure Name or ID as an argument will
  385. # list rooms only for that structure.
  386. # This differentiates itself from ::Nest::DeviceNamesByStructure because it returns
  387. # a unique list of rooms. It also automatically checks all room names that any
  388. # device is placed into.
  389. # Syntax:
  390. # ::Nest::listRooms "Home"
  391.  
  392. set l ""
  393. if {$struct == "all"} {
  394. set l [dict keys [dict get $::Nest::devices thermostats]]
  395. lappend l {*}[dict keys [dict get $::Nest::devices protects]]
  396. } else {
  397. if {![dict exists $::Nest::devices structures $struct]} {
  398. LOG "Getting Name from Structure ID"; set struct [::Nest::nameFromID $struct structures]
  399. }
  400. foreach deviceID [dict get $::Nest::devices structures $struct thermostats] {lappend l [::Nest::nameFromID $deviceID thermostats]}
  401. foreach deviceID [dict get $::Nest::devices structures $struct protects] {lappend l [::Nest::nameFromID $deviceID protects]}
  402. }
  403. LOG "Raw List is:\n$l"
  404. set l [lsort -unique $l]
  405. LOG "Formatted List is:\n$l"
  406. return $l
  407. }
  408.  
  409. proc findDeviceKind {id} {
  410. # This Procedure will return the "Kind" of Device based on a specified ID
  411. # Example: ::Nest::findDeviceKind _MXPPHwK669HMC6Mtqb51qtN3uLTVu2B
  412. # Returns: < thermostats , protects, structures >
  413. # You can then call ::Nest::nameFromID $response to get the name/room/structure.
  414. # set name [::Nest::nameFromID $id [::Nest::findDeviceKind $id]]
  415.  
  416. set l [dict values [dict get $::Nest::devices thermostats]]
  417. if {[string match *$id* $l]} {return thermostats}
  418. set l [dict values [dict get $::Nest::devices protects]]
  419. if {[string match *$id* $l]} {return protects}
  420. set l ""
  421. set structNames [dict keys [dict get $::Nest::devices structures]]
  422. foreach struct $structNames {
  423. lappend l {*}[dict get $::Nest::devices structures $struct id]
  424. }
  425. if {[string match *$id* $l]} {return structures}
  426. }
  427.  
  428. # Trace the Devices Variable to trigger changes to UI when new devices are added or changed
  429. # in any way.
  430. trace add variable ::Nest::devices write {::System::traceCallback devices}
  431. trace add variable ::Nest::Status write {::System::traceCallback status}
  432. trace add variable ::Nest::presence write {::System::traceCallback presence}
  433. }
  434.  
  435. namespace eval URC {
  436.  
  437. variable UpdateListing ""
  438.  
  439. proc UpdateUI {eventType deviceID newValue} {
  440. # This Procedure is called when a setting needs to be
  441. # updated to URC User Interfaces.
  442. # The previous 10 changes are provided for the module
  443. # script to parse using a "foreach" function or similar.
  444. # Newest Events will be first in the listing
  445. # Note: The Temp Type is fed based on the thermostats current
  446. # scale setting. Be sure to set the scale based on the _$s value.
  447. # {presence _FNXMFJHKHKSHJ away} {targetTemp_F _FNXKSLFJSLF 75} ...
  448. LOG "Adding UI Event to Update Queue"
  449. set e "$eventType $deviceID $newValue"
  450. set ::URC::UpdateListing "{$e} $::URC::UpdateListing"
  451. if {[llength $::URC::UpdateListing] > $::API::maxUIEvents} {set ::URC::UpdateListing [lreplace $::URC::UpdateListing 10 end]}
  452. LOG "Latest UI Events:\n$::URC::UpdateListing"
  453. }
  454.  
  455. proc onPresenceChange {id status} {
  456. # Called when Presence change is considered valid.
  457. # Will include the Structure ID of the Status Change
  458. # as well as the status.
  459. # It is safe to Execute Event at this point.
  460. # Status Will be Either: <away, home>
  461.  
  462. LOG "Presence Changed for ID: $id\nChanged to: $status"
  463. ::URC::UpdateUI presence $id $status
  464. }
  465.  
  466. proc interfaceServer {get} {
  467. # Interfaces with URC Interfaces using the device_proc command
  468.  
  469. switch -nocase -- $get {
  470. status {if {[dict exists $::Nest::Status current]} {return [dict get $::Nest::Status current]}}
  471. events {return $::URC::UpdateListing}
  472. devices {return $::Nest::devices}
  473. structureNames {return [dict keys [dict get $::Nest::devices structures]]}
  474. default {return -1}
  475. }
  476. }
  477. LOG "-- URC Module Information --"
  478. # LOG "Module Version: $::varIfVersionNum"
  479. LOG "Created for: Nest API, Firmware v4.1"
  480.  
  481. }
  482.  
  483. namespace eval System {
  484.  
  485. proc traceCallback {value args} {
  486. LOG "Trace Callback Initiated"
  487. LOG "Value: $value"
  488. LOG "Args:\n$args"
  489.  
  490. switch -- $value {
  491. status {
  492. LOG "Nest Status has been Updated!"
  493. if {[dict exists $::Nest::Status old]} {set changes [::System::dictCompare [dict get $::Nest::Status current] [dict get $::Nest::Status old]]
  494. } else {LOG "First Status Update, Ending"; return}
  495. # In General Response will follow this index map:
  496. # 0 - <devices, structures>
  497. # 1 - (Devices) - <thermostats, smoke_co_alarms> | (Structures) - <ID>
  498. # 2 - (Devices) - <ID> | (Structures) - <valueKey>
  499. # 3 - (Devices) - <valueKey>
  500. LOG "Changes Response:\n$changes"
  501. foreach change $changes {
  502. set newValue [lindex $change 1]
  503. if {[lindex [lindex $change 0] 0] == "devices"} {
  504.  
  505. } elseif {[lindex [lindex $change 0] 0] == "structures"} {
  506.  
  507. }
  508. if {[regexp -all {_f|_c} $change] > 1} {}
  509. }
  510. }
  511. i {set ::API::CMDS::Type i}
  512. s {set ::API::CMDS::Type s}
  513. devices {
  514. LOG "|---- NEW Devices Variable Value:\n$::Nest::devices"
  515. # ::URC::UpdateUI devices
  516. }
  517. presence {
  518. LOG "|---- Presence Change Detected"
  519. dict for {id val} $::Nest::presence {
  520. if {[dict get $::Nest::presence $id startup] == 0} {
  521. LOG "Presence Change Validated"
  522. dict set ::Nest::presence $id startup -1
  523. ::URC::onPresenceChange $id [dict get $::Nest::presence $id status]
  524. }
  525. }
  526.  
  527.  
  528. }
  529. default {LOG "ERROR in traceCallback"}
  530. }
  531. }
  532.  
  533. proc convertJSON {data} {
  534. LOG "|--- Convert JSON Starts"
  535. set newData [string map {"data: " ""} $data]
  536. if {$newData == "null"} {LOG "Heartbeat Detected, Ending Parse"; return}
  537. # set newData [split $data " "]
  538. set newData [json::json2dict $newData]
  539. return $newData
  540. }
  541.  
  542. proc difflists {l1 l2} {
  543. set lcsData [struct::list longestCommonSubsequence $l1 $l2]
  544. set diffs [struct::list lcsInvert $lcsData [llength $l1] [llength $l2]]
  545. set changedKeys ""
  546. foreach d $diffs {
  547. set op ""
  548. lassign $d type i1 i2; lassign $i1 s1 e1; lassign $i2 s2 e2
  549. LOG "$type: [lrange $l2 {*}$i2] to -> [lrange $l1 {*}$i1]"
  550. #:[lindex $l1 $e1] -> [lindex $l2 $s2-1]:[lindex $l2 $e2]
  551. set flatkey [lindex $l1 $s1-1]; lappend op [string map {. " "} $flatkey]
  552. lappend op [lrange $l1 {*}$i1]
  553. lappend changedKeys $op
  554. }
  555. return $changedKeys
  556. }
  557.  
  558. proc dict_flatten {dictVal} {
  559. dict for {k v} $dictVal {
  560. if {[llength $v] % 2 == 0} {
  561. set dictVal [dict remove $dictVal[set dictVal {}] $k]
  562. dict for {sk sv} [dict_flatten $v] {dict set dictVal ${k}.${sk} $sv}
  563. }
  564. }
  565. return $dictVal
  566. }
  567.  
  568. proc dictCompare {newDict oldDict} {
  569. # Compares Two Dictionaries and Returns with a List
  570. # with all changed Dictionary Keys.
  571. # Example Resonse:
  572. # {key1 key1 key1} {key1 key1 key2} {key2 key1 key1}
  573. # Get Values using the {*} operator on the response:
  574. # dict get $dict {*}[lindex $differences 0]
  575. set differences [difflists [dict_flatten $newDict] [dict_flatten $oldDict]]
  576. return $differences
  577. }
  578. }
  579. set thermID _MXPPHwK669HMC6Mtqb51qtN3uLTVu2B
  580.  
  581. Nest::Query
  582. after 5000 {::Nest::Set -command $::API::CMDS::HighTemp_f -value 75 -device $thermID -kind thermostat}
  583. after 10000 {set i 0}
  584. vwait i