<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Mqtt on tsvinc</title>
		<link>https://tsvinc.dev/tags/mqtt/</link>
		<description>Recent content in Mqtt on tsvinc</description>
		<generator>Hugo</generator>
		<language>en-us</language>
		
		
		
		
			<lastBuildDate>Mon, 02 Nov 2026 00:00:00 +0000</lastBuildDate>
		
			<atom:link href="https://tsvinc.dev/tags/mqtt/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>Record MQTT topics to a MikroTik router&#39;s SSD</title>
				<link>https://tsvinc.dev/guides/mqtt-recorder-routeros-container/</link>
				<pubDate>Mon, 02 Nov 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/guides/mqtt-recorder-routeros-container/</guid>
				<description>&lt;p&gt;This runs &lt;code&gt;mosquitto_sub&lt;/code&gt; in a RouterOS 7.24 container and appends every message on a set of MQTT topics to a file on the router&#39;s SSD, one file per UTC day, with a timestamp on each line.&lt;/p&gt;&#xA;&#xA;&lt;dl class=&#34;guide-scope&#34;&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Tested on&lt;/dt&gt;&lt;dd&gt;a MikroTik router with RouterOS 7.24, the container package and a USB SSD (&lt;code&gt;usb1-part1&lt;/code&gt;), image &lt;code&gt;eclipse-mosquitto:2.1.2-alpine&lt;/code&gt; pulled from Docker Hub, broker in another container on the same container bridge (2026-11-02). The stop-signal behaviour of &lt;code&gt;run.sh&lt;/code&gt; was re-checked in Docker 29.9.2 on Fedora 44 the same day.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row is-gap&#34;&gt;&lt;dt&gt;Not verified&lt;/dt&gt;&lt;dd&gt;the rollback; the day switch of the file name (the recorder has run for less than a day); this exact order (my first deploy named another container&#39;s envlist, and I moved the running container to &lt;code&gt;mqttrec&lt;/code&gt; later the same day); and the commands of steps 1, 3 and 6 as written here: my notes record what each step did, not these exact lines.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Needs&lt;/dt&gt;&lt;dd&gt;a container bridge with a gateway address on the router and an MQTT broker reachable from it; the router&#39;s container registry set to Docker Hub; a read-only MQTT user; SSH and SCP to the router; an external disk with free space.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Time&lt;/dt&gt;&lt;dd&gt;20 min plus the image pull.&lt;/dd&gt;&lt;/div&gt;&lt;div class=&#34;guide-scope-row&#34;&gt;&lt;dt&gt;Risk&lt;/dt&gt;&lt;dd&gt;low. The container only opens an outbound MQTT connection and writes to one directory. Nothing rotates or deletes the files: about 3.5 to 5 MB a day, so a large SSD lasts decades, a small USB stick does not. Rollback removes one container, one mount, one envlist, one bridge port and one veth.&lt;/dd&gt;&lt;/div&gt;&lt;/dl&gt;&#xA;&lt;h2 id=&#34;before-you-start&#34;&gt;Before you start&lt;a class=&#34;heading-anchor&#34; href=&#34;#before-you-start&#34; aria-label=&#34;Link to section: Before you start&#34;&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;Placeholders used below:&lt;/p&gt;&#xA;&lt;div class=&#34;table-wrap&#34; aria-label=&#34;Table: Placeholder, Meaning (mine)&#34;&gt;&#xA;&lt;table&gt;&#xA;  &lt;thead&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;th&gt;Placeholder&lt;/th&gt;&#xA;      &lt;th&gt;Meaning (mine)&lt;/th&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/thead&gt;&#xA;  &lt;tbody&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;&amp;lt;ROUTER&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;SSH &lt;code&gt;user@address&lt;/code&gt; of the router, plus your own &lt;code&gt;-i &amp;lt;KEY&amp;gt;&lt;/code&gt; options&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;&amp;lt;DISK&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;the external disk (&lt;code&gt;usb1-part1&lt;/code&gt;)&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;&amp;lt;CT_BRIDGE&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;the bridge your containers&#39; veths are ports of (&lt;code&gt;bridge-containers&lt;/code&gt;)&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;&amp;lt;CT_ADDR&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;CT_GW&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;a free address on that bridge for the recorder, and the bridge&#39;s gateway (&lt;code&gt;172.17.0.5&lt;/code&gt;, &lt;code&gt;172.17.0.1&lt;/code&gt;)&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;&amp;lt;BROKER_IP&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;the broker&#39;s address as seen from the container bridge (&lt;code&gt;172.17.0.3&lt;/code&gt;)&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;    &lt;tr&gt;&#xA;      &lt;td&gt;&lt;code&gt;&amp;lt;MQTT_USER&amp;gt;&lt;/code&gt; / &lt;code&gt;&amp;lt;MQTT_PASSWORD&amp;gt;&lt;/code&gt;&lt;/td&gt;&#xA;      &lt;td&gt;the read-only MQTT login the recorder uses&lt;/td&gt;&#xA;    &lt;/tr&gt;&#xA;  &lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;/div&gt;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The registry is Docker Hub. &lt;code&gt;remote-image&lt;/code&gt; pulls from the registry set in &lt;code&gt;/container/config&lt;/code&gt;, and MikroTik&#39;s documented default is &lt;code&gt;https://lscr.io/&lt;/code&gt;. Check &lt;code&gt;registry-url&lt;/code&gt; in &lt;code&gt;ssh &amp;lt;ROUTER&amp;gt; &#39;/container/config/print&#39;&lt;/code&gt;; if it isn&#39;t Docker Hub, set it as MikroTik&#39;s page shows: &lt;code&gt;/container/config/set registry-url=https://registry-1.docker.io&lt;/code&gt;.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;The broker accepts the read-only user and lets it subscribe to the topics you put into &lt;code&gt;run.sh&lt;/code&gt; in step 2.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;li&gt;&#xA;&lt;p&gt;&lt;strong&gt;Backup:&lt;/strong&gt; take a config export before step 1 (&lt;code&gt;/export file=&amp;lt;DISK&amp;gt;/backups/&amp;lt;NAME&amp;gt;&lt;/code&gt;) and copy it off the router.&lt;/p&gt;&#xA;&lt;/li&gt;&#xA;&lt;/ul&gt;</description>
			</item>
			<item>
				<title>The DJI went silent for four hours</title>
				<link>https://tsvinc.dev/posts/dji-went-silent-four-hours/</link>
				<pubDate>Mon, 02 Nov 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/dji-went-silent-four-hours/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://tsvinc.dev/posts/dji-power-2000-ups-over-bluetooth/&#34;&gt;Part 4&lt;/a&gt; ended on a rule for the observer, the Go service on my router that turns two power stations&#39; readings into NUT (Network UPS Tools) status for the machines they keep running: a DJI that stops answering for minutes is an alert in its own right, never a sign that the grid is fine. On 20 October the DJI Power 2000 stopped answering at about 13:10. It answered again a little before 18:00, after I power-cycled it. While the grid was up, nothing alerted me to the silence.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Pulling plugs on purpose</title>
				<link>https://tsvinc.dev/posts/pulling-plugs-on-purpose/</link>
				<pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/pulling-plugs-on-purpose/</guid>
				<description>&lt;p&gt;A test that passes tells you the thing works. That is the reading everyone defaults to, and the first power-bank test of the ESP32 bridge was a clean example of why it&#39;s wrong.&lt;/p&gt;&#xA;&lt;p&gt;The bridge from &lt;a href=&#34;https://tsvinc.dev/posts/esp32-bluetti-ac180-bridge/&#34;&gt;the second UPS post&lt;/a&gt; runs off a USB power bank, which charges from the wall. The worry was simple: when the grid goes and the power bank stops charging, does it hold its output steady, or does the board reboot right at the start of the outage it is supposed to report? I cut the power bank&#39;s charging, and the board&#39;s uptime counter kept going, from 14160 seconds to 14180. No reboot.&lt;/p&gt;</description>
			</item>
			<item>
				<title>A null battery reading cleared the low-battery alarm</title>
				<link>https://tsvinc.dev/posts/nut-observer-fails-safe/</link>
				<pubDate>Fri, 16 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/nut-observer-fails-safe/</guid>
				<description>&lt;p&gt;Here is the first version of the observer handling a station at 12 % charge, on battery, followed by a reading where the bridge couldn&#39;t decode the charge:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;=== RUN   TestProbeNullSoCClearsLB&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    probe_test.go:20: soc 12 -&amp;gt; &amp;#34;OB DISCHRG LB&amp;#34;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    probe_test.go:25: then soc null -&amp;gt; &amp;#34;OB DISCHRG&amp;#34;, battery.charge present=false&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;--- PASS: TestProbeNullSoCClearsLB (0.00s)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;OB DISCHRG LB&lt;/code&gt; is the line that makes every machine on that tier shut down. One reading later, it was gone. A &lt;code&gt;null&lt;/code&gt; in the bridge&#39;s JSON means &amp;quot;I couldn&#39;t read this field&amp;quot;, and the observer had treated it as &amp;quot;nothing to worry about&amp;quot;. With no charge in the latest reading, the low-battery check had nothing to compare against its threshold. &lt;code&gt;LB&lt;/code&gt; dropped and &lt;code&gt;battery.charge&lt;/code&gt; vanished from the replies. In a real outage the hosts would have been stood down while the battery kept draining.&lt;/p&gt;</description>
			</item>
			<item>
				<title>The DJI&#39;s grid flag is labeled charge type</title>
				<link>https://tsvinc.dev/posts/dji-power-2000-ups-over-bluetooth/</link>
				<pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/dji-power-2000-ups-over-bluetooth/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update, 2026-11-02:&lt;/strong&gt; The station&#39;s minutes field has a second meaning. Byte 4 of its battery record (tag &lt;code&gt;0x3020&lt;/code&gt;) is a time type, and while it is &lt;code&gt;1&lt;/code&gt; the minutes are a recharging time, the time to full charge, not a runtime. The bridge now leaves &lt;code&gt;runtime_min&lt;/code&gt; out whenever that byte says recharging, and the observer ignores the field while input power flows. The story is in &lt;a href=&#34;https://tsvinc.dev/posts/ups-said-two-minutes-at-95/&#34;&gt;The UPS said 2 minutes at 95 %&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;/blockquote&gt;</description>
			</item>
			<item>
				<title>Secure session ready. Battery: ?</title>
				<link>https://tsvinc.dev/posts/esp32-bluetti-ac180-bridge/</link>
				<pubDate>Wed, 14 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/esp32-bluetti-ac180-bridge/</guid>
				<description>&lt;p&gt;Porting a key exchange from Python to C++ should be the boring part. The Python works, it is already validated against this exact station, and mbedTLS ships every primitive it uses: MD5, AES-CBC, P-256, ECDSA, ECDH. Translate, compile, flash. The first run on the ESP32 went along with that, right up to the last line:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;found AC180&amp;lt;redacted&amp;gt; XX:XX:XX:XX:XX:XX rssi=-57&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;connected, mtu=517&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;KEX: challenge answered&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;KEX: challenge accepted&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;KEX: pubkey sent&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;KEX: pubkey sent&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;KEX: secure session ready&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;handshake 1913 ms&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;RESULT BATTERY_SOC=? DC_OUTPUT_POWER=? AC_OUTPUT_POWER=?&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Every step of the handshake reported success. Then came the reads, the part that was supposed to be easy, and the first three came back as question marks. The capture ended there.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Twenty-five percent is a countdown</title>
				<link>https://tsvinc.dev/posts/ups-observer-speaks-nut/</link>
				<pubDate>Wed, 14 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/ups-observer-speaks-nut/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;Update, 2026-11-02:&lt;/strong&gt; The thresholds in this design (60, 35 and 15 % for the three tiers, with the first tier also acting after 5 minutes on battery) are no longer the deployed ones. Since 2026-11-02 all three tiers act at 15 % or 8 minutes of runtime, with no on-battery timer. Why is in &lt;a href=&#34;https://tsvinc.dev/posts/three-shutdowns-three-causes/&#34;&gt;Three shutdowns, three causes&lt;/a&gt;.&lt;/p&gt;&#xA;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;The NUT client&#39;s man page has a sentence that decided most of this design before any of it was drawn:&lt;/p&gt;</description>
			</item>
	</channel>
</rss>
