Reading Afvalwijzer in JSscripting or Jython

Update 9-1-2022: added javascript version.

To retrieve the garbage pickup dates, the following rule can be used. This rule updates a number of items:

Group gAfvalData "Afval ophaaldata" <calendar> (gTuin)
DateTime AfvalData_gft             "Groente, Fruit en Tuin [%1$ta %1$td/%1$tm]"  <garbage_gft>        (gAfvalData)
DateTime AfvalData_papier          "Papier [%1$ta %1$td/%1$tm]"                  <garbage_papier>     (gAfvalData)
DateTime AfvalData_restafval       "Restafval [%1$ta %1$td/%1$tm]"               <garbage_restafval>  (gAfvalData)
DateTime AfvalData_pmd             "Plastic [%1$ta %1$td/%1$tm]"                 <garbage_pmd>        (gAfvalData) 
DateTime AfvalData_kca             "Chemisch [%1$ta %1$td/%1$tm]"                <garbage_kca>        (gAfvalData) 
DateTime AfvalData_volgendeDatum   "Volgende afvalophaaldag [%1$ta %1$td/%1$tm]" <clock>              (gAfvalData)
String   AfvalData_volgendeType    "Volgende afvalophaalsoort"                                        (gAfvalData)

To use the rule you have to install the javascript scripting add-in. Furthermore, you have to edit the CONFIG object according to your personal situation. When you want to use mijnafvalwijzer.nl, you have to provide an API key. I cannot provide that, so you have to use Google to find it. I’m planning to adapt the script to make a scraper version for mijnafvalwijzer.nl when I find more time. Then the API key will no longer be needed.

/**
 * 
 * Dit script bevat een JSRule die afval ophaaldata kan lezen van diverse afvalkalenders. De afvalkalenders worden
 * twee maal per dag uitgelazen. 
 * 
 * Creëer de volgende Items om met de afvalkalender te kunnen werken:
 * 
 *      Group gAfvalData "Afval ophaaldata" <calendar> (gTuin)
 *      DateTime AfvalData_gft             "Groente, Fruit en Tuin [%1$ta %1$td/%1$tm]"  <garbage_gft>        (gAfvalData)
 *      DateTime AfvalData_papier          "Papier [%1$ta %1$td/%1$tm]"                  <garbage_papier>     (gAfvalData)
 *      DateTime AfvalData_restafval       "Restafval [%1$ta %1$td/%1$tm]"               <garbage_restafval>  (gAfvalData)
 *      DateTime AfvalData_pmd             "Plastic [%1$ta %1$td/%1$tm]"                 <garbage_pmd>        (gAfvalData) 
 *      DateTime AfvalData_kca             "Chemisch [%1$ta %1$td/%1$tm]"                <garbage_kca>        (gAfvalData) 
 *      DateTime AfvalData_volgendeDatum   "Volgende afvalophaaldag [%1$ta %1$td/%1$tm]" <clock>              (gAfvalData)
 *      String   AfvalData_volgendeType    "Volgende afvalophaalsoort"                                        (gAfvalData)
 * 
 *  En pas daarna de onderstaande configuratie aan. 
 *  Voor MijnAfvalwijzer zijn de volgende gegevens nodig:
 *      postcode, huisnummer, toevoeging en apikey
 *  Voor Afvalkalender zijn de volgende gegevens nodig:
 *      postcode, huisnummer, afvalKalenderUri
 * 
 * Kies voor de Afvalkalender eventueel een afvalkalender uit de onderstaande lijst
 * 
 * Cyclus NV: https://afvalkalender.cyclusnv.nl
 * HVC: https://apps.hvcgroep.nl
 * Dar: https://afvalkalender.dar.nl
 * Afvalvrij / Circulus-Berkel: https://afvalkalender.circulus-berkel.nl
 * Meerlanden: https://afvalkalender.meerlanden.nl
 * Cure: https://afvalkalender.cure-afvalbeheer.nl
 * Avalex: https://www.avalex.nl
 * RMN: https://inzamelschema.rmn.nl
 * Venray: https://afvalkalender.venray.nl
 * Den Haag: https://huisvuilkalender.denhaag.nl
 * Berkelland: https://afvalkalender.gemeenteberkelland.nl
 * Alphen aan den Rijn: https://afvalkalender.alphenaandenrijn.nl
 * Waalre: https://afvalkalender.waalre.nl
 * ZRD: https://afvalkalender.zrd.nl
 * Spaarnelanden: https://afvalwijzer.spaarnelanden.nl
 * Montfoort: https://afvalkalender.montfoort.nl
 * GAD: https://inzamelkalender.gad.nl
 * Cranendonck: https://afvalkalender.cranendonck.nl
 * 
 * Deze lijst is overgenomen uit: https://www.gadget-freakz.com/domoticz-dzvents-getgarbagedates-script/
 * 
 */

const logger = log('afvalkalender');

// const MijnAfvalWijzerConfig = {
//     huisnummer: "41",
//     toevoeging: '',
//     postcode: "3825AL",
//     apikey: "<voer-hier-de-apikey-in>"
// };

const AfvalkalenderConfig = {  
    huisnummer: "17",
    postcode: "5581BG",
    afvalkalenderUri: 'https://afvalkalender.waalre.nl'
};

//const CONFIG = MijnAfvalWijzerConfig;
const CONFIG = AfvalkalenderConfig;
//CONFIG.itemPrefix = "MijnEigenAfvalItem";

rules.JSRule({
    name: "Afval ophaaldata updaten",
    description: "Haal gegevens op uit de afvalwijzer en/of afvalkalender over ophalen van afval",
    triggers: [
        triggers.GenericCronTrigger('0 30 7 * * ?'),
        triggers.GenericCronTrigger('0 30 19 * * ?')
    ],
    execute: data => {
        const { postcode, huisnummer, toevoeging } = CONFIG;
        itemPrefix = CONFIG.itemPrefix ?? "AfvalData";

        logger.debug(`Afval ophaaldata ophalen voor ${postcode}-${huisnummer}${toevoeging}`);
        if ('afvalkalenderUri' in CONFIG && CONFIG.afvalkalenderUri)
            pickupdates = getPickupdatesAfvalkalender(postcode, huisnummer, CONFIG.afvalkalenderUri);
        else
            pickupdates = getPickupdatesMijnafvalwijzer(postcode, huisnummer, toevoeging, CONFIG.apikey);
        
            if (!pickupdates || pickupdates.length == 0) {
            logger.warn(`Kon ophaaldata niet downloaden voor ${postcode}-${huisnummer}${toevoeging}`);
            return;
        }


        processPickupdates(pickupdates, 'papier',    'oud papier',                              `${itemPrefix}_papier`);
        processPickupdates(pickupdates, 'gft',       'groente-, fruit- en tuinafval',           `${itemPrefix}_gft`);
        processPickupdates(pickupdates, 'restafval', 'restafval',                               `${itemPrefix}_restafval`);
        processPickupdates(pickupdates, 'pmd',       'plastic- en metaalafval en drinkkartons', `${itemPrefix}_pmd`);
        processPickupdates(pickupdates, 'kca',       'klein chemisch afval',                    `${itemPrefix}_kca`);
        
        setNextDateAndType(pickupdates, `${itemPrefix}_volgendeDatum`, `${itemPrefix}_volgendeType`);
    }
});

class pickupDate {
    constructor(date, type) {
        this.date = date;
        this.type = type;
    }
}

function processPickupdates(dates, afvalType, afvalTypeDescription, item, now = undefined) {
    if (!pickupdates || pickupdates.length == 0)
        return;
    const afvalTypeDates = dates.filter(d => d.type == afvalType).map(d => d.date);
    if (afvalTypeDates.length == 0)
        return;
    if (now == undefined)
        now = time.ZonedDateTime.now();
    logger.debug(`Ophaaldata verwerken voor ${afvalType}: ${afvalTypeDescription} en Item ${item} on ${now}`);
    if (items.getItem(item, true) == null) {
        logger.warn(`Kan ophaaldata niet verwerken want Item ${item} bestaat niet`);
        return;
    }
    const today = now.toLocalDate();
    const tomorrow = today.plusDays(1);
    let pickupDate = afvalTypeDates.find(d => !d.isBefore(today));
    if (pickupDate == undefined)
        pickupDate = afvalTypeDates.at(-1);
    
    items.getItem(item).postUpdate(pickupDate.format(time.DateTimeFormatter.ISO_LOCAL_DATE));
    const currentTime = now.toLocalTime();
    if (today.isEqual(pickupDate) && currentTime.hour() <= 12) {
        message = `Vandaag wordt het ${afvalTypeDescription} opgehaald.`;
        logger.info(message);
        actions.NotificationAction.sendBroadcastNotification(message);
    }
    else if (tomorrow.isEqual(pickupDate) && currentTime.hour() > 12) {
        message = `Morgen wordt het ${afvalTypeDescription} opgehaald.`;
        logger.info(message);
        actions.NotificationAction.sendBroadcastNotification(message);
    }
}

function setNextDateAndType(dates, nextDateItem, nextTypeItem, now=undefined)
{
    if (items.getItem(nextDateItem, true) == null) {
        logger.warn(`Kan volgende ophaaldatum niet verwerken want Item ${nextDateItem} bestaat niet`);
        return;
    }
    if (items.getItem(nextTypeItem, true) == null) {
        logger.warn(`Kan volgende ophaaldatum niet verwerken want Item ${nextTypeItem} bestaat niet`);
        return;
    }

    now = now ?? time.ZonedDateTime.now();
    const today = now.toLocalDate();
    const sortedDates = dates.sort((d1, d2) => d1.date.compareTo(d2.date));
    let nextDate = sortedDates.find(d => !d.date.isBefore(today));
    if (nextDate == undefined)
        nextDate = sortedDates.at(-1);
    items.getItem(nextDateItem).postUpdate(nextDate.date.format(time.DateTimeFormatter.ISO_LOCAL_DATE));
    items.getItem(nextTypeItem).postUpdate(nextDate.type);    
}

function getJSONResponse(uri)
{
    const result = actions.HTTP.sendHttpGetRequest(uri);
    if (!result)
    {
        logger.warn(`Geen respons gekregen van ${uri}`);
        return undefined;
    }
    try {
        return JSON.parse(result);
    }
    catch (error)
    {
        logger.warn(`Kon respons van ${uri} niet naar JSON omzetten: ${error}`);
        return undefined;
    }
}

/** 
 * Gets pickup dates from different garbage collectors from mijnafvalwijzer.
 */
function getPickupdatesMijnafvalwijzer(postcode, huisnummer, toevoeging, apikey) {
    const dateFormatter = time.DateTimeFormatter.ofPattern('yyyy-MM-dd');
    const today = time.ZonedDateTime.now().format(dateFormatter);
    const uri = `https://api.mijnafvalwijzer.nl/webservices/appsinput/?method=postcodecheck&street=&postcode=${postcode}&huisnummer=${huisnummer}&toevoeging=${toevoeging}&apikey=${apikey}&app_name=afvalwijzer&platform=phone&mobiletype=android&afvaldata=${today}&version=58&langs=nl`;
    logger.debug(`Afvalwijzer URI: ${uri}`); 
    try {
        const response = getJSONResponse(uri);
        const ophaaldagen = response.ophaaldagen;
        if (ophaaldagen.response !== 'OK')
        {
            logger.warn(`Ongeldige respons bij het downloaden van ophaaldata van ${uri}: ${pickupdates.error}`);
            return undefined;
        }
        pickupdates = [];
        return ophaaldagen.data.map(d => new pickupDate(time.LocalDate.parse(d.date, dateFormatter), d.type));
    }
    catch (error)
    {
        logger.warn(`Kon ophaaldata niet destileren uit respons van ${uri}: ${error}`);
        return undefined;
    }    
}

/**
 * Gets pickup dates from different garbage collectors from afvalkalender
 */

function getPickupdatesAfvalkalender(postcode, huisnummer, afvalkalenderUri) {
    const uriAddress=`${afvalkalenderUri}/rest/adressen/${postcode}-${huisnummer}`;
    const responseAddress = getJSONResponse(uriAddress);
    if (!responseAddress) {
        logger.warn(`Kon adresgegevens niet downloaden van ${uriAddress}`);
        return undefined;
    }
    const bagId = responseAddress[0].bagId;
    const uriPickupdates=`${afvalkalenderUri}/rest/adressen/${bagId}/afvalstromen`;
    const responsePickupdates = getJSONResponse(uriPickupdates);
    if (!responsePickupdates)
    {
        logger.warn(`Kon ophaaldata niet downloaden van ${uriPickupdates}`);
        return undefined;
    }
    pickupdates = [];
    return responsePickupdates
        .filter(d => d.icon && d.ophaaldatum)
        .map(d =>  new pickupDate(time.LocalDate.parse(d.ophaaldatum,time.DateTimeFormatter.ofPattern('yyyy-MM-dd')), d.icon === "rest" ? "restafval" : d.icon));
}

The Python/Jython version of this Rule is also available. For this you have to install the Jython addon and the Jython helper libraries. I don’t use Jython anymore, so this version will not receive updates.

'''
This script contains a rule that consults afvalkalenders 
for garbage pickup times twice a day and update Items 
with the dates. The script can create the Items needed, if they do not exist.

Please configure this script by filling in your address data in afvalkalender_configuration below. 
Please select the base URI for your garbage collector provider from the list below

MijnAfvalWijzer: https://json.mijnafvalwijzer.nl
Cyclus NV: https://afvalkalender.cyclusnv.nl
HVC: https://apps.hvcgroep.nl
Dar: https://afvalkalender.dar.nl
Afvalvrij / Circulus-Berkel: https://afvalkalender.circulus-berkel.nl
Meerlanden: https://afvalkalender.meerlanden.nl
Cure: https://afvalkalender.cure-afvalbeheer.nl
Avalex: https://www.avalex.nl
RMN: https://inzamelschema.rmn.nl
Venray: https://afvalkalender.venray.nl
Den Haag: https://huisvuilkalender.denhaag.nl
Berkelland: https://afvalkalender.gemeenteberkelland.nl
Alphen aan den Rijn: https://afvalkalender.alphenaandenrijn.nl
Waalre: https://afvalkalender.waalre.nl
ZRD: https://afvalkalender.zrd.nl
Spaarnelanden: https://afvalwijzer.spaarnelanden.nl
Montfoort: https://afvalkalender.montfoort.nl
GAD: https://inzamelkalender.gad.nl
Cranendonck: https://afvalkalender.cranendonck.nl

List taken from: https://www.gadget-freakz.com/domoticz-dzvents-getgarbagedates-script/
'''
'''
This script contains a rule that consults afvalkalenders 
for garbage pickup times twice a day and update Items 
with the dates. The script can create the Items needed, if they do not exist.

Please configure this script by filling in your address data in afvalkalender_configuration below. 
Please select the base URI for your garbage collector provider from the list below

MijnAfvalWijzer: https://json.mijnafvalwijzer.nl
Cyclus NV: https://afvalkalender.cyclusnv.nl
HVC: https://apps.hvcgroep.nl
Dar: https://afvalkalender.dar.nl
Afvalvrij / Circulus-Berkel: https://afvalkalender.circulus-berkel.nl
Meerlanden: https://afvalkalender.meerlanden.nl
Cure: https://afvalkalender.cure-afvalbeheer.nl
Avalex: https://www.avalex.nl
RMN: https://inzamelschema.rmn.nl
Venray: https://afvalkalender.venray.nl
Den Haag: https://huisvuilkalender.denhaag.nl
Berkelland: https://afvalkalender.gemeenteberkelland.nl
Alphen aan den Rijn: https://afvalkalender.alphenaandenrijn.nl
Waalre: https://afvalkalender.waalre.nl
ZRD: https://afvalkalender.zrd.nl
Spaarnelanden: https://afvalwijzer.spaarnelanden.nl
Montfoort: https://afvalkalender.montfoort.nl
GAD: https://inzamelkalender.gad.nl
Cranendonck: https://afvalkalender.cranendonck.nl

List taken from: https://www.gadget-freakz.com/domoticz-dzvents-getgarbagedates-script/
'''
import json
from datetime import date, datetime, timedelta

from core.rules import rule
from core.triggers import when
from core.items import add_item
from core.actions import HTTP
from core.utils import postUpdate as post_update
from personal.etxean_utils import push_notification

afvalkalender_configuration = {
    # 'afvalkalender_url': 'https://json.mijnafvalwijzer.nl,
    'huisnummer': "5",
    'toevoeging': '',
    'postcode': "1234AB",
    'create_missing_items': False,
}

MIJNAFVALWIJZER_APIKEY = "<specify-key-here>"

@rule("TrashPickupdates", description="Haal de gegevens over ophalen van afval op uit de afvalwijzer", tags=["afvalkalender"])
@when("Time cron 0 30 7 * * ?")
@when("Time cron 0 30 19 * * ?")
def send_trash_pickup_notifications(event):
    
    # Get data from mijnafvalwijzer
    postcode = afvalkalender_configuration['postcode']
    huisnummer = afvalkalender_configuration['huisnummer']
    toevoeging = afvalkalender_configuration['toevoeging']

    send_trash_pickup_notifications.log.debug('Getting pickup dates for {}-{}{}'.format(postcode, huisnummer, toevoeging))
    if 'afvalkalender_uri' in afvalkalender_configuration and afvalkalender_configuration['afvalkalender_uri'] and afvalkalender_configuration['afvalkalender_uri'] != 'https://json.mijnafvalwijzer.nl':
        pickupdates = get_pickupdates_afvalkalender(postcode, huisnummer, afvalkalender_configuration['afvalkalender_uri'])
    else:
        pickupdates = get_pickupdates_mijnafvalwijzer(postcode, huisnummer, toevoeging)

    process_pickupdates(pickupdates, 'papier', 'oud papier', 'AfvalData_papier')
    process_pickupdates(pickupdates, 'gft', 'groente-, fruit- en tuinafval', 'AfvalData_gft')
    process_pickupdates(pickupdates, 'restafval', 'restafval', 'AfvalData_restafval')
    process_pickupdates(pickupdates, 'pmd', 'plastic- en metaalafval en drinkkartons', 'AfvalData_pmd')
    process_pickupdates(pickupdates, 'kca', 'klein chemisch afval', 'AfvalData_kca')

the_log = send_trash_pickup_notifications.log

def process_pickupdates(dates, type, description, item, now=None):
    log = the_log
    if not dates or len(dates) == 0:
        return
    pickup_dates = [x['date'] for x in dates if x['type'] == type]
    if len(pickup_dates) == 0:
        return
    if now is None:
        now = datetime.now()
    log.debug('Processing pickup dates for {}: {} and item {} on {}'.format(type, description, item, now))
    if ir.getItems(item) == []:
        if afvalkalender_configuration['create_missing_items']:
            add_item(item, item_type="DateTime", label="{} [%%1$td-%1$tm]".format(description), category="Calendar", groups=[], tags=[])
        else:
            return
    today = str(now.date())
    tomorrow = str(now.date() + timedelta(days=1))
    next_date = next((d for d in pickup_dates if d >= today), pickup_dates[-1])
    next_date_list = next_date.split("-")
    pickup_date = DateTimeType().zonedDateTime.withYear(int(next_date_list[0])).withMonth(int(next_date_list[1])).withDayOfMonth(int(next_date_list[2])).withHour(0).withMinute(0).withSecond(0).withNano(0)
    post_update(item, str(pickup_date))
    current_time = now.time()
    if today == next_date and current_time.hour <= 12:
        message = "Vandaag wordt het {} opgehaald.".format(description)
        log.info(message)
        push_notification(message)
    if tomorrow == next_date and current_time.hour > 12:
        message = "Morgen wordt het {} opgehaald.".format(description)
        log.info(message)
        push_notification(message)


def get_json_response(uri):
    log = the_log
    the_page = HTTP.sendHttpGetRequest(uri)
    if not the_page:
        log.warn("Could not get response from {}".format(uri))
        return None
    try:
        return json.loads(the_page.decode('utf-8'))
    except Exception as ex:
        log.warn('Could not interpret json response from {}: {}'.format(uri, ex))
        return None


def get_pickupdates_mijnafvalwijzer(postcode, huisnummer, toevoeging=''):
    '''Gets pickup dates from different garbage collectors from mijnafvalwijzer.'''
    log = the_log
    today = date.today().strftime("%d-%m-%Y")
    uri = 'https://api.mijnafvalwijzer.nl/webservices/appsinput/?method=postcodecheck&street=&postcode={0}&huisnummer={1}&toevoeging={2}&apikey={3}&app_name=afvalwijzer&platform=phone&mobiletype=android&afvaldata={4}&version=58&langs=nl'.format(postcode, huisnummer, toevoeging, MIJNAFVALWIJZER_APIKEY,today)
    log.info('Afvalkalender uri: {}'.format(uri))
    pickupdates = {}
    try:
        response = get_json_response(uri)
        pickupdates = response['ophaaldagen']
        if pickupdates[u'response'] != u'OK':
            log.warn('Invalid response while getting garbage pickup dates from {}: {}'.format(uri, pickupdates['error']))
            return None
        return pickupdates['data']
    except Exception as ex:
        log.warn('Could not interpret garbage pickup dates from {}: {}'.format(uri, ex))
        return None


def get_pickupdates_afvalkalender(postcode, huisnummer, afvalkalender_uri):
    '''Gets pickup dates from different garbage collectors.'''
    log = the_log
    uri_address='{0}/rest/adressen/{1}-{2}'.format(afvalkalender_uri, postcode, huisnummer)
    response_address = get_json_response(uri_address)
    if not response_address:
        log.warn('Could not get garbage pickup information from {}'.format(uri_address))
        return None
    uri_pickupdates='{0}/rest/adressen/{1}/afvalstromen'.format(afvalkalender_uri, response_address[0]['bagId'])
    response_pickupdates = get_json_response(uri_pickupdates)
    if not response_pickupdates:
        log.warn('Could not get garbage pickup dates from {}'.format(uri_pickupdates))
        return None
    pickupdates = []
    for pickupdate in response_pickupdates:
        newdate = {}
        if pickupdate['icon'] and pickupdate['ophaaldatum']:
            newdate['type'] = pickupdate['icon']
            newdate['date'] = pickupdate['ophaaldatum']
            if newdate['type'] == 'rest':
                newdate['type'] = 'restafval'
            pickupdates.append(newdate)
 
    return pickupdates
3 Likes

Nice! After running this through pylint :wink:, please consider contributing this to the community section of the helper library repo. I have a commit for what I’d built for the other topic, but I’ll hold off on pushing it.

Hmmm… maybe we should add both…

After some more Googling, I came up with a solution for the communities that do not use mijnafvalwijzer.nl. Just exchange the original get_pickupdates with the code below. Not thoroughly tested though.

postcode='5581BG'
huisnummer='17'

'''
Gets pickup dates from different garbage collectors. Please select the base URI for 
your garbage collector from the list below

Cyclus NV: https://afvalkalender.cyclusnv.nl
HVC: https://apps.hvcgroep.nl
Dar: https://afvalkalender.dar.nl
Afvalvrij / Circulus-Berkel: https://afvalkalender.circulus-berkel.nl
Meerlanden: https://afvalkalender.meerlanden.nl
Cure: https://afvalkalender.cure-afvalbeheer.nl
Avalex: https://www.avalex.nl
RMN: https://inzamelschema.rmn.nl
Venray: https://afvalkalender.venray.nl
Den Haag: https://huisvuilkalender.denhaag.nl
Berkelland: https://afvalkalender.gemeenteberkelland.nl
Alphen aan den Rijn: https://afvalkalender.alphenaandenrijn.nl
Waalre: https://afvalkalender.waalre.nl
ZRD: https://afvalkalender.zrd.nl
Spaarnelanden: https://afvalwijzer.spaarnelanden.nl
Montfoort: https://afvalkalender.montfoort.nl
GAD: https://inzamelkalender.gad.nl
Cranendonck: https://afvalkalender.cranendonck.nl

List taken from: https://www.gadget-freakz.com/domoticz-dzvents-getgarbagedates-script/
'''
def get_pickupdates_alternative(postcode, huisnummer, base_URI = 'https://afvalkalender.waalre.nl'):
    log = send_trash_pickup_notifications.log
    URI1='{0}/rest/adressen/{1}-{2}'.format(base_URI, postcode, huisnummer)
    the_page = HTTP.sendHttpGetRequest(URI1)
    if not the_page:
        log.warn("Could not get response from {}".format(URI1))
        return
    response1 = json.loads(the_page.decode('utf-8'))
    URI2='{0}/rest/adressen/{1}/afvalstromen'.format(base_URI, response1[0]['bagId'])
    the_page = HTTP.sendHttpGetRequest(URI2)
    if not the_page:
        log.warn("Could not get response from {}".format(URI2))
        return
    response2 = json.loads(the_page.decode('utf-8'))
    pickupdates = []
    for pd in response2:
        newdate = {}
        if pd['icon'] and pd['ophaaldatum']:
            newdate['type'] = pd['icon']
            newdate['date'] = pd['ophaaldatum']
            if newdate['type'] == 'rest':
                newdate['type'] = 'restafval'
            pickupdates.append(newdate)
    
    return pickupdates
1 Like

Nice :slight_smile:

Sorry, it DOES seem to work!

removed

The OP has been updated. I’ve refactored my code:

  • added the querying of afvalkalender as an alternative to mijnafvalwijzer;
  • improved formatting after using pylint.
2 Likes

Really would like to use something like this. But i can;t get it to work. If i read Please verify the url:' + jsonUrl + 'manually in your browser · Issue #72 · xirixiz/homeassistant-afvalwijzer · GitHub the json.mijnafvalwijzer.nl no longer works. they moved to an api with apikey.

As the topic at the HA forum suggests, this API key can be obtained with some “inspection” of the Android app. I managed to get it that way and supplied in the script. See the OP for the revised python script. You will have to find and enter the APi key yourself though.

Hi,

Thanks for the work you put in this rule.
Does it still works under OH3, when I try to save the rule when I fill out the configuration, the rule don’t get loaded because of this error:

==> /var/log/openhab/openhab.log <==

2021-12-15 16:38:53.828 [INFO ] [el.core.internal.ModelRepositoryImpl] - Loading model 'Afvalwijzer.rules'

2021-12-15 16:38:53.965 [WARN ] [el.core.internal.ModelRepositoryImpl] - Configuration model 'Afvalwijzer.rules' has errors, therefore ignoring it: [1,8]: no viable alternative at input 'from'

[2,22]: no viable alternative at input ','

[4,24]: no viable alternative at input 'rule'

[5,1]: no viable alternative at input 'from'

[21,6]: no viable alternative at input '('

Any thoughts? Looking forward to use this rule!

Hi Stéphane,

This is not a DSL rule that you can use in a .rules file. This is a Python script and for that you need to install the Jython add-on:

and some helper libraries that can be found here:
Jython helper libraries for OH3

Hi Ard,

Ah that’s explains it, sorry my mistake.
Will look into how to use the script with the add-on, not used anything like that before.

Thanks for the clarification :slight_smile:

Is this script still working? because i see i need an api key. That is not available anymore.
MIJNAFVALWIJZER_APIKEY = “”

It does work, but I cannot provide the api key. Did you by any chance google the terms “afvalwijzer webservices appsinput” and looked at the URL of one of the first results? :wink:

Thank you. but the api key will stop working soon? When I read the topic on home-assistant they use a scraper.

The API key is still valid. I’ll try to make a scraping version when I find more time or once the API key is no longer valid (whatever comes first).

I added a javascript version of the rule because I no longer use Jython as my main rule engine.

1 Like

@ArdKuijpers I think your zipcode and housenumber is still in the JavaScript version.

1 Like

Actually, it is not mine (although not far away). I found it the code mentioned here: Idea: use python to directly generate rules files and I have since then used it to test my code.

Hi Ard,

I tried the script. Everytime the script runs it creates a new rule “afval ophaaldata updaten”.

How do i remove these rules if i don’t want to use the script anymore?
I can’t delete them through the ui because “Some of the selected rules are not modifiable because they have been provisioned by files”

EDIT: Found out they get deleted on a system reboot.

Thanks @ArdKuijpers wrote in the xmas break a js version as well, but not as extensive as yours…
Do you have a hint on this error?
EDIT: Seems I can prevent the error by removing the 2 actions.NotificationAction.sendBroadcastNotification(message); lines. Guess they are not known as I don’t have cloud notifications enabled.

note I chose the application/javascript;version=ECMAScript-2021 version… is that the right one or I should use the other one.

10:44:09.704 [DEBUG] [enhab.automation.script.afvalkalender] - Afval ophaaldata ophalen voor 4700ZZ-25
10:44:09.867 [DEBUG] [enhab.automation.script.afvalkalender] - Afvalwijzer URI: https://api.mijnafvalwijzer.nl/webservices/appsinput/?method=postcodecheck&street=&postcode=4700ZZ&huisnummer=25&toevoeging=&apikey=5ef443e778f41c4f75c69459eea6e6ae0c2d92de729aa0fc61653815fbd6a8ca&app_name=afvalwijzer&platform=phone&mobiletype=android&afvaldata=2022-01-10&version=58&langs=nl
10:44:10.542 [DEBUG] [enhab.automation.script.afvalkalender] - Ophaaldata verwerken voor papier: oud papier en Item AfvalData_papier on 2022-01-10T10:44:10.531+01:00[SYSTEM]
10:44:10.556 [INFO ] [openhab.event.ItemStateChangedEvent  ] - Item 'AfvalData_papier' changed from NULL to 2022-01-10T00:00:00.000+0100
10:44:10.557 [DEBUG] [org.openhab.automation.script.items  ] - Posted update 2022-01-10 to AfvalData_papier
10:44:10.559 [INFO ] [enhab.automation.script.afvalkalender] - Vandaag wordt het oud papier opgehaald.
10:44:10.564 [ERROR] [org.openhab.automation.script.rules  ] - Failed to execute rule Afval-ophaaldata-updaten-0afa0a3c-3c53-46cb-aa47-dc7982bbd6ae: TypeError: undefined has no such function "sendBroadcastNotification": TypeError: undefined has no such function "sendBroadcastNotification"
        at processPickupdates (<eval>:129)
        at execute (<eval>:88)
        at doExecute (webpack://openhab/./node_modules/openhab/rules/rules.js?:110)
10:44:10.566 [WARN ] [re.automation.internal.RuleEngineImpl] - Fail to execute action: 1
org.graalvm.polyglot.PolyglotException: TypeError: undefined has no such function "sendBroadcastNotification"
        at <js>.processPickupdates(<eval>:129) ~[?:?]
        at <js>.execute(<eval>:88) ~[?:?]
        at <js>.doExecute(webpack://openhab/./node_modules/openhab/rules/rules.js?:110) ~[?:?]
        at org.graalvm.polyglot.Value.invokeMember(Value.java:934) ~[?:?]
        at com.oracle.truffle.host.adapters.SimpleRule$$Adapter.execute(Unknown Source) ~[?:?]
        at org.openhab.automation.jsscripting.internal.threading.ThreadsafeSimpleRuleDelegate.execute(ThreadsafeSimpleRuleDelegate.java:59) ~[?:?]
        at org.openhab.core.automation.module.script.rulesupport.shared.simple.SimpleRuleActionHandlerDelegate.execute(SimpleRuleActionHandlerDelegate.java:34) ~[?:?]
        at org.openhab.core.automation.module.script.rulesupport.internal.delegates.SimpleActionHandlerDelegate.execute(SimpleActionHandlerDelegate.java:59) ~[?:?]
        at org.openhab.core.automation.internal.RuleEngineImpl.executeActions(RuleEngineImpl.java:1180) ~[?:?]
        at org.openhab.core.automation.internal.RuleEngineImpl.runNow(RuleEngineImpl.java:1032) ~[?:?]
        at org.openhab.core.automation.internal.RuleEngineImpl.runNow(RuleEngineImpl.java:1048) ~[?:?]
        at org.openhab.core.automation.rest.internal.RuleResource.runNow(RuleResource.java:327) ~[?:?]
        at jdk.internal.reflect.GeneratedMethodAccessor158.invoke(Unknown Source) ~[?:?]
        at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
        at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
        at org.apache.cxf.service.invoker.AbstractInvoker.performInvocation(AbstractInvoker.java:179) ~[bundleFile:3.4.5]
        at org.apache.cxf.service.invoker.AbstractInvoker.invoke(AbstractInvoker.java:96) ~[bundleFile:3.4.5]
        at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:201) ~[bundleFile:3.4.5]
        at org.apache.cxf.jaxrs.JAXRSInvoker.invoke(JAXRSInvoker.java:104) ~[bundleFile:3.4.5]
        at org.apache.cxf.interceptor.ServiceInvokerInterceptor$1.run(ServiceInvokerInterceptor.java:59) ~[bundleFile:3.4.5]
        at org.apache.cxf.interceptor.ServiceInvokerInterceptor.handleMessage(ServiceInvokerInterceptor.java:96) ~[bundleFile:3.4.5]
        at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:308) ~[bundleFile:3.4.5]
        at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121) ~[bundleFile:3.4.5]
        at org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:265) ~[bundleFile:3.4.5]
        at org.apache.cxf.transport.servlet.ServletController.invokeDestination(ServletController.java:234) ~[bundleFile:3.4.5]
        at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:208) ~[bundleFile:3.4.5]
        at org.apache.cxf.transport.servlet.ServletController.invoke(ServletController.java:160) ~[bundleFile:3.4.5]
        at org.apache.cxf.transport.servlet.CXFNonSpringServlet.invoke(CXFNonSpringServlet.java:225) ~[bundleFile:3.4.5]
        at org.apache.cxf.transport.servlet.AbstractHTTPServlet.handleRequest(AbstractHTTPServlet.java:298) ~[bundleFile:3.4.5]
        at org.apache.cxf.transport.servlet.AbstractHTTPServlet.doPost(AbstractHTTPServlet.java:217) ~[bundleFile:3.4.5]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:707) ~[bundleFile:3.1.0]
        at org.apache.cxf.transport.servlet.AbstractHTTPServlet.service(AbstractHTTPServlet.java:273) ~[bundleFile:3.4.5]
        at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:799) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:550) ~[bundleFile:9.4.43.v20210629]
        at org.ops4j.pax.web.service.jetty.internal.HttpServiceServletHandler.doHandle(HttpServiceServletHandler.java:71) ~[bundleFile:?]
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:602) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:235) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:1624) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.handler.ScopedHandler.nextHandle(ScopedHandler.java:233) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1434) ~[bundleFile:9.4.43.v20210629]
        at org.ops4j.pax.web.service.jetty.internal.HttpServiceContext.doHandle(HttpServiceContext.java:294) ~[bundleFile:?]
        at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:188) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:501) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:1594) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.handler.ScopedHandler.nextScope(ScopedHandler.java:186) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1349) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141) ~[bundleFile:9.4.43.v20210629]
        at org.ops4j.pax.web.service.jetty.internal.JettyServerHandlerCollection.handle(JettyServerHandlerCollection.java:82) ~[bundleFile:?]
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:127) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.Server.handle(Server.java:516) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.HttpChannel.lambda$handle$1(HttpChannel.java:388) ~[bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.HttpChannel.dispatch(HttpChannel.java:633) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:380) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:277) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.runTask(EatWhatYouKill.java:338) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.doProduce(EatWhatYouKill.java:315) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.tryProduce(EatWhatYouKill.java:173) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.run(EatWhatYouKill.java:131) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.util.thread.ReservedThreadExecutor$ReservedThread.run(ReservedThreadExecutor.java:386) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883) [bundleFile:9.4.43.v20210629]
        at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034) [bundleFile:9.4.43.v20210629]
        at java.lang.Thread.run(Thread.java:829) [?:?]

Struggled with the same…
I found that restarting the rulesupport will work as well…

openhab> bundle:list | grep RuleSupport
160 x Active    x  80 x 3.2.0                 x openHAB Core :: Bundles :: Automation Script RuleSupport
openhab> bundle:restart 160

I chose this version (2021) and it works well. Well…a sort of.
A first i’ll get this message:
Failed to retrieve script script dependency listener from engine bindings. Script dependency tracking will be disabled.

Then a couple of seconds later the script executes without warnings.

The problem i encounter is that only items (after manually running the rule “afval ophaaldata updaten”):
AfvalData_volgendeDatum and
AfvalData_volgendeType

get updated. The other items don’t and keep their NULL value.

Thanks.