Tempo signal in Domoticz (For French electrical market only)

Moderator: leecollings

Post Reply
Michel13
Posts: 77
Joined: Thursday 07 January 2016 19:31
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.7
Location: France
Contact:

Tempo signal in Domoticz (For French electrical market only)

Post by Michel13 »

This tutorial is intended for subscribers to the Tempo electricity tariff plan, which is exclusively available in the French electricity market.

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.
Indeed, retrieving information from the RTE website is a two-step process:
  • 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.
In order to keep the "client ID and secret ID" in a safer place, create a file named "rte_api.key" where you store your client ID and secret ID in base 64. I have saved mine in /home/pi/domoticz/scripts/. You will have to tel the script where you have stored yours.

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 entire script is written exclusively in French, as it only concerns the French electricity distribution system.

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
Last edited by Michel13 on Monday 22 December 2025 20:15, edited 2 times in total.
lost
Posts: 699
Joined: Thursday 10 November 2016 9:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Tempo signal in Domoticz (For French electrical market only)

Post by lost »

Michel13 wrote: Monday 22 September 2025 19:55 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.
That's IMO useless: 2nd site takes it's own information from RTE, this can be seen from site source, as it's open-source:
https://github.com/jbromain/rte-tempo/b ... ommand.php

Line 27, you can see base RTE_API_URL="https://www.services-rte.com/cms/open_d ... po?season="

Current season would be (this change each 1st of September) 2025-2026, to add in the end.

This URL does not need any registration to access freely available data and is the one used by hereupper script referer URL page:
https://www.services-rte.com/fr/visuali ... tempo.html

Setting browser debug tab to monitor network data & refresh page shows the use of hereupper RTE_API_URL and added season format.
One disadvantage vs api-couleur-tempo is all current season days data is retrieved, thus needs a bit of processing to count each day-colors already used and see if next day color is already defined. Big advantage is this data is published early vs official hour (after 10h00), usually at 6h15 thus more time to start an heavy consumer early before going to work if the next day will be charged as "red".

There was plenty of scripts using various possibilities on french forum!
Michel13
Posts: 77
Joined: Thursday 07 January 2016 19:31
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.7
Location: France
Contact:

Re: Tempo signal in Domoticz (For French electrical market only)

Post by Michel13 »

This script's sole purpose is to retrieve the color codes for the current day and the following day. I need this information reliably for my automated system that switches between the heat pump and the oil boiler. So...
lost wrote: Tuesday 23 September 2025 9:11 That's IMO useless: 2nd site takes it's own information from RTE, this can be seen from site source, as it's open-source:
https://github.com/jbromain/rte-tempo/b ... ommand.php
You're right. As I mentioned at the beginning, the information comes from the same source, RTE. However, no system is immune to bugs, especially during data retrieval. Having a backup of the information isn't a luxury; it's essential, particularly if the data is important.
lost wrote: Tuesday 23 September 2025 9:11 Line 27, you can see base RTE_API_URL="https://www.services-rte.com/cms/open_d ... po?season="
This link doesn't work... Furthermore, I do not need the season information, just the D and D+1 information, but I need it reliably.

I did see the script https://github.com/jbromain/rte-tempo/b ... ommand.php, but, unless I misunderstood, it retrieves JSON data from a web page. There's nothing more volatile than a web page, which can be modified by a developer at any time. Personally, I find it safer to query an API rather than a web page. Furthermore, I don't really need a history of that data, as I can easily find it again online if necessary.
lost wrote: Tuesday 23 September 2025 9:11 There was plenty of scripts using various possibilities on french forum!
Exactly : "There were." As mentioned earlier, there are many of them, and many are outdated. The only source that I could find which is still available is api-couleur-tempo.fr, which I use as a backup in my script.
lost
Posts: 699
Joined: Thursday 10 November 2016 9:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Tempo signal in Domoticz (For French electrical market only)

Post by lost »

Michel13 wrote: Tuesday 23 September 2025 10:38 Having a backup of the information isn't a luxury
(...)
This link doesn't work... Furthermore, I do not need the season information, just the D and D+1 information, but I need it reliably.

I did see the script https://github.com/jbromain/rte-tempo/b ... ommand.php, but, unless I misunderstood, it retrieves JSON data from a web page. There's nothing more volatile than a web page.
That's a copy of RTE information, so not a backup.... and the link does work (you may have forgot to add current season in the end?), and still does no later than this early morning! I would get warning every hour if last update is over 24h anyway. Sometime get one or two after 6h30 but that's quite rare situation. Retries every 15mn starting at 6h15 lasts until the information is available anyway, so it also handles temporary access/unavailability/RTE maintenance issues. Did not get a single day failure after more than a year of usage.

Data is retrieved as JSON from a web URL, so not a web page but formatted data for programmatic use. RTE web page use and formats this JSON data but the URL feeding the data can be used directly. And if RTE, as the primary source of this information, fails to feed this no way to have it from secondary sources.

api-couleur-tempo.fr author in hereupper source states official API not being reliable, to explain why he does not use it:
Nb: cette commande n'utilise pas pour l'instant l'API officielle car elle semble souffir de nombreux bugs.
Maybe that's better now, but the hassle of creating an account to just get public data is IMO really a very bad idea in the first place and can be avoided: IMO, the tradeoff between skipping authentication vs getting all current Tempo year elapsed days data is lesser complexity and probably even less data exchange (so up to 365 days at 31 of August, and annual average being half of this figure) as well. +Authentification infra (both client&server side) is quite complex stuff with failures still very common that should impact reliability.

This of course may change in the future (probable root cause for the previous APIs you spoke about removal was EDF stop of it's own URLs on, 01/09/2024) but just as any published API and for this one, RTE doe not even use it so this can even change without needing their public information site rework.
Michel13
Posts: 77
Joined: Thursday 07 January 2016 19:31
Target OS: Raspberry Pi / ODroid
Domoticz version: 2024.7
Location: France
Contact:

Re: Tempo signal in Domoticz (For French electrical market only)

Post by Michel13 »

lost wrote: Tuesday 23 September 2025 12:22 Maybe that's better now, but the hassle of creating an account to just get public data is IMO really a very bad idea in the first place and can be avoided: IMO, the tradeoff between skipping authentication vs getting all current Tempo year elapsed days data is lesser complexity and probably even less data exchange (so up to 365 days at 31 of August, and annual average being half of this figure) as well. +Authentification infra (both client&server side) is quite complex stuff with failures still very common that should impact reliability.
I completely agree that the system implemented by RTE for retrieving public data is far too complex.
That being said, going through this process might be a bit of a challenge and a learning experience, but it suits my needs.

To be honest, I don't have enough data yet to say whether the information I'm retrieving via the API is reliable, since this script has only been in production for two days.
What I do know, and what I mentioned in my previous reply, is that retrieving the JSON data wasn't easy, simply because the API documentation doesn't clearly state that the "Accept: application/json" header must be included. Without it, the response is often empty with an internal server error (500). And that's probably what was considered a bug. So far, all my numerous requests, both for testing and in production, have been 100% successful.

As for the number of requests the script makes, it's only two per day, for a JSON file of 20 lines max. Not a big deal. Here is the outpout for yesterday :

Code: Select all

{"tempo_like_calendars": {
   "start_date": "2025-09-22T00:00:00+02:00",
   "end_date": "2025-09-24T00:00:00+02:00",
   "values":    [
            {
         "start_date": "2025-09-23T00:00:00+02:00",
         "end_date": "2025-09-24T00:00:00+02:00",
         "value": "BLUE",
         "fallback": false,
         "updated_date": "2025-09-22T10:20:00+02:00"
      },
            {
         "start_date": "2025-09-22T00:00:00+02:00",
         "end_date": "2025-09-23T00:00:00+02:00",
         "value": "BLUE",
         "fallback": false,
         "updated_date": "2025-09-21T10:20:00+02:00"
      }
   ]
}}
Ultimately, I only need two pieces of information; the rest isn't relevant for my home automation use. I'm convinced that this is the case for most users, and I think my little script is perfectly adequate.

Once registration is made, there's no complexity involved and I don't see any particular disadvantage to using an API instead of accessing a third-party website.
I will, of course, monitor the performance over time and will come back if I encounter any problems.

P.S.: Are you the author of the GitHub script, or just a user?
lost
Posts: 699
Joined: Thursday 10 November 2016 9:30
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: Tempo signal in Domoticz (For French electrical market only)

Post by lost »

The author of the github repo is the maintainer of https://www.api-couleur-tempo.fr you use as a secondary source... which takes it's data from the account free RTE url.

I'm not the author of this site.

FYI, one topic on this subject from last year, when EDF removed it's own account-free API... that triggered many switches to others. Many snippets or scripts on this subject (including mine).
https://easydomoticz.com/forum/viewtopic.php?t=14136
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest