Toggling Adguard services from HA dashboard

I just wanted to share how I set up toggles for switching different internet services on and off directly from my mobile dashboard.

You’ll need:

  1. An Adguard Home instance (e.g. the Adguard Home add-on for HA)Note
  2. A custom rest_command to toggle the service
  3. An input_boolean helper for each of the apps you want to block/unblock
  4. An automation that uses the rest_command to set the state of the services/apps, based on the state of the input_boolean’s.
  5. Add input_booleans to the front-end.

Note: If using the HA add-on, you need to manually activate port 3000 in the Adguard Add-on config.


REST command:

I’ll start at step 2, since installing the add-on is already well-documented.
To add the rest_command, go to configuration.yaml and add this:

rest_command:
  adguard_services:
    url: "http://<adguard-ip>:3000/control/blocked_services/set"
    method: POST
    headers:
      Content-Type: 'application/json'
      Authorization: 'Basic dXNlcm5hbWU6cGFzc3dvcmQ='
    payload: '{{apps}}'

Change the IP address and add your Base64 encoded username:password (Example. Encode your own on https://www.base64encode.org).


Input booleans:

Next, create an input_boolean for each app you want to block/unblock.
The (friendly) name must be called the name of the service. I’ll add a list below with the (currently) available services in adguard.
Modify the entity_id to include “adguard_block_”. This will be used to filter the states in the automation later.




Automation:

Add this automation. Ensure that all your adguard input_booleans are added as triggers.

alias: Adguard toggle services
description: ""
trigger:
  - platform: state
    entity_id:
      - input_boolean.adguard_block_roblox
      - input_boolean.adguard_block_youtube
condition: []
action:
  - service: rest_command.adguard_services
    data:
      apps: >
        {{states.input_boolean |selectattr('entity_id', 'search',
        'adguard_block')
         |selectattr('state', 'search', 'on')
         |map(attribute='name') |list | tojson }}
mode: single



Front-end:

Add the input_booleans to your frontend. I’m using Mushroom cards:

square: false
type: grid
cards:
  - type: custom:mushroom-template-card
    entity: input_boolean.adguard_block_youtube
    layout: default
    name: Youtube
    secondary: YouTube
    icon: mdi:youtube
    tap_action:
      action: toggle
    icon_color: |
      {%if states(config.entity) == "on"%}
        grey
      {%else%}
        red   
      {%endif%}         
    badge_icon: |
      {%if states(config.entity) == "on"%}
        mdi:stop
      {%else%}
        mdi:web
      {%endif%}  
    badge_color: |
      {%if states(config.entity) == "on"%}
        deep-orange
      {%else%}
        green  
      {%endif%}
    card_mod:
      style: |
        ha-card {

          border-radius: 15px;
          margin-right: 2px;
          margin-left: 15px;
         # background-image: linear-gradient(to bottom ,  var(--primary-background-color), var(--primary-color) );
          --card-primary-font-size: 16px;
          --card-primary-font-weight: bold;            
          --card-secondary-font-size: 12px;  
          font-family: "Montserrat" !important;
        }      
        :host {
          --mush-icon-size: 45px;    
          
        }   
  - type: custom:mushroom-template-card
    entity: input_boolean.adguard_block_roblox
    layout: default
    name: Roblox
    secondary: Roblox
    icon: mdi:gamepad-square
    tap_action:
      action: toggle
    icon_color: |
      {%if states(config.entity) == "on"%}
        grey
      {%else%}
        green  
      {%endif%}         
    badge_icon: |
      {%if states(config.entity) == "on"%}
        mdi:stop
      {%else%}
        mdi:web
      {%endif%}  
    badge_color: |
      {%if states(config.entity) == "on"%}
        deep-orange
      {%else%}
        green  
      {%endif%}
    card_mod:
      style: |
        ha-card {

          border-radius: 15px;
          margin-right: 15px;
          margin-left: 2px;
         # background-image: linear-gradient(to bottom ,  var(--primary-background-color), var(--primary-color) );
          --card-primary-font-size: 16px;
          --card-primary-font-weight: bold;            
          --card-secondary-font-size: 12px;  
          font-family: "Montserrat" !important;
        }      
        :host {
          --mush-icon-size: 45px;    
          
        }   
columns: 2



Information:

Every time one of the input_booleans are toggled, the automation will check the state of all the input_booleans, and send an array of all apps that should be blocked to adguard (“On” means blocked). There is currently no feedback from adguard about which services are blocked, so if you go to adguards own interface and modify, this is not reflected on your dashboard. But if you always manage it from HA, there will be no issues :slight_smile:

List of supported services/apps (19th April 2024):

"4chan",
"500px",
"9gag",
"activision_blizzard",
"aliexpress",
"amazon",
"amazon_streaming",
"amino",
"apple_streaming",
"battle_net",
"betano",
"betfair",
"betway",
"bigo_live",
"bilibili",
"blaze",
"blizzard_entertainment",
"bluesky",
"box",
"canais_globo",
"claro",
"cloudflare",
"clubhouse",
"coolapk",
"crunchyroll",
"dailymotion",
"deezer",
"directvgo",
"discord",
"discoveryplus",
"disneyplus",
"douban",
"dropbox",
"ebay",
"electronic_arts",
"epic_games",
"espn",
"facebook",
"fifa",
"flickr",
"globoplay",
"gog",
"hbomax",
"hulu",
"icloud_private_relay",
"iheartradio",
"imgur",
"instagram",
"iqiyi",
"kakaotalk",
"kik",
"kook",
"lazada",
"leagueoflegends",
"line",
"linkedin",
"lionsgateplus",
"looke",
"mail_ru",
"mastodon",
"mercado_libre",
"minecraft",
"nebula",
"netflix",
"nintendo",
"nvidia",
"ok",
"olvid",
"onlyfans",
"origin",
"paramountplus",
"pinterest",
"playstation",
"plenty_of_fish",
"plex",
"pluto_tv",
"privacy",
"qq",
"rakuten_viki",
"reddit",
"riot_games",
"roblox",
"rockstar_games",
"samsung_tv_plus",
"shein",
"shopee",
"signal",
"skype",
"snapchat",
"soundcloud",
"spotify",
"steam",
"telegram",
"temu",
"tidal",
"tiktok",
"tinder",
"tumblr",
"twitch",
"twitter",
"ubisoft",
"valorant",
"viber",
"vimeo",
"vk",
"voot",
"wargaming",
"wechat",
"weibo",
"whatsapp",
"wizz",
"xboxlive",
"xiaohongshu",
"youtube",
"yy",
"zhihu"
5 Likes

Why do you need to turn it on and off?

Screentime control.

This is great. Thanks so much for making it so easy! Screentime control is my exact use case as well and also for YouTube and Roblox as it happens so buttons in HomeAssistant is perfect.

One question, do I need to add input_boolean helpers for every potential service that I want to keep blocked, even if I don’t necessarily want to manage it with HomeAssistant. I know the AdGuard api call doesn’t give blocked service status per your note, but I was surprised that pressing either the YouTube or Roblox button appears to unblock every other service if they aren’t named in the call. See below. The blocked status for both YouTube and Roblox will be reflected accurately in the event either of those are trigged, but doing so will reset any other service to unblocked. Thanks again!

I’m afraid so - I only needed to block YouTube and Roblox occasionally and not any other service, so I’ve not run into this problem. But afaik you need to define all services you need blocked in each rest call (or maybe I’ve overlooked something) :slight_smile:

Good to know, thanks!

Hello. I have the same concern as you after seeing this post. I have some “baseline” blocked services which I don’t wish to unblock when I toggle certain other services. So I got into more reading and I posted on reddit what I ended up with. I appreciate this post by cklit for pointing me towards the direction I needed. Looking forward to learning more.

2 Likes

Good job! The state feedback was the missing part :+1:

awesome update, thanks to the OP and for your additional contribution
A more advanced usecase… I have many services blocked and take advantage of schedules.
As a sensor can only have 255 characters, this implementation only works for a small number of blocked services… (fine for most people I’m sure!)
I had a quick look, and it doesn’t seem to be possible to save as an attribute (which can be longer) without resorting to a python script - I may need to do that.
The other point to note is that while it syncs the configuration status - it does not reflect if blocking is “paused”; some adguard api investigation required for that one!

@omeromano I read your post, and a quick question regarding this section from the sensor:

  value_template: >
    {% set clean_value = value.replace('\"', '"').replace('
', '').replace('\n', '') %}

… the 2nd replace is to replace a space , or something like \space, or something else?

BTW, /blocked_services/list and /blocked_services/set are depreciated. Per the document they want us to use get and update , it seems.

= = = = = = = =
And it is true that one cannot do more than 255 characters into the state property of any sensor. But!! yes, @230delphi, with attributes it can be longer… and we can use json_attributes.

The JSON output using /blocked_services/list is a bit different, so here are some tweaks to first save the blocked services into the attribute:

- platform: rest
  name: "Blocked Services per AdGuard Home"
  unique_id: "agh_blocked_services"
  authentication: basic
  username: !secret user
  password: !secret pwd
  resource: http://x.x.x.x/control/blocked_services/get
  method: GET
  headers:
    User-Agent: Homeassistant REST
    Content-Type: application/json
  value_template: '1'   # dummy value because we are saving the list into attribute instead - see next line
  json_attributes: 
    - ids
  scan_interval: 60 
  verify_ssl: false

We still have to parse the names of the services under the “ids” value, yes?

@k8gg thanks for that pointer! I missed json_attributes building on your work I’ve fleshed out this package:

  1. allow standard blocked services, with optional control of services: simply create input_booleans
  2. allow/mainain scheduled periods where services are unblocked. (enhancement might allow update)
  3. sensor.agh_blocked_services shows the number of services blocked (if the unblock schedule is not enabled)

11/nov update: to include service to sync HA with adguard service… only really necessary on first install and if changes are made directly to AGH on services with input_boolean.

sensor:
  - platform: rest
    name: "agh_blocked_services"
    unique_id: "agh_blocked_services"
    authentication: basic
    username: !secret adguarduser
    password: !secret adguardpwd
    resource: !secret adguard_blocked_services_get
    method: GET
    timeout: 15
    headers:
      User-Agent: Homeassistant REST
      Content-Type: application/json
    # evaluate schedule, return the number of blocked services based on schedule. 0 if schedule is paused.
    value_template: >
      {% set day=now().strftime('%a')|lower%}
      {% set midnight=as_timestamp(now().replace(hour=0).replace(minute=0).replace(second=0)) %}
      {% set msecs_since_midnight=((as_timestamp(now()) - midnight)*1000)|int %}
      {% if not (
              states.sensor.agh_blocked_services is defined 
                and 
              not (state_attr("sensor.agh_blocked_services","schedule") == none)
            ) %}
        -1
      {%- elif day in (state_attr('sensor.agh_blocked_services','schedule')).keys() %}
        {% if ((state_attr('sensor.agh_blocked_services','schedule')[day]).start < msecs_since_midnight) and ((state_attr('sensor.agh_blocked_services','schedule')[day]).end > msecs_since_midnight)  -%}
          0
        {%- else -%}
          {{ (value|from_json).ids|list|count }}
        {%- endif %}
      {%- else -%}
        {{ (value|from_json).ids|list|count }}
      {%- endif %}
    json_attributes:
      - ids
      - schedule
    scan_interval: 60
    verify_ssl: false

script:
  agh_sync_with_ha:
    sequence:
      # check that there are services to be turned on; then do it.. 
      - condition: template
        value_template: >
          {{state_attr('sensor.agh_blocked_services','ids')|list|length > 0 }}
      - action: input_boolean.turn_on
        data_template:
          entity_id: >
            {% set data_on = namespace(matches=[]) %}
            {% for serviceid in state_attr('sensor.agh_blocked_services','ids') %}
              {% if(states.input_boolean|selectattr('entity_id', 'search', 'adg_block_' + serviceid)|map(attribute='name')|list|length)>0 %}
                {% set data_on.matches = data_on.matches + ["input_boolean.adg_block_" + serviceid] %}
              {%- endif %}
            {% endfor %}
            {{data_on.matches | join(', ')}}
      # check that there are services to be turned off; then do it.. 
      - condition: template
        value_template: >
          {% set data_off = namespace(matches=[]) %}
          {% for serviceid in states.input_boolean |selectattr('entity_id', 'search', 'adg_block_')
                  |selectattr('state', 'search', 'on')
                    |map(attribute='name') |list%}
            {% if not serviceid in state_attr("sensor.agh_blocked_services","ids") %}
              {% set data_off.matches = data_off.matches + ["input_boolean.adg_block_" + serviceid] %}
            {%- endif %}
          {% endfor %}
          {{(data_off.matches|length)>0}}
      - action: input_boolean.turn_off
        data_template:
          entity_id: >
            {% set data_off = namespace(matches=[]) %}
            {% for serviceid in states.input_boolean |selectattr('entity_id', 'search', 'adg_block_')
                    |selectattr('state', 'search', 'on')
                      |map(attribute='name') |list%}
              {% if not serviceid in state_attr("sensor.agh_blocked_services","ids") %}
                {% set data_off.matches = data_off.matches + ["input_boolean.adg_block_" + serviceid] %}
              {%- endif %}
            {% endfor %}
            {{ data_off.matches| join(', ') }}

automation:
  - alias: Update Blocked Services on Adguard
    mode: restart
    description: "loop through each service flag and set on adguard"
    triggers:
      - trigger: state
        entity_id:
          - input_boolean.adg_block_roblox
          - input_boolean.adg_block_youtube
          - input_boolean.adg_block_discord
          - input_boolean.adg_block_minecraft
    conditions: "{{not (state_attr('sensor.agh_blocked_services','ids') == none)}}"
    actions:
      - action: rest_command.agh_update_blocked_services
        data: {}
      # delay to ensure agh updates
      - delay: "00:00:15"
      - action: homeassistant.update_entity
        target:
          entity_id: sensor.agh_blocked_services
        data: {}

  - alias: Update Blocked Services on HA
    mode: restart
    description: "if agh services change, update HA"
    triggers:
      - trigger: homeassistant
        event: start
      - trigger: time
        at: "23:59:00"
      - trigger: state
        entity_id:
          - sensor.agh_blocked_services
    actions:
      - action: script.agh_sync_with_ha

rest_command:
  agh_update_blocked_services:
    url: !secret adguard_blocked_services_update
    username: !secret adguarduser
    password: !secret adguardpwd
    method: PUT
    headers:
      User-Agent: Homeassistant REST
      Content-Type: application/json
    payload: >
      {% set turn_on = states.input_boolean |selectattr('entity_id', 'search', 'adg_block_')
        |selectattr('state', 'search', 'on')
          |map(attribute='name') |list %}
      {% set turn_off = states.input_boolean |selectattr('entity_id', 'search', 'adg_block_')
        |selectattr('state', 'search', 'off')
          |map(attribute='name') |list  %}
      {% set blocked_services = (state_attr('sensor.agh_blocked_services', 'ids') + turn_on)|list|reject('in', turn_off )|unique|list%}
      {% set adg_services_config = { 
        "schedule": state_attr('sensor.agh_blocked_services', 'schedule'), 
        "ids": blocked_services
        }
      %}
      {{ adg_services_config|to_json }}

input_boolean:
  adg_block_youtube:
    name: youtube
    icon: mdi:youtube
  adg_block_discord:
    name: "discord"
    icon: mdi:forum-plus
  adg_block_minecraft:
    name: "minecraft"
    icon: mdi:controller
  adg_block_roblox:
    name: "roblox"
    icon: mdi:controller
  adg_block_steam:
    name: "steam"
    icon: mdi:controller
  adg_block_nintendo:
    name: "nintendo"
    icon: mdi:controller
  adg_block_activision_blizzard:
    name: "activision_blizzard"
    icon: mdi:controller

group:
  adg_block_games:
    name: Block Games
    icon: mdi:controller
    all: true
    entities:
      - input_boolean.adg_block_minecraft
      - input_boolean.adg_block_roblox
      - input_boolean.adg_block_steam
      - input_boolean.adg_block_nintendo
      - input_boolean.adg_block_activision_blizzard

And the secrets file contains

adguarduser: <insert ha user name>
adguardpwd: <insert ha password>
adguard_blocked_services_update: http://<insert AGH IP and port>/control/blocked_services/update
adguard_blocked_services_set: http://<insert AGH IP and port>/control/blocked_services/set
1 Like

So, regarding this line:

    url: !secret adguard_blocked_services_update

Is the format of the link something like this?

http://x.x.x.x/control/blocked_services/update

Yes exactly! My apologies I should have noted;
updated with example secrets file, some fixes to catch edge cases and a script to sync status/changes in agh back to HA.

I struggled a bit trying to figure out where my typos were, and it turned out to be a random space in my codes, not the url format.

So thank you for your directions, in the end I have figured everything out.

I also like the idea of using blocked services count vs the schedule to decide what to do with the status of the sensor. :+1: … something I never considered previously.

So I am glad I learned something new today :slight_smile:

1 Like

@230delphi Thanks for the package. I took this and made adjustments to block services by client.
I want edto be able to block the kids ipads from youtube without taking it down everywhere.

For me this only worked after going on the kids ipad and manually setting the DNS Server to the adguard host, as well as deleting other DNS sources.

I also added a static IP route for their devies. From their I just used the ip assigned from my route and added them to http://<adguard_ip>#clients. Then additions I made to the package be able work.

I also added input boolean for different client/service combinations I wanted to control:

input_boolean:
  adg_block_youtube:
    name: youtube
    icon: mdi:youtube
  ...
  adg_block_<client1>_youtube:
    name: "Block Kid1 YouTube"
    icon: mdi:youtube
  adg_block_<client1>_minecraft:
    name: "Block Kid1  Minecraft"
    icon: mdi:controller
  adg_block_<client2>_youtube:
    name: "Block <client2> YouTube"
    icon: mdi:youtube

Note that the client1 name should match the clientid you want an input boolean for in adguard.

and added to the secrets

adguard_blocked_services_update: http://<adguard_host_ip>/control/blocked_services/update
adguard_blocked_services_set: http://<adguard_host_ip>/control/blocked_services/set
adguard_blocked_services_get: http://<adguard_host_ip>/control/blocked_services/get
adguard_clients_get: http://<adguard_host_ip>/control/clients
adguard_clients_update: http://<adguard_host_ip>/control/clients/update


Full package

sensor:
  - platform: rest
    name: "agh_clients"
    unique_id: "agh_clients"
    authentication: basic
    username: !secret adguarduser
    password: !secret adguardpwd
    resource: !secret adguard_clients_get
    method: GET
    headers:
      User-Agent: Homeassistant REST
      Content-Type: application/json
    scan_interval: 10
    value_template: >
      {{ value_json.clients | length }}
    json_attributes:
      - clients
      - auto_clients
      - supported_tags
  - platform: rest
    name: "agh_blocked_services"
    unique_id: "agh_blocked_services"
    authentication: basic
    username: !secret adguarduser
    password: !secret adguardpwd
    resource: !secret adguard_blocked_services_get
    method: GET
    headers:
      User-Agent: Homeassistant REST
      Content-Type: application/json
    value_template: >
      {% set day=now().strftime('%a')|lower%}
      {% set midnight=as_timestamp(now().replace(hour=0).replace(minute=0).replace(second=0)) %}
      {% set msecs_since_midnight=((as_timestamp(now()) - midnight)*1000)|int %}
      {% if not (
              states.sensor.agh_blocked_services is defined 
                and 
              not (state_attr("sensor.agh_blocked_services","schedule") == none)
            ) %}
        -1
      {%- elif day in (state_attr('sensor.agh_blocked_services','schedule')).keys() %}
        {% if ((state_attr('sensor.agh_blocked_services','schedule')[day]).start < msecs_since_midnight) and ((state_attr('sensor.agh_blocked_services','schedule')[day]).end > msecs_since_midnight)  -%}
          0
        {%- else -%}
          {{ (value|from_json).ids|list|count }}
        {%- endif %}
      {%- else -%}
        {{ (value|from_json).ids|list|count }}
      {%- endif %}
    # value_template: >
    #  {% set clean_value = value.replace('\"', '"').replace('', '').replace('\n', '') %}
    #  {{ clean_value }}
    scan_interval: 15 
    json_attributes:
      - ids
      - schedule    
    verify_ssl: false
  - platform: template
    sensors:
      agh_client_list:
        value_template: >
          {{ state_attr('sensor.agh_clients', 'clients') | length }}
        attribute_templates:
          clients: >
            {{ state_attr('sensor.agh_clients', 'clients') }}
          client_names: >
            {{ state_attr('sensor.agh_clients', 'clients') | map(attribute='name') | list }}
          client_ids: >
            {{ state_attr('sensor.agh_clients', 'clients') | map(attribute='ids') | list }}
          client_services: >
            {% set data = namespace(pairs=[]) %}
            {% for client in state_attr('sensor.agh_clients', 'clients') %}
              {% set data.pairs = data.pairs + [[client.name, client.blocked_services|default([])]] %}
            {% endfor %}
            {{ dict(data.pairs) }}
          tags: >
            {% set data = namespace(pairs=[]) %}
            {% for client in state_attr('sensor.agh_clients', 'clients') %}
              {% set data.pairs = data.pairs + [[client.name, client.tags|default([])]] %}
            {% endfor %}
            {{ dict(data.pairs) }}      
script:
  agh_sync_clients:
    sequence:
      # Update client-specific blocks based on AGH state
      - condition: template
        value_template: >
          {{ state_attr('sensor.agh_clients', 'clients') is not none }}
      - service: input_boolean.turn_on
        data_template:
          entity_id: >
            {% set data_on = namespace(matches=[]) %}
            {% for client in state_attr('sensor.agh_clients', 'clients')|default([]) %}
              {% if client.blocked_services|default([]) %}
                {% for service in client.blocked_services %}
                  {% set switch = 'input_boolean.adg_block_' ~ client.name|lower|replace(' ', '_') ~ '_' ~ service %}
                  {% if states(switch) in ['on', 'off'] %}
                    {% set data_on.matches = data_on.matches + [switch] %}
                  {% endif %}
                {% endfor %}
              {% endif %}
            {% endfor %}
            {{ data_on.matches|join(', ') if data_on.matches else [] }}
      # Turn off switches that aren't in AGH anymore
      - service: input_boolean.turn_off
        data_template:
          entity_id: >
            {% set data_off = namespace(matches=[]) %}
            {% for switch in states.input_boolean|selectattr('entity_id', 'match', 'input_boolean.adg_block_*') %}
              {% if switch.state == 'on' %}
                {% set parts = switch.entity_id.split('_') %}
                {% set service = parts[-1] %}
                {% set client_name = parts[3:-1]|join(' ') %}
                {% set client = state_attr('sensor.agh_clients', 'clients')|selectattr('name', 'eq', client_name|title)|first|default(none) %}
                {% if not client or service not in client.blocked_services|default([]) %}
                  {% set data_off.matches = data_off.matches + [switch.entity_id] %}
                {% endif %}
              {% endif %}
            {% endfor %}
            {{ data_off.matches|join(', ') if data_off.matches else [] }}
            
  agh_sync_with_ha:
    sequence:
      # check that there are services to be turned on; then do it.. 
      - condition: template
        value_template: >
          {{state_attr('sensor.agh_blocked_services','ids')|list|length > 0 }}
      - action: input_boolean.turn_on
        data_template:
          entity_id: >
            {% set data_on = namespace(matches=[]) %}
            {% for serviceid in state_attr('sensor.agh_blocked_services','ids') %}
              {% if(states.input_boolean|selectattr('entity_id', 'search', 'adg_block_' + serviceid)|map(attribute='name')|list|length)>0 %}
                {% set data_on.matches = data_on.matches + ["input_boolean.adg_block_" + serviceid] %}
              {%- endif %}
            {% endfor %}
            {{data_on.matches | join(', ')}}
      # check that there are services to be turned off; then do it.. 
      - condition: template
        value_template: >
          {% set data_off = namespace(matches=[]) %}
          {% for serviceid in states.input_boolean |selectattr('entity_id', 'search', 'adg_block_')
                  |selectattr('state', 'search', 'on')
                    |map(attribute='name') |list%}
            {% if not serviceid in state_attr("sensor.agh_blocked_services","ids") %}
              {% set data_off.matches = data_off.matches + ["input_boolean.adg_block_" + serviceid] %}
            {%- endif %}
          {% endfor %}
          {{(data_off.matches|length)>0}}
      - action: input_boolean.turn_off
        data_template:
          entity_id: >
            {% set data_off = namespace(matches=[]) %}
            {% for serviceid in states.input_boolean |selectattr('entity_id', 'search', 'adg_block_')
                    |selectattr('state', 'search', 'on')
                      |map(attribute='name') |list%}
              {% if not serviceid in state_attr("sensor.agh_blocked_services","ids") %}
                {% set data_off.matches = data_off.matches + ["input_boolean.adg_block_" + serviceid] %}
              {%- endif %}
            {% endfor %}
            {{ data_off.matches| join(', ') }}

automation:
  - alias: "Update Client Services on AGH"
    mode: restart
    description: "Update AGH when client service switches change"
    trigger:
      - platform: state
        entity_id:
          - input_boolean.adg_block_client1_youtube
          - input_boolean.adg_block_client1_minecraft
          - input_boolean.adg_block_client1_roblox
          - input_boolean.adg_block_client2_youtube
          - input_boolean.adg_block_client2_minecraft
          - input_boolean.adg_block_client2_roblox          
    condition:
      - condition: template
        value_template: "{{ state_attr('sensor.agh_clients', 'clients') != none }}"
    action:
      - repeat:
          sequence:
            - service: rest_command.agh_update_client
              data:
                client_name: "{{ repeat.item }}"
            - delay: "00:00:02"
          for_each: "{{ state_attr('sensor.agh_client_list', 'client_names') }}"
      - delay: "00:00:15"
      - service: homeassistant.update_entity
        target:
          entity_id: sensor.agh_clients
          
          
  - alias: Update Blocked Services on Adguard
    mode: restart
    description: "loop through each service flag and set on adguard"
    triggers:
      - trigger: state
        entity_id:
          - input_boolean.adg_block_roblox
          - input_boolean.adg_block_youtube
          - input_boolean.adg_block_discord
          - input_boolean.adg_block_minecraft
    conditions: "{{not (state_attr('sensor.agh_blocked_services','ids') == none)}}"
    actions:
      - action: rest_command.agh_update_blocked_services
        data: {}
      # delay to ensure agh updates
      - delay: "00:00:15"
      - action: homeassistant.update_entity
        target:
          entity_id: sensor.agh_blocked_services
        data: {}
  - alias: Update Blocked Services on HA
    mode: restart
    description: "if agh services change, update HA"
    triggers:
      - trigger: homeassistant
        event: start
      - trigger: time
        at: "23:59:00"
      - trigger: state
        entity_id:
          - sensor.agh_blocked_services
    actions:
      - action: script.agh_sync_with_ha

rest_command:
  agh_update_client:
    url: !secret adguard_clients_update 
    username: !secret adguarduser
    password: !secret adguardpwd
    method: POST
    headers:
      User-Agent: Homeassistant REST
      Content-Type: application/json
    payload: >-
      {% set clients = state_attr('sensor.agh_clients', 'clients')|default([]) %}
      {% set client = clients|selectattr('name', 'eq', client_name)|first %}
      {% set blocked = namespace(services=[]) %}
      {% for service in states.input_boolean |selectattr('entity_id', 'search', 'adg_block_' ~ client_name|lower|replace(' ', '_'))
              |selectattr('state', 'eq', 'on')
                |map(attribute='entity_id')|list %}
        {% set service_name = service.split('_')[-1] %}
        {% set blocked.services = blocked.services + [service_name] %}
      {% endfor %}
      {
        "name": "{{ client_name }}",
        "data": {
          "upstreams": [],
          "tags": {{client.tags|tojson}},
          "safe_search": {{client.safe_search|tojson}},
          "blocked_services_schedule": {
            "time_zone": "Local"
          },
          "name": "{{ client_name }}",
          "blocked_services": {{ blocked.services|tojson }},
          "ids": {{client.ids|tojson}},
          "filtering_enabled": false,
          "parental_enabled": false,
          "safebrowsing_enabled": false,
          "safesearch_enabled": false,
          "use_global_blocked_services": false,
          "use_global_settings": true,
          "ignore_querylog": false,
          "ignore_statistics": false,
          "upstreams_cache_size": 0,
          "upstreams_cache_enabled": false
        }
      }
  agh_update_blocked_services:
    url: !secret adguard_blocked_services_update
    username: !secret adguarduser
    password: !secret adguardpwd
    method: PUT
    headers:
      User-Agent: Homeassistant REST
      Content-Type: application/json
    payload: >
      {% set turn_on = states.input_boolean |selectattr('entity_id', 'search', 'adg_block_')
        |selectattr('state', 'search', 'on')
          |map(attribute='name') |list %}
      {% set turn_off = states.input_boolean |selectattr('entity_id', 'search', 'adg_block_')
        |selectattr('state', 'search', 'off')
          |map(attribute='name') |list  %}
      {% set blocked_services = (state_attr('sensor.agh_blocked_services', 'ids') + turn_on)|list|reject('in', turn_off )|unique|list%}
      {% set adg_services_config = { 
        "schedule": state_attr('sensor.agh_blocked_services', 'schedule'), 
        "ids": blocked_services
        }
      %}
      {{ adg_services_config|to_json }}

input_boolean:
  adg_block_youtube:
    name: youtube
    icon: mdi:youtube
  adg_block_discord:
    name: "discord"
    icon: mdi:forum-plus
  adg_block_minecraft:
    name: "minecraft"
    icon: mdi:controller
  adg_block_roblox:
    name: "roblox"
    icon: mdi:roblox
  adg_block_steam:
    name: "steam"
    icon: mdi:controller
  adg_block_nintendo:
    name: "nintendo"
    icon: mdi:controller
  adg_block_activision_blizzard:
    name: "activision_blizzard"
    icon: mdi:controller
  adg_block_disneyplus:
    name: "activision_blizzard"
    icon: mdi:controller  
  adg_block_client1_youtube:
    name: "Block Client1 YouTube"
    icon: mdi:youtube
  adg_block_client1_minecraft:
    name: "Block Client1  Minecraft"
    icon: mdi:controller
  adg_block_client1_roblox:
    name: "Block Client1  Roblox"
    icon: mdi:roblox 
  adg_block_client1_discord:
    name: "Block Client1 Discord"
    icon: mdi:controller  
  adg_block_client2_youtube:
    name: "Block Client2 YouTube"
    icon: mdi:youtube
  adg_block_client2_minecraft:
    name: "Block Client2  Minecraft"
    icon: mdi:controller
  adg_block_client2_roblox:
    name: "Block Client2  Roblox"
    icon: mdi:roblox 
  adg_block_client2_discord:
    name: "Block Client2 Discord"
    icon: mdi:controller     
    

group:
  adg_block_games:
    name: Block Games
    icon: mdi:controller
    all: true
    entities:
      - input_boolean.adg_block_minecraft
      - input_boolean.adg_block_roblox
      - input_boolean.adg_block_steam
      - input_boolean.adg_block_nintendo
      - input_boolean.adg_block_activision_blizzard
  kids_ipad_blocks:
   name: "Clients 1 & 2 Blocks"
   icon: mdi:tablet
   entities:
    - input_boolean.adg_block_client1_youtube
    - input_boolean.adg_block_client1_discord
    - input_boolean.adg_block_client1_minecraft
    - input_boolean.adg_block_client1_roblox   
    - input_boolean.adg_block_client2_youtube
    - input_boolean.adg_block_client2_minecraft
    - input_boolean.adg_block_client2_roblox 
    - input_boolean.adg_block_client2_discord
     
 

Dashboard example

2 Likes