Currently this intergration is not working because of an expired ssl-certificate. I dropped a message at the Fraunhofer Institute and they assured me they will renew the cert ASAP.
Sadly, this stopped working: Script execution of rule with UID āStromAmpel-2ā failed: For input string: ā{ādetailā:āNot Foundā}ā in StromAmpel
Any ideas?
Fraunhofer changed the interface a bit. I fixed the rules on my end and will post them in the next few days, once Iām sure itās working.
EDIT 2025-10-26: Just to let you know, that Iām still waiting for a changing state/prediction, but itās been āgreenā for days now and apparently same tomorrow.
Here are the updated rules:
rule "Get new power traffic light data"
when
Time cron "0 0 2/6 ? * *" // update every six hours, offset at 2AM (forecast data available around 19:00 give or take)
then
var response = sendHttpGetRequest("https://api.energy-charts.info/signal?country=de")
// some day introduce some checks on response here...
iTrafficLight_raw.postUpdate(response)
end
rule "Update Power traffic light"
when
Time cron "0 */15 * ? * *" // update at every 15th minute
then
var String input_string = (iTrafficLight_raw.state as StringType).toString
val json_arr_length = Integer::parseInt(transform("JSONPATH", "$.unix_seconds.length()", input_string))
// find index corresponding to current time
val long now_epoch = now.toInstant.toEpochMilli
var long data_epoch = 0
var now_index = -1
var i = 0
var boolean flag = true
while(flag) {
data_epoch = Long::parseLong(transform("JSONPATH", "$.unix_seconds[" + i + "]", input_string)) * 1000
if(Math::abs(data_epoch - now_epoch) < 450000) {
now_index = i
flag = false
}
if (i == (json_arr_length - 1)) {
flag = false
}
i = i + 1
}
// get current status
val current_signal = Integer::parseInt(transform("JSONPATH", "$.signal[" + now_index + "]", input_string))
iCurrentPowerTrafficLight.postUpdate(current_signal)
// stop here and return if we hit the end of time
if (i >= json_arr_length) {
iNextPowerTrafficLight.postUpdate(3)
iNextPowerPeriodStart.postUpdate(new DateTimeType())
return
}
// now check for next status and time of next status - starting from index i above
var data_signal = 0
var next_period_index = 0
flag = true
while(flag) {
data_signal = Integer::parseInt(transform("JSONPATH", "$.signal[" + i + "]", input_string))
if(data_signal != current_signal) {
next_period_index = i
flag = false
}
if (i == (json_arr_length - 1)) {
flag = false
if(data_signal != current_signal) {
// nothing changes until end of time, so we don't know next phase
data_signal = 3 // set to unknown
next_period_index = i // slightly misleading as this is not the next phase, but just the end of time
}
}
i = i + 1
}
val next_signal = data_signal
val next_period_epoch = Long::parseLong(transform("JSONPATH", "$.unix_seconds[" + next_period_index + "]", input_string)) * 1000
val next_period_time = Instant.ofEpochMilli(next_period_epoch).atZone(ZoneId.systemDefault())
iNextPowerTrafficLight.postUpdate(next_signal)
iNextPowerPeriodStart.postUpdate(new DateTimeType(next_period_time))
end
The changes are minor, but affect the URL, the json data naming and the timestamps, which are now epoch seconds instead of epoch millis.
I wasnāt trying to win a beauty contest with the rule code
since I migrated things, items, etc. to be managed through the UI and now hope for a long cold winter to redo my rules. The next update might be in jrubyā¦
I hope you donāt mind me writing the jruby version of your rule above:
require 'json'
UNKNOWN_SIGNAL = 3
rule "Get new power traffic light data" do
cron hour: "2/6" # every six hours starting from 2AM (forecast data available around 19:00 give or take)
run do
response = HTTP.get("https://api.energy-charts.info/signal?country=de")
iTrafficLight_raw.update(response)
end
end
rule "Update Power traffic light" do
every 15.minutes
run do
data = JSON.parse(iTrafficLight_raw.state.to_s)
data_length = data["unix_seconds"].length
now = Time.now.to_i
now_index = data["unix_seconds"].index { |timestamp| (now - timestamp).abs < 450 }
next_index = data_length
if now_index
current_signal = data["signal"][now_index]
iCurrentPowerTrafficLight.update(current_signal)
next_index = now_index + 1
end
if next_index == data_length # stop here and return if we hit the end of time
iNextPowerTrafficLight.update(UNKNOWN_SIGNAL)
iNextPowerPeriodStart.update(Time.now)
next
end
next_period_index = data["signal"].slice(next_index..).index { |signal| signal != current_signal }
if next_period_index # found a change in signal
next_period_index += next_index # adjust to the index of the full array
next_period = data["unix_seconds"][next_period_index]
next_signal = data["signal"][next_period_index]
else # no change in signal found, use the last entry
next_period = data["unix_seconds"].last
next_signal = UNKNOWN_SIGNAL
end
iNextPowerTrafficLight.update(next_signal)
iNextPowerPeriodStart.update(Time.at(next_period))
end
end
Hereās an improved / simplified version of the rule. I havenāt tested this though (havenāt tested the one above either).
I hope this is a lot easier to understand / follow. This version doesnāt need to keep track of the array indices at all.
require 'json'
UNKNOWN_SIGNAL = 3
rule "Get new power traffic light data" do
cron hour: "2/6" # every six hours starting from 2AM (forecast data available around 19:00 give or take)
run do
response = HTTP.get("https://api.energy-charts.info/signal?country=de")
iTrafficLight_raw.update(response)
end
end
rule "Update Power traffic light" do
every 15.minutes
run do
data = JSON.parse(iTrafficLight_raw.state.to_s)
data = data.values_at("unix_seconds", "signal").transpose
# data will look like this after being transposed
# [[timestamp1, signal1], [timestamp2, signal2], [timestamp3, signal3], ...]
now = Time.now.to_i
current_signal = nil
next_signal = nil
data.each do |timestamp, signal|
if current_signal.nil?
if (now - timestamp).abs < 450
current_signal = signal
iCurrentPowerTrafficLight.update(current_signal)
end
elsif signal != current_signal
# Found the next period
next_signal = signal
iNextPowerTrafficLight.update(signal)
iNextPowerPeriodStart.update(Time.at(timestamp))
break
end
end
if next_signal.nil?
iNextPowerTrafficLight.update(UNKNOWN_SIGNAL)
# the timestamp of the last data
iNextPowerPeriodStart.update(Time.at(data.last.first))
# alternatively, set it to UNDEF:
# iNextPowerPeriodStart.update(UNDEF)
# or to the current time:
# iNextPowerPeriodStart.update(Time.now)
end
end
end
Also: you donāt really need to store the fetched data in an item, unless you need it elsewhere. You could fetch it, parse it, and store it in a variable. This avoids json parsing every 15 minutes and you just use the parsed data. Hereās how:
require 'json'
UNKNOWN_SIGNAL = 3
@data = nil
def fetch_data
raw_data = HTTP.get("https://api.energy-charts.info/signal?country=de")
@data = JSON.parse(raw_data).values_at("unix_seconds", "signal").transpose
end
rule "Get new power traffic light data" do
cron hour: "2/6" # every six hours starting from 2AM (forecast data available around 19:00 give or take)
run do
fetch_data
end
end
rule "Update Power traffic light" do
every 15.minutes
run do
@data ||= fetch_data # ensure data is fetched at least once
now = Time.now.to_i
current_signal = nil
next_signal = nil
@data.each do |timestamp, signal|
if current_signal.nil?
if (now - timestamp).abs < 450
current_signal = signal
iCurrentPowerTrafficLight.update(current_signal)
end
elsif signal != current_signal
# Found the next period
next_signal = signal
iNextPowerTrafficLight.update(signal)
iNextPowerPeriodStart.update(Time.at(timestamp))
break
end
end
if next_signal.nil?
iNextPowerTrafficLight.update(UNKNOWN_SIGNAL)
iNextPowerPeriodStart.update(Time.at(@data.last.first))
# alternatively, set it to UNDEF:
# iNextPowerPeriodStart.update(UNDEF)
# or to the current time:
# iNextPowerPeriodStart.update(Time.now)
end
end
end
Thanks for the translation! While I did have an eye on jruby for quite a while, itās now locked in I guess. ![]()