<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>tbox on Jahvon Dockery</title>
    <link>https://jahvon.dev/architecture/tbox/</link>
    <description>Recent content in tbox on Jahvon Dockery</description>
    <image>
      <title>Jahvon Dockery</title>
      <url>https://jahvon.dev/images/og-default.png</url>
      <link>https://jahvon.dev/images/og-default.png</link>
    </image>
    <generator>Hugo -- 0.153.4</generator>
    <language>en-us</language>
    <atom:link href="https://jahvon.dev/architecture/tbox/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Platform Integrations</title>
      <link>https://jahvon.dev/architecture/tbox/integrations/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/tbox/integrations/</guid>
      <description>The hubs and services tbox talks to, and the abstract device model that keeps the rest of the system from caring which is which.</description>
      <content:encoded><![CDATA[<h2 id="current-platform-integrations">Current Platform Integrations</h2>
<p><strong>Hubitat Hub Integration</strong></p>
<ul>
<li>Protocol: HTTP REST API using Makers API</li>
<li>Authentication: Access token with App ID</li>
<li>Communication: Local network for minimal latency</li>
<li>Event Handling: Webhook-based real-time device updates</li>
</ul>
<p><strong>Flair HVAC Integration</strong></p>
<ul>
<li>Protocol: OAuth 2.0 REST API with automatic token refresh</li>
<li>Components: Structures, rooms, HVAC units, sensor bridges</li>
<li>Capabilities: Mini-split control, room temperature monitoring</li>
</ul>
<p><strong>Weather Service Integration</strong> (currently disabled)</p>
<ul>
<li>Provider: WeatherAPI.com with API key authentication</li>
<li>Data Points: Temperature, humidity, conditions, feels-like temperature</li>
<li>Caching: Local cache with 30-minute refresh intervals</li>
</ul>
<h3 id="device-management-system">Device Management System</h3>
<p>Most of my devices are registered in the Hubitat platform, which provides a local API for device management.
I have also been evaluating Home Assistant as a potential alternative for future integrations but have not yet migrated.
The core requirements for the device management system include:</p>
<ul>
<li><strong>Unified Device Model</strong>: Abstract representation of devices across platforms</li>
<li><strong>Capability-Based Architecture</strong>: Devices expose capabilities like switches, sensors, and thermostats</li>
<li><strong>Room Organization</strong>: Devices are grouped by rooms with hierarchical structure</li>
</ul>
<p>Device states are synchronized into local cache storage, providing fast API responses while maintaining eventual consistency with upstream platforms.</p>
<p><strong>Abstract Device Interface</strong></p>
<p>All devices implement a common interface to ensure consistent interaction across platforms:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> Device <span style="color:#e67e80">interface</span> {
</span></span><span style="display:flex;"><span>  <span style="color:#b2c98f">ID</span>() <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Name</span>() <span style="color:#dbbc7f">string</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Room</span>() room.Room
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">MatchesName</span>(<span style="color:#dbbc7f">string</span>) <span style="color:#dbbc7f">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Capabilities</span>() []CapabilityName
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">As</span>(CapabilityName) Capability
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This interface is implemented for each platform / device type once and reused across the system.
It allows for flexible device management and interaction without needing to know the underlying platform details.</p>
<h4 id="capability-based-controls">Capability-Based Controls</h4>
<p>Devices expose capabilities that define their functionality, allowing for flexible control and automation. For instance:</p>
<ul>
<li>Switch capabilities for on/off control</li>
<li>Sensor capabilities for environmental data</li>
<li>Thermostat capabilities for HVAC control</li>
<li>Button capabilities for trigger events</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> Capability <span style="color:#e67e80">interface</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Name</span>() CapabilityName
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">IsStateful</span>() <span style="color:#dbbc7f">bool</span>
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">CurrentState</span>() CapabilityState
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">Merge</span>(Capability)
</span></span><span style="display:flex;"><span>	<span style="color:#b2c98f">SendCommand</span>(<span style="color:#dbbc7f">string</span>) <span style="color:#dbbc7f">error</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h4 id="room-organization">Room Organization</h4>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">type</span> Room <span style="color:#e67e80">struct</span> {
</span></span><span style="display:flex;"><span>	Name    <span style="color:#dbbc7f">string</span>   <span style="color:#b2c98f">`json:&#34;name,omitempty&#34;`</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>Events and Rules</title>
      <link>https://jahvon.dev/architecture/tbox/events/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://jahvon.dev/architecture/tbox/events/</guid>
      <description>The event pipeline, how state is enriched on the way through, and the YAML rules engine that acts on it.</description>
      <content:encoded><![CDATA[<pre><code>Aliases []string `json:&quot;aliases,omitempty&quot;`
Groups  []Group  `json:&quot;groups,omitempty&quot;`
Rank    uint     `json:&quot;rank,omitempty&quot;`
</code></pre>
<p>}</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>- Hierarchical room structure with groups and aliases
</span></span><span style="display:flex;"><span>- Device-to-room mapping for contextual automation
</span></span><span style="display:flex;"><span>- Room-based filtering and bulk operations functionality
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>### Automation Engine
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>#### Event Processing
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>The gateway now runs a fully async pipeline. HTTP ingestion returns immediately so device platforms are never blocked waiting on rule evaluation:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>1. HTTP ingestion returns 202 immediately (under 50ms)
</span></span><span style="display:flex;"><span>2. Deduplication window (5 seconds) prevents duplicate event processing
</span></span><span style="display:flex;"><span>3. Events are persisted to SQLite before processing, so no events are lost on failure
</span></span><span style="display:flex;"><span>4. Worker pool processes events in parallel (currently 10 workers)
</span></span><span style="display:flex;"><span>5. Enrichment stage attaches device metadata, room context, and previous state
</span></span><span style="display:flex;"><span>6. Rule evaluation and action execution across relevant platforms
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>#### Rules Engine
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Rules started as Go handlers and are moving toward declarative YAML definitions. Both coexist in the current system, with the Go approach still in place for backward compatibility.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>The Go handler interface:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>```go
</span></span><span style="display:flex;"><span>type Rule struct {
</span></span><span style="display:flex;"><span>	Name       string
</span></span><span style="display:flex;"><span>	Aliases    []string
</span></span><span style="display:flex;"><span>	HandleFunc Handler
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>type Handler func(Event, room.List, device.List) (handled bool, err error)
</span></span></code></pre></div><p>At the moment, all events flow through all rule handlers so they must handle their own filtering. The goal is smarter routing by device or event type, along with hot-reload so rules can be updated without a deployment.</p>
<p>YAML rule definitions are now available for basic conditions and actions, with validation enforced at load time.</p>
<h4 id="scene-management">Scene Management</h4>
<p>Scenes follow a similar structure to rules but are predefined automation scenarios that coordinate multiple devices across platforms. Currently they&rsquo;re defined in Go:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e67e80">func</span> <span style="color:#b2c98f">pbChillHandler</span>(_ room.List, curDevices device.List) (<span style="color:#dbbc7f">bool</span>, <span style="color:#dbbc7f">error</span>) {
</span></span><span style="display:flex;"><span>	tableLamp <span style="color:#7a8478">:=</span> curDevices.<span style="color:#b2c98f">GetDevice</span>(registry.PrimaryBedroomTableLamp)
</span></span><span style="display:flex;"><span>	ceilingLight <span style="color:#7a8478">:=</span> curDevices.<span style="color:#b2c98f">GetDevice</span>(registry.PrimaryBedroomCeilingLight)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>	<span style="color:#e67e80">switch</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#e67e80">case</span> timeframe.<span style="color:#b2c98f">CurrentDay</span>().<span style="color:#b2c98f">IsWeekday</span>() <span style="color:#7a8478">&amp;&amp;</span> timeframe.<span style="color:#b2c98f">Night</span>().<span style="color:#b2c98f">CurTimeInFrame</span>():
</span></span><span style="display:flex;"><span>		<span style="color:#e67e80">if</span> err <span style="color:#7a8478">:=</span> tableLamp.<span style="color:#b2c98f">As</span>(device.SwitchName).<span style="color:#b2c98f">SendCommand</span>(device.OnCommand); err <span style="color:#7a8478">!=</span> <span style="color:#e67e80">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#e67e80">return</span> <span style="color:#e67e80">false</span>, err
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#e67e80">if</span> err <span style="color:#7a8478">:=</span> ceilingLight.<span style="color:#b2c98f">As</span>(device.SwitchName).<span style="color:#b2c98f">SendCommand</span>(device.OffCommand); err <span style="color:#7a8478">!=</span> <span style="color:#e67e80">nil</span> {
</span></span><span style="display:flex;"><span>			<span style="color:#e67e80">return</span> <span style="color:#e67e80">false</span>, err
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>		<span style="color:#e67e80">return</span> <span style="color:#e67e80">true</span>, <span style="color:#e67e80">nil</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e67e80">default</span>:
</span></span><span style="display:flex;"><span>		<span style="color:#e67e80">return</span> <span style="color:#e67e80">false</span>, <span style="color:#e67e80">nil</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This works but is a bit clunky. The goal is to get to declarative YAML definitions that can be created and edited without touching Go code:</p>
<div class="highlight"><pre tabindex="0" style="color:#d6cbb4;background-color:#252b2e;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#7a8478">scenes</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#7a8478">PBRChill</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">devices</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#7a8478">room</span>: <span style="color:#b2c98f">&#34;primary_bedroom&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7a8478">type</span>: <span style="color:#b2c98f">&#34;switch&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7a8478">action</span>: <span style="color:#b2c98f">&#34;dim_to_30&#34;</span>
</span></span><span style="display:flex;"><span>      - <span style="color:#7a8478">room</span>: <span style="color:#b2c98f">&#34;primary_bedroom&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7a8478">type</span>: <span style="color:#b2c98f">&#34;hvac&#34;</span>
</span></span><span style="display:flex;"><span>        <span style="color:#7a8478">action</span>: <span style="color:#b2c98f">&#34;cool_to_68&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#7a8478">window</span>: <span style="color:#b2c98f">&#34;weekday night&#34;</span>
</span></span></code></pre></div><p>YAML scene definitions are planned alongside the dashboard UI.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
