Hourly Tibber rates on matrix displays (e.g. Ulanzi TC001)

User story:
I would like to see a graphical representation of the hourly Tibber rates for today and tomorrow on a matrix display.

BOM:

Setup:

  1. Get your TC001 up and running (just follow the installation instructions).
  2. If you can’t read Chinese, I strongly recommend to switch the UI to English, see Ulanzi Desktop Pixel Clock TC001 Review | Blakadder's Smarthome Shenanigans.
  3. Install AWTRIX.
  4. Configure your TC001 as an AWTRIX controller (‘Atrix Simulator’, port 7001), see Ulanzi Desktop Pixel Clock TC001 Review | Blakadder's Smarthome Shenanigans.
  5. Use the AWTRIX UI to enable MQTT.
  6. Install lua and json-lua (sudo luarocks install json-lua).
  7. Copy the following script to your filesystem:
#!/bin/bash
mosquitto_pub -m "{\"remove\":\"TibberApp\"}" -h <MQTT broker IP address> -u <MQTT user> -P <MQTT password>  -t awtrix/temporaryapp
curl 2>/dev/null \
-H "Authorization: Bearer <your personal Tibber API token>" \
-H "Content-Type: application/json" \
-X POST \
-d  '{ "query": "{ viewer { homes { currentSubscription{ priceInfo{ today { total } } } } }}" }' https://api.tibber.com/v1-beta/gql \
| lua create_awtrix_tibber_app.lua | tr -d '\n' | mosquitto_pub -l -h <MQTT broker IP address> -u <MQTT user> -P <MQTT password> -t awtrix/temporaryapp
  1. Adjust <your personal Tibber API token> and the MQTT parameters.
  2. Copy the following Lua script into create_awtrix_tibber_app.lua and place it in the same directory as the shell script:
json = require "JSON"

function print_color( p )

if p <= red
 then
  print( '  "color":[255,0,0]' )
 else
  if p <= orange
   then
    print( '  "color":[255,165,0]' )
   else
    print( '  "color":[0,255,0]' )
   end
 end

end


function analyse_day( res, today )

 red    = 0.35
 orange = 0.30

 prices = {}

 min = math.huge
 max = -math.huge

 if today
  then
   dataset = "today"
  else
   dataset = "tomorrow"
  end

 if #res["data"]["viewer"]["homes"][1]["currentSubscription"]["priceInfo"][ dataset ] == 24
  then

   for x,y in pairs( res["data"]["viewer"]["homes"][1]["currentSubscription"]["priceInfo"][ dataset ])
    do

     prices[x-1] = ( y["total"] ) * 1.0

        if prices[x-1] > max
         then

          max = prices[x-1]
      max_pos = x-1

         end

        if prices[x-1] < min
         then

          min = prices[x-1]
          min_pos = x-1

         end

   end

  diff = max - min

  red    = 7 - math.floor( ( red - min )    * 7 / diff + 0.5 )
  orange = 7 - math.floor( ( orange - min ) * 7 / diff + 0.5 )

  min_pixel = 7 - math.floor( ( min - min ) * 7 / diff + 0.5 )
  max_pixel = 7 - math.floor( ( max - min ) * 7 / diff + 0.5 )

  current_hour = os.date("*t").hour

  price_current = prices[ current_hour ]

  if not today
   then

    print(' {')
    print( '    "type": "fill",')
    print( '     "color": [50,50,50]')
    print( '   },')

   end

  if today
   then

    print( '{ "type":"pixel",' )
    print( '  "position":['..current_hour..",0]," )
    print( '  "color":[255,255,255]' )
    print( '},')

    print( '{ "type":"pixel",' )
    print( '  "position":['..current_hour..",7]," )
    print( '  "color":[255,255,255]' )
    print( '},')

   end

  for i = 0,23 do prices[i] = 7 - math.floor( ( prices[i]-min ) * 7 / diff + 0.5 ) end

  for i = 0,23
   do

    print( '{ "type":"pixel",' )
    print( '  "position":['..i..","..prices[i].."]," )

    print_color( prices[i] )

    print( '},')

   end

  if today
   then

    print( '{ "type":"text",' )
    print( '  "string":"' ..  math.floor( price_current * 100 + 0.5 )..'",')
    print( '  "position":[24,0],' )
    print_color( prices[ current_hour ] )
    print( '},')

    print( '{')
    print( '      "type": "show"')
        print( '    },')
    print( '    {')
    print( '      "type": "wait",')
    print( '      "ms": 10000')
    print( '    },')

   else

    print( '{ "type":"text",' )
    print( '  "string":"' ..  math.floor( min * 100 + 0.5 )..'",')
    print( '  "position":[24,0],' )
    print_color( min_pixel )
    print( '},')

    print( '{ "type":"pixel",' )
    print( '  "position":['..min_pos..",0]," )
    print( '  "color":[255,255,255]' )
    print( '},')

        print(' {')
    print( '    "type": "line",')
    print( '     "start": [27,7],')
    print( '     "end": [29,7],')
    print( '     "color": [255,255,255]')
    print( '   },')
    print(' {')
    print( '    "type": "pixel",')
    print( '     "position": [28,6],')
    print( '     "color": [255,255,255]')
    print( '   },')

    print( '   {')
    print( '     "type": "show"')
    print( '   },')
    print( '   {')
    print( '     "type": "wait",')
    print( '     "ms": 3000')
    print( '   },')

  if not today
   then

    print(' {')
    print( '    "type": "fill",')
    print( '     "color": [50,50,50]')
    print( '   },')

        print(' {')
    print( '    "type": "line",')
    print( '     "start": [27,6],')
    print( '     "end": [29,6],')
    print( '     "color": [255,255,255]')
    print( '   },')
    print(' {')
    print( '    "type": "pixel",')
    print( '     "position": [28,7],')
    print( '     "color": [255,255,255]')
    print( '   },')

    for i = 0,23
     do

      print( '{ "type":"pixel",' )
      print( '  "position":['..i..","..prices[i].."]," )

      print_color( prices[i] )

          print( '},')

     end

   end

--[[
    for i = 0, 7
     do

      print(' {')
      print( '    "type": "line",')
      print( '     "start": [24,'..i..'],')
      print( '     "end": [31,'..i..'],')
      print( '     "color": [50,50,50]')
      print( '   },')

     end
--]]

    print( '{ "type":"text",' )
    print( '  "string":"' ..  math.floor( max * 100 + 0.5 )..'",')
    print( '  "position":[24,0],' )
    print_color( max_pixel )
    print( '},')

    print( '{ "type":"pixel",' )
    print( '  "position":['..max_pos..",7]," )
    print( '  "color":[255,255,255]' )
    print( '},')


    print( '{')
    print( '      "type": "show"')
    print( '    },')
    print( '    {')
    print( '      "type": "wait",')
    print( '      "ms": 3000')
    print( '    },')

   end

  end -- data available

end -- function analyse_day

txt = io.read( '*a' )

res = json:decode( txt )

print( '{')
print( '   "name":"TibberApp",')
print( '   "lifetime":9999,')
print( '   "drawing":true,')
print( '   "data":[')

analyse_day( res, true )
analyse_day( res, false )

print( '    {')
print( '      "type": "exit"')
print( '    }')
print( '  ]')
print( '}')

  1. Execute the shell script to test your installation. I strongly recommend to use MQTT Explorer to check the installation (topic awtrix/temporaryapp):
    grafik
    The result should look like:


    Today from 7 pm to 8 pm (current hour): 30 ct/kWh

    Tomorrow from 12 noon to 1 pm lowest rate: 23 ct/kWh:

    Tomorrow from 7 pm to 8 pm highest rate: 36 ct/kWh:

Screen #1 and screen #2 are from the AWTRIX Time App and screens #3 - #5 were created by the new temporary Tibber App for AWTRIX. The white cursor marks the current hour (screen #3) and the hour with the lowest rate (screen #4) and the hour with the highest rate (screen #5), the number in the upper right corner is the current rate (screen #3), lowest (screen #4) and highest rate (screen #5) for tomorrow (unit: ct/kWh). The thresholds for the color of the graphs and the rates can be configured in the Lua script.
11. Setup a rule or use the Exec Binding to run the shell script every hour on the hour.

I am open to suggestions on how to integrate this framework into openHAB.

Edit #1:
If you are going to use the shell script via executeCommandLine: Make sure you prepend the correct path to create_awtrix_tibber_app.lua in the shell script - it took me some time to debug my openHAB rule.

Edit #2:
Min/max rates for tomorrow added, shell script and Lua script updated.

2 Likes