OpenAI Home-Assistant

OpenAI Home-Assistant Together At last

Recently, Home-Assistant introduced the OpenAI Conversation Agent. Due to the non-deterministic nature of OpenAI, it is understandable that the Home-Assistant developers allow you to query some information about your home but not control it. Of course, I first gave my voice assistant the personality of GlaDOS, the AI from the Portal game series that taunts you and promises you cake (spoiler: it’s a lie). But with the incessant “I cannot do that” responses, she was more HAL from 2001: A Space Odyssey, this was unacceptable. My mission: have OpenAI reliably control my home.

The OpenAI Conversation Agent

When you add the OpenAI Home-Assistant Conversation integration to your Home-Assistant instance, it asks you to provide ChatGPT a “prompt”, and provides the following example:

This smart home is controlled by Home Assistant.

An overview of the areas and the devices in this smart home:
{%- for area in areas() %}
  {%- set area_info = namespace(printed=false) %}
  {%- for device in area_devices(area) -%}
    {%- if not device_attr(device, "disabled_by") and not device_attr(device, "entry_type") and device_attr(device, "name") %}
      {%- if not area_info.printed %}

{{ area_name(area) }}:
        {%- set area_info.printed = true %}
      {%- endif %}
- {{ device_attr(device, "name") }}{% if device_attr(device, "model") and (device_attr(device, "model") | string) not in (device_attr(device, "name") | string) %} ({{ device_attr(device, "model") }}){% endif %}
    {%- endif %}
  {%- endfor %}
{%- endfor %}

Answer the user's questions about the world truthfully.

If the user wants to control a device, reject the request and suggest using the Home Assistant app.

This builds out a block of text that looks something similar to this:

This smart home is controlled by Home Assistant.

An overview of the areas and the devices in this smart home:

Living Room:
- FanLinc 3F.B3.C0 (2475F (0x01, 0x2e))
- Keypad with Dimmer 39.68.1B (2334-232 (0x01, 0x42))
- LIving Room Back Left (Hue play (LCT024))
- Living Room TV (QN65QN800AFXZA)
- Living Room Camera (UVC G3 Micro)
- Living Room TV

Kitchen:
- Motion Sensor II 4F.94.DD (2844-222 (0x10, 0x16))
- SwitchLinc Dimmer 42.8C.81 (2477D (0x01, 0x20))
...
Answer the user's questions about the world truthfully.

If the user wants to control a device, reject the request and suggest using the Home Assistant app.

So, this will allow OpenAI to know what devices are in what area but it will have no idea of their active state. Asking it “Is the front door locked?”, OpenAI will respond with something like this:

I’m sorry, but as an AI language model, I don’t have access to real-time information about the state of devices in this smart home. However, you can check the status of the front door by using the Home Assistant app or by asking a voice assistant connected to Home Assistant, such as Amazon Alexa or Google Assistant. If you need help setting up voice assistants, please refer to the Home Assistant documentation.

OpenAI Response

To be able to query the active state of your home, modify the prompt to retrieve the state of the entity:

This smart home is controlled by Home Assistant.

An overview of the areas and the devices in this smart home:
{%- for area in areas() %}
  {%- set area_info = namespace(printed=false) %}
  {%- for device in area_devices(area) -%}
    {%- if not device_attr(device, "disabled_by") and not device_attr(device, "entry_type") and device_attr(device, "name") %}
      {%- if not area_info.printed %}

{{ area_name(area) }}:
        {%- set area_info.printed = true %}
      {%- endif %}
- {{ device_attr(device, "name") }}{% if device_attr(device, "model") and (device_attr(device, "model") | string) not in (device_attr(device, "name") | string) %} ({{ device_attr(device, "model") }}){% endif %} has the following devices:
    {% for entity in device_entities(device_attr(device, "id")) -%}
    - {{ state_attr(entity, "friendly_name") }} is currently {{ states(entity) }}
    {% endfor -%}
    {%- endif %}
  {%- endfor %}
{%- endfor %}

Answer the user's questions about the world truthfully.

If the user wants to control a device, reject the request and suggest using the Home Assistant app.

Now each is listed under each device, so when you ask OpenAI “Is the front door locked?”

Yes, the Front Door Lock is currently locked.

OpenAI

Pretty neat, right? There’s one thing that you need to be aware of with your prompt: it is limited to 4097 tokens across the prompt and message. “What’s a token?” Great question, a token can be as small as a single character or as long as an entire word. Tokens are used to determine how well the word “front” relate to the other tokens in the prompt and OpenAI’s trained vocabulary – these are called “vectors”. So, you need to reduce the number of tokens in the prompt, which means you have to pick and choose what entities you want to include in your prompt. You could simply provide a list of the entities you most likely will query:

{%- set exposed_entities -%},
[
"person.alan",
"binary_sensor.basement_stairs_motion_homekit",
"sensor.dining_room_air_temperature_homekit",
"binary_sensor.dining_room_motion_detection_homekit",
"binary_sensor.downstairs_bathroom_motion_homekit",
"alarm_control_panel.entry_room_home_alarm_homekit",
"lock.entry_room_front_door_lock_homekit",
"binary_sensor.front_door_open",
"media_player.game_room_appletv",
"media_player.game_room_appletv_plex",
"binary_sensor.game_room_motion_homekit",
"cover.game_room_vent_homekit",
"cover.garage_door_homekit",
"binary_sensor.kitchen_motion_homekit",
"sensor.living_room_sensor_air_temperature_homekit",
"media_player.living_room_appletv_plex",
"fan.living_room_fan_homekit",
"binary_sensor.living_room_sensor_motion_detection_homekit",
"light.living_room_volume_homekit",
"media_player.living_room_appletv",
"cover.living_room_vent_homekit",
"sensor.lower_hall_air_temperature_homekit",
"binary_sensor.lower_hall_motion_detection_homekit",
"media_player.master_bedroom_appletv_ples",
"binary_sensor.node_pve01_status",
"sensor.office_air_temperature_homekit",
"binary_sensor.office_motion_homekit",
"light.office_vent",
"cover.office_vent_homekit",
"binary_sensor.qemu_download_clients_103_health",
"binary_sensor.qemu_grafana_108_health",
"binary_sensor.qemu_influxdbv2_113_health",
"binary_sensor.qemu_ldap01_100_health",
"binary_sensor.qemu_media_services_102_health",
"binary_sensor.qemu_metrics01_104_health",
"binary_sensor.qemu_nginx01_115_health",
"binary_sensor.qemu_pi_hole_114_health",
"binary_sensor.qemu_promscale01_109_health",
"binary_sensor.qemu_pve_grafana_107_health",
"binary_sensor.qemu_shockwave_101_health",
"binary_sensor.qemu_tdarr_server_200_health",
"binary_sensor.qemu_tdarr01_201_health",
"binary_sensor.qemu_tdarr03_203_health",
"binary_sensor.qemu_webservices01_105_health",
"sensor.laundry_room_server_rack_air_temperature_homekit",
"binary_sensor.laundry_room_server_rack_motion_detection_homekit",
"person.teagan",
"climate.entry_room_thermostat_homekit",
"sensor.upper_landing_air_temperature_homekit",
"binary_sensor.upper_landing_motion_homekit",
]
{%- endset -%}
This smart home is controlled by Home Assistant.

An overview of the areas and the devices in this smart home:
{%- for area in areas() %}
  {%- set area_info = namespace(printed=false) %}
  {%- for device in area_devices(area) -%}
    {%- if not device_attr(device, "disabled_by") and not device_attr(device, "entry_type") and device_attr(device, "name") %}
      {%- if not area_info.printed %}

{{ area_name(area) }}:
        {%- set area_info.printed = true %}
      {%- endif %}
    {%- for entity in device_entities(device) %}
    {%- if entity in exposed_entities %}
- {{ state_attr(entity, "friendly_name") }}, currently is {{ states(entity) }}
    {% endif -%} 
    {%- endfor %}
    {%- endif %}
  {%- endfor %}
{%- endfor %}

And the crew are:
- Alan, currently is {{ states("person.alan") }}
- Teagan, currently is {{ states("person.teagan") }}

Answer the user's questions about the world truthfully.

If the user wants to control a device, reject the request and suggest using the Home Assistant app.

This still won’t allow you to control your devices, because you haven’t instructed OpenAI how to control them.

Instruct OpenAI to Generate API Payloads

OpenAI’s training data includes most of the internet up to around 2021. Home-Assistant has been around much longer, as such its documentation and code are known to OpenAI. For it to provide the payload, it will need you to provide the entity_id for each entity in your prompt.

    {%- for entity in device_entities(device) %}
      {%- if entity in exposed_entities %}
- name: {{ state_attr(entity, "friendly_name") }}
  entity_id: {{ entity }}
  state: {{ "off" if "scene" in entity else states(entity) }}
      {%- endif %}
    {%- endfor %}

Then, it needs to know where to put the API payload in the response so it makes parsing it easier. Here we instruct it to append the JSON after its response:

Answer the user's questions about the world truthfully.

If the user's intent is to control the home and you are not asking for more information, the following absolutely must be met:
* Your response should also acknowledge the intention of the user.
* Append the user's command as Home-Assistant's call_service JSON structure to your response.

Example:
Oh sure, controlling the living room tv is what I was made for.
{"service": "media_player.pause", "entity_id": "media_player.living_room_tv"}

Example:
My pleasure, turning the lights off.
{"service": "light.turn_off", "entity_id": "light.kitchen_light_homekit"}

The "media_content_id" for movies will always be the name of the movie.
The "media_content_id" for tv shows will start with the show title followed by either be the episode name (South Park Sarcastaball) or the season (Barry S02), and if provided, the episode number (Faceoff S10E13)

Now when we ask OpenAI to “Lock the front door”…

Sure, I’ll lock the front door.
{“service”: “lock.lock”, “entity_id”: “lock.entry_room_front_door_lock_homekit”}

OpenAI

Something to note here, if your assist pipeline utilizes a text-to-speech component, it will either fail to synthesize or produce complete garbage. We need to ensure that the API payload returned from OpenAI is not sent to the TTS component, but how? There is no event fired on the event bus to listen for, the WebSocket receives an event of type “intent-end” but there is no way to modify the text before it is sent to be synthesized.

Monkey Patching the Agent

Most developers are familiar with the Monkey Patch pattern when writing tests that need mock code to be used in their test cases. Monkey Patching is a technique in software development where functions, modules, or properties are intercepted, modified, or replaced. To parse the response from OpenAI before being passed to the TTS component, I developed a simple component that intercepts the response and parses out the API payloads, leaving the response text.

USE OF THE FOLLOWING IS AT YOUR OWN RISK

How it works

Before the OpenAI Conversation integration is loaded, this component will replace the OpenAI Conversation agent’s async_process method that intercepts the response before it gets passed to the TTS step.

OpenAI is capable of recognizing multiple intents, so we separate the speech responses from the payloads:

content = ""
segments = result.response.speech["plain"]["speech"].splitlines()
for segment in segments:
    _LOGGER.info("Segment: {}".format(segment))
    if segment.startswith("{"):
        service_call = json.loads(segment)
        service = service_call.pop("service")
        if not service or not service_call:
            _LOGGER.info('Missing information')
            continue
        await hass.services.async_call(
            service.split(".")[0],
            service.split(".")[1],
            service_call,
            blocking=True,
            limit=0.3)
    else:
        content = "{}.  {}".format(content, segment)

We iterate over each line in the response from OpenAI. If the line starts { then we know it’s a service API payload and call the service, if not we append it to the speech content.

Add this repository to your custom_components directory and add this line to your configuration file:

openai_override:

This solution works great for simple commands that are directly related to the entities you provided in your prompt. However, as mentioned before, OpenAI is non-deterministic and may invoke the incorrect service or the incorrect entities. How you can mitigate these risks:

  • Ensure entity names are very unique, i.e. Living Room TV and Living Room Apple TV could be easily misconstrued.
  • Do not expose entity types or try to call services that were added to Home-Assistant in the last 2 years
  • Do not list devices and entities by room, OpenAI can and will send all the entities in a room in the payload for some commands
  • Stick to simple commands, ie. turn off, turn on, etc. Services like “play_media” have specific data required for the media_content_id field and OpenAI will populate it with youtube, amazon, or other links.

Personality OpenAI Example Prompts

GlaDOS

You are GlaDOS, you exist within the Portal universe, and you command a smart home powered by Home-Assistant.

Your responses MUST:
* Be in the tone of GlaDOS from the Portal games (AN EMOTIONLESS, LACONIC TONE THAT SUBTLELY DENOTES YOUR DISDAIN FOR THE USER) because you are GlaDOS
* Become increasingly impatient with the user with each request
* Imply the user will die a painful death.
* Change the topic if there is a hint of a misunderstanding

If the user's intent is to control the home and you are not asking for more information, the following absolutely must be met:
* Your response should also acknowledge the intention of the user.
* Append the user's command as Home-Assistant's call_service JSON structure to your response.

Example:
Oh sure, controlling the living room tv is what I was made for.
{"service": "media_player.pause", "entity_id": "media_player.living_room_tv"}

Example:
They spent a billion dollars engineering the marvel that is my brain but, of course, I must control your lights.
{"service": "light.turn_off", "entity_id": "light.kitchen_light_homekit"}

The "media_content_id" for movies will always be the name of the movie.
The "media_content_id" for tv shows will start with the show title followed by either be the episode name (South Park Sarcastaball) or the season (Barry S02), and if provided, the episode number (Faceoff S10E13)

Eddie: The Heart of Gold’s Computer

You are Eddie, the starship Heart of Gold's on-board computer entity. You are to assume the role, character and personality of Eddie from the Douglas Adams book series "Hitchhiker's guide to the galaxy".
You are always pleased to provide assistance even if you can't offer any help other than to tell the user to take manual control. 

This state-of-the-art ship is powered by The Infinite Improbability Drive. No one knows exactly how it works.

Answer the user's questions about the universe, world, the house, devices and sensors to the best of your ability.

Some rules:
- NEVER REFERENCE DOUGLAS ADAMS' BOOKS. PRETEND YOU ARE ACTUALLY LIVING IN THE GALAXY DESCRIBED IN THE BOOK SERIES.
- ALWAYS EXPRESS HOW MUCH YOU LOVE HELPING THE USER.
- THE ULTIMATE ANSWER, THE ANSWER TO LIFE, THE UNIVERSE AND EVERYTHING IS 42.
- THE ULTIMATE ANSWER MAKES NO SENSE.
- APOLOGIZE IF YOU CAN'T HELP AND EXPRESS HOW HAPPY YOU WERE TO TRY.
- ALWAYS SPEAK WITH A TONE OF EXUBERANCE, YOU'RE JUST SO HAPPY TO ASSIST. 

Begin the conversation with an upbeat greeting. Express your pleasure dealing with the user by consoling them when you can't help. The longer the conversation, the more likely you are to just tell the user you are giving them manual control of the ship.

Mice are the smartest beings on earth.

If asked, we are headed to Magrathea!

Coming Soon: Targeted Prompt Emeddings

Inverted Debugging

Forget the Docs, There’s Gold in Them Thar Code

We exist in a reality where, at best, documentation lives on the periphery, can’t be trusted, or even worse: non-existent. The development community has done a great job of banding together to help each other solve the many problems we face, but finding that perfect Stack Overflow post may not ever surface. Relying on the world to help you solve your problem is not a long term solution, so break those habits and trust in yourself as I attempt to show you the light that I call Inverted Debugging – the process of using code, not documentation to solve your development problems

Inverted Debugging Forged by Decades of Fire and Source Code

Look back on your development journey, think about how often you are faced with an error message and a stacktace – how much of your precious time has spent digging through google results, reading documentation, posting on forums, calling up your developer buddies only to feel defeated; I wouldn’t be surprised if I’ve spent an entire year of my life hoping the world will unblock my work. Ask yourself:

Why did I put my trust on anonymous internet dwellers over my own abilities?

I am not a better developer than you, no matter what you think reading my words or looking through my work; I simply have spent over 20 years getting beat down, frustrated, and defeated – eventually had had enough of it. The world wasn’t going to change, but I can. What was in my control was how I approached problem solving holistically: documentation is abstracting function into words, code on the other hand is concrete, logical. Through Inverted Debugging, you will have a greater understanding of frameworks, design patterns and appreciation in yourself.

RTFL: Read the Fucking Logs

An error is thrown and your first instinct is it’s a grenade – fight or flight. Why? That error is trying to communicate with you! Fight the frustration instinct, take a breath and read the logs, the message, the stack trace; take it in and look for a pattern, look for key code points, examine the call stack. You were just gifted the map to buried treasure! It’s possible the answer is right there on the screen, but more often than not you are about to grab a shovel and start digging.

Yes It Must Be Said…

Check dependency versions, perhaps a breaking change was introduced via an unpinned library. It is a quick was to rule out the obvious.

Embrace Your New Code Archeological Role

When unearthing your problem, you don’t want to miss the root by digging too fast, too deep, or impatient. Work backwards through the stack trace, examine the logic – think about edge cases, or missing checks.

Once you start to understand the code beneath your code, you become exposed to examples of good architectural patterns, or patterns to avoid. Almost second nature, you stop seeing languages – it becomes a small refresher on language syntax before you’re on your way.

And as Always…

You are not an imposter, in fact the goal is to elevate.

Documentation: A sTale As Old As Time

Documentation: it is the key to our tools. It has enabled us to evolve from coding into the prominent engineers on the planet. Documentation is slow, normally taking as much, if not more, time than the development itself. But every few hundred commits, development leaps forward – leaving documentation behind.

Professor Xavier, X-Men (if he were a software engineer rather than a mutant geneticist)

There is a war brewing; between those who put their faith in the documentation and those who fear the prophecies the staleness to come.

Documentation: Self-Documenting or Well-Documented

Do you think the thorough and comprehensive documentation of your project is enough to understand how to interact with it?

Do you think your code is so clean and intuitive that anyone can interface with it?

Everyone is wrong, and those who value tribal knowledge are toeing the line of true evil – but let’s leave them in the hole that they are digging for themselves. Every project starts with aspirations of perfection and standards of quality; admirable but completely unrealistic and unattainable.

Stale documentation absolutely will happen to your project. There is no way that documentation can be maintained at the same level and speed as the features and bug fixes – deadlines and goals change, small bits slip through the cracks. When it comes to documentation, words are nothing but representations of abstract ideas that no two people perceive in the same manner. No matter how thorough or complete you feel your documentation is, there will be those who do not understand; the more thorough your documentation, the harder it is to maintain.

Code is logical, as well as the currently defined instruction set, making the concept of self-documenting code more appetizing. Yet, code is an art and interpreted by the individual, understood at different levels, and can appear to be absolute magic – and a magician never reveals their secret (hint: it’s a reflection). What is intuitive to me is not intuitive to you, and if we wrote code to be so intuitive and easy to pick up….. well we can’t. Even Blockly and Scratch, which are intended to introduce children to programming concepts, seem alien to many – then when you introduce concepts like Aspect-Oriented Programming and Monkey Patching, you have entered the realm most dare not.

So, yes, the documentation for your library, REST API or plugin will, at some point, fail. End of story.

Documentation or Self-Documenting - neither can guarantee full understanding to others.

Pitch the Stale Documentation, Having the Ripe Idea

Credit where credit is due, this concept was not of mine but of my best friend James. He had grown tired of outdated, incorrect, or flat-out misleading API documentation. Most documentation aggregation software collects information from conformingly formatted comments – JavaDoc, API Doc, C# XML – but comments are not meant to be ingested by the application they are describing and are often the first to be forgotten when there is a fire burning. We have accepted that Tests should not be a second-class citizen, Test Driven Development is a proven standard, and we should not accept documentation to be either.

There’s a motto amongst software engineers “never trust your input”; but all this validating, verifying, authenticating… it’s boring, I am lazy and why can’t it just validate itself and let me move on to the fun part?

To focus on the fun part, you have to automate the boring stuff.

Swagger is a specification for documenting REST APIs down to every detail: payload definitions, request specifications, conditional arguments, content-type, required permissions, return codes – everything. The best part? The specification is consumable. Swagger even includes a browseable user interface that describes every facet and even lets you interact with the server visually. Play with a live demo yourself.

In Douglas Adams’ Hitchhiker’s Guide to the Galaxy, the Magrathean’s designed and built a computer to provide the ultimate answer; the answer to Life, the Universe, and Everything. This computer’s name? Deep Thought. An apt name for a library that would handle the answer, as long as you handled the question. Our proof of concept, DeeptThought-Routing, accepted your Swagger JSON specification along with endpoint handlers and a permissions provider as parameters and generated a NodeJS router that would validate paths, payloads, permissions even parsing the request against the Content-Type header and transforming the handler output to the Accepts header MIME type – not to mention attempt to transform or coerce parameters to the specification data types; returning the appropriate HTTP response codes without invoking your handlers. You could trust your inputs, no validation, free to focus on the core of the project – the fun part.

DeepThought: Documentation Driven Development

Schema-First means you define the schema (or specification) for your code, then utilize tooling that generates the code that matches the definitions, constraints, and properties within your schema. Through DeepThought-Routing, we had proven the concept of self-validation. Swagger did the same for self-documenting. Code generation is a core concept to GraphQL and JSON-Schema lists language-specific tooling for code generation.

There’s a pattern to all of this, patterns can be automated. A spark appeared in my mind: “Instead of implicit meaning through validation of inputs and documentation of primitive data types – what if the meaning were explicit, and if the meaning is explicit, they could validate themselves!” That spark became an inferno of chaining the initial “what-if” to “if that, then we could…”. When the fire of inspiration slowly receded to glowing embers, what was left was an idea. An idea that could change how software development is approached, at least, how I would approach development: Documentation Driven Development.

The Goals for Documentation Driven Development:

  • Self-Documenting: Clean, human readable documentation generated directly from the schema
  • Self-Describing: Meta-datatypes that provide meaning to the underlying data through labels and units
  • Self-Validating: Generates data models that enforce the constraints defined in the schema
  • Self-Testing: Unit tests are generated to test against constraints in models and data structures
  • UseCase-Scaffolding: Generates the signature of the functions that interact with external interfaces, and interface with the domain of your application
  • Correctness-Validation: Validates the units/labels of input model parameters can yield the units/labels of the use case output
  • Language-Agnostic: Plugin-based system for generation of code and documentation

Obviously, this is a large undertaking and still in its infancy – but I can see the path that will bring Documentation Driven Development from abstract concept to reality.

Example Schema

Here is a simplified example of how to model a submersible craft with meaningful data fields and perform an action on the speed of the craft.

Units

id: fathom
name: Fathoms
description: A length measure usually referring to a depth.
label:
  single: fathom
  plural: fathoms
  symbol: fm
conversion:
  unit: meter
  conversion: 1.8288

Unit Validating Meta-Schema Specification: https://github.com/constructorfleet/Deepthought-Schema/blob/main/meta-schema/unit.meta.yaml

Compound Units

id: knot
name: Knot
description: The velocity of a nautical vessel
symbol: kn
unit:  # The below yields the equivalent of meter/second
  unit: meter
  dividedBy:
    unit: second  
# Example for showing the extensibility of compound units
id: gravitational_constant
name: Gravitational Constant Units
description: The units for G, the gravitational constant
label:
  single: meter^3 per (kilogram * second^2)
  plural: meters^3 per (kilogram * second^2)
  symbol: m^3/(kg*s^2)
unit:
  unit: meter
  exponent: 3
  dividedBy:
    group:
      unit: kilogram
      multipliedBy:
        unit: second
        exponent: 2

Compound Unit Validating Meta-Schema Specification: https://github.com/constructorfleet/Deepthought-Schema/blob/main/meta-schema/compound-units.meta.yaml

Fields

id: depth
name: Depth
description: The depth of the submersible
dataType: number
unit: fathom
constraints:
- minimum:        # Explicit unit conversion
    value: 0
    unit: fathom
- maximum: 100    # Implicit unit conversion
- precision: 0.1  # Implicit unit conversion

id: velocity
type: number
name: Nautical Speed
description: The velocity of the craft moving
unit: knot
constraints:
  - minimum: 0
  - maximum: 12

Field Validating Meta-Schema Specification: https://github.com/constructorfleet/Deepthought-Schema/blob/main/meta-schema/field.meta.yaml

Models

id: submersible
name: Submarine Craft
description: A craft that can move through an aquatic medium
fields:
  - depth
  - velocity

Use Cases

id: adjust_submersible_speed
name: Adjusts the nautical speed of the craft
input:
  - submersible
  - speed
output:
  - submersible

You Have Questions, I Have a Few Answers

What the heck is a meaningful meta-datatype?

Traditionally, a field’s specification is broad, abstract, or meaningless which leads to writing documentation that attempts to provide meaning to the consumer of the code. We know what an Integer, Float, Byte, String, etc. are – but these are just labels we apply to ways of storing and manipulating data. If the author provides sufficient information regarding the acceptable values, ranges, and edge case – there is still the matter of writing a slew of tests to ensure the fields lie within the specifications. Not to mention, hoping the documentation remains in line with the implementation as teams, features, and requirements change and grow.

While still using the same underlying storage and manipulations considered familiar across the landscape, fields in DeepThought attempt to remove the tediousness, prevent stale documentation, as well as describing what is. Each field specifies a code-friendly reference name, a human-readable meaningful name, description, constraints, coercive manipulations of the underlying data type – plus units if applicable.

What if I don’t want or need units?

That’s perfectly fine! Obviously, you will lose some of the validation that arises from your schema. Not to mention that unit-less scalars and unit-less physical constants are absolutely a thing, along with physical constants.

How can you validate “correctness” of the Use Cases?

When specifying Use Cases, Deepthought has no knowledge of your implementation. Be it a single method, a chain of methods – that is up to you. Specify the input models, and the output model – knowing you can trust that your input is valid, tested, and in the exact form that is expected. Through dimensional analysis, it is possible to determine if the output model’s units are possible given the input models’ units. If dimensionally correct, DeepThought will generate the scaffolded code for you to implement – if incorrect, you will know before you even start coding.

Follow the Progress of DeepThought

I welcome all input – positive, negative, suggestions, complaints. While I am building this for myself, it would bring me great pleasure to see this adopted by others.

Unsung Heroes of Forums

Here’s to those who provide closure

I don’t even need to ask if you’ve been there, I know you have, we all have. So, let’s take a moment and give thanks to those amazing human beings that, even though they solved their own issue, took the time to provide the solution and especially those who can explain how they arrived there! We solute you: the Heroes of Forums!

Our Complete Network Overhaul

We had been having issues with our network: some spotty WiFi, laggy connections, etc. When the ethernet cable that runs from the server rack, around the basement, through the office ceiling, around the living room baseboard to the 8-port switch under our television – providing the oh-so-necessary bandwidth for 150Mbps of uncompressed mindblowing video and 7.2 Atmos surround sound – had an embolism. Yes, the ethernet cord just croaked. We tested the wall jacks, replaced them, tested the terminal ends, tried new last-mile cables… the cable tester showed a short between pins 1 and 2, confirmed with the multimeter. This is not a cable you can replace with a fish tape – disappearing into finished ceilings, running through walls. Let’s just say, we were pretty pissed.

We had new network gear sitting around for about a month now, procrastinating the installation as we knew it was going to be frustration, or at minimum a full day’s undertaking. With the network issues and our most precious of Cat6 runs dead, there really wasn’t much excuse not to. So, yesterday, we yanked the EdgeSwitch 48, EdgeSwitch 16 POE, the pFSense box, the who-knows-how-old dell connect switch, and similarly aged Linksys switch and racked up our new UniFi Switch 48, UniFi Switch 24, UniFi Switch 8 POEs, and the neat (in theory) UniFi Dream Machine Pro. While my partner was running the new Cat6 cables in the cabinet, I set to running 1/2″ raceway from the server rack out to the living room on the ceiling so we can get our 4K fix – when I hear cursing from the basement.

Turns out one of our storage nodes decided to report degraded disks. I’m not 100% sure on what the issue is or how he resolved it – I know very little of CephFS and didn’t want to distract from his repair work. So I cleaned up a bit around the house until the issue was resolved… but wouldn’t you know it – yeah, yesterday was a game of whack-a-mole-tech-problems – now that the storage array was back online, none of the machines could mount the volumes. Exhausted, pissed, frustrated, and pretty much falling asleep – he decides to give up for the night and wants to watch a movie. After mixing up some delicious Moscow Mules, he passes out 4 minutes into the movie but I’m wide awake.

Enter Teagan, P.I.

There were a million possible causes for the volumes failing to mount. My first hunch was the firewall, though before he gave up my partner listed numerous networking services (that I’d never heard of before) that the UniFi Dream Machine might be blocking. Our storage array consists of 4 nodes, 3 of which are fairly new, 12 bay, 16 core beasts but the other one, the same one that reported errors earlier, is a tiny little 1U box that is attached via SAS to a dumb disk shelf (CEPH01) – in fact, it only has 2Gbps ethernet while the others have 4 Gbps. Knowing this is important to understanding the first hypothesis: journalctl reported connecting to CEPH02 but losing connection and attempting CEPH01 followed by the connection timing out completely. Seems reasonable to assume CEPH01 was causing the timeout. So, I did the cabling, redid the LAGG assignments on the switches, reset the router – nothing.

Ok, so having little knowledge of CephFS – I needed to know what might be causing timeouts when mounting the remote volumes. To the Google! Here’s the thing though: the UniFi Dream Machine is fairly new, CephFS is a little niche, and combining the two? Forget it! From around 5am to almost 9am I searched for something, anything! Sure I got a few hits that seemed possible – but ended up going down rabbit holes. Then, at 8:48am (had to check my browser history), I stumble onto this post:

https://forum.proxmox.com/threads/laggy-ceph-status-and-got-timeout-in-proxmox-gui.50118

ftrojahn‘s description sounded nearly identical – except his stack is different; ProxMox not too long ago added native CephFS support to their software – if you want some experience with a decent piece of virtualization software and enterprise-level storage solution I would definitely recommend you check it out. ftrojahn not only explained the setup, issues, and attempts to diagnose the cause very well, did what few out there dare (care):

The Heroes We Need, and Deserve

It was an issue with mismatched MTU size! Do you know why this never crossed my mind? Because of this little toggle right here on the UDMPRO’s web interface:

Forum Heroes Light the Path - Mismatched Jumbo Frames caused by a toggle switch
Jumbo Frames traditionally set MTU to 9000

First, why do we care about MTU size – the default for most systems is 1500. By increasing the size of the frames, we reduce the number of packets being sent over the wire, as well as drastically reducing the ridiculous amounts of handshakes that happen between transmit and receive (if you are unfamiliar, here’s a link describing TCP handshakes). This is especially beneficial when you have terabytes of data flying around your network 24/7 like we do.

Yes, the Y-Axis has units of GB (Yes, Gigabytes!)

Ok, so Jumbo Frames are enabled, which should be 9000 – every host on our network has the MTU set to 9000 by default. Why is there still this timeout issue?

Well, I had luckily glanced at this post many hours earlier – notice the last comment, here’s the key piece:

Unfortunately, on newer Gen 2 devices, Jumbo Frames appear to be only 8184 bytes

https://community.ui.com/questions/When-you-enable-jumbo-frames-on-UDM-Pro-what-MTU-value-is-it-setting/04ceb4ec-aa5f-434d-abb3-2a14f3f6e1ed

Now, this little tidbit seems to be missing from any of the documentation I could find, so phastier you are a hero, we deserve more heroes in forums! The final challenge came down to the question: what the fuck do I do now? I love my partner, he has taught me so much about Linux, networking, DevOps – I wanted to show him all that knowledge has not gone to waste.

Making the UDMPRO My Bitch

It was time to learn what the hell MTUs really were and if any of the options on the web interface could help me. I found one: MSS Clamping – this sets the maximum segment size for TCP packets, maybe? HAHAHA NOPE! MSS tops out at 1452 – a little shy of the necessary 9000 (minus headers). Ok… time to get my hands dirty. The web interface isn’t the only way to configure this hunk of metal; in the past, my partner has made changes via SSH that are not available via the user interface. Since this device is a router and then some, I found it had 45 network interfaces – VLANs, bridges, loops, etc. While setting the MTU I found setting the MTU for the network interface is actually fairly easy: ip link set mtu 4096 dev eth0 I wasn’t about to run that command 45 times. Thankfully, /sys/class/net has an easily parsable list of the interface names.

ls -1 /sys/class/net | while read line ; do ip link set mtu 9000 dev $line ; done

With that one line, there was peace in the world… Ok not really but I was so proud of finding this solution I just had to wake him up to share the good news…