Posted to tcl by Napier at Fri Jul 11 15:46:47 GMT 2014view raw
- # --------------------------------------------
- # Nest & Smoke+CO TCL Integration Library
- # Connects to the Nest API for control &
- # monitoring of NEST Products.
- #
- # Author: Braden Napier & Remi Rosa
- # --------------------------------------------
- # Main Command Syntax:
- # -fanTimer <start,stop> -mode <off, heat, cool, heat-cool> -
- #
-
- enableLOG 0
- set varIfMRXMAC "000"
-
- proc enableLOG { status } {
- global LOGENABLE
- set LOGENABLE $status
- }
-
- proc LOG {data} {
- puts $data
- }
-
- # Include Packages in the includePackages List
- set includePackages "http tls json json::write tdom uri struct::list"
- foreach {pkg} $includePackages {package require $pkg}
-
- tls::init -tls1 true
- http::register https 443 tls::socket
-
- namespace eval API {
- # Be Sure to Set your Client Secret & Client ID
- # Automation Apps
- variable FB_Auth "auth=c.BIm6hEjxafQbnF6CUXuxkmIvX7ztXndPiNbwZAhlYVpla6jZCjAquph6OhwA2gtel1uSeHGtvcMAf5i4reaOxEha4S7bKyBuIiXnSHrVWjvbuCcr1mZ8oEu3cfzj5R9Zp0lrT753ywiyzc42"
- variable authState $::varIfMRXMAC
- variable baseURL "https://developer-api.nest.com"
- variable basePort 443
- variable client_secret "wCJzrq2VuyWcH70DjENWpjupi"
- variable client_id "7c3fe371-5740-48ac-a1b2-74df90145813"
- variable ThermBaseURL "devices/thermostats/"
- variable authRequestURL "https://home.nest.com/login/oauth2?client_id=7c3fe371-5740-48ac-a1b2-74df90145813&state=ip"
- variable authorizeURL "https://api.home.nest.com/oauth2/access_token?code=|AUTHPIN|&client_id=${client_id}&client_secret=${client_secret}&grant_type=authorization_code"
- variable maxNestEvents 10
- variable maxUIEvents 10
- # variable forecast "https://home.nest.com/api/0.1/weather/forecast/${zip},${country}"
-
- namespace eval CMDS {
- variable FanTimer "fan_timer_active" ; # Boolean - Sets 15 Minute Fan Timer {"fan_timer_active": true }
- # JSON Example: { "fan_timer_active": true }
- variable Temp_f "target_temperature_f" ; # Integer - Sets Fahrenheit {"target_temperature_f": 70} (1 Accuracy)
- # JSON Example: { "target_temperature_f": 70 }
- variable Temp_c "target_temperature_c" ; # Integer - Sets Celcius (0.5 Accuracy)
- # JSON Example: { "target_temperature_c": 30.5 }
- variable HighTemp_f "target_temperature_high_f" ; # Integer - Set High Temp in Fahrenheit (1 Accuracy)
- # JSON Example: { "target_temperature_high_f": 75 }
- variable HighTemp_c "target_temperature_high_c" ; # Integer - Set High Temp in Celcius (0.5 Accuracy)
- # JSON Example: { "target_temperature_high_c": 40.5 }
- variable LowTemp_f "target_temperature_low_f" ; # Integer - Set Low Temp in Fahrenheit (1 Accuracy)
- # JSON Example: { "target_temperature_low_f": 65 }
- variable LowTemp_c "target_temperature_low_c" ; # Integer - Set Low Temp in Celcius (0.5 Accuracy)
- # JSON Example: { "target_temperature_low_c": 20.5 }
- variable mode "hvac_mode" ; # String - Set HVAC Mode <heat, cool, heat-cool, off>
- # JSON Example: {"hvac_mode": "heat-cool"}
-
- variable Type 0
-
- trace add variable ::API::CMDS::FanTimer read {::System::traceCallback i}
- trace add variable ::API::CMDS::Temp_f read {::System::traceCallback i}
- trace add variable ::API::CMDS::Temp_c read {::System::traceCallback i}
- trace add variable ::API::CMDS::HighTemp_f read {::System::traceCallback i}
- trace add variable ::API::CMDS::HighTemp_c read {::System::traceCallback i}
- trace add variable ::API::CMDS::LowTemp_f read {::System::traceCallback i}
- trace add variable ::API::CMDS::LowTemp_c read {::System::traceCallback i}
- trace add variable ::API::CMDS::mode read {::System::traceCallback s}
- }
- }
-
- namespace eval Web {
-
- variable RequestData [dict create]
- variable streamToken ""
-
- proc Send {args} {
- # Syntax:
- # -method $METHOD -url $URL -params $PARAMS -suffix $URL-Suffix -port $port -body $body -request $request
- # -request feeds the RequestData Variable to Identify to the Callback
- # Web::Send -method PATCH -url https://www.url.com -params "Hi Hello" -suffix "devices/hello" -body $json -port 9553 would PATCH:
- # https://www.url.com:9553/devices/hello?auth={AUTH CODE}&hi=hello with the value of $json as the body
- LOG "Beginning Web::Send $args"
- http::cleanup $::Web::streamToken
- set tempDict $args
- if {$::API::FB_Auth == 0} {LOG "OAuth Required Before Continuing"; #TODO: Build Re-Auth Service}
- if {![dict exists $tempDict -suffix]} {LOG "No Suffix Detected"; dict set tempDict -suffix ""}
- if {![dict exists $tempDict -url]} {LOG "No URL using Default"; dict set tempDict -url $::API::baseURL}
- if {![dict exists $tempDict -method]} {LOG "No Method Defined, Using GET"; dict set tempDict -method "GET"}
- if {[dict exists $tempDict -params]} {LOG "Formatting Query"; dict set tempDict -params "&[::http::formatQuery {*}[dict get $tempDict -params]]"
- } else {LOG "No Params Detected"; dict set tempDict -params ""}
- if {[dict exists $tempDict -port]} {LOG "Custom Port Detected: [dict get $tempDict -port]"; http::register https [dict get $tempDict -port] tls::socket
- } else {http::register https $::API::basePort tls::socket}
- if {![dict exists $tempDict -body]} {LOG "No Body Detected"; dict set tempDict -body ""}
- if {![dict exists $tempDict -request]} {LOG "No Request Detected"; dict set tempDict -request "None"
- } elseif {[dict get $tempDict -request] == "Query"} {LOG "Set Event Stream Header"; http::config -accept "text/event-stream"
- } else {LOG "Set Accept All Header"; http::config -accept "/*"}
- set ::Web::RequestData $tempDict
- 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]
- } else {set token [http::geturl [dict get $tempDict -url]/[dict get $tempDict -suffix]?${::API::FB_Auth}[dict get $tempDict -params] \
- -method [dict get $tempDict -method] -query [dict get $tempDict -body] -command ::Web::httpCallback]
- }
- }
-
- proc httpCallback {token} {
- set data $::Web::RequestData
- set response [dict create data [http::data $token] code [http::code $token]]
- LOG "----- RECEIVING DATA -----"
- LOG "$response"
- LOG "--------------------------"
- LOG "Token: $token"
- if {[string match {30[1237]} [::http::ncode $token]]} {LOG "Redirect Required!"; ::Web::startRedirect $token
- } elseif {[::http::ncode $token] == 200} {LOG "Successfull Call Detected"
- ::Nest::Query
- #http::cleanup $token
- #switch -- [dict get $data -request] {
- # Authorize {LOG "Authorize Data Assumed"; set response [::json::json2dict [dict get $response data]]}
- # default {LOG "Unknown or Undefined Request, Saving Value to ::Nest::StatusUndefined"; set ::Nest::StatusUndefined $data}
- #}
- # Query {LOG "Query Data Assumed"; ::Nest::setStatus [::json::json2dict [dict get $response data]]}
- } elseif {[::http::ncode $token] == 429} {LOG "!!!! ---- |||| Too Many Requests Error Received |||| ----- !!!!"}
- }
- set secondSend 0
- set dataBuff ""
-
-
- proc httpProgress {token total current} {
- LOG "Start Progress"; LOG "Total: $total"; LOG "Current: $current"
- # The Following if Statement is used to parse out the random "e" received
- # at the start of the Event Stream Data.
- if {$current == 1} {LOG "END"; return}
- upvar #0 $token state
- if {$::Web::secondSend == 1} {
- LOG "!!!!!! Sending AGAIN"
- append ::Web::dataBuff [http::code $token]
- set body $::Web::dataBuff
- set ::Web::secondSend 0
- } else {
- LOG "No more Data?"
- set body "$state(body)"
- if {$state(body) == "e"} {return}
- }
- if {$current == 8193} {
- set ::Web::secondSend 1
- set ::Web::dataBuff $body
- Nest::Query
- return
- } else {
- LOG "Data has Finished Buffering"
- set occurrences [regsub -all {\s*\n\s*\n(\s*\n)*} $body "\n" body]
- set body [split $body \n]
- }
-
- if {[dict exists [lindex $body 0] "event:"] && [dict get [lindex $body 0] "event:"] == "auth_revoked"} {::Nest::AuthRevoked}
- # The occurrences variable actively tracks how many events are currently saved
- # in the $state(body) variable. It is likely a good idea to flush this out at
- # a set interval by closing (http::cleanup) and re-initiating the event.
- LOG "Event Occurences: $occurrences"
- if {$occurrences >= $::API::maxNestEvents} {LOG "Flushing Events"
- # Process to Flush Events & Re-Initiate the Event Stream
- # should go here.
- set state(body) ""
- }
- if {$::Nest::Status == ""} {
- set startData [::System::convertJSON [lindex $body 1]]
- LOG "Start Data:\n$startData"
- ::Nest::setStatus [dict get $startData data]
- } else {
- set newData [::System::convertJSON [lindex $body end-1]]
- if {$newData != ""} {
- LOG "New Data:\n$newData"
- if {[dict exists $::Nest::Status current] && [dict get $::Nest::Status current] != [dict get $newData data]} {
- LOG "Data is Different, Setting Nest Status Variable"; ::Nest::setStatus [dict get $newData data]
- } else {LOG "New Data has Not Changed, Do Nothing"}
- }
- }
- }
-
- proc startRedirect {token} {
- set data $::Web::RequestData
- array set meta [set ${token}(meta)]
- LOG "[parray {*}$token]"
- http::cleanup $token
- if {![info exist meta(Location)]} {LOG "No Redirection Indicator?"; return}
- array set uri [::uri::split $meta(Location)]
- LOG "URI:"; LOG "[parray uri]"
- unset meta
- if {$uri(host) == ""} {set uri(host) $uri(host)}
- set url [eval ::uri::join [array get uri]]
- LOG "URL: $url"
- if {[dict get $data -method] == "GET"} {http::config -accept "text/event-stream"; set ::Web::streamToken [http::geturl $url -keepalive 1 -progress ::Web::httpProgress]
- } else {set token [http::geturl $url -method [dict get $data -method] -query [dict get $data -body] -command ::Web::httpCallback]}
- }
-
- proc sendEmail {to msg args} {
- # This Procedure will Prepare and Send an E-Mail to the address(es) provided in
- # the "to" argument. It will include the content in the "msg" argument.
- # Additionally the following arguments can be defined if needed:
- # -footer $txt
-
- }
- }
-
- namespace eval Nest {
- variable Status ""
- variable StatusUndefined 0
- variable thermostats [dict create]
- variable devices [dict create thermostats "" protects "" structures ""]
- variable presence [dict create]
-
-
- proc Authorize {authPIN} {
- # Sends PIN to Nest to retrieve authentication string
- set authURL [string map "|AUTHPIN| $authPIN" $::API::authorizeURL]
- LOG "Authorization URL:\n $authURL"
- Web::Send -method POST -url $authURL -request Authorize
- #TODO: Send the Authorization as POST with Parameters
- }
-
- proc AuthRevoked {args} {
- # This Procedure is called when the "Auth_Revoked" Event
- # is Received from Nest. You will need to re-authenticate
- # and get a new "Auth Token" when this occurs.
- }
-
- proc Query {args} {
- # Queries The Nest API Based on Your Parameters
- # If blank (::Nest::Query) it will query all parameters into the main dictionary
- # Do NOT do this Often as Nest limits the amount of calls you can do per
- # month per token.
- # All data will be kept up-to-date using the Rest Streaming Feature of the API.
- # -thermostat <therm ID or name> -smoke <smokeCO ID or name>
- Web::Send -request "Query"
- }
-
- proc Set {args} {
- # This Procedure allows you to Control Nest Products using
- # their Official API.
- # -command - Specify the command. Command options and examples can be found in the
- # ::API::CMDS Namespace and should be referenced as that variable.
- # -value - Specify the Value you would like to feed to the Process. This should follow
- # the examples in the "::API::CMDS" namespace.
- # -device {deviceID} - This argument specifies the device's ID which
- # should be manipulated. It can also use the device name directly.
- # -kind {kind} - Specify what kind of device will be controlled.
- # this is an optional value but specifying it will increase overall speed.
- # Syntax Example:
- # ::Nest::Set -command $::API::CMDS::Temp_f -value 70 -device Kitchen -kind thermostat
- LOG "Beginning Set Procedure"
-
- if {[dict exists $args -device]} {
- set device [dict get $args -device]
- LOG "Device is: $device"
- if {[dict exists $::Nest::devices thermostats $device]} {
- set device [dict get $::Nest::devices thermostats $device]
- LOG "Device is now: $device"
- }
- }
- if {[dict exists $args -kind]} {
- LOG "Kind was Specified: [dict get $args -kind]"
- switch -regexp -- [dict get $args -kind] {
- {^thermostats?$|^therms?$} {::Nest::setThermostat [dict get $args -command] [dict get $args -value] $device}
- {^protects?$|^smokecos?$|^detectors?$} {LOG "Nest Protect Specified\nThere are currently No Official Set Options for Nest Protect"}
- default {LOG "Error: Set Params Incorrect"}
- }
- } else {
- LOG "Kind was Not Specified"
- }
- }
-
- proc setThermostat {cmd value id} {
- LOG "Setting the Thermostat with ID: $id"
- # This Procedure should generally not be called directly, use ::Nest::Set $cmd $value -kind (optional)
- # Currently Adds JSON and sends one Command at a Time - another procedure will be written to send multiple
- # values with a single call.
-
- if {$::API::CMDS::Type == "i"} { set json [::json::write object $cmd $value]
- } elseif {$::API::CMDS::Type == "s"} { set json [::json::write object $cmd [::json::write string $value]]
- }
- LOG "|--- Sending in Patch Body:\n$json"
- LOG "|--- Sending to Suffix:\n${::API::baseURL}/${::API::ThermBaseURL}$id"
- ::Web::Send -method PATCH -suffix ${::API::ThermBaseURL}$id -body $json
- }
-
- proc Experimental {args} {
- # This procedure will house non-official API procedures that
- # may not be supported in the long-term. Generally this will
- # emulate features used on the Official Nest App & Web Control
- # app.
-
- }
-
- proc setStatus {newStatus args} {
- # This Procedure handles Status Setting when a Query is Made. If Status was already present, it will save the
- # "current" Status into the "old" Status Dictionary and the newly received status into the "current" dictionary.
- # This can then be used to evaluate events as needed for Macro Integration
-
- LOG "---- New Nest Status Received!\n$newStatus"
- if {$::Nest::Status != "" && [dict exists $::Nest::Status current]} {
- dict set ::Nest::Status old [dict get $::Nest::Status current]
- }
- dict set ::Nest::Status current $newStatus
- getDevices all
- }
-
- proc getDevices {kind} {
- # Parses the Data from the Queries and returns "kind" to the proper
- # Dictionary with name/id as the key/value pairs
- # Options for Kind are: thermostats, smoke
- set tempDict [dict create]
- if {$::Nest::Status == ""} {LOG "Query Never Performed? Attempting Query..."; Nest::Query}
- switch -regexp -nocase -- $kind {
- {^thermostats?$} {
- LOG "Getting Thermostats"
- if {[dict exists $::Nest::Status current devices thermostats]} {set thermIDs [dict keys [dict get $::Nest::Status current devices thermostats]]
- } else {LOG "No Thermostats Detected"; return -1}
- LOG "Therm IDs: $thermIDs"
- foreach id $thermIDs {
- set name [dict get $::Nest::Status current devices thermostats $id name]
- dict set tempDict $name $id
- }
- if {[dict exists $::Nest::devices thermostats] && [dict get $::Nest::devices thermostats] == $tempDict} {LOG "No Change to Thermostats, Do Nothing"; return}
- LOG "Change Detected to Thermostats, Setting Devices Dictionary"
- dict set ::Nest::devices thermostats $tempDict
- return
- }
- {^protects?$} {
- LOG "Getting Protects"
- if {[dict exists $::Nest::Status current devices smoke_co_alarms]} {set protectIDs [dict keys [dict get $::Nest::Status current devices smoke_co_alarms]]
- } else {LOG "No Protects Were Detected"; return -1}
- LOG "Protect IDs: $protectIDs"
- foreach id $protectIDs {
- set name [dict get $::Nest::Status current devices smoke_co_alarms $id name]
- dict set tempDict $name $id
- }
- if {[dict exists $::Nest::devices protects] && [dict get $::Nest::devices protects] == $tempDict} {LOG "No Change to Protects, Do Nothing"; return}
- LOG "Change Detected to Protects, Setting Devices Dictionary"
- dict set ::Nest::devices protects $tempDict
- return
- }
- {^structures?$|^homes?$|^buildings?$} {
- LOG "Getting Structures"
- if {[dict exists $::Nest::Status current structures]} {set structIDs [dict keys [dict get $::Nest::Status current structures]]
- } else {LOG "No Structured Were Detected"; return -1}
- foreach id $structIDs {
- set name [dict get $::Nest::Status current structures $id name]
- if {[dict exists $::Nest::Status current structures $id thermostats]} {set therms [dict get $::Nest::Status current structures $id thermostats]
- } else {LOG "No Thermostats for Structure $name"; set therms ""}
- if {[dict exists $::Nest::Status current structures $id smoke_co_alarms]} {set protects [dict get $::Nest::Status current structures $id smoke_co_alarms]
- } else {LOG "No Protects for Structure: $name"; set protects ""}
- # Check Presence Status for Structures and Report to the System if it has changed
- dict set tempDict $name [dict create id $id thermostats $therms protects $protects]
- set presence [dict get $::Nest::Status current structures $id away]
- if {![dict exists $::Nest::presence $id]} {LOG "Creating Presence for First Time"; dict set ::Nest::presence $id [dict create status $presence startup 1]
- } 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}}
- }
- if {[dict exists $::Nest::devices structures] && [dict get $::Nest::devices structures] == $tempDict} {LOG "No Change to Structures, Do Nothing"; return}
- LOG "Change Detected to Structures, Setting Devices Dictionary"
- dict set ::Nest::devices structures $tempDict
- return
- }
- {^all} {LOG "Getting All Devices & Structures"
- getDevices thermostats; getDevices protects; getDevices structures
- }
- default {LOG "Kind was not Recognized - should be thermostats, protects, or all"}
- }
- }
-
- proc nameFromID {id {kind "thermostats"}} {
- # Used to Return the "Room Name" or "Structure Name" of a given "ID"
-
- if {$kind != "structures"} {
- LOG "Parsing ID"
- dict for {k v} [dict filter [dict get $::Nest::devices $kind] value $id] {
- set keys $k
- }
- } else {
- LOG "Getting Structure Name"
- set structNames [dict keys [dict get $::Nest::devices structures]]
- foreach struct $structNames {
- if {$id == [dict get $::Nest::devices structures $struct id]} {
- set keys $struct
- } else {continue}
- }
- }
- LOG "Returning: $keys"
- return $keys
- }
-
- proc deviceNamesByStructure {struct {kind "thermostats"} args} {
- # Will return a list with the "kind" names for a given Structure.
- # Structure can be the Structure ID or Structure Name.
- # If -unique 1 is specified as an argument, it will erase any duplications
- # that would occur should a Thermostat and Detector share the same name
- set names ""
- if {![dict exists $::Nest::devices structures $struct $kind]} {
- LOG "Getting Name from Structure ID"; set struct [::Nest::nameFromID $struct structures]
- }
- foreach deviceID [dict get $::Nest::devices structures $struct $kind] {lappend names [::Nest::nameFromID $deviceID $kind]}
- LOG "$kind in Room: $names"
- if {[dict exists $args -unique] && [dict get $args -unique] == 1} {set names [lsort -unique $names]}
- return $names
- }
-
- proc listRooms {{struct "all"}} {
- # This procedure will return the rooms within the project which includes both
- # the Thermostats & the Nest Protects. Returns into List which should be useable
- # for URC's TCL List Objects. Automatically handles duplicate values and only returns
- # unique values.
- # By default listRooms will list all rooms in the System regardless of what Nest
- # structure they are within. Providing a Structure Name or ID as an argument will
- # list rooms only for that structure.
- # This differentiates itself from ::Nest::DeviceNamesByStructure because it returns
- # a unique list of rooms. It also automatically checks all room names that any
- # device is placed into.
- # Syntax:
- # ::Nest::listRooms "Home"
-
- set l ""
- if {$struct == "all"} {
- set l [dict keys [dict get $::Nest::devices thermostats]]
- lappend l {*}[dict keys [dict get $::Nest::devices protects]]
- } else {
- if {![dict exists $::Nest::devices structures $struct]} {
- LOG "Getting Name from Structure ID"; set struct [::Nest::nameFromID $struct structures]
- }
- foreach deviceID [dict get $::Nest::devices structures $struct thermostats] {lappend l [::Nest::nameFromID $deviceID thermostats]}
- foreach deviceID [dict get $::Nest::devices structures $struct protects] {lappend l [::Nest::nameFromID $deviceID protects]}
- }
- LOG "Raw List is:\n$l"
- set l [lsort -unique $l]
- LOG "Formatted List is:\n$l"
- return $l
- }
-
- proc findDeviceKind {id} {
- # This Procedure will return the "Kind" of Device based on a specified ID
- # Example: ::Nest::findDeviceKind _MXPPHwK669HMC6Mtqb51qtN3uLTVu2B
- # Returns: < thermostats , protects, structures >
- # You can then call ::Nest::nameFromID $response to get the name/room/structure.
- # set name [::Nest::nameFromID $id [::Nest::findDeviceKind $id]]
-
- set l [dict values [dict get $::Nest::devices thermostats]]
- if {[string match *$id* $l]} {return thermostats}
- set l [dict values [dict get $::Nest::devices protects]]
- if {[string match *$id* $l]} {return protects}
- set l ""
- set structNames [dict keys [dict get $::Nest::devices structures]]
- foreach struct $structNames {
- lappend l {*}[dict get $::Nest::devices structures $struct id]
- }
- if {[string match *$id* $l]} {return structures}
- }
-
- # Trace the Devices Variable to trigger changes to UI when new devices are added or changed
- # in any way.
- trace add variable ::Nest::devices write {::System::traceCallback devices}
- trace add variable ::Nest::Status write {::System::traceCallback status}
- trace add variable ::Nest::presence write {::System::traceCallback presence}
- }
-
- namespace eval URC {
-
- variable UpdateListing ""
-
- proc UpdateUI {eventType deviceID newValue} {
- # This Procedure is called when a setting needs to be
- # updated to URC User Interfaces.
- # The previous 10 changes are provided for the module
- # script to parse using a "foreach" function or similar.
- # Newest Events will be first in the listing
- # Note: The Temp Type is fed based on the thermostats current
- # scale setting. Be sure to set the scale based on the _$s value.
- # {presence _FNXMFJHKHKSHJ away} {targetTemp_F _FNXKSLFJSLF 75} ...
- LOG "Adding UI Event to Update Queue"
- set e "$eventType $deviceID $newValue"
- set ::URC::UpdateListing "{$e} $::URC::UpdateListing"
- if {[llength $::URC::UpdateListing] > $::API::maxUIEvents} {set ::URC::UpdateListing [lreplace $::URC::UpdateListing 10 end]}
- LOG "Latest UI Events:\n$::URC::UpdateListing"
- }
-
- proc onPresenceChange {id status} {
- # Called when Presence change is considered valid.
- # Will include the Structure ID of the Status Change
- # as well as the status.
- # It is safe to Execute Event at this point.
- # Status Will be Either: <away, home>
-
- LOG "Presence Changed for ID: $id\nChanged to: $status"
- ::URC::UpdateUI presence $id $status
- }
-
- proc interfaceServer {get} {
- # Interfaces with URC Interfaces using the device_proc command
-
- switch -nocase -- $get {
- status {if {[dict exists $::Nest::Status current]} {return [dict get $::Nest::Status current]}}
- events {return $::URC::UpdateListing}
- devices {return $::Nest::devices}
- structureNames {return [dict keys [dict get $::Nest::devices structures]]}
- default {return -1}
- }
- }
- LOG "-- URC Module Information --"
- # LOG "Module Version: $::varIfVersionNum"
- LOG "Created for: Nest API, Firmware v4.1"
-
- }
-
- namespace eval System {
-
- proc traceCallback {value args} {
- LOG "Trace Callback Initiated"
- LOG "Value: $value"
- LOG "Args:\n$args"
-
- switch -- $value {
- status {
- LOG "Nest Status has been Updated!"
- if {[dict exists $::Nest::Status old]} {set changes [::System::dictCompare [dict get $::Nest::Status current] [dict get $::Nest::Status old]]
- } else {LOG "First Status Update, Ending"; return}
- # In General Response will follow this index map:
- # 0 - <devices, structures>
- # 1 - (Devices) - <thermostats, smoke_co_alarms> | (Structures) - <ID>
- # 2 - (Devices) - <ID> | (Structures) - <valueKey>
- # 3 - (Devices) - <valueKey>
- LOG "Changes Response:\n$changes"
- foreach change $changes {
- set newValue [lindex $change 1]
- if {[lindex [lindex $change 0] 0] == "devices"} {
-
- } elseif {[lindex [lindex $change 0] 0] == "structures"} {
-
- }
- if {[regexp -all {_f|_c} $change] > 1} {}
- }
- }
- i {set ::API::CMDS::Type i}
- s {set ::API::CMDS::Type s}
- devices {
- LOG "|---- NEW Devices Variable Value:\n$::Nest::devices"
- # ::URC::UpdateUI devices
- }
- presence {
- LOG "|---- Presence Change Detected"
- dict for {id val} $::Nest::presence {
- if {[dict get $::Nest::presence $id startup] == 0} {
- LOG "Presence Change Validated"
- dict set ::Nest::presence $id startup -1
- ::URC::onPresenceChange $id [dict get $::Nest::presence $id status]
- }
- }
-
-
- }
- default {LOG "ERROR in traceCallback"}
- }
- }
-
- proc convertJSON {data} {
- LOG "|--- Convert JSON Starts"
- set newData [string map {"data: " ""} $data]
- if {$newData == "null"} {LOG "Heartbeat Detected, Ending Parse"; return}
- # set newData [split $data " "]
- set newData [json::json2dict $newData]
- return $newData
- }
-
- proc difflists {l1 l2} {
- set lcsData [struct::list longestCommonSubsequence $l1 $l2]
- set diffs [struct::list lcsInvert $lcsData [llength $l1] [llength $l2]]
- set changedKeys ""
- foreach d $diffs {
- set op ""
- lassign $d type i1 i2; lassign $i1 s1 e1; lassign $i2 s2 e2
- LOG "$type: [lrange $l2 {*}$i2] to -> [lrange $l1 {*}$i1]"
- #:[lindex $l1 $e1] -> [lindex $l2 $s2-1]:[lindex $l2 $e2]
- set flatkey [lindex $l1 $s1-1]; lappend op [string map {. " "} $flatkey]
- lappend op [lrange $l1 {*}$i1]
- lappend changedKeys $op
- }
- return $changedKeys
- }
-
- proc dict_flatten {dictVal} {
- dict for {k v} $dictVal {
- if {[llength $v] % 2 == 0} {
- set dictVal [dict remove $dictVal[set dictVal {}] $k]
- dict for {sk sv} [dict_flatten $v] {dict set dictVal ${k}.${sk} $sv}
- }
- }
- return $dictVal
- }
-
- proc dictCompare {newDict oldDict} {
- # Compares Two Dictionaries and Returns with a List
- # with all changed Dictionary Keys.
- # Example Resonse:
- # {key1 key1 key1} {key1 key1 key2} {key2 key1 key1}
- # Get Values using the {*} operator on the response:
- # dict get $dict {*}[lindex $differences 0]
- set differences [difflists [dict_flatten $newDict] [dict_flatten $oldDict]]
- return $differences
- }
- }
-
- Nest::Query
- after 10000 {set i 0}
- vwait i