ROVA in Home Assistant: de afvalkalender op je dashboard, met een eigen kaart

·

Illustratie van een voortuin met vier kliko's met groene, blauwe, rode en grijze deksel, een vijfde bak aan de stoeprand, en een tablet aan de muur met een dashboard, verbonden met kaartjes voor kalender, blad, recycling en een bel

Wanneer moet de groene bak weer aan de weg? Bij ons was het antwoord altijd: even de ROVA-app openen. Met de ROVA-integratie in Home Assistant staat het nu gewoon op het dashboard, en hoeft mijn vrouw de app er niet meer bij te pakken. In dit artikel: de installatie, een privacytip die je niet wilt missen, en de kaart die ik ervoor gebruik.

Wat de integratie doet

ROVA haalt het afval op in het midden en oosten van Nederland. De ROVA-integratie zit standaard in Home Assistant: je hoeft niets te downloaden en ook niets via HACS te installeren. Hij gebruikt een onofficiële API van ROVA, dus je hebt geen account nodig; alleen je adres. De officiële uitleg staat op de integratiepagina van Home Assistant.

Je krijgt vier sensoren, één per bak:

  • GFT
  • Papier
  • Plastic (PMD)
  • Restafval

Elke sensor is een datumsensor: de toestand is de eerstvolgende ophaaldatum. Is er voor een bak nog geen datum bekend, dan staat hij op unknown.

Installeren

De snelste weg: klik op ROVA toevoegen aan Home Assistant. Die link opent het installatiescherm in je eigen Home Assistant. De eerste keer vraagt hij het adres van je installatie, bijvoorbeeld http://homeassistant.local:8123. Liever met de hand? Dan zo:

  1. Ga naar Instellingen → Apparaten & diensten.
  2. Klik rechtsonder op Integratie toevoegen en kies ROVA.
  3. Vul je postcode, huisnummer en eventueel een toevoeging in.
  4. Klaar. De vier sensoren verschijnen onder een dienst met je adres als naam.

Krijg je de melding dat ROVA op dit adres niet ophaalt, dan valt je adres buiten het gebied van ROVA. Verhuis je, of stopt ROVA met ophalen op je adres, dan meldt Home Assistant dat bij de reparaties en kun je de integratie verwijderen.

Privacytip: hernoem je entiteiten

De integratie gebruikt je adres als naam. Daardoor krijgen de sensoren entiteit-ID’s met je postcode en huisnummer erin, zoals sensor.1234ab_1_gft. Dat merk je pas als je een screenshot, een stuk YAML of een vraag op een forum deelt: dan deel je ongemerkt je adres.

Hernoem ze daarom meteen. Ga naar Instellingen → Apparaten & diensten → Entiteiten, open de sensor, klik op het tandwiel en verander de entiteit-ID, bijvoorbeeld in sensor.kliko_gft. Doe hetzelfde met de naam van de dienst. Dat is ook handig als je je configuratie door Claude laat nakijken.

Het ontwerp: de eerstvolgende kliko in één oogopslag

Donkere Home Assistant-kaart met bovenaan in een groen omlijnd vak de eerstvolgende kliko, GFT op maandag 12 oktober, overmorgen, en daaronder vier tegels voor GFT, papier, PMD en restafval met hun ophaaldatum
De kaart op mijn dashboard: bovenaan de eerstvolgende kliko, eronder alle bakken op volgorde. Restafval heeft hier nog geen datum en staat daarom vaag.

De kaart beantwoordt één vraag: welke bak moet er als eerste aan de weg, en wanneer?

  • Bovenaan staat de eerstvolgende bak, in zijn eigen kleur, met de dag en “Vandaag!”, “Morgen”, “Overmorgen” of “Over 5 dagen”. Is het vandaag of morgen, dan kleurt dat woord oranje.
  • Eronder staan alle bakken als tegels, op volgorde van ophaaldatum.
  • Een bak zonder datum staat achteraan, half doorzichtig en met een streepje.
  • Foutwaarden zoals unknown en unavailable, en datums in het verleden, slaat de kaart over. Er komt dus nooit “Invalid Date” op je scherm.

Wat je nodig hebt

De kaart gebruikt custom:button-card. Die installeer je via HACS: zoek op “button-card”, installeer hem en ververs je browser. Uitleg en alle opties staan op de GitHub-pagina van button-card. Daarna voeg je op je dashboard een kaart toe via Kaart toevoegen → Handmatig en plak je de code hieronder. Werk je met dashboards in YAML-modus, dan plak je hem in je dashboardbestand.

De code

type: custom:button-card
show_icon: false
show_name: false
show_label: false
custom_fields:
  inhoud: |
    [[[
      // Vervang de vier entiteit-ID's door die van jou.
      const bakken = [
        { id: 'sensor.1234ab_1_gft',     naam: 'GFT',    kleur: '#00c853', rgb: '0,200,83',    icoon: 'mdi:leaf' },
        { id: 'sensor.1234ab_1_papier',  naam: 'Papier', kleur: '#2979ff', rgb: '41,121,255',  icoon: 'mdi:file-outline' },
        { id: 'sensor.1234ab_1_plastic', naam: 'PMD',    kleur: '#ff4081', rgb: '255,64,129',  icoon: 'mdi:recycle' },
        { id: 'sensor.1234ab_1_rest',    naam: 'Rest',   kleur: '#b0bec5', rgb: '176,190,197', icoon: 'mdi:trash-can-outline' },
      ];
      const vandaag = new Date(); vandaag.setHours(0,0,0,0);
      const dagen = ['zo','ma','di','wo','do','vr','za'];
      const dagenLang = ['Zondag','Maandag','Dinsdag','Woensdag','Donderdag','Vrijdag','Zaterdag'];
      const maanden = ['jan','feb','mrt','apr','mei','jun','jul','aug','sep','okt','nov','dec'];
      const lijst = [];
      for (const b of bakken) {
        const s = states[b.id];
        let datum = null;
        if (s && !['unknown','unavailable',''].includes(s.state)) {
          const d = new Date(s.state);
          if (!isNaN(d)) { d.setHours(0,0,0,0); if (d >= vandaag) datum = d; }
        }
        const dagenTot = datum ? Math.round((datum - vandaag) / 86400000) : null;
        lijst.push({ ...b, datum, dagenTot });
      }
      const bekend = lijst.filter(b => b.datum).sort((a, b) => a.datum - b.datum);
      const eerst = bekend[0];
      const wanneer = (n) => n === null ? 'onbekend' : n === 0 ? 'Vandaag!' : n === 1 ? 'Morgen'
                           : n === 2 ? 'Overmorgen' : `Over ${n} dagen`;
      const kort = (b) => b.datum ? `${dagen[b.datum.getDay()]} ${b.datum.getDate()} ${maanden[b.datum.getMonth()]}` : '—';

      // kop: eerstvolgende bak
      let kop;
      if (!eerst) {
        kop = `<div style="padding:14px 16px;color:rgba(255,255,255,0.55);font-size:13px;">Geen ophaaldatum bekend</div>`;
      } else {
        const accent = eerst.dagenTot <= 1 ? '#ff9800' : 'rgba(255,255,255,0.6)';
        kop = `
          <div style="display:flex;align-items:center;gap:16px;padding:14px 16px 12px;
                      border-radius:12px;background:rgba(${eerst.rgb},0.15);border:2px solid ${eerst.kleur};">
            <div style="width:44px;height:44px;border-radius:12px;flex:none;display:flex;align-items:center;justify-content:center;
                        background:rgba(${eerst.rgb},0.25);">
              <ha-icon icon="${eerst.icoon}" style="--mdc-icon-size:28px;color:${eerst.kleur};"></ha-icon>
            </div>
            <div style="text-align:left;line-height:1.2;min-width:0;">
              <div style="font-size:11px;letter-spacing:.12em;color:rgba(255,255,255,0.55);">EERSTVOLGENDE KLIKO</div>
              <div style="font-size:22px;font-weight:600;color:${eerst.kleur};margin:2px 0;">${eerst.naam}</div>
              <div style="font-size:13px;color:#fff;">${dagenLang[eerst.datum.getDay()]} ${eerst.datum.getDate()} ${maanden[eerst.datum.getMonth()]}
                <span style="font-weight:600;color:${accent};margin-left:6px;">${wanneer(eerst.dagenTot)}</span></div>
            </div>
          </div>`;
      }

      // tegels: alle bakken op volgorde
      const tegels = bekend.concat(lijst.filter(b => !b.datum)).map(b => `
        <div style="flex:1;min-width:0;padding:10px 6px;border-radius:12px;text-align:center;
                    background:rgba(255,255,255,0.05);border:1px solid rgba(255,255,255,0.1);
                    opacity:${b.datum ? 1 : 0.45};">
          <ha-icon icon="${b.icoon}" style="--mdc-icon-size:22px;color:${b.kleur};"></ha-icon>
          <div style="font-size:13px;font-weight:600;color:#fff;margin-top:4px;">${b.naam}</div>
          <div style="font-size:11px;color:rgba(255,255,255,0.6);">${kort(b)}</div>
        </div>`).join('');

      return `<div style="display:flex;flex-direction:column;gap:10px;padding:10px;">
                ${kop}
                <div style="display:flex;gap:8px;">${tegels}</div>
              </div>`;
    ]]]
styles:
  card:
    - background: rgba(255,255,255,0.05)
    - border-radius: 12px
    - border: 1px solid rgba(255,255,255,0.1)
    - padding: 0
    - box-shadow: none
  grid:
    - grid-template-areas: '"inhoud"'
  custom_fields:
    inhoud:
      - justify-self: stretch
tap_action:
  action: none

Aanpassen

  • Je eigen sensoren: vervang de vier entiteit-ID’s bovenaan in bakken.
  • Minder bakken: haal de regel weg van een bak die je niet hebt.
  • Kleuren en iconen: elke bak heeft een kleur, dezelfde kleur als rgb-waarde voor de doorschijnende achtergrond, en een icoon uit Material Design Icons.
  • Lichte achtergrond: de kaart is gemaakt voor een donker thema. Gebruik je een licht thema, vervang dan de witte teksten (#fff en rgba(255,255,255,…)) door donkere.

Extra: een melding de avond ervoor

Het dashboard helpt alleen als je erlangs loopt. Deze automatisering stuurt om 19:00 een melding naar je telefoon als er de volgende dag een bak wordt opgehaald. Zijn het er twee, dan staan ze allebei in de melding.

automation:
  - id: kliko_melding_avond_ervoor
    alias: "Kliko - melding de avond ervoor"
    triggers:
      - trigger: time
        at: "19:00:00"
    variables:
      morgen: >
        {% set bakken = {
             'GFT': 'sensor.1234ab_1_gft',
             'Papier': 'sensor.1234ab_1_papier',
             'PMD': 'sensor.1234ab_1_plastic',
             'Restafval': 'sensor.1234ab_1_rest'} %}
        {% set ns = namespace(lijst=[]) %}
        {% set datum_morgen = (now() + timedelta(days=1)).date() %}
        {% for naam, entiteit in bakken.items() %}
          {% set d = as_datetime(states(entiteit), none) %}
          {% if d is not none and as_local(d).date() == datum_morgen %}
            {% set ns.lijst = ns.lijst + [naam] %}
          {% endif %}
        {% endfor %}
        {{ ns.lijst | join(' en ') }}
    conditions:
      - condition: template
        value_template: "{{ morgen | length > 0 }}"
    actions:
      - action: notify.mobile_app_telefoon
        data:
          title: "Kliko aan de weg"
          message: "Morgen ophalen: {{ morgen }}. Zet de bak vanavond buiten."

Drie dingen om op te letten:

  • Gebruik as_local. ROVA geeft de ophaaldag door als een tijdstip in UTC. Maandag 00:00 bij ons is dan zondag 22:00 UTC. Vergelijk je zonder as_local, dan denkt de automatisering dat de bak vandaag al was, en krijg je nooit een melding.
  • Een sensor zonder datum (unknown of unavailable) slaat de template over, dankzij de standaardwaarde none in as_datetime.
  • Je eigen telefoon: vervang notify.mobile_app_telefoon door je eigen meldingsdienst. Wil je dat meerdere mensen de melding krijgen, maak dan een notify-groep.

De template is los getest met Jinja2 in Python: een bak morgen, twee bakken op dezelfde dag, een bak vandaag, een sensor zonder datum en de overgang naar wintertijd. Hoe je dat zelf doet, staat in Zo gebruik ik Claude voor Home Assistant.

Waarom op het dashboard

Het is een kleine automatisering, maar wel een die het hele huis gebruikt. Wie langs het dashboard loopt, ziet meteen welke bak eraan komt, zonder app en zonder in te loggen. En voor wie er niet langsloopt, is er de melding van de avond ervoor.

Links op een rij


Dit werkt in mijn situatie: ROVA als afvalinzamelaar en custom:button-card op een donker dashboard. De integratie gebruikt een onofficiële API en kan stoppen met werken als ROVA iets verandert. Controleer het tegen je eigen documentatie. Stand: oktober 2026.


Lees ook


Reacties

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *