<?xml version='1.0' encoding='UTF-8'?><feed xmlns="http://www.w3.org/2005/Atom"><title type="html">kevinschweikert.de</title><id>kevinschweikert.de</id><updated>2026-04-27T12:19:04.486974583Z</updated><link href="https://kevinschweikert.de/feed.xml" rel="self"/><author><name>Kevin Schweikert</name></author><entry><title type="html">Composable SFTP streams in Elixir</title><link href="https://kevinschweikert.de/posts/composable-sftp-streams/" rel="alternate"/><id>composable-sftp-streams</id><published>2026-04-26T00:00:00Z</published><updated>2026-04-26T00:00:00Z</updated><summary type="html">Stream files over SFTP using Elixir&#39;s Stream and Collectable protocols</summary><content type="html">&lt;!-- lustre:fragment --&gt;&lt;h1&gt;Composable SFTP streams in Elixir&lt;/h1&gt;&lt;div class=&quot;text-sm&quot;&gt;Published on &lt;time datetime=&quot;2026-04-26&quot;&gt;April 26, 2026&lt;/time&gt;&lt;!-- lustre:fragment --&gt;&lt;!-- /lustre:fragment --&gt;&lt;!-- lustre:fragment --&gt; · &lt;a href=&quot;https://github.com/kevinschweikert/kevinschweikert.de/commit/f294096ea496004e62cc685aa4fc423558256d67&quot;&gt;f294096e&lt;/a&gt;&lt;!-- /lustre:fragment --&gt;&lt;/div&gt;&lt;div class=&quot;note note&quot;&gt;&lt;p&gt;I&amp;#39;m still new to writing and I want to disclose that AI helped in creating this post. All words are my own but AI was used in spelling correction and structural discussions.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Recently I was tasked to add media file sync feature via SFTP. OTP has all the necessary tools for this so I started with:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;{:ok, channel, connection_ref} =
  :ssh_sftp.start_channel(&amp;quot;example.com&amp;quot;, 22)

video_file = File.read!(&amp;quot;my_video.mp4&amp;quot;)
:ok = :ssh_sftp.write_file(channel, &amp;quot;/var/media/video.mp4&amp;quot;, video_file)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;But there&amp;#39;s a big (literally!) problem. By using &lt;a href=&quot;https://hexdocs.pm/elixir/File.html#read/1&quot;&gt;&lt;code&gt;File.read!/1&lt;/code&gt;&lt;/a&gt; the whole file gets loaded into memory. For small files this is fine but in our case we had intraframe heavy video files which can be hundreds of GB big. We don&amp;#39;t want them to be loaded into RAM. But Elixir has a solution for this - lazy enumerables.&lt;/p&gt;&lt;p&gt;Elixir differentiates between two kinds of enumerables. Eager (&lt;a href=&quot;https://hexdocs.pm/elixir/Enum.html&quot;&gt;&lt;code&gt;Enum&lt;/code&gt;&lt;/a&gt; module) and lazy (&lt;a href=&quot;https://hexdocs.pm/elixir/Stream.html&quot;&gt;&lt;code&gt;Stream&lt;/code&gt;&lt;/a&gt; module). Both build on the &lt;a href=&quot;https://hexdocs.pm/elixir/Enumerable.html&quot;&gt;&lt;code&gt;Enumerable&lt;/code&gt; protocol&lt;/a&gt; but differ in how they are consumed. Lazy means the enumerable is only calculated when consumed.&lt;/p&gt;&lt;p&gt;We can enumerate the contents of a file lazily by opening it as a stream with &lt;a href=&quot;https://hexdocs.pm/elixir/File.html#stream!/2&quot;&gt;&lt;code&gt;File.stream!/2&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;chunk_size = 2 ** 16
file_stream = File.stream!(&amp;quot;my_big_video.mov&amp;quot;, chunk_size)
&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;info info&quot;&gt;&lt;p&gt;Because this is a &lt;code&gt;Stream&lt;/code&gt; the file will be read in 64KB chunks one after another when we enumerate it. Until then, nothing will be loaded into memory. The 64KB are a tradeoff between memory consumption and network overhead. This can be tuned for specific use cases.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;We could end it here and use it like this:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;{:ok, handle} =
  :ssh_sftp.open(channel, &amp;quot;/archive/my_big_video.mov&amp;quot;, [:write, :binary])

for chunk &amp;lt;- file_stream do
  :ok = :ssh_sftp.write(channel, handle, chunk)
end

:ok = :ssh_sftp.close(channel, handle)
:ok = :ssh.close(connection_ref)
&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;info info&quot;&gt;&lt;p&gt;The &lt;code&gt;for&lt;/code&gt; comprehension enumerates the stream, and now the chunks will be consumed&lt;/p&gt;&lt;/div&gt;&lt;p&gt;From a functional standpoint this works as desired. But from an architectural point of view it has several drawbacks:&lt;/p&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;No cleanup on failure. If &lt;a href=&quot;https://www.erlang.org/doc/apps/ssh/ssh_sftp.html#write/3&quot;&gt;&lt;code&gt;:ssh_sftp.write/3&lt;/code&gt;&lt;/a&gt; returns an error, we raise. If we remove the match, we&amp;#39;d have to inspect the resulting chunk results.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Leaks the Erlang API. Every place that uploads a file has to know about &lt;a href=&quot;https://www.erlang.org/doc/apps/ssh/ssh_sftp.html#open/3&quot;&gt;&lt;code&gt;:ssh_sftp.open/3&lt;/code&gt;&lt;/a&gt;, &lt;code&gt;:ssh_sftp.write/3&lt;/code&gt; and &lt;a href=&quot;https://www.erlang.org/doc/apps/ssh/ssh_sftp.html#close/2&quot;&gt;&lt;code&gt;:ssh_sftp.close/2&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Not a value. We&amp;#39;d have to wrap everything in a function but now the caller has to actually call it. An external library would need to know, how to use it.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Let&amp;#39;s go back to the file streaming example and extend it to a sink, because &lt;code&gt;File.stream!/2&lt;/code&gt; has one more trick up its sleeve! It also implements the  &lt;a href=&quot;https://hexdocs.pm/elixir/Collectable.html&quot;&gt;&lt;code&gt;Collectable&lt;/code&gt; protocol&lt;/a&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;source_stream = File.stream!(&amp;quot;my_big_video.mov&amp;quot;, chunk_size)
destination_stream =
  File.stream!(&amp;quot;/archive/my_big_video.mov&amp;quot;, chunk_size)

Stream.into(source_stream, destination_stream)
|&amp;gt; Stream.run()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;a href=&quot;https://hexdocs.pm/elixir/Stream.html#into/2&quot;&gt;&lt;code&gt;Stream.into/2&lt;/code&gt;&lt;/a&gt; (or &lt;a href=&quot;https://hexdocs.pm/elixir/Enum.html#into/2&quot;&gt;&lt;code&gt;Enum.into/2&lt;/code&gt;&lt;/a&gt; for the eager version) is the missing link here, and consumes the enumerable from the first argument and collects it into the second argument.&lt;/p&gt;&lt;p&gt;This is basically what we want to do but with the SFTP stream as the sink.&lt;/p&gt;&lt;div class=&quot;tip tip&quot;&gt;&lt;p&gt;In Elixir a protocol can be implemented on any datatype including custom structs! &lt;code&gt;File.stream!/2&lt;/code&gt; returns a &lt;code&gt;%File.Stream&lt;/code&gt; struct which implements &lt;code&gt;Enumerable&lt;/code&gt; and &lt;code&gt;Collectable&lt;/code&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;This gives us all we need. Protocols are an Elixir concept so we don&amp;#39;t have a streaming collectable SFTP sink in OTP. But nothing stops us from implementing it on our own data structure.&lt;/p&gt;&lt;p&gt;Let&amp;#39;s write a simple example to understand the structure of the &lt;code&gt;Collectable&lt;/code&gt; protocol:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;defmodule MyInspector do
  defstruct []

  defimpl Collectable do
    def into(struct) do
      collector_fun = fn
        _acc, {:cont, chunk} -&amp;gt; IO.inspect(chunk)
        acc, :done -&amp;gt; acc
        acc, :halt -&amp;gt; acc
      end

      {struct, collector_fun}
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;into/1&lt;/code&gt; returns a tuple of the &lt;code&gt;{initial accumulator, collector_fun}&lt;/code&gt;. The collector is a 2-arity function called for each step in the enumeration:&lt;/p&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;acc, {:cont, chunk}&lt;/code&gt;: called on a new chunk&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;acc, :done&lt;/code&gt;: called when enumerable has no more chunks&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;acc, :halt&lt;/code&gt;: called when enumeration is aborted&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;And put it to action:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;source_stream = File.stream!(&amp;quot;my_big_video.mov&amp;quot;, chunk_size)

Stream.into(source_stream, %MyInspector{})
|&amp;gt; Stream.take(5)
|&amp;gt; Stream.run()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#39;s combine everything we&amp;#39;ve learned into implementing the &lt;code&gt;Collectable&lt;/code&gt; protocol for a custom SFTP stream.&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;defmodule SFTP.Stream do
  defstruct [:channel, :path, :handle, :status]

  def build(channel, path) when is_pid(channel) and is_binary(path),
    do: %__MODULE__{channel: channel, path: path}

  defimpl Collectable do
    def into(%{channel: channel, path: path} = stream) do
      {:ok, handle} = :ssh_sftp.open(channel, path, [:write, :binary])
      stream = %{stream | handle: handle, status: :ok}

      collector_fun = fn
        %__MODULE__{status: :ok, handle: handle} = stream , {:cont, chunk} -&amp;gt;
          case :ssh_sftp.write(channel, handle, chunk) do
            :ok -&amp;gt; stream
            {:error, err} -&amp;gt; %{stream | status: err}
          end
        
        stream , {:cont, _chunk} -&amp;gt;
          # status != :ok
          # we don&amp;#39;t do anything anymore and wait for the :done call
          stream

        %__MODULE__{status: status, handle: handle, channel: channel}, :done -&amp;gt; 
          :ssh_sftp.close(channel, handle)
          status

        %__MODULE__{handle: handle, channel: channel}, :halt -&amp;gt;
          :ssh_sftp.close(channel, handle)
      end

      {stream, collector_fun}
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Back to our example but this time with our new implementation:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;file_stream = File.stream!(&amp;quot;my_big_video.mov&amp;quot;, chunk_size)

{:ok, channel, connection_ref} =
  :ssh_sftp.start_channel(&amp;quot;example.com&amp;quot;, 22)
sftp_stream = SFTP.Stream.build(channel, &amp;quot;my_big_video.mov&amp;quot;)

Stream.into(file_stream, sftp_stream)
|&amp;gt; Stream.run()

:ok = :ssh.close(connection_ref)
&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;note note&quot;&gt;&lt;p&gt;Notice that &lt;a href=&quot;https://www.erlang.org/doc/apps/ssh/ssh_sftp.html#start_channel/2&quot;&gt;&lt;code&gt;:ssh_sftp.start_channel/2&lt;/code&gt;&lt;/a&gt; lives in the caller&amp;#39;s code and not inside the &lt;code&gt;Collectable&lt;/code&gt; implementation. That&amp;#39;s by design: opening an SSH connection is expensive (network, key exchange, auth) and we may want to transfer many files through one channel. We can always wrap it in a convenience function if needed.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Remember the file transfer example? This is exactly the same, we just swapped the destination file stream for an SFTP file stream. Without changing the code which loads from a file or how the chunks are consumed!&lt;/p&gt;&lt;h2 id=&quot;Why-bother-implementing-protocols&quot;&gt;Why bother implementing protocols?&lt;/h2&gt;&lt;p&gt;The protocol implementation seems more complicated than the for comprehension. So why should we go through the trouble of implementing it?&lt;/p&gt;&lt;p&gt;Because now it is flexible to work with. The &lt;code&gt;Collectable&lt;/code&gt; protocol is defined in the Elixir standard library, so any other project can either implement it or work with it.&lt;/p&gt;&lt;p&gt;For example the &lt;a href=&quot;https://hex.pm/packages/req&quot;&gt;&lt;code&gt;Req&lt;/code&gt;&lt;/a&gt; library accepts a &lt;code&gt;Collectable&lt;/code&gt; with the &lt;a href=&quot;https://hexdocs.pm/req/0.5.17/Req.html#new/1&quot;&gt;&lt;code&gt;:into&lt;/code&gt;&lt;/a&gt; option. This allows us to stream an HTTP response into a file over SFTP:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;sftp_stream = SFTP.Stream.build(channel, &amp;quot;my_downloaded_file&amp;quot;)
Req.new(url: &amp;quot;https://example.com/download&amp;quot;, into: sftp_stream)
|&amp;gt; Req.get!()
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Remember the &lt;code&gt;for&lt;/code&gt; comprehension from the beginning? It also has an  &lt;a href=&quot;https://hexdocs.pm/elixir/Kernel.SpecialForms.html#for/1&quot;&gt;&lt;code&gt;:into&lt;/code&gt;&lt;/a&gt; option:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;for chunk &amp;lt;- file_stream, into: sftp_stream, do: chunk
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;What&amp;#39;s important is that the caller never needs to know any of this. From the outside, our SFTP destination looks identical to &lt;code&gt;File.stream!/2&lt;/code&gt;. We can swap one for the other, compose them with &lt;code&gt;Stream.into/2&lt;/code&gt;, pass the resulting stream to a Task or a worker queue — all without changing the pipeline. &lt;/p&gt;&lt;p&gt;Turns out, it wasn&amp;#39;t really about SFTP. It was about using the right abstractions and creating a building block. Think about it next time when you upload files to S3 or create a big CSV.&lt;/p&gt;&lt;p&gt;If you want to play around with this concepts use &lt;a href=&quot;/files/composable-sftp-streams.livemd&quot;&gt;this Livebook&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://livebook.dev/run?url=https%3A%2F%2Fkevinschweikert.de%2Ffiles%2Fcomposable-sftp-streams.livemd&quot;&gt;&lt;img alt src=&quot;https://livebook.dev/badge/v1/black.svg&quot;&gt;&lt;/a&gt;&lt;/p&gt;&lt;!-- /lustre:fragment --&gt;</content></entry><entry><title type="html">Build Home Assistant Devices with Elixir and Nerves</title><link href="https://kevinschweikert.de/posts/build-home-assistant-devices-with-elixir-and-nerves/" rel="alternate"/><id>build-home-assistant-devices-with-elixir-and-nerves</id><published>2026-01-18T00:00:00Z</published><updated>2026-01-26T00:00:00Z</updated><summary type="html">Learn how to integrate Elixir applications and Nerves devices into Home Assistant using MQTT</summary><content type="html">&lt;!-- lustre:fragment --&gt;&lt;h1&gt;Build Home Assistant Devices with Elixir and Nerves&lt;/h1&gt;&lt;div class=&quot;text-sm&quot;&gt;Published on &lt;time datetime=&quot;2026-01-18&quot;&gt;January 18, 2026&lt;/time&gt;&lt;!-- lustre:fragment --&gt; · updated on &lt;time datetime=&quot;2026-01-26&quot;&gt;January 26, 2026&lt;/time&gt;&lt;!-- /lustre:fragment --&gt;&lt;!-- lustre:fragment --&gt; · &lt;a href=&quot;https://github.com/kevinschweikert/kevinschweikert.de/commit/af13e2bbdf0151620b22ac59a1bc59359cfa88f6&quot;&gt;af13e2bb&lt;/a&gt;&lt;!-- /lustre:fragment --&gt;&lt;/div&gt;&lt;p&gt;I love &lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;Home Assistant&lt;/a&gt;. I love that I can throw in all these devices from different manufacturers, display their data in a unified dashboard and write automations that combine devices never intended to know of each other. Like switching on Philips Hue lights when the IKEA door sensor opens, or turning on the switch of a power outlet to activate a dehumidifier when an Aqara humidity sensor reports high values.&lt;/p&gt;&lt;p&gt;But wouldn&amp;#39;t it be nice to integrate devices we built ourselves?&lt;/p&gt;&lt;p&gt;I was working through the Book &lt;a href=&quot;https://pragprog.com/titles/passweather/build-a-weather-station-with-elixir-and-nerves/&quot;&gt;Build a Weather Station with Elixir and Nerves&lt;/a&gt; where the authors show how to integrate sensors for temperature, humidity, pressure and air quality to then collect the measurements on a &lt;a href=&quot;https://nerves-project.org/&quot;&gt;Nerves&lt;/a&gt;. In the second part of the book, we learn how to create a Phoenix API to receive the measurements and how to set up Grafana to visualize them. This is extremely cool but still does not show all my other devices or let&amp;#39;s me control them.&lt;/p&gt;&lt;p&gt;So, last year I had the idea to combine the learnings from the book and my own Home Assistant journey and posted in the &lt;a href=&quot;https://elixirforum.com/t/nerves-home-assistant-integration/70920&quot;&gt;Elixir Forum&lt;/a&gt; my initial ideas on how to tackle this. &lt;/p&gt;&lt;p&gt;I&amp;#39;ve outlined three options on how to communicate and interact with Home Assistant.&lt;/p&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://www.home-assistant.io/integrations/mqtt&quot;&gt;MQTT&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://developers.home-assistant.io/docs/api/rest&quot;&gt;REST&lt;/a&gt; / &lt;a href=&quot;https://developers.home-assistant.io/docs/api/websocket&quot;&gt;WebSocket API&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://developers.home-assistant.io/docs/creating_component_index&quot;&gt;Native Python integration&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I quickly discovered that you cannot create new entities via the REST/WebSocket API and that at least the first iteration should be all in Elixir instead of writing a whole new integration in Python. So I settled on MQTT.&lt;/p&gt;&lt;p&gt;Before we start, I want to clarify some wording i&amp;#39;ll be using:&lt;/p&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;entity&lt;/code&gt; - one thing you can see/control (switch, sensor, camera)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;device&lt;/code&gt; - a thing (often physical) that groups entities (the Nerves weather station)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;integration&lt;/code&gt; - the code that let&amp;#39;s Home Assistant interact with a device/brand (MQTT, Zigbee, Unifi, …)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;platform&lt;/code&gt; - the entity type within an integration (switch, sensor, camera)&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;MQTT&quot;&gt;MQTT&lt;/h2&gt;&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;[Elixir Application] &amp;lt;--&amp;gt; [MQTT Broker] &amp;lt;--&amp;gt; [Home Assistant]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To add our own entities, Home Assistant has a built-in &lt;a href=&quot;https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery&quot;&gt;auto discovery mechanism&lt;/a&gt; when using MQTT. There are different options like device or single component discovery but here&amp;#39;s what we are doing.&lt;/p&gt;&lt;h3 id=&quot;Discovery&quot;&gt;Discovery&lt;/h3&gt;&lt;p&gt;Home Assistant listens on a pre-configured MQTT topic for a JSON payload which is the discovery message. The default topic is:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;/homeassistant/device/[YOUR_DEVICE_ID]/config
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;and it expects a payload like this:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;json&quot;&gt;{
  &amp;quot;device&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;My Device&amp;quot;,
    &amp;quot;identifiers&amp;quot;: [
      &amp;quot;My Device&amp;quot;
    ]
  },
  &amp;quot;origin&amp;quot;: {
    &amp;quot;name&amp;quot;: &amp;quot;homex&amp;quot;
  },
  &amp;quot;components&amp;quot;: {
    &amp;quot;my_switch_44962374&amp;quot;: {
      &amp;quot;name&amp;quot;: &amp;quot;my-switch&amp;quot;,
      &amp;quot;platform&amp;quot;: &amp;quot;switch&amp;quot;,
      &amp;quot;unique_id&amp;quot;: &amp;quot;my_switch_44962374&amp;quot;,
      &amp;quot;state_topic&amp;quot;: &amp;quot;homex/switch/my_switch_44962374&amp;quot;,
      &amp;quot;command_topic&amp;quot;: &amp;quot;homex/switch/my_switch_44962374/set&amp;quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;platform&lt;/strong&gt;: this is a common and required field for all components and lets Home Assistant know how to treat it and which configuration values to expect&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;unique_id&lt;/strong&gt;: important when you want to have Home Assistant remember the component across restarts&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;All other fields are dependent on the kind of platform. A detailed documentation can be found on the Home Assistant
website, e.g. &lt;a href=&quot;https://www.home-assistant.io/integrations/switch.mqtt/&quot;&gt;MQTT Switch&lt;/a&gt;&lt;/p&gt;&lt;div class=&quot;info info&quot;&gt;&lt;p&gt;Here, Home Assistant publishes commands to &lt;code&gt;state_topic&lt;/code&gt;, and your app publishes updates to &lt;code&gt;command_topic&lt;/code&gt;.&lt;/p&gt;&lt;/div&gt;&lt;h3 id=&quot;Client&quot;&gt;Client&lt;/h3&gt;&lt;p&gt;There are only a few MQTT clients in Elixir and i&amp;#39;ve settled on &lt;a href=&quot;https://hex.pm/packages/emqtt&quot;&gt;&lt;code&gt;emqtt&lt;/code&gt;&lt;/a&gt; which is an Erlang library made by the &lt;a href=&quot;https://www.emqx.com&quot;&gt;EMQX&lt;/a&gt; folks. EMQX is a MQTT broker and the client seems actively developed. It is also the only one which supports MQTT 5.0. The only current thing to look out for are some native/&lt;a href=&quot;https://www.erlang.org/doc/system/nif.html&quot;&gt;NIF&lt;/a&gt; dependencies for &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Glossary/QUIC&quot;&gt;QUIC&lt;/a&gt; support which are hard to build on Nerves. But we can override if we want to when using the &lt;code&gt;git&lt;/code&gt; dependency:&lt;/p&gt;&lt;pre data-file=&quot;mix.exs&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;def deps() do
  ...
  {:emqtt, github: &amp;quot;emqx/emqtt&amp;quot;, system_env: [{&amp;quot;BUILD_WITHOUT_QUIC&amp;quot;, &amp;quot;1&amp;quot;}]}
end
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;Building-it&quot;&gt;Building it&lt;/h2&gt;&lt;p&gt;The following examples focus on functionality and ignore error handling in the most places. If you want to jump to the finished version go to &lt;a href=&quot;#Homex&quot;&gt;Homex&lt;/a&gt;&lt;/p&gt;&lt;h3 id=&quot;Manager&quot;&gt;Manager&lt;/h3&gt;&lt;p&gt;Let&amp;#39;s create a &lt;a href=&quot;https://hexdocs.pm/elixir/GenServer.html&quot;&gt;&lt;code&gt;GenServer&lt;/code&gt;&lt;/a&gt;, creatively called &lt;code&gt;Homex.Manager&lt;/code&gt; to handle the client life-cycle and discovery mechanism. Here we send an empty components payload on startup:&lt;/p&gt;&lt;pre data-file=&quot;lib/manager.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;defmodule Homex.Manager do
  use GenServer

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end
  
  @impl GenServer
  def init(_opts) do
    {:ok, emqtt_pid} = :emqtt.start_link(host: &amp;quot;localhost&amp;quot;, user: &amp;quot;admin&amp;quot;, password: &amp;quot;admin&amp;quot;)
    :ok = :emqtt.connect(emqtt_pid)

    {:ok, emqtt_pid, {:continue, :discovery}}
  end

  @impl GenServer
  def handle_continue(:discovery, emqtt_pid) do

    # TODO: lookup entities to fill discovery components
  
    discovery_config = %{
      device: %{name: &amp;quot;Homex&amp;quot;, identifiers: [&amp;quot;Homex&amp;quot;]},
      origin: %{name: &amp;quot;homex&amp;quot;},
      components: %{}
    }

    payload = Jason.encode!(discovery_config)
    :emqtt.publish(emqtt_pid, &amp;quot;/homeassistant/device/homex/config&amp;quot;, payload)
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we need to know when entities are added or when they are removed. They need to somehow register themselves and let the manager know about them. This sounds like a job for the Elixir &lt;a href=&quot;https://hexdocs.pm/elixir/Registry.html&quot;&gt;&lt;code&gt;Registry&lt;/code&gt;&lt;/a&gt; module. We can start a new Registry in our application and set the Manager as a listener, so that we get notified when an Entity registers itself there:&lt;/p&gt;&lt;pre data-file=&quot;application.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;{Registry,
  name: Homex.SubscriptionRegistry,
  keys: :duplicate,
  listeners: [Homex.Manager]
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The keys will be the subscribe topics and we will receive the following events in &lt;code&gt;Homex.Manager&lt;/code&gt;:&lt;/p&gt;&lt;pre data-file=&quot;lib/manager.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;def handle_info({:register, _registry, topic, _pid, _value}, emqtt_pid) do
  :emqtt.subscribe(emqtt_pid, topic)
  {:noreply, emqtt_pid, {:continue, :discovery}}
end

def handle_info({:unregister, _registry, topic, _pid}, emqtt_pid) do
  :emqtt.unsubscribe(emqtt_pid, topic)
  {:noreply, emqtt_pid, {:continue, :discovery}}
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Perfect! Now we get notified when a new Entity registers itself and we subscribe to the requested topics. If a new message on the subscribed topics arrives, we have to delegate that message to the registered entities. We can make use of &lt;a href=&quot;https://hexdocs.pm/elixir/Registry.html#dispatch/4&quot;&gt;&lt;code&gt;Registry.dispatch/4&lt;/code&gt;&lt;/a&gt; to implement our own PubSub mechanism:&lt;/p&gt;&lt;pre data-file=&quot;lib/manager.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;def handle_info({:publish, %{topic: topic, payload: payload}}, emqtt_pid) do
  Registry.dispatch(Homex.SubscriptionRegistry, topic, fn registered -&amp;gt;
    for {entity_pid, _value} &amp;lt;- registered do
      send(entity_pid, {:message, topic, payload})
    end
  end)

  {:noreply, emqtt_pid}
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We just need to give the future entities a way to publish new messages in return:&lt;/p&gt;&lt;pre data-file=&quot;lib/manager.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;def publish(topic, message) do
  GenServer.call(__MODULE__, {:publish, topic, message})
end

def handle_call({:publish, topic, message}, _from, emqtt_pid) do
  payload = Jason.encode!(message)
  result = :emqtt.publish(emqtt_pid, topic, payload)
  {:reply, result, emqtt_pid}
end
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id=&quot;Entity&quot;&gt;Entity&lt;/h3&gt;&lt;p&gt;Almost done! Before we implement the real entities, we plan ahead and define a common interface for all future entity implementations:&lt;/p&gt;&lt;pre data-file=&quot;lib/entity.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;defmodule Homex.Entity do
  @callback unique_id() :: String.t()
  @callback component() :: Map.t()
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Alright, final stretch! This is the base for our switch entity:&lt;/p&gt;&lt;pre data-file=&quot;lib/entity.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;defmodule Homex.Entity.Switch do
  use GenServer

  @name &amp;quot;My Switch&amp;quot;
  @platform &amp;quot;switch&amp;quot;
  @unique_id &amp;quot;my_switch_#{:erlang.phash2([@platform, __MODULE__])}&amp;quot;
  @state_topic &amp;quot;/homex/switch/my_switch&amp;quot;
  @command_topic &amp;quot;/homex/switch/my_switch/set&amp;quot;

  def start_link(opts) do
    GenServer.start_link(__MODULE__, opts, name: __MODULE__)
  end
  
  @impl GenServer
  def init(_opts) do
    Registry.register(Homex.SubscriptionRegistry, @state_topic, __MODULE__)
    {:ok, :off}
  end

  @impl GenServer
  def handle_info({:message, @state_topic, payload}, state) do
    case payload do
      &amp;quot;ON&amp;quot; -&amp;gt; {:noreply, :on}
      &amp;quot;OFF&amp;quot; -&amp;gt; {:noreply, :off}
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We defined a stable unique ID, registered our process in the registry and will handle a message on the state topic. We don&amp;#39;t have to handle the command topic, because we will only write to it. Let&amp;#39;s also implement the &lt;code&gt;Entity&lt;/code&gt; behaviour, we defined earlier:&lt;/p&gt;&lt;pre data-file=&quot;lib/entity/swtich.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;@behaviour Homex.Entity

def unique_id(), do: @unique_id

def component() do
  %{
    name: @name,
    platform: @platform,
    unique_id: @unique_id,
    state_topic: @state_topic,
    command_topic: @command_topic
  }
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is enough to handle state updates from the Home Assistant side but we also want to manipulate the switch from our side. Let&amp;#39;s define a public API and the necessary handlers:&lt;/p&gt;&lt;pre data-file=&quot;lib/entity/swtich.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;def toggle() do
  GenServer.cast(__MODULE__, :toggle)
end

@impl GenServer
def handle_cast(:toggle, :on) do
  Homex.Manager.publish(@command_topic, &amp;quot;OFF&amp;quot;)
  {:noreply, :off}
end

def handle_cast(:toggle, :off) do
  Homex.Manager.publish(@command_topic, &amp;quot;ON&amp;quot;)
  {:noreply, :on}
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We can start the &lt;code&gt;Homex.Manager&lt;/code&gt; statically but the entities are more dynamic. So we will use a &lt;a href=&quot;https://hexdocs.pm/elixir/DynamicSupervisor.html&quot;&gt;&lt;code&gt;DynamicSupervisor&lt;/code&gt;&lt;/a&gt;. The supervisor can tell us which children are started. That&amp;#39;s why we don&amp;#39;t need a separate Registry to figure out which modules/components are started and should be included in the discovery message:&lt;/p&gt;&lt;pre data-file=&quot;application.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;{DynamicSupervisor, name: Homex.EntitySupervisor, strategy: :one_for_one}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This starts a new DynamicSupervisor with the strategy &lt;code&gt;:one_for_one&lt;/code&gt; which only restarts the crashed process and no other children:&lt;/p&gt;&lt;pre data-file=&quot;lib/manager.ex&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;def add_entity(module) do
  GenServer.call(__MODULE__, {:add_entity, module})  
end

def handle_call({:add_entity, module}, _from, emqtt_pid) do
  {:ok, _pid} = DynamicSupervisor.start_child(Homex.EntitySupervisor, module)
  {:reply, :ok, {:continue, :discovery}}
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And last but not least we can update our &lt;a href=&quot;https://hexdocs.pm/elixir/GenServer.html#c:handle_continue/2&quot;&gt;&lt;code&gt;handle_continue/2&lt;/code&gt;&lt;/a&gt; function to send a full discovery message containing all supervised entities:&lt;/p&gt;&lt;pre data-file=&quot;lib/manager.ex&quot; data-lang=&quot;elixir&quot;&gt;&lt;code data-lang=&quot;elixir&quot;&gt;def handle_continue(:discovery, emqtt_pid) do
  entities =
    DynamicSupervisor.which_children(Homex.EntitySupervisor)
    |&amp;gt; Enum.map(fn {_id, _pid, _type, modules} -&amp;gt; modules end)
    |&amp;gt; List.flatten()

  components =
    for module &amp;lt;- entities, into: %{} do
      {module.unique_id(), module.component()}
    end

  discovery_config = %{
    device: %{name: &amp;quot;Homex&amp;quot;, identifiers: [&amp;quot;Homex&amp;quot;]},
    origin: %{name: &amp;quot;homex&amp;quot;},
    components: components
  }

  payload = Jason.encode!(discovery_config)
  :emqtt.publish(emqtt_pid, &amp;quot;/homeassistant/device/homex/config&amp;quot;, payload)
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Let&amp;#39;s try it out!&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;iex&quot;&gt;1&amp;gt; Homex.Manager.start_link([])
{:ok, _pid}
2&amp;gt; Homex.Manager.add_entity(Homex.Entity.Switch)
:ok
3&amp;gt; Homex.Entity.Switch.toggle()
:ok
4&amp;gt; Homex.Entity.Switch.toggle()
:ok
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We should see the switch entity in Home Assistant and we can see that it gets toggled whenever we call &lt;code&gt;toggle/0&lt;/code&gt; on the switch module.&lt;/p&gt;&lt;h2 id=&quot;Homex&quot;&gt;Homex&lt;/h2&gt;&lt;p&gt;I am excited to tell you, that all the work above and more is already available as an Elixir library called &lt;a href=&quot;https://hex.pm/packages/homex&quot;&gt;&lt;code&gt;homex&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;A bridge between Elixir and Home Assistant&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Homex takes care of&lt;/p&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;Autodiscovery&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Supervision&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Automatic Reconnection&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Message deduplication&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Timed measurements&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Be it a temperature sensor or a light switch. This makes it especially interesting for Nerves devices to integrate with some hardware directly but you can also use it to switch feature flags in your day to day Elixir application.&lt;/p&gt;&lt;h3 id=&quot;What-can-you-build-with-it&quot;&gt;What can you build with it?&lt;/h3&gt;&lt;p&gt;Homex is just a generic framework to create and control entities in Home Assistant. Here are some ideas what you can build with it.&lt;/p&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;Weather station like in the book from the beginning&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Surveillance camera with the Raspberry Pi Camera&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Sensor hub for RF devices&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Water management system - Measure water levels and control valves&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Report application metrics&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;But in the end it is only limited by the &lt;a href=&quot;https://www.home-assistant.io/integrations/?search=mqtt&quot;&gt;supported MQTT platforms&lt;/a&gt; and your imagination&lt;/p&gt;&lt;h2 id=&quot;Basic-usage&quot;&gt;Basic usage&lt;/h2&gt;&lt;p&gt;To create a new switch entity in Homex you define a new module using the &lt;code&gt;Homex.Entity.Switch&lt;/code&gt; macro. After passing a name you can implement different callbacks as seen in the &lt;a href=&quot;https://hexdocs.pm/homex/Homex.Entity.Switch.html&quot;&gt;docs&lt;/a&gt;. In this demo we restrict ourselves to&lt;/p&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;handle_on/1&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;handle_off/1&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;These are called when you switch the light on or off from the Home Assistant side, either manually or via automations. Here we just print the received value to the console to see that it works:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;defmodule MySwitch do
  use Homex.Entity.Switch, name: &amp;quot;my-switch&amp;quot;

  def handle_on(entity) do
    IO.puts(&amp;quot;Switch turned on&amp;quot;)
    entity
  end

  def handle_off(entity) do
    IO.puts(&amp;quot;Switch turned off&amp;quot;)
    entity
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To expose the new entity to Home Assistant you have to set up a MQTT broker (see &lt;a href=&quot;https://www.home-assistant.io/integrations/mqtt/#setting-up-a-broker&quot;&gt;Setting up a broker&lt;/a&gt;) and tell Homex how to connect. This could look like this:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;config :homex,
  broker: [
    host: &amp;quot;localhost&amp;quot;,
    port: 1883,
    username: &amp;quot;admin&amp;quot;,
    password: &amp;quot;admin&amp;quot;
  ]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Finally we can start Homex and add the switch:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;Homex.start_link([])
Homex.add_entity(MySwitch)
&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;info info&quot;&gt;&lt;p&gt;Typically you would start Homex in your supervision tree.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;After that we can see the new entity in Home Assistant and some logs in our application when we toggle the switch on and off.&lt;/p&gt;&lt;p&gt;&lt;img alt=&quot;MySwitch in Home Assistant&quot; src=&quot;/images/ha_my_switch.png&quot;&gt;&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;13:27:17.973 [info] added entity my-switch
Switch turned on
Switch turned off
Switch turned on
&lt;/code&gt;&lt;/pre&gt;&lt;div class=&quot;tip tip&quot;&gt;&lt;p&gt;You can optionally specify a list of entities in the config and Homex will add them automatically on startup&lt;/p&gt;&lt;/div&gt;&lt;h2 id=&quot;More-advanced-use-cases&quot;&gt;More advanced use cases&lt;/h2&gt;&lt;p&gt;To store data in the process state we can use &lt;code&gt;put_private/3&lt;/code&gt; and &lt;code&gt;get_private/2&lt;/code&gt;. This can be a reference to a sensor or in this case the reader of a webcam. With this and some &lt;code&gt;image&lt;/code&gt; magic we are able to stream pictures right into Home Assistant:&lt;/p&gt;&lt;pre&gt;&lt;code data-lang=&quot;elixir&quot;&gt;defmodule MyCamera do
  use Homex.Entity.Camera, 
    name: &amp;quot;my-camera&amp;quot;, 
    image_encoding: &amp;quot;b64&amp;quot;, 
    update_interval: 1_000

  def handle_init(entity) do
    r = Xav.Reader.new!(&amp;quot;C922 Pro Stream Webcam&amp;quot;, device?: true)
    entity |&amp;gt; put_private(:reader, r)
  end

  def handle_timer(entity) do
    r = entity |&amp;gt; get_private(:reader)
    {:ok, %Xav.Frame{} = frame} = Xav.Reader.next_frame(r)

    {:ok, img} = Vix.Vips.Image.new_from_binary(frame.data, frame.width, frame.height, 3, :VIPS_FORMAT_UCHAR)

    img = img |&amp;gt; Image.thumbnail!(200)
    binary = img |&amp;gt; Image.write!(:memory, suffix: &amp;quot;.jpg&amp;quot;) |&amp;gt; Base.encode64()

    entity
    |&amp;gt; set_image(binary)
    |&amp;gt; set_attributes(%{width: Image.width(img), height: Image.height(img)})
  end
end
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id=&quot;Closing-thoughts&quot;&gt;Closing thoughts&lt;/h2&gt;&lt;p&gt;Playing around with home automation is fun. Playing around with Elixir is fun! Playing around with both of them is even more fun!&lt;/p&gt;&lt;p&gt;Homex is still young and evolving. It works, but expect some breaking changes in the early versions. I&amp;#39;ve already received some feedback and these are the things i want to tackle next&lt;/p&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;Make entities more flexible to define them at runtime and allow multiple instances&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Allow support for multiple logical devices, so a hub can expose and delegate devices&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Add APIs to query the state of the entities&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Optional &lt;a href=&quot;https://hex.pm/packages/telemetry&quot;&gt;telemetry&lt;/a&gt; hooks&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Expand support for more MQTT platforms&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Make using/building &lt;code&gt;emqtt&lt;/code&gt; easier&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;I’d also love to integrate Homex into existing projects, for example:&lt;/p&gt;&lt;ul style=&quot;list-style-type:disc;&quot;&gt;&lt;li&gt;&lt;p&gt;Nerves itself (system metrics)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://hex.pm/packages/soleil&quot;&gt;Soleil&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href=&quot;https://hex.pm/packages/govee_lights&quot;&gt;Govee lights&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div class=&quot;tip tip&quot;&gt;&lt;p&gt; You even want to write your automations in Elixir? You&amp;#39;re in luck - there&amp;#39;s already an article on how to do exactly that &lt;a href=&quot;https://www.jonashietala.se/blog/2024/10/08/writing_home_assistant_automations_using_genservers_in_elixir&quot;&gt;Writing Home Assistant automations using Genservers in Elixir&lt;/a&gt; by Jonas Hietala.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;For the long term, &lt;a href=&quot;https://csa-iot.org/all-solutions/matter/&quot;&gt;Matter&lt;/a&gt; could be an option to not just support the Home Assistant use-case but integrate in the home automation hub of your choice. &lt;/p&gt;&lt;p&gt;Do you have ideas where Homex could improve or be used? &lt;a href=&quot;/contact.html&quot;&gt;Let me know&lt;/a&gt;&lt;/p&gt;&lt;h2 id=&quot;Update-2026-01-26&quot;&gt;Update 2026-01-26&lt;/h2&gt;&lt;p&gt;I presented Homex in the &lt;a href=&quot;https://nervesmeetup.eu/&quot;&gt;Nerves Meetup EU&lt;/a&gt;. Here&amp;#39;s the link to the recording: &lt;a href=&quot;https://www.youtube.com/watch?v=9Bu8JjFVjsM&quot;&gt;Bringing Nerves and Home Assistant together&lt;/a&gt;&lt;/p&gt;&lt;!-- /lustre:fragment --&gt;</content></entry></feed>