After the recent issues with the sources for retrieving Tempo information (http://domogeek.entropialux.com/tempoedf, which disappeared from the web, and then https://particulier.edf.fr/services/res ... TempoStore, which stopped working at the end of 2024), there is still one third-party service (api-couleur-tempo.fr) that provides the information directly from RTE data.
However, as with the two previous sites, there is no guarantee that this service will remain available in the long term. Therefore, I thought it would be better to request the data directly from RTE, rather than using a third-party service that could disappear overnight.
This is what I implemented for my own needs, and I would like to share it with this tutorial.
In practice, I used both sources to retrieve the information for the current day and the following day, and I compare the results to confirm that both sources are working and consistent. I prioritize retrieving the data from RTE, and if it is unavailable, I retrieve it from api-couleur-tempo.fr.
Note that I am not a scripting expert, and I therefore used ChatGPT extensively to write this script. There is probably room for improvement, and I welcome any suggestions.
Let's start with RTE.
- 1. Go to the website https://data.rte-france.com/web/guest# to register.
2. Click on "Créer mon compte". Enter the required information and submit. You must wait for the confirmation email before proceeding. This first step was not straightforward, as I never received the email that would have allowed me to access my account. Without it, it was impossible to proceed. I had to contact the hotline ([email protected]) to have them unlock my email address and activate my account.
3. Once logged into your account, search for the "Tempo Like Supply Contract" API, then click on "Découvrir l’API" and "Abonnez-vous à l’API" to create an application.
4. Give your application a name; this application can be used later for other APIs if desired. Select "Web/Server" as the type, enter a description for your application, and then save.
5. Once your application is created, go to the "Mes Applications" tab where you will find the application you just created. Click to open it. At the bottom, you should see the "Tempo Like Supply Contract" API associated with it.
6. Your application should display your "Client ID" and "Secret ID". While these values are important, what we need is their Base 64 encoded version. Click the "Copier en Base64" button and save this copy for use in the script. This information will allow you to get the token that grants access to the API.
- 1. Get the token OAuth2 using your application's Base 64 encoded credentials. This token is valid for 1 hour.
2. Query the API to retrieve the JSON data corresponding to your request using the authentication token.
Now let's move on to Domoticz.
First, we will create two virtual selector switches named "Tempo Aujourd’hui" and "Tempo Demain". For each of them, we define 3 levels: Bleu = 10, Blanc = 20, Rouge = 30. We can disable level 0 (Off) for the first switch if desired.
All that remains is to install the following dzVents script in the Events section, replace the Idx of the two selector switches by the ones in your Domoticz and give the path to your rte_api.key file in the "Récupération RTE" section:
Code: Select all
-- Script de récupération des informations Tempo J et J+1
-- À partir de l'API RTE en priorité (https://data.rte-france.com)
-- et les données du site www.api-couleur-tempo.fr en secours
return {
active = true,
on = { timer = { 'at 06:15', 'at 11:15' } },
execute = function(dz)
---------------------------------------
-- Configuration des devices (IDX)
---------------------------------------
local TEMPO_AUJOURDHUI_IDX = 999 -- À REMPLACER par votre IDX
local TEMPO_DEMAIN_IDX = 999 -- À REMPLACER par votre IDX
dz.log("⏳ Récupération des couleurs Tempo via RTE (priorité) + fallback Tiers...", dz.LOG_INFO)
---------------------------------------
-- Fonctions utilitaires
---------------------------------------
-- Détection automatique du fuseau horaire (heure d'été/hiver)
local function getFrenchTimezone()
-- Méthode simple : comparer l'heure locale et UTC
-- En hiver : UTC+1, en été : UTC+2
local now = os.time()
local utcDate = os.date("!*t", now) -- Date UTC
local localDate = os.date("*t", now) -- Date locale
-- Calculer la différence en heures
local utcTime = utcDate.hour * 3600 + utcDate.min * 60 + utcDate.sec
local localTime = localDate.hour * 3600 + localDate.min * 60 + localDate.sec
local diff = (localTime - utcTime) / 3600
-- Gérer le passage de minuit
if diff < -12 then diff = diff + 24 end
if diff > 12 then diff = diff - 24 end
-- En France : +1 en hiver, +2 en été
local offset = math.floor(diff + 0.5)
-- RTE utilise l'heure française : UTC+2 en été, UTC+1 en hiver
return string.format("+%02d:00", offset)
end
local function codeToLevelRTE(val)
if val == "BLUE" then return 10, "BLEU"
elseif val == "WHITE" then return 20, "BLANC"
elseif val == "RED" then return 30, "ROUGE"
else return 0, "Pas d'info"
end
end
local function codeToLevelTiers(code)
code = tonumber(code)
if code == 1 then return 10, "BLEU"
elseif code == 2 then return 20, "BLANC"
elseif code == 3 then return 30, "ROUGE"
else return 0, "Pas d'info"
end
end
-- Lecture de la clé API depuis un fichier
local function loadAPIKey(path)
local f, err = io.open(path, "r")
if not f then
return nil, "❌ Impossible de charger la clé API RTE : " .. (err or "")
end
local raw = f:read("*a")
f:close()
if not raw or raw == "" then
return nil, "❌ Clé API RTE vide après lecture"
end
-- Nettoyage espaces / retours chariot éventuels
local key = raw:gsub("^%s+", ""):gsub("%s+$", "")
return key
end
local function getToken(apiKey)
local maxRetries = 2
local retryDelay = 3
for attempt = 1, maxRetries do
local handle = io.popen(
"curl -s -X POST 'https://digital.iservices.rte-france.com/token/oauth/' " ..
"-H 'Content-Type: application/x-www-form-urlencoded' " ..
"-H 'Authorization: Basic " .. apiKey .. "' " ..
"-d 'grant_type=client_credentials'"
)
local result = handle:read("*a")
handle:close()
local data = dz.utils.fromJSON(result)
if data and data.access_token then
if attempt > 1 then
dz.log("✅ Token RTE obtenu après " .. attempt .. " tentative(s)", dz.LOG_INFO)
end
return data.access_token
end
if attempt < maxRetries then
dz.log("⚠️ Échec obtention token RTE (tentative " .. attempt .. "/" .. maxRetries .. "), réessai dans " .. retryDelay .. "s", dz.LOG_WARNING)
os.execute("sleep " .. retryDelay)
end
end
dz.log("❌ Impossible d'obtenir un token RTE après " .. maxRetries .. " tentatives", dz.LOG_ERROR)
return nil
end
local function getRTEData(token, start_date, end_date)
local url = "https://digital.iservices.rte-france.com/open_api/tempo_like_supply_contract/v1/tempo_like_calendars" ..
"?start_date=" .. start_date ..
"&end_date=" .. end_date ..
"&fallback_status=true"
local cmd = "curl -s -X GET '" .. url .. "' " ..
"-H 'Accept: application/json' " ..
"-H 'Authorization: Bearer " .. token .. "'"
local handle = io.popen(cmd)
local result = handle:read("*a")
handle:close()
return dz.utils.fromJSON(result)
end
local function getTiersData()
local urls = {
["Tempo Aujourd'hui"] = "https://www.api-couleur-tempo.fr/api/jourTempo/today",
["Tempo Demain"] = "https://www.api-couleur-tempo.fr/api/jourTempo/tomorrow"
}
local out = {}
for name, url in pairs(urls) do
local handle = io.popen("curl -s '" .. url .. "'")
local result = handle:read("*a")
handle:close()
local data = dz.utils.fromJSON(result)
if data and data.codeJour then
local lvl, couleur = codeToLevelTiers(data.codeJour)
out[name] = { level = lvl, couleur = couleur }
else
dz.log("⚠️ Donnée Tiers manquante pour " .. name, dz.LOG_WARNING)
end
end
return out
end
---------------------------------------
-- Récupération RTE
---------------------------------------
local apiKey, err = loadAPIKey("/chemin/vers/votre/rte_api.key") -- À MODIFIER
if not apiKey then
dz.log(err, dz.LOG_ERROR)
end
local token = getToken(apiKey)
local dataRTE = {}
if token then
-- Détection automatique du timezone français
local timezone = getFrenchTimezone()
local today = os.date("%Y-%m-%dT00:00:00") .. timezone
local afterTomorrow = os.date("%Y-%m-%dT00:00:00", os.time() + 2*24*3600) .. timezone
dz.log("🔍 Plage demandée à RTE: " .. today .. " → " .. afterTomorrow, dz.LOG_INFO)
local json = getRTEData(token, today, afterTomorrow)
if json and json.tempo_like_calendars and json.tempo_like_calendars.values then
dz.log("🔍 Données RTE reçues : " .. #json.tempo_like_calendars.values .. " entrée(s)", dz.LOG_INFO)
for _, entry in ipairs(json.tempo_like_calendars.values) do
local lvl, couleur = codeToLevelRTE(entry.value)
local startDate = entry.start_date:sub(1,10)
local todayDate = os.date("%Y-%m-%d")
local tomorrowDate = os.date("%Y-%m-%d", os.time() + 24*3600)
if startDate == todayDate then
dataRTE["Tempo Aujourd'hui"] = { level = lvl, couleur = couleur }
elseif startDate == tomorrowDate then
dataRTE["Tempo Demain"] = { level = lvl, couleur = couleur }
end
end
-- Log récapitulatif de ce qui a été trouvé dans RTE
if dataRTE["Tempo Aujourd'hui"] then
dz.log("✅ RTE fournit Tempo Aujourd'hui: " .. dataRTE["Tempo Aujourd'hui"].couleur, dz.LOG_INFO)
else
dz.log("⚠️ RTE ne fournit PAS Tempo Aujourd'hui", dz.LOG_WARNING)
end
if dataRTE["Tempo Demain"] then
dz.log("✅ RTE fournit Tempo Demain: " .. dataRTE["Tempo Demain"].couleur, dz.LOG_INFO)
else
dz.log("⚠️ RTE ne fournit PAS Tempo Demain", dz.LOG_WARNING)
end
else
dz.log("⚠️ Réponse RTE invalide ou vide", dz.LOG_WARNING)
end
end
---------------------------------------
-- Récupération Tiers (fallback)
---------------------------------------
local dataTiers = getTiersData()
---------------------------------------
-- Choix final et mise à jour (RTE prioritaire, puis Tiers)
---------------------------------------
local finalVals = {}
local sourcesUsed = {}
-- Table de correspondance nom logique -> IDX
local devices = {
["Tempo Aujourd'hui"] = TEMPO_AUJOURDHUI_IDX,
["Tempo Demain"] = TEMPO_DEMAIN_IDX
}
for _, deviceName in ipairs({ "Tempo Aujourd'hui", "Tempo Demain" }) do
local src = nil
local couleur = nil
local level = 0
-- Priorité RTE, fallback Tiers
if dataRTE[deviceName] then
src = "RTE"
level = dataRTE[deviceName].level
couleur = dataRTE[deviceName].couleur
elseif dataTiers[deviceName] then
src = "Tiers (fallback)"
level = dataTiers[deviceName].level
couleur = dataTiers[deviceName].couleur
end
-- Appliquer / logger
if src then
dz.log("🎯 " .. deviceName .. " : " .. (couleur or "inconnu") .. " (source = " .. src .. ")", dz.LOG_INFO)
-- Mise à jour du device par son IDX (plus fiable que par nom)
local deviceIdx = devices[deviceName]
dz.devices(deviceIdx).switchSelector(level)
else
dz.log("❌ Aucune donnée disponible pour " .. deviceName, dz.LOG_ERROR)
end
finalVals[deviceName] = { src = src, couleur = couleur, level = level }
if src then
table.insert(sourcesUsed, src)
end
-- Comparaison si les deux sources sont présentes pour ce device
if dataRTE[deviceName] and dataTiers[deviceName] then
if dataRTE[deviceName].couleur ~= dataTiers[deviceName].couleur then
dz.log("⚠️ DIVERGENCE : RTE = " .. dataRTE[deviceName].couleur ..
" / Tiers = " .. dataTiers[deviceName].couleur, dz.LOG_ERROR)
else
dz.log("✅ Cohérence confirmée pour " .. deviceName, dz.LOG_INFO)
end
end
end -- fin for
-- Log récapitulatif des sources utilisées
if #sourcesUsed > 0 then
local uniqueSources = {}
for _, s in ipairs(sourcesUsed) do
uniqueSources[s] = true
end
local sourcesList = {}
for s in pairs(uniqueSources) do
table.insert(sourcesList, s)
end
dz.log("📊 Sources utilisées : " .. table.concat(sourcesList, ", "), dz.LOG_INFO)
end
dz.log("------ Fin récupération Tempo", dz.LOG_INFO)
end
}
The data for day "J" and day "J+1" are normally available at 6:00 AM and 11:00 AM respectively. Therefore, the script is run twice, with a 15-minute delay between runs, to ensure that the data is available on both websites.
You can, of course, modify the script as needed, for example, to retrieve more comprehensive data from the RTE website (data available since 2014, up to a maximum of 366 days per request). If you would like more information on the methods for retrieving Tempo data from RTE, you can consult and download the documentation available directly through the API.
Last change : 22/12/2025
Enjoy