<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Power on tsvinc</title>
		<link>https://tsvinc.dev/tags/power/</link>
		<description>Recent content in Power 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/power/index.xml" rel="self" type="application/rss+xml" />
			<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>The UPS said 2 minutes at 95 %</title>
				<link>https://tsvinc.dev/posts/ups-said-two-minutes-at-95/</link>
				<pubDate>Mon, 02 Nov 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/ups-said-two-minutes-at-95/</guid>
				<description>&lt;p&gt;My storage box and gaming PC run on a DJI Power 2000 power station used as a UPS, and a monitor on my router decides when they shut down. At 11:39:39 on 1 November, twenty-three minutes into a power cut, my alert channels got this from it:&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;ups dji2000-t2: LOW BATTERY, its hosts should shut down (runtime 2m0s &amp;lt;= 20m0s (ABC))&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And the same for the third tier, against its 8-minute limit. Two minutes of runtime left on the DJI. I could see the station&#39;s display at the time. It said about 95 %.&lt;/p&gt;</description>
			</item>
			<item>
				<title>Three shutdowns, three causes</title>
				<link>https://tsvinc.dev/posts/three-shutdowns-three-causes/</link>
				<pubDate>Mon, 02 Nov 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/three-shutdowns-three-causes/</guid>
				<description>&lt;p&gt;My storage box sits on a DJI Power 2000 used as a UPS. Over two days, 1 and 2 November, it went down a few times. Some of those had an obvious reason and some had none, and every time I looked, the DJI showed about 95 %. I went in looking for one fault: something that turns a full battery into a dead machine.&lt;/p&gt;&#xA;&lt;p&gt;There were three, and only one of them was clearly the station.&lt;/p&gt;</description>
			</item>
			<item>
				<title>The alert about the blackout needs the internet too</title>
				<link>https://tsvinc.dev/posts/ups-alerts-survive-the-outage/</link>
				<pubDate>Sat, 17 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/ups-alerts-survive-the-outage/</guid>
				<description>&lt;p&gt;&lt;a href=&#34;https://tsvinc.dev/posts/nut-observer-fails-safe/&#34;&gt;Part 5&lt;/a&gt; left the observer able to shut machines down and unable to tell me anything, with one item still open: an alert for an observer that stops. The alerts looked like one function. Log the line, and if a notifier is configured, hand it over:&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-go&#34; data-lang=&#34;go&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;kd&#34;&gt;func&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;alertf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;format&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;string&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;kt&#34;&gt;any&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;msg&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;:=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;fmt&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Sprintf&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;format&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;,&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;a&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;...&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;log&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Print&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;msg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;if&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;alerts&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;o&#34;&gt;!=&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;kc&#34;&gt;nil&lt;/span&gt;&lt;span class=&#34;w&#34;&gt; &lt;/span&gt;&lt;span class=&#34;p&#34;&gt;{&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&#x9;&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;alerts&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;.&lt;/span&gt;&lt;span class=&#34;nf&#34;&gt;Send&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;(&lt;/span&gt;&lt;span class=&#34;nx&#34;&gt;msg&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;)&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;w&#34;&gt;&#x9;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;}&lt;/span&gt;&lt;span class=&#34;w&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That function hasn&#39;t changed since. The file around it, &lt;code&gt;notify.go&lt;/code&gt;, is now 217 lines, and its tests are another 197. Most of it comes from one inconvenient fact: a message about a power cut leaves through an internet uplink, and the cut can take the uplink with it. I can keep the router running on battery. I can&#39;t vouch for anything upstream of it.&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 pin template was for a different plug</title>
				<link>https://tsvinc.dev/posts/tuya-plug-openbeken-grid-canary/</link>
				<pubDate>Fri, 16 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/tuya-plug-openbeken-grid-canary/</guid>
				<description>&lt;p&gt;The smart plug&#39;s vendor app put its energy monitoring behind a subscription. The metering chip inside measures voltage alongside current and power all the same, whatever the app agrees to show. I wanted that voltage. Read off a plug on wall power, it says whether the grid is up, and my &lt;a href=&#34;https://tsvinc.dev/posts/bluetti-ac180-ups-over-bluetooth/&#34;&gt;power-station UPS&lt;/a&gt; needed exactly that: a second opinion on the grid from something not behind a battery.&lt;/p&gt;&#xA;&lt;p&gt;So the plug had to leave Tuya&#39;s firmware for &lt;a href=&#34;https://github.com/openshwprojects/OpenBK7231T_App&#34;&gt;OpenBeken&lt;/a&gt;, an open firmware for the Beken chips in most Tuya plugs. That took one evening. Getting the pins right took the next day, because the template I borrowed was for a different plug, and the plug&#39;s own leftover settings were the only thing that knew.&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>The power went out twice. My UPS log saw it once.</title>
				<link>https://tsvinc.dev/posts/bluetti-ac180-ups-over-bluetooth/</link>
				<pubDate>Wed, 14 Oct 2026 00:00:00 +0000</pubDate>
				<guid>https://tsvinc.dev/posts/bluetti-ac180-ups-over-bluetooth/</guid>
				<description>&lt;p&gt;Thirty lines of log, one outage. Twenty-two lines on grid, seven on battery, one back on grid. Clean, readable, and short by one event: the plug came out of the wall twice.&lt;/p&gt;&#xA;&lt;p&gt;The setup itself works. A BLUETTI AC180 portable power station sits between the wall and my workstation as a UPS. Linux reads it over Bluetooth LE, through an encrypted link, with no account and no pairing, in about nine seconds per read. What it can&#39;t do is tell you about anything that happens between two of those reads. Neither can anything else built on polling. This one put it in writing.&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>
