Mich hat die Domain-Umstellung von Netatmo ganz schön ins Grübeln gebracht. Nix ging mehr. Heute bin ich der Sache auf den Grund gegangen und dabei auf diesen Text gestoßen: We will be retiring the domain api.netatmo.net and consolidating all API traffic under the existing domain api.netatmo.com on September 8, 2025.
Die Domain-Änderung ist in dieser Version der netatmo.tcl eingearbeitet. Zudem habe ich auf meiner CCU3 das Problem, dass die im tmp-Verzeichnis abgelegte netatmo.dat nach einem Neustart der CCU gelöscht war. Ich habe sie daher nach /usr/local/addons/netatmo/ verschoben. Diese Pfadänderung ist ebenfalls eingearbeitet.
Code: Alles auswählen
# Ausführung in der WinSCP-Terminal: tclsh netatmo.tcl oder tclsh netatmo.tcl -cfginfo
#!/bin/tclsh
# Steffen B. 11.09.2025 auf Basis von Indigo aus 2015
load tclrega.so
#---------------------------------------------------------------------------------------------------------------#
# CONFIG #
#---------------------------------------------------------------------------------------------------------------#
# to obtain your own client ID and API key please register a new app here: http://dev.netatmo.com/dev/listapps
#set clientId "xxxxxxxxxxxxxxxxxxxxxxxx"
#set clientSecret "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# the following are MAC addresses of your indoor station and the outside module and rain module
#set deviceid "XX:XX:XX:XX:XX:XX"
#set moduleid "XX:XX:XX:XX:XX:XX"
#set rainid "XX:XX:XX:XX:XX:XX"
set windid "XX:XX:XX:XX:XX:XX"
set ::env(LD_LIBRARY_PATH) "/usr/local/addons/cuxd"
#set cfgfile "/tmp/netatmo.dat" Pfad geändert 10.09.2025
set cfgfile "/usr/local/addons/netatmo/netatmo.dat"
set logtag "netatmo.tcl"
set logfacility "local1"
# 0=panic, 1=alert 2=crit 3=err 4=warn 5=notice 6=info 7=debug
set loglevel 6
#---------------------------------------------------------------------------------------------------------------#
#---------------------------------------------------------------------------------------------------------------#
# GLOBAL VARIABLES #
#---------------------------------------------------------------------------------------------------------------#
set startprm ""
set accesstoken ""
set refreshtoken ""
set tokenexpireU 0
#---------------------------------------------------------------------------------------------------------------#
set loglevels {panic alert crit err warn notice info debug}
proc log {lvl msg} {
global logtag
global logfacility
global loglevel
global loglevels
set lvlnum [lsearch $loglevels $lvl]
if {$lvlnum <= $loglevel} {
if {$lvlnum <= 3} {
catch {exec logger -s -t $logtag -p $logfacility.$lvl $msg}
} else {
puts "$lvl: $msg"
catch {exec logger -t $logtag -p $logfacility.$lvl $msg}
}
}
}
proc loadAccessToken {} {
global accesstoken
global refreshtoken
global tokenexpireU
global cfgfile
global startprm
log info "loading file content from $cfgfile."
set fp [open $cfgfile r]
set file_data [read $fp]
close $fp
log debug "file content is:\n$file_data"
set data [split $file_data "\n"]
set accesstoken [lindex $data 0]
set refreshtoken [lindex $data 1]
set tokenexpireU [lindex $data 2]
set tokenexpireN [lindex $data 3]
set tokenscope [lindex $data 4]
#set tokenexpireN [clock format $tokenexpireU -format "%H:%M:%S"]
log info "accesstoken is: $accesstoken"
log info "refreshtoken is: $refreshtoken"
log info "tokenexpireU is: $tokenexpireU"
log info "tokenexpireN is: $tokenexpireN"
log info "tokenscope is: $tokenscope"
if {$startprm == "-cfginfo"} {
log info "script has been terminated."
exit 0
}
}
proc refreshToken {rt ci cs} {
log debug "refreshing token."
set url "https://api.netatmo.com/oauth2/token"
set header "Content-Type: application/x-www-form-urlencoded;charset=UTF-8"
set parameter "grant_type=refresh_token&refresh_token=$rt&client_id=$ci&client_secret=$cs"
catch {exec /usr/local/addons/cuxd/curl -k -i -H $header -X POST -d $parameter $url} response
log debug "response was $response"
return $response
}
proc parseOAuthResponse {input} {
log debug "parsing authentication result."
global accesstoken
global refreshtoken
regexp {HTTP/1.1\s(\d*)} $input dummy returncode
regexp {\"access_token\":\"(.*?)\"} $input dummy accesstoken
regexp {\"refresh_token\":\"(.*?)\"} $input dummy refreshtoken
regexp {\"expires_in\":(.*?)\,} $input dummy expiresin
regexp {\"scope\":\s*(\[[^\]]*\])} $input dummy scope
log debug "returncode is $returncode"
log debug "accesstoken is $accesstoken"
log debug "refreshtoken is $refreshtoken"
log debug "expires in $expiresin"
log debug "scope is $scope"
if {[expr $returncode]!=200} {
log error "Authentication failed with code $returncode and response $input."
exit 1
}
return "$expiresin $scope"
}
proc saveAccessToken {input} {
global accesstoken
global refreshtoken
global tokenexpireU
global cfgfile
set input [split $input " "]
set expin [lindex $input 0]
set scope [lindex $input 1]
set now [clock seconds]
set tokenexpireU [expr $now + $expin]
# Umwandlung der Unix-Zeit in Normal-Zeit
set tokenexpireN [clock format $tokenexpireU -format "%H:%M:%S"]
log notice "new accesstoken expires at $tokenexpireN."
log debug "saving new token to $cfgfile."
set fileId [open $cfgfile "w"]
puts $fileId $accesstoken
puts $fileId $refreshtoken
puts $fileId $tokenexpireU
puts $fileId $tokenexpireN
puts $fileId $scope
close $fileId
}
log debug "Script has been started."
set startprm [lindex $argv 0]
if { [file exists $cfgfile] == 1} {
log info "cfgfile $cfgfile found."
loadAccessToken
set now [clock seconds]
log debug "current time is [clock format $now -format "%Y-%m-%dT%H:%M:%S"], accesstoken expires at [clock format $tokenexpireU -format "%Y-%m-%dT%H:%M:%S"]"
if {[expr $now >= $tokenexpireU] == 1} {
log notice "token has already expired."
saveAccessToken [parseOAuthResponse [refreshToken $refreshtoken $clientId $clientSecret]]
log notice "oauth token successfully refreshed."
} else {
log info "token is still valid."
}
} else {
log info "cfgfile $cfgfile not found."
log info "create a new file with PC cmd: python NetatmoInitialRequest.py."
log info "copy the new file from PC %tmp%/netatmo.dat to CCU $cfgfile."
}
# Info for Weather Station:
# max -> Temperature, CO2, Humidity, Pressure, Noise, Rain (if module_id is a rain sensor)
# 30min, 1hour, 3hours -> Temperature, CO2, Humidity, Pressure, Noise, min_temp, max_temp, min_hum, max_hum, min_pressure, max_pressure, min_noise, max_noise, sum_rain (if module_id is a rain sensor)
# 1day, 1week, 1month -> Temperature, Co2, Humidity, Pressure, Noise, min_temp, date_min_temp, max_temp, date_max_temp, min_hum, date_min_hum, max_hum, date_max_hum, min_pressure, date_min_pressure, max_pressure, date_max_pressure, min_noise, date_min_noise, max_noise, date_max_noise, date_min_co2, date_max_co2, sum_rain (if module_id is a rain sensor)
if {$deviceid != "XX:XX:XX:XX:XX:XX"} {
log debug "polling main module..."
set url "https://api.netatmo.com/api/getmeasure?access_token=$accesstoken&device_id=$deviceid&scale=max&type=Temperature,Humidity,CO2,Pressure,Noise&date_end=last"
log debug "querying $url"
catch {exec /usr/local/addons/cuxd/curl -k -# $url} response
log debug "response is: $response"
regexp {\"value\":\[\[(.*?),(.*?),(.*?),(.*?),(.*?)\]} $response dummy itemp ihum ico2 ipressure inoise
log debug "LogI is $response"
log info "Inside temperature is $itemp"
log info "Inside humidity is $ihum"
log info "Inside CO2 level $ico2"
log info "Inside pressure is $ipressure"
log info "Inside noise level is $inoise"
}
if {$moduleid != "XX:XX:XX:XX:XX:XX"} {
log debug "polling outdoor module..."
set url "https://api.netatmo.com/api/getmeasure?access_token=$accesstoken&device_id=$deviceid&module_id=$moduleid&scale=max&type=Temperature,Humidity&date_end=last"
log debug "querying $url"
catch {exec /usr/local/addons/cuxd/curl -k -# $url} response
log debug "response is: $response"
regexp {\"value\":\[\[(.*?),(.*?)\]} $response dummy otemp ohum
log info "Outside temperature is $otemp"
log info "Outside humidity is $ohum"
}
if {$rainid != "XX:XX:XX:XX:XX:XX"} {
log debug "polling regensensor module...30min"
set url "https://api.netatmo.com/api/getmeasure?access_token=$accesstoken&device_id=$deviceid&module_id=$rainid&scale=30min&type=Rain,sum_rain&date_end=last"
log debug "querying $url"
catch {exec /usr/local/addons/cuxd/curl -k -# $url} response
log debug "response is: $response"
regexp {\"value\":\[\[(.*?),(.*?)\]} $response dummy rainNow rain30min
log debug "LogR is $response"
log info "Outside rainNow is $rainNow"
log info "Outside rain30min is $rain30min"
log debug "polling regensensor module...1h"
set url "https://api.netatmo.com/api/getmeasure?access_token=$accesstoken&device_id=$deviceid&module_id=$rainid&scale=1hour&type=Rain,sum_rain&date_end=last"
log debug "querying $url"
catch {exec /usr/local/addons/cuxd/curl -k -# $url} response
log debug "response is: $response"
regexp {\"value\":\[\[(.*?),(.*?)\]} $response dummy dummy rain1h
log debug "LogR is $response"
log info "Outside rain1h is $rain1h"
log debug "polling regensensor module...1d"
set url "https://api.netatmo.com/api/getmeasure?access_token=$accesstoken&device_id=$deviceid&module_id=$rainid&scale=1day&type=sum_rain&date_end=last"
log debug "querying $url"
catch {exec /usr/local/addons/cuxd/curl -k -# $url} response
log debug "response is: $response"
regexp {\"value\":\[\[(.*?)\]} $response dummy rain1d
log debug "LogR is $response"
log info "Outside rain1d is $rain1d"
log debug "polling regensensor module...1w"
set url "https://api.netatmo.com/api/getmeasure?access_token=$accesstoken&device_id=$deviceid&module_id=$rainid&scale=1week&type=sum_rain&date_end=last"
log debug "querying $url"
catch {exec /usr/local/addons/cuxd/curl -k -# $url} response
log debug "response is: $response"
regexp {\"value\":\[\[(.*?)\]} $response dummy rain1w
log debug "LogR is $response"
log info "Outside rain1w is $rain1w"
}
if { $windid != "XX:XX:XX:XX:XX:XX"} {
log debug "polling wind module..."
set url "https://api.netatmo.com/api/getmeasure?access_token=$accesstoken&device_id=$deviceid&module_id=$windid&scale=max&type=WindAngle,WindStrength,GustAngle,GustStrength&date_end=last"
log debug "quering $url"
catch {exec /usr/local/addons/cuxd/curl -k -# $url} response
log debug "respnose is $response"
regexp {\"value\":\[\[(.*?),(.*?),(.*?),(.*?)\]} $response dummy windangle windstrength gustangle guststrength
log info "WindAngle is $windangle"
log info "Windstrength is $windstrength"
log info "GustAngle is $gustangle"
log info "Guststrength is $guststrength"
}
# set ReGaHss variables
set rega_cmd ""
append rega_cmd "var ITemp = dom.GetObject('CUxD.CUX9002001:1.SET_TEMPERATURE');"
append rega_cmd "var IHumi = dom.GetObject('CUxD.CUX9002001:1.SET_HUMIDITY');"
if {$moduleid != "XX:XX:XX:XX:XX:XX"} {
append rega_cmd "var OTemp = dom.GetObject('CUxD.CUX9002002:1.SET_TEMPERATURE');"
append rega_cmd "var OHumi = dom.GetObject('CUxD.CUX9002002:1.SET_HUMIDITY');"
}
if {$rainid != "XX:XX:XX:XX:XX:XX"} {
append rega_cmd "var Rain1 = dom.GetObject('Regenmenge_aktuell');"
append rega_cmd "var Rain2 = dom.GetObject('Regenmenge_30min');"
append rega_cmd "var Rain3 = dom.GetObject('Regenmenge_1h');"
append rega_cmd "var Rain4 = dom.GetObject('Regenmenge_1d');"
append rega_cmd "var Rain5 = dom.GetObject('Regenmenge_1w');"
}
append rega_cmd "var IPress = dom.GetObject('Luftdruck');"
append rega_cmd "var ICO2 = dom.GetObject('CO2');"
append rega_cmd "var INoise = dom.GetObject('Sonometer');"
if {$windid != "XX:XX:XX:XX:XX:XX"} {
append rega_cmd "var windA = dom.GetObject('Windrichtung');"
append rega_cmd "var windS = dom.GetObject('Windstaerke');"
append rega_cmd "var gustA = dom.GetObject('Gustangle');"
append rega_cmd "var gustS = dom.GetObject('Guststaerke');"
}
if {$moduleid != "XX:XX:XX:XX:XX:XX"} {
append rega_cmd "OTemp.State('$otemp');"
append rega_cmd "OHumi.State('$ohum');"
}
append rega_cmd "ITemp.State('$itemp');"
append rega_cmd "IHumi.State('$ihum');"
append rega_cmd "IPress.State('$ipressure');"
append rega_cmd "ICO2.State('$ico2');"
append rega_cmd "INoise.State('$inoise');"
if {$rainid != "XX:XX:XX:XX:XX:XX"} {
append rega_cmd "Rain1.State('$rainNow');"
append rega_cmd "Rain2.State('$rain30min');"
append rega_cmd "Rain3.State('$rain1h');"
append rega_cmd "Rain4.State('$rain1d');"
append rega_cmd "Rain5.State('$rain1w');"
}
if {$windid != "XX:XX:XX:XX:XX:XX"} {
append rega_cmd "windA.State('$windangle');"
append rega_cmd "windS.State('$windstrength');"
append rega_cmd "gustA.State('$gustangle');"
append rega_cmd "gustS.State('$guststrength');"
}
append rega_cmd "var sdatetime = system.Date('%d.%m.%Y %H:%M:%S');"
append rega_cmd "var netatmosynctime = dom.GetObject('SyncTime');"
append rega_cmd "netatmosynctime.Variable(sdatetime.ToString());"
rega_script $rega_cmd
log debug "rega_cmd is $rega_cmd"
log info "script has been terminated."