Integration of Energy-Chart Electricity Traffic Light (Stromampel) into OpenHab

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.

1 Like

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 :wink: 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…

1 Like

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
1 Like

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
1 Like

Thanks for the translation! While I did have an eye on jruby for quite a while, it’s now locked in I guess. :wink: