• Evidence-based cocktail recipes

    I always enjoy reading the ongoing profile Claude maintains as part of its memory feature. As with anything generated by a LLM, it tends to be a mixture of surprisingly great synthesis of my AI use and total whimsical nonsense.

    By far the most important part of my personal profile:

    I’ve always said, never mix a drink that isn’t evidence-based!

  • Calendar-Based Presence Detection for Kids in Home Assistant

    Lately, I’ve been working on some fun Home Assistant automations based on whether my kids are in the house. The challenge? My kids don’t have cell phones, so traditional presence detection won’t work. So instead, I hacked together a solution using Google Calendar events and day-of-week logic.

    With this setup, I can do things like:

    • Get a notification if the TV turns on when it shouldn’t be
    • Turn off the lights in the kids’ rooms automatically when they’re at school

    But since my kids don’t have cell phones, presence detection is trickier than normal, so I’ve had to mimic it using Google Calendar events and simple logic surrounding the days of the week.

    This is pretty easy to do by creating an input_boolean helper entity (Settings → Devices & Services → Helpers → Create Helper → Toggle) and a corresponding automation to update it:

    alias: Update Kids In House Status
    description: Updates kids' presence in house based on criteria
    triggers:
      - minutes: /5
        trigger: time_pattern
      - entity_id: calendar.google_calendar_personal
        trigger: state
      - event: start
        trigger: homeassistant
    conditions: []
    actions:
      - target:
          entity_id: calendar.google_calendar_personal
        data:
          start_date_time: "{{ today_at('00:00') }}"
          end_date_time: "{{ today_at('23:59') }}"
        response_variable: todays_events
        action: calendar.get_events
      - variables:
          event_summaries: >-
            {{ todays_events['calendar.google_calendar_personal'].events |
            map(attribute='summary') | list }}
          has_grandma: "{{ 'Kids with Grandma' in event_summaries }}"
          has_no_school: "{{ 'No School' in event_summaries }}"
          current_hour: "{{ now().hour }}"
          is_saturday: "{{ now().weekday() == 5 }}"
          is_sunday: "{{ now().weekday() == 6 }}"
          school_day: >-
            {{ (not is_saturday and not is_sunday) and current_hour >= 8 and current_hour <
            15 }}
          kids_away: "{{ has_grandma or (school_day and not has_no_school) }}"
      - if:
          - condition: template
            value_template: "{{ kids_away }}"
        then:
          - target:
              entity_id: input_boolean.kids_in_house
            action: input_boolean.turn_off
        else:
          - target:
              entity_id: input_boolean.kids_in_house
            action: input_boolean.turn_on
    mode: single
    

    The automation checks every 5 minutes whether specific calendar events exist (“Kids with Grandma” or “No School”), combines that with school hours (8am-3pm on weekdays), and flips the input_boolean accordingly. When the kids are away—either at school during normal hours or with grandma per the calendar—the boolean turns off. Otherwise, it stays on.

  • Transcribing and collating Uniden radio recordings with Whisper AI

    One of my favorite features of Uniden BCDx36- and SDS- series radios is the ability to enable a favorites list and let the radio record continuously for days or weeks.

    This feature is very effective for discovering activity on new systems, digging through frequencies found in the FCC database to determine what’s being used, or quickly getting a read on which ham radio repeaters are active in a given area – all without being in front of the radio all day.

    It also pairs very well with the “negative delay” feature, which lets you make sure that a dead carrier or trunked system control channel won’t tie up your radio and storage space for more than 10 seconds before it moves on.

    The downside is that it’s easy to just let the radio sit recording for weeks on end, leading to a large pile of recordings that can be time-consuming to sift through, even with tools like Universal Scanner Audio Player.

    To that end, I built a proof-of-concept tool in Perl that can dig through a folder full of WAV files, collate them by frequency/tone, attempt to transcribe them using a local instance of OpenAI Whisper, and provide an easy-to-read HTML page showing the transcriptions, timestamps, and easy access to the recording audio.

    I’ve posted the example code on Github and will probably continue to add features to it.

    This project depends on the excellent work from Bearcatter, whose wavparse Go library makes it possible to extract Uniden-specific metadata from WAV files.

  • Dynamic drive time reminders with Home Assistant

    Tired of manually checking Google Maps and your calendar to figure out when to leave for your next meeting?

    I recently realized that Home Assistant already has all the necessary pieces – my current geolocation, calendar events, and Waze travel time – so I put together an automation to provide real-time notifications telling me exactly when I need to head out the door.

    It’s pretty simple, following these steps every five minutes:

    • Retrieve all events in the next 4 hours from my work and personal calendars
    • Eliminate all-day events, events with no location, or events I’ve already been reminded of (stored in a helper input sensor called last_notified_event_id)
    • For the remaining events, retrieve Waze travel time from my current location to the event location (using entity location versus a static address or lat/long is important because I might not be at home, affecting travel time)
    • Determine departure time (start time minus travel time minus 5 minutes of wiggle room)
    • Determine reminder time (30 minutes prior to departure time)
    • Send a notification to my phone alerting me to the upcoming event, estimated travel time, and the time I need to leave
    • Store a synthetic “event ID” (the start time and summary) in an input helper sensor, so we can make sure I’m not reminded again

    Note: I’m pretty new to Home Assistant templating (though a past life as a Perl dev has hammered YAML syntax into my head irreversably) so there may be stupidity in this automation. But, it works for me (so far!)

    alias: Drive time alert
    description: ""
    # fire every five minutes
    triggers:
      - trigger: time_pattern
        minutes: /5
    conditions: []
    actions:
      - action: calendar.get_events
        metadata: {}
        data:
          duration:
            hours: 4
            minutes: 0
            seconds: 0
        target:
          entity_id:
            - calendar.personal_calendar
            - calendar.work_calendar
        response_variable: cal_events
      - repeat:
          for_each: "{{ cal_events.values() | map(attribute='events') | sum(start=[]) }}"
          sequence:
            - variables:
                thisEventId: "{{ repeat.item.start }} {{ repeat.item.summary }}"
            - condition: template
              value_template: |
                {{ repeat.item.location is not none and
                   (repeat.item.location | string | trim != "") and
                   'T' in (repeat.item.start | string) and
                   states('input_text.last_notified_event_id') and
                   thisEventId != states('input_text.last_notified_event_id') }}
            - data:
                origin: person.mike
                destination: "{{ repeat.item.location }}"
                region: us
              response_variable: waze_eta_result
              continue_on_error: false
              action: waze_travel_time.get_travel_times
            - variables:
                wiggle_room: "{{ 5 }}"
                waze_duration: "{{ waze_eta_result['routes'][0].duration | round(0) }}"
                start_time: "{{ repeat.item.start }}"
                start_time_friendly: "{{ as_datetime(start_time).strftime('%I:%M %p') }}"
                location: "{{ repeat.item.location }}"
                summary: "{{ repeat.item.summary }}"
                departure_time: >-
                  {{ as_datetime(start_time) - timedelta(minutes=waze_duration) -
                  timedelta(minutes=wiggle_room) }}
                departure_time_friendly: "{{ as_datetime(departure_time).strftime('%I:%M %p') }}"
                reminder_time: "{{ as_datetime(departure_time) - timedelta(minutes=30) }}"
            - condition: template
              value_template: |
                {{ as_datetime(reminder_time) <= now() }}
            - action: notify.mobile_app_pixel_6_mike
              metadata: {}
              data:
                data:
                  ttl: 0
                  priority: high
                message: >-
                  Drive time to your next appointment ({{summary}}) is 
                  {{waze_duration}} minutes. Depart by {{departure_time_friendly}}
                  for arrival by {{start_time_friendly}}.
                title: Drive Time Alert - {{ repeat.item.summary }}
            - action: input_text.set_value
              metadata: {}
              data:
                value: "{{ thisEventId }}"
              target:
                entity_id: input_text.last_notified_event_id
    mode: single