<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Symbioquine]]></title><description><![CDATA[Here is written stuff about systems, technology, ecology, and more.]]></description><link>https://symbioquine.net/</link><image><url>https://symbioquine.net//logos/logo-48.png</url><title>Symbioquine</title><link>https://symbioquine.net/</link></image><generator>GatsbyJS Advanced Starter</generator><lastBuildDate>Sat, 31 Dec 2022 21:54:26 GMT</lastBuildDate><atom:link href="https://symbioquine.net//rss.xml" rel="self" type="application/rss+xml"/><copyright><![CDATA[Copyright © 2022. All rights reserved.]]></copyright><item><title><![CDATA[MultiPlus Inverter Metrics]]></title><description><![CDATA[I am lucky enough to have a pair of Victron MultiPlus inverters (Multi(s) hereafter). Originally, I just had one hooked up and kept the…]]></description><link>https://symbioquine.net//2022-12-31-multiplus-inverter-metrics</link><guid isPermaLink="false">https://symbioquine.net//2022-12-31-multiplus-inverter-metrics</guid><category><![CDATA[Off-grid]]></category><category><![CDATA[Monitoring]]></category><pubDate>Sat, 31 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I am lucky enough to have a pair of Victron MultiPlus inverters (Multi(s) hereafter). Originally, I just had one hooked up and kept the other as a backup - I
have semi-critical loads like a freezer that would be wasteful/costly in the event of a prolonged outage. However, recently our winter charging needs dictated
that charging faster via the generator was in order - this provides extra flexibility, keeps the noise down for our neighbors, and &lt;del&gt;I suspect&lt;/del&gt; yields better
generator efficiency (more on that in another post). Thus for me, hooking up the second Multi was an obvious next step.&lt;/p&gt;
&lt;p&gt;This post will describe some things I have learned about the Multi&apos;s data protocols and how I&apos;ve been using them to monitor our generator usage.&lt;/p&gt;
&lt;p&gt;Originally, I had a very simple script to pull data using a library Victron references in their
&lt;a href=&quot;https://www.victronenergy.com/live/open_source:start#multis_quattros_mk2&quot;&gt;open source link-farm&lt;/a&gt;, &lt;a href=&quot;https://github.com/izak/ib.victron&quot;&gt;izak/ib.victron&lt;/a&gt;
(izak/ib.victron hereafter). It&apos;s not clear if that library is still being maintained and it has some other issues - e.g. &lt;a href=&quot;https://github.com/izak/ib.victron/pull/3&quot;&gt;still on Python
2&lt;/a&gt;, only supports a single bus device properly, and makes a few assumptions which break with the current version of
the protocol. I don&apos;t want to beat up on that library too much because it served me well for a year and helped me understand the protocol immensely. Just that
my use-case outgrew it. In addition to sharing what I&apos;ve learned here, I hope to eventually share the library I&apos;ve written as a spiritual successor to
izak/ib.victron.&lt;/p&gt;
&lt;p&gt;Unfortunately, Victron puts their protocol documentation behind an email form so I can&apos;t share a direct link, but the relevant documentation can be found quite
easily by requesting &quot;Interfacing with VE Bus products - MK2 protocol&quot; from &lt;a href=&quot;https://www.victronenergy.com/support-and-downloads/technical-information&quot;&gt;Victron&apos;s Technical information downloads
page&lt;/a&gt;. The rest of this post will reference version 3.14 of that documentation,
however I will try to provide enough detail so having that documentation in-hand isn&apos;t essential.&lt;/p&gt;
&lt;h3 id=&quot;bus-vs-device-communication&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#bus-vs-device-communication&quot; aria-label=&quot;bus vs device communication permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Bus vs Device Communication&lt;/h3&gt;
&lt;p&gt;Multi&apos;s (and some other Victron devices) have RJ45 ports (the same physical ports used for ethernet) which are used to chain one or more devices together into
a single physical communication &quot;bus&quot;. This bus (VE.Bus) is used by the devices themselves to coordinate parallel/split-phase/multi-phase operation and by
monitoring/control hardware/software. In order to access the bus from a computer, a special USB adapter (&lt;a href=&quot;https://www.amazon.com/dp/B06Y5WS3VZ/&quot;&gt;MK3-USB&lt;/a&gt;) is
used to translate between the bus transport and expose it as a serial interface.&lt;/p&gt;
&lt;p&gt;On the software side, Victron calls the protocol for interfacing with the bus &quot;MK2&quot;. Messages in the MK2 protocol are called &quot;frames&quot;. Most of these messages
take the form of a command/response pattern where devices wait for frames which request information or make changes, then respond with the requested
information or confirmation/errors. There are two broad categories of commands, commands which pertain to the bus as a whole, and &quot;Winmon&quot; commands which
pertain to specific devices only.&lt;/p&gt;
&lt;p&gt;The izak/ib.victron library mainly only exposes the bus-level information. Interestingly, a lot of the bus-level information can&apos;t be interpreted without also
requesting supporting data from the individual bus devices. The rational for this doesn&apos;t seem to be documented in the protocol. Nor does it seem to be
documented whether, in systems with several Multis, it is valid to use the supporting data from the first device to interpret the the bus-level information for
all the devices - we kind of have to assume so, but it feels like a major gap in the protocol documentation.&lt;/p&gt;
&lt;p&gt;Another gap in the protocol documentation seems to be in terms of the meaning of parts of the bus-level information when multiple devices are hooked up.
Specifically, I was interested in the steady-state power cost of having two inverters hooked up all the time. With one inverter, I had been monitoring the DC
power usage as as measured by the inverter and it seemed to match up with the baseline power usage for my system as a whole. When I hooked up the second
inverter though, I was puzzled since that bus-level power usage number didn&apos;t appear to double. I still can&apos;t totally explain that phenomena (I will touch on
the phenomena in a later section to describe what I do know, but - as I say - it&apos;s still unexplained.) It is that quandary that sent me down the path of
trying to monitor the DC power usage for each inverter independently.&lt;/p&gt;
&lt;p&gt;In the MK2 protocol, there is a bus-level address command which is used to set or retrieve the address of the device to which subsequent Winmon commands are
directed. The izak/ib.victron library allows an address to be specified, but only once and only one address. It doesn&apos;t allow switching between devices on the
fly and although it provides a primitive locking mechanism which would appear to allow multiple instances to work with the same bus, there isn&apos;t any logic
to track which device is currently being addressed so it isn&apos;t really viable to use as-is for sending Winmon commands to multiple bus devices.&lt;/p&gt;
&lt;h3 id=&quot;device-discovery&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#device-discovery&quot; aria-label=&quot;device discovery permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Device Discovery&lt;/h3&gt;
&lt;p&gt;The MK2 documentation also describes how to discover which devices are on the bus. This is done by iteratively selecting addresses and testing whether a
response is received when issuing a Winmon command. The izak/ib.victron library doesn&apos;t handle this at all.&lt;/p&gt;
&lt;p&gt;Interestingly, the MK2 protocol documentation doesn&apos;t clarify whether the result of this device discovery should be assumed to be valid for the entire life
of a bus connection. In the case of my inverters I&apos;m assuming so - since they&apos;re set up to switch as a group, meaning they can&apos;t be disconnected without
interrupting all the devices and I don&apos;t have any more VE.Bus devices to connect.&lt;/p&gt;
&lt;h3 id=&quot;device-ram-snapshots&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#device-ram-snapshots&quot; aria-label=&quot;device ram snapshots permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Device RAM Snapshots&lt;/h3&gt;
&lt;p&gt;The MK2 protocol documentation suggests that, to get accurate point-in-time power data, one can issue a bus-level command to snapshot up to 6 specific RAM
variables, then read the data from that snapshot on each device. The available RAM IDs appear to correspond pretty closely to data that is also available via
the bus-level info commands, but the mapping between the two isn&apos;t explicitly described in the documentation.&lt;/p&gt;
&lt;h3 id=&quot;poorly-documented-ram-variables&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#poorly-documented-ram-variables&quot; aria-label=&quot;poorly documented ram variables permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Poorly Documented RAM Variables&lt;/h3&gt;
&lt;p&gt;Along similar lines, some of the RAM variables have cryptic or misleading descriptions. Specifically, RAM variables 14 and 15 appear to have the exact same
description, but return different values. Presumably, folks at Victron who wrote the documentation copied and pasted between the two variables&apos; descriptions
but forgot to update one of them.&lt;/p&gt;
&lt;p&gt;By recording both of these and looking at them both with and without the generator running, I think I can explain what they actually mean.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;RAM[2] * RAM[3]&lt;/code&gt;: &quot;vainv&quot; (Abs volt-amps passing through inverter hardware)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;RAM[4] * RAM[5]&lt;/code&gt;: &quot;wbat&quot; (Abs watts from battery)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;RAM[14]&lt;/code&gt;: &quot;invpow&quot; (Power passing through inverter hardware)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;RAM[15]&lt;/code&gt;: &quot;mainpow&quot; (Total power from mains: load + charging)&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;RAM[16]&lt;/code&gt;: &quot;outpow&quot; (Power to load)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d381c79489b3a3eab8281dec5cf2c568/2adcb/inverter-ram-variables.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 51.445086705202314%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAC4jAAAuIwF4pT92AAABOklEQVQoz22RaW6DMBhEOUMCxisYb9CkSSDp/a82lRcQtPx4Gn9Yfhqbqmk5roSBUoGp1xg7jdDlXLkNFrfBpbVTPRrCcG0ouOwRpjv8eIP1I4TSqFYhZwKLdZiNw2tweJkdg00sxuFbG2wluMJgAzptk4xQcRQ+ysEzHtriGZv2JjWsS8tL3W7EOQtjfSbxsh6L81j8SsizdXj7gE8IeBq7Cc+oSCu2hi8XMtaXdc7ZjyUDvq1PV653LfdZrZtROJfrPcv1Dui8dy9v+Fe4NawJx6WhYFT8l5wQ3/CaRJma0JJFSJkAYxydkOlA/MuzOeZS1jEfgwHnAoxzNO1JwyiUUkIrhcVafJzDx62ZeZf5xznM1kIplSBFuKciVKKmEox3mLTB1Bf0jvLtSxuEfgBhEg2Vh7dcG/4C4Z8QSdibu7wAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Inverter RAM Variables Graph&quot;
        title=&quot;Inverter RAM Variables Graph&quot;
        src=&quot;/static/d381c79489b3a3eab8281dec5cf2c568/1e043/inverter-ram-variables.png&quot;
        srcset=&quot;/static/d381c79489b3a3eab8281dec5cf2c568/991de/inverter-ram-variables.png 173w,
/static/d381c79489b3a3eab8281dec5cf2c568/e4d6b/inverter-ram-variables.png 345w,
/static/d381c79489b3a3eab8281dec5cf2c568/1e043/inverter-ram-variables.png 690w,
/static/d381c79489b3a3eab8281dec5cf2c568/e3189/inverter-ram-variables.png 1035w,
/static/d381c79489b3a3eab8281dec5cf2c568/b1001/inverter-ram-variables.png 1380w,
/static/d381c79489b3a3eab8281dec5cf2c568/2adcb/inverter-ram-variables.png 1984w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In the above graph I have multiplied &quot;outpow&quot; (&lt;code class=&quot;language-text&quot;&gt;RAM[15]&lt;/code&gt;) by &lt;code class=&quot;language-text&quot;&gt;-1&lt;/code&gt; to make it easier to see how it is the sum of the load power plus the charging power. In my
installation, this value is always zero or less, indicating power flowing from my generator to charge the batteries and power loads. I don&apos;t know whether there
are scenarios where a positive value is possible, but we can surmise it might indicate power flowing back to mains - perhaps where power is being feed back to
the grid.&lt;/p&gt;
&lt;p&gt;I included &quot;vainv&quot; (the product of &lt;code class=&quot;language-text&quot;&gt;RAM[2] * RAM[3]&lt;/code&gt;) and &quot;wbat&quot; (the product of &lt;code class=&quot;language-text&quot;&gt;RAM[4] * RAM[5]&lt;/code&gt;) for comparison. I suspect the small differences where vainv
is always a little higher than &quot;invpow&quot; (&lt;code class=&quot;language-text&quot;&gt;RAM[14]&lt;/code&gt;) are the result of my loads&apos; power factor being slightly less than one - the three RAM values were captured
as part of the same snapshot. Similarly, the difference between the inverter power and &quot;wbat&quot; should show us the overhead or self-consumption of the inverter.&lt;/p&gt;
&lt;p&gt;Finally, &quot;outpow&quot; (&lt;code class=&quot;language-text&quot;&gt;RAM[16]&lt;/code&gt;) is pretty self-explanatory. It is just the power actually used by loads on the AC output of the Multis and does not seem to
effected by whether the inverter hardware is charging or inverting. (Except for the edge case I&apos;ll describe next...)&lt;/p&gt;
&lt;h3 id=&quot;missing-background-power-usage&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#missing-background-power-usage&quot; aria-label=&quot;missing background power usage permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Missing Background Power Usage&lt;/h3&gt;
&lt;p&gt;As mentioned earlier, I was surprised to see that the zero-load usage of the new inverter was zero which didn&apos;t match the specification sheet, nor my experience
with the first inverter. After much experimenting with the metrics side of things, I finally decided to stick a clamp ammeter on the cable to the inverter and
found around 0.95 amps @ 26.5v DC with zero load - literally nothing connected to the output. Since that didn&apos;t appear in any of the metrics, I next tested with
a known load by putting a 40 watt incandescent light bulb directly on the terminals. Immediately, the Multi started reporting that it was consuming 69 watts from
the battery which is much more like what I was expecting. Thus in my present configuration it appears the second Multi has an idle power draw in the range of 30
watts, but has trouble self-reporting that usage unless there is also some AC load current flowing.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/d47d42f0cc082075f62e61e5d6786da5/e24fe/inverter-self-consumption.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 54.33526011560693%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAgnAAAZhwGkFvs+AAAA3klEQVQoz32Qa2oDMQyE9w5r6zGyZHu3/Vna+5+u2KFJSNaFDyEYiRlpE0RmrGBGoqW6sRYSEL8hUPfijghWy6wvA2NZLUhshQp4IY1lQVzY3oIRmEbNYpcDI/bqJMAcUIBELy/f1Ooqs5mFWURYiZvHa+x/nBOBshznx9f3T7S+k+a583AW+Pj2AmGwQs1ZMJ0fTGd4Yk2s+a/eoFmFNJMmukvP0XQr0frRWq+t12M20epx1vOzqwXggqhtqHXOjKZF6+FRN1bz2lGilPDa5mMlk+wkiWTPg0xP8L3XX31nQA/RG1PpAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Inverter Self-consumption Graph&quot;
        title=&quot;Inverter Self-consumption Graph&quot;
        src=&quot;/static/d47d42f0cc082075f62e61e5d6786da5/1e043/inverter-self-consumption.png&quot;
        srcset=&quot;/static/d47d42f0cc082075f62e61e5d6786da5/991de/inverter-self-consumption.png 173w,
/static/d47d42f0cc082075f62e61e5d6786da5/e4d6b/inverter-self-consumption.png 345w,
/static/d47d42f0cc082075f62e61e5d6786da5/1e043/inverter-self-consumption.png 690w,
/static/d47d42f0cc082075f62e61e5d6786da5/e3189/inverter-self-consumption.png 1035w,
/static/d47d42f0cc082075f62e61e5d6786da5/e24fe/inverter-self-consumption.png 1073w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I will probably work around this discrepancy in the future by just shifting some of my loads on to the other split-phase leg and then not worry about it.&lt;/p&gt;
&lt;h3 id=&quot;monitoring-generator-usage&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#monitoring-generator-usage&quot; aria-label=&quot;monitoring generator usage permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Monitoring Generator Usage&lt;/h3&gt;
&lt;p&gt;For a while now I&apos;ve been tracking my generator usage. This helps me understand the ongoing costs of my off-grid system. Some interesting efficiency data has
come out of that which will probably be the subject of another post, but the part I want to discuss here is the actual monitoring mechanism because that is based
on the metrics from the Multis and my learnings above.&lt;/p&gt;
&lt;p&gt;In my system all generator power flows through the Multis which charge the batteries and also pass power through to my AC loads. There are several advantages to
this configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It eliminates the need for a transfer switch since the Multis can do that internally - seamlessly switching from inverter to pass-through modes as needed&lt;/li&gt;
&lt;li&gt;The Multis can automatically manage the max generator current such that the charging rate is attenuated when large loads come on&lt;/li&gt;
&lt;li&gt;The Multis provide a common point where many of the key usage metrics can be measured&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To that last point, I can then have a tiny service which utilizes the learnings above to poll the Multis and write the relevant metrics into InfluxDB. Those
raw metrics are available in a dashboard, but also are available for analysis purposes. In this case, I have a script which reads the data from InfluxDB and
outputs the generator run time and kWh produced for each instance the generator is run.&lt;/p&gt;
&lt;p&gt;I&apos;ll probably share the actual code for doing that in a future post, but for now, I&apos;ll just outline how it works. The script pulls 1 minute data for the last week
and looks for datapoints where the inverter is charging instead of drawing power from the batteries. For those time periods it tracks the total runtime and sums
the 1 minute averages of the power used to charge and power passing through to the loads.&lt;/p&gt;
&lt;p&gt;The resulting data can help with understanding the generator&apos;s performance characteristics - i.e. at what average loading do we get the most kWh per unit of
generator fuel. It also is a convenient way to track generator run hours which is useful for scheduling maintenance tasks like oil changes and filter cleaning.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I don&apos;t know what the ideal intersection is for open source/knowledge with proprietary upper-mid-teir hardware like Victron. Clearly, there is an argument for
Victron funding more first-party open source tooling and improving their documentation to disambiguate some of the points that I&apos;ve highlighted above. At
the same time I appreciate that there exists &lt;em&gt;some&lt;/em&gt; documentation, and admire the scrappy nature of the community open source projects they&apos;ve linked to.&lt;/p&gt;
&lt;p&gt;I hope that this post is helpful to others who want to gain access to detailed inverter metrics and attain a deeper understanding of the behaviour their systems.
Perhaps it will inspire people to share their learnings and library improvements.&lt;/p&gt;
&lt;p&gt;More generally, I am optimistic that the sum of the broader hacker/tinkerer communities&apos; efforts with various alternative/renewable/off-grid energy systems and
components will yield a more robust and adaptable technical ecosystem. That in turn could allow folks to build systems they can afford and which reward
conserving resources. Acknowledging of course that in many/(most?) cases, if available, standard grid power will probably be the cleanest and cheapest option
overall.&lt;/p&gt;
&lt;p&gt;Happy New Year and happy/safe hacking!&lt;/p&gt;</content:encoded><author>symbioquine@gmail.com</author></item><item><title><![CDATA[Building Drupal Modules with GitHub Actions]]></title><description><![CDATA[Sometimes CI architectures are more a product of circumstance than we'd like. The strategy described in this post is a prime example of that…]]></description><link>https://symbioquine.net//2022-09-28-github-actions-drupal-module-build-step</link><guid isPermaLink="false">https://symbioquine.net//2022-09-28-github-actions-drupal-module-build-step</guid><category><![CDATA[Software Engineering]]></category><pubDate>Wed, 28 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Sometimes CI architectures are more a product of circumstance than we&apos;d like. The strategy described in this post is a prime example of that.&lt;/p&gt;
&lt;p&gt;Many Drupal modules don&apos;t require a build step, typically they are just some PHP, yaml, and maybe some Twig/JS/CSS files all checked-in. Those
files then directly form the contents of the release for the module.&lt;/p&gt;
&lt;p&gt;Drupal.org&apos;s infrastructure is tailored to that style of module. Internally Drupal.org uses an instance of GitLab to host the source code for
each module. Project maintainers tag releases in GitLab then manually create releases at the project level which then become the installable
versions for the Composer &quot;drupal/&quot; prefix. (See the &lt;a href=&quot;https://www.drupal.org/docs/develop/using-composer/manage-dependencies&quot;&gt;Drupal docs on using Composer&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Unfortunately, there&apos;s another style of module which doesn&apos;t seem to be well served by the current Drupal.org infrastructure - modules which
need an arbitrary build step. The example I am going to use in this post is a module that includes some Javascript code that is built with
&lt;a href=&quot;https://webpack.js.org/&quot;&gt;Webpack&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;project-setup--code&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#project-setup--code&quot; aria-label=&quot;project setup  code permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Project Setup / Code&lt;/h3&gt;
&lt;p&gt;To start off, create a new GitHub repository and Drupal.org module project. For clarity and convenience, I recommend making the GitHub repository
name and the Drupal.org project &quot;short name&quot; the same. For that, the name needs to follow the Drupal
&lt;a href=&quot;https://www.drupal.org/docs/creating-custom-modules/naming-and-placing-your-drupal-module&quot;&gt;conventions for module naming&lt;/a&gt; - basically just
alphanumeric characters and underscores. I&apos;ve chosen to call my example for this tutorial &quot;symbioquine_dot_net_built_drupal_module_example&quot;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example&quot;&gt;https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.drupal.org/project/symbioquine_dot_net_built_drupal_module_example&quot;&gt;https://www.drupal.org/project/symbioquine_dot_net_built_drupal_module_example&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I let GitHub generate a default branch, readme, and &lt;code class=&quot;language-text&quot;&gt;.gitignore&lt;/code&gt; file. Then I renamed the branch from &lt;code class=&quot;language-text&quot;&gt;main&lt;/code&gt; to &lt;code class=&quot;language-text&quot;&gt;release&lt;/code&gt; - this will be important
later since our GitHub workflow will update that branch using the results of our automated build when we push specific tags to the development branch.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; checkout &lt;span class=&quot;token parameter variable&quot;&gt;-b&lt;/span&gt; development&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textsrcmainjscode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textsrcmainjscode&quot; aria-label=&quot;code classlanguage textsrcmainjscode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;src/main.js&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Wait until all attached Drupal libraries get loaded&lt;/span&gt;
document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;DOMContentLoaded&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&apos;#example-page-app&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;innerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello world!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textpackagejsoncode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textpackagejsoncode&quot; aria-label=&quot;code classlanguage textpackagejsoncode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;package.json&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;symbioquine_dot_net_built_drupal_module_example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;A module showing how to automatically build JS code with Webpack and push releases to Drupal.org&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;license&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GPL-3.0-or-later&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;repository&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;git&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example.git&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;build&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;webpack --config webpack.config.js --mode production&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;devDependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;webpack&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^5.36.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&quot;webpack-cli&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^4.6.0&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string-property property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textwebpackconfigjscode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textwebpackconfigjscode&quot; aria-label=&quot;code classlanguage textwebpackconfigjscode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;webpack.config.js&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;js&quot;&gt;&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;entry&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&apos;built_drupal_module_example&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token string-property property&quot;&gt;&apos;import&apos;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;__dirname&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/src/main.js&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;__dirname&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/drupal_module_src/js&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;filename&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;[name].js&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;clean&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textdrupal_module_srccomposerjsoncode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textdrupal_module_srccomposerjsoncode&quot; aria-label=&quot;code classlanguage textdrupal_module_srccomposerjsoncode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;drupal_module_src/composer.json&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;json&quot;&gt;&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;symbioquine/symbioquine_dot_net_built_drupal_module_example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;A module showing how to automatically build JS code with Webpack and push releases to Drupal.org&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;drupal-module&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;homepage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;authors&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Symbioquine&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;homepage&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/symbioquine&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token property&quot;&gt;&quot;role&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Maintainer&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;support&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;issues&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example/issues&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;source&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;license&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;GPL-3.0-or-later&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;minimum-stability&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;dev&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_exampleinfoymlcode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_exampleinfoymlcode&quot; aria-label=&quot;code classlanguage textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_exampleinfoymlcode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;drupal_module_src/symbioquine_dot_net_built_drupal_module_example.info.yml&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Symbioquine.net Built Drupal Module Example
&lt;span class=&quot;token key atrule&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; This module shows an example of how to automatically build JS code with Webpack and push releases to Drupal.org
&lt;span class=&quot;token key atrule&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; module
&lt;span class=&quot;token key atrule&quot;&gt;package&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Example
&lt;span class=&quot;token key atrule&quot;&gt;core_version_requirement&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ^9
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_examplelibrariesymlcode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_examplelibrariesymlcode&quot; aria-label=&quot;code classlanguage textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_examplelibrariesymlcode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;drupal_module_src/symbioquine_dot_net_built_drupal_module_example.libraries.yml&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;built_drupal_module_example&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;js/built_drupal_module_example.js&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;preprocess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;false&lt;/span&gt;
      &lt;span class=&quot;token key atrule&quot;&gt;minified&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean important&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_exampleroutingymlcode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_exampleroutingymlcode&quot; aria-label=&quot;code classlanguage textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_exampleroutingymlcode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;drupal_module_src/symbioquine_dot_net_built_drupal_module_example.routing.yml&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;symbioquine_dot_net_built_drupal_module_example_page.content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;/symbioquine_dot_net_built_drupal_module_example&apos;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;defaults&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;_controller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; symbioquine_dot_net_built_drupal_module_example.top_level_controller&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;content
    &lt;span class=&quot;token key atrule&quot;&gt;_title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;Example Page&apos;&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;requirements&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;_permission&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;access content&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_exampleservicesymlcode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_exampleservicesymlcode&quot; aria-label=&quot;code classlanguage textdrupal_module_srcsymbioquine_dot_net_built_drupal_module_exampleservicesymlcode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;drupal_module_src/symbioquine_dot_net_built_drupal_module_example.services.yml&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;symbioquine_dot_net_built_drupal_module_example.top_level_controller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Drupal\symbioquine_dot_net_built_drupal_module_example\Controller\ExamplePageController
    &lt;span class=&quot;token key atrule&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textdrupal_module_srcsrccontrollerexamplepagecontrollerphpcode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textdrupal_module_srcsrccontrollerexamplepagecontrollerphpcode&quot; aria-label=&quot;code classlanguage textdrupal_module_srcsrccontrollerexamplepagecontrollerphpcode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;drupal_module_src/src/Controller/ExamplePageController.php&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;php&quot;&gt;&lt;pre class=&quot;language-php&quot;&gt;&lt;code class=&quot;language-php&quot;&gt;&lt;span class=&quot;token php language-php&quot;&gt;&lt;span class=&quot;token delimiter important&quot;&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;namespace&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;Drupal&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;symbioquine_dot_net_built_drupal_module_example&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;Controller&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;use&lt;/span&gt; &lt;span class=&quot;token package&quot;&gt;Drupal&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;Core&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;Controller&lt;span class=&quot;token punctuation&quot;&gt;\&lt;/span&gt;ControllerBase&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/**
 * Defines ExamplePageController class.
 */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name-definition class-name&quot;&gt;ExamplePageController&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ControllerBase&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/**
   * Constructs a new ExamplePageController object.
   *
   */&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;__construct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/**
   * Top-level handler for demo page requests.
   */&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function-definition function&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
      &lt;span class=&quot;token string single-quoted-string&quot;&gt;&apos;app&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string single-quoted-string&quot;&gt;&apos;#markup&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token string single-quoted-string&quot;&gt;&apos;&amp;lt;div id=&quot;example-page-app&quot;&gt;&amp;lt;/div&gt;&apos;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token string single-quoted-string&quot;&gt;&apos;#attached&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
          &lt;span class=&quot;token string single-quoted-string&quot;&gt;&apos;library&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
            &lt;span class=&quot;token string single-quoted-string&quot;&gt;&apos;symbioquine_dot_net_built_drupal_module_example/built_drupal_module_example&apos;&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id=&quot;code-classlanguage-textgithubworkflowscreate-releaseymlcode&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#code-classlanguage-textgithubworkflowscreate-releaseymlcode&quot; aria-label=&quot;code classlanguage textgithubworkflowscreate releaseymlcode permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;&lt;code class=&quot;language-text&quot;&gt;.github/workflows/create-release.yml&lt;/code&gt;&lt;/h4&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;yml&quot;&gt;&lt;pre class=&quot;language-yml&quot;&gt;&lt;code class=&quot;language-yml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Build
&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# Sequence of patterns matched against refs/tags&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;tags&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;unbuilt-v*&apos;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# Push events to matching unbuilt-v*, i.e. unbuilt-v1.0.0&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Create Release
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Set RELEASE_VERSION environment variable
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; echo &quot;RELEASE_VERSION=$&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;GITHUB_REF&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;19&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&quot; &lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt; $GITHUB_ENV

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Checkout code
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v2
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; main

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Checkout release branch
        &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v2
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; release
          &lt;span class=&quot;token key atrule&quot;&gt;ref&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; release
          &lt;span class=&quot;token key atrule&quot;&gt;fetch-depth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;node@v1
        &lt;span class=&quot;token key atrule&quot;&gt;with&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;node-version&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;16.x&apos;&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; NPM Build
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          cd ./main/
          npm ci
          npm run build&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Copy Module to Release Working Dir
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          # Don&apos;t let stale build artifacts accumulate in our release branch
          rm -rf ./release/js
          cp ./main/{README.md,CHANGELOG.md,LICENSE} ./release/
          cp -r ./main/drupal_module_src/* ./release/&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Push Changes to Release Branch and Tag
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          cd ./release
          git config user.name github-actions
          git config user.email github-actions@github.com
          git add .
          git commit -m &quot;Release ${{ env.RELEASE_VERSION }}&quot;
          git tag ${{ env.RELEASE_VERSION }}
          git push --atomic origin HEAD:release ${{ env.RELEASE_VERSION }}&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Setup SSH Keys and known_hosts for drupal.org
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;SSH_AUTH_SOCK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /tmp/ssh_agent.sock
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          mkdir -p ~/.ssh/
          echo &quot;${{ secrets.DRUPAL_DOT_ORG_SSH_KNOWN_HOSTS }}&quot; &gt;&gt; ~/.ssh/known_hosts
          ssh-agent -a $SSH_AUTH_SOCK &gt; /dev/null
          ssh-add - &amp;lt;&amp;lt;&amp;lt; &quot;${{ secrets.DRUPAL_DOT_ORG_SSH_PRIVATE_KEY }}&quot;&lt;/span&gt;

      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Push Changes to Release Branch and Tag on drupal.org Gitlab
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;SSH_AUTH_SOCK&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; /tmp/ssh_agent.sock
        &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token scalar string&quot;&gt;
          cd ./release
          git config user.name github-actions
          git config user.email github-actions@github.com
          git remote add drupal-dot-org git@git.drupal.org:project/symbioquine_dot_net_built_drupal_module_example.git
          git fetch drupal-dot-org
          git push --tags --force drupal-dot-org &apos;HEAD:refs/heads/release&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;test-build&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#test-build&quot; aria-label=&quot;test build permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Test Build&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; run build&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;drupalorg-gitlab-deploy-key-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#drupalorg-gitlab-deploy-key-setup&quot; aria-label=&quot;drupalorg gitlab deploy key setup permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Drupal.org GitLab Deploy Key Setup&lt;/h3&gt;
&lt;p&gt;Add a new Deploy key to the Drupal.org GitLab for the project. For my example this is at &lt;code class=&quot;language-text&quot;&gt;https://git.drupalcode.org/project/symbioquine_dot_net_built_drupal_module_example/-/settings/repository&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I&apos;ve been using the RSA key generation instructions from &lt;a href=&quot;https://git.drupalcode.org/help/user/ssh&quot;&gt;https://git.drupalcode.org/help/user/ssh&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;e.g.&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;$ ssh-keygen &lt;span class=&quot;token parameter variable&quot;&gt;-t&lt;/span&gt; rsa &lt;span class=&quot;token parameter variable&quot;&gt;-b&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2048&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-C&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;symbioquine_dot_net_built_drupal_module_example drupal.org Gitlab deploy key&quot;&lt;/span&gt;
Generating public/private rsa key pair.
Enter &lt;span class=&quot;token function&quot;&gt;file&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;which&lt;/span&gt; to save the key &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;/home/symbioquine/.ssh/id_rsa&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;: /home/symbioquine/.ssh/symbioquine_dot_net_built_drupal_module_example
Enter passphrase &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;empty &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; no passphrase&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;:
Enter same passphrase again:
Your identification has been saved &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; /home/symbioquine/.ssh/symbioquine_dot_net_built_drupal_module_example
Your public key has been saved &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; /home/symbioquine/.ssh/symbioquine_dot_net_built_drupal_module_example.pub
The key fingerprint is:
SHA256:YmArT17dAmWhTRKPYL6DHrBBktL6pC1hbTf1f+edWaI symbioquine_dot_net_built_drupal_module_example drupal.org Gitlab deploy key
The key&apos;s randomart image is:
+---&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;RSA &lt;span class=&quot;token number&quot;&gt;2048&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;----+
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;.+  o o.&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;.       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; .o &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; @         &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;+&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt; + &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; +        &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;o&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;.&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; * o o       &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;o*&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; * &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; S o      &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;o.o* + &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token builtin class-name&quot;&gt;.&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt; o       &lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;o.&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;            E  +.&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;                 &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
+----&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;SHA256&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;-----+&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then I copied the contents of &lt;code class=&quot;language-text&quot;&gt;/home/symbioquine/.ssh/symbioquine_dot_net_built_drupal_module_example.pub&lt;/code&gt; into a new Drupal.org GitLab deploy key. Make sure
to check the box that says &quot;Grant write permissions to this key&quot; since we&apos;ll be using that key to push releases into the Drupal.org GitLab repo.&lt;/p&gt;
&lt;h3 id=&quot;github-secrets-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#github-secrets-setup&quot; aria-label=&quot;github secrets setup permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;GitHub Secrets Setup&lt;/h3&gt;
&lt;p&gt;Add two new secrets for GitHub Actions. For my example these are added at &lt;code class=&quot;language-text&quot;&gt;https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example/settings/secrets/actions&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First I copied the contents of &lt;code class=&quot;language-text&quot;&gt;/home/symbioquine/.ssh/symbioquine_dot_net_built_drupal_module_example&lt;/code&gt; into a secret named &lt;code class=&quot;language-text&quot;&gt;DRUPAL_DOT_ORG_SSH_PRIVATE_KEY&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next I copied the output of running &lt;code class=&quot;language-text&quot;&gt;ssh-keyscan git.drupal.org&lt;/code&gt; into a secret named &lt;code class=&quot;language-text&quot;&gt;DRUPAL_DOT_ORG_SSH_KNOWN_HOSTS&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;tag-and-push-initial-release&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#tag-and-push-initial-release&quot; aria-label=&quot;tag and push initial release permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Tag and Push Initial Release&lt;/h3&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;node_modules&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; .gitignore
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;drupal_module_src/js&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; .gitignore
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Release 1.0.0&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; tag unbuilt-v1.0.0
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; push &lt;span class=&quot;token parameter variable&quot;&gt;--atomic&lt;/span&gt; origin HEAD:development unbuilt-v1.0.0&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;create-drupalorg-release&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#create-drupalorg-release&quot; aria-label=&quot;create drupalorg release permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Create Drupal.org release&lt;/h3&gt;
&lt;p&gt;Once the GitHub Actions workflow completes a new &lt;code class=&quot;language-text&quot;&gt;1.0.0&lt;/code&gt; tag will have been created on the Drupal.org GitLab for the project.&lt;/p&gt;
&lt;p&gt;Then I go to the project page on Drupal.org and create the corresponding release.&lt;/p&gt;
&lt;h3 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Now we have a Drupal module with JS code built via GitHub actions and installable via the &lt;code class=&quot;language-text&quot;&gt;drupal/&lt;/code&gt; composer prefix;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;composer&lt;/span&gt; require drupal/symbioquine_dot_net_built_drupal_module_example
drush en symbioquine_dot_net_built_drupal_module_example&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Once installed, the example page can be accessed under the Drupal site at &lt;code class=&quot;language-text&quot;&gt;/symbioquine_dot_net_built_drupal_module_example&lt;/code&gt;. e.g. &lt;a href=&quot;https://drupal.example.com/symbioquine_dot_net_built_drupal_module_example&quot;&gt;https://drupal.example.com/symbioquine_dot_net_built_drupal_module_example&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The full source of this example can be found in the GitHub repository at &lt;a href=&quot;https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example&quot;&gt;https://github.com/symbioquine/symbioquine_dot_net_built_drupal_module_example&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For further reference, here are a number of other modules that I maintain which use variations of this same strategy;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/symbioquine/farmOS_land_drawing_tool&quot;&gt;https://github.com/symbioquine/farmOS_land_drawing_tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/symbioquine/farmOS_wfs&quot;&gt;https://github.com/symbioquine/farmOS_wfs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/symbioquine/farm_map_sjc&quot;&gt;https://github.com/symbioquine/farm_map_sjc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/symbioquine/farmOS_asset_link&quot;&gt;https://github.com/symbioquine/farmOS_asset_link&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/symbioquine/farm_map_google&quot;&gt;https://github.com/symbioquine/farm_map_google&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/symbioquine/drupal_jupyterlite&quot;&gt;https://github.com/symbioquine/drupal_jupyterlite&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have fun and build some cool stuff!&lt;/p&gt;</content:encoded><author>symbioquine@gmail.com</author></item><item><title><![CDATA[Local-first self-hosting]]></title><description><![CDATA[While "The Cloud" has dramatically changed the landscape of hosted computing, there are still some scenarios where it is compelling to self…]]></description><link>https://symbioquine.net//2022-09-25-local-first-self-hosting</link><guid isPermaLink="false">https://symbioquine.net//2022-09-25-local-first-self-hosting</guid><category><![CDATA[Software Engineering]]></category><pubDate>Sun, 25 Sep 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While &quot;The Cloud&quot; has dramatically changed the landscape of hosted computing, there are still some scenarios where it is compelling to self-host and particularly self-host on ones own hardware and network.&lt;/p&gt;
&lt;p&gt;Prominent among these scenarios is the case where the the application needs to be resilient to outages such that it remains locally accessible even if the wider Internet is not available.&lt;/p&gt;
&lt;p&gt;One way to achieve that while still making the application accessible via the public Internet is to reverse tunnel ports 80 and 443 from a remote server server to the local one. Then use a local gateway firewall to
route IP packets bound for the remote server and those ports to the local server directly. In this way devices on the local network will transparently get the benefits of local network performance/availability without
needing to differentiate at the DNS/IP layer - which causes issues for devices like phones that may automatically switch between the local network and the public Internet via their cell connections.&lt;/p&gt;
&lt;h3 id=&quot;remote-server-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#remote-server-setup&quot; aria-label=&quot;remote server setup permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Remote Server Setup&lt;/h3&gt;
&lt;p&gt;The only things that should be needed are to enable your SSH key in the remote server&apos;s &lt;code class=&quot;language-text&quot;&gt;authorized_keys&lt;/code&gt; file and to enable OpenSSH&apos;s &lt;code class=&quot;language-text&quot;&gt;GatewayPorts&lt;/code&gt; option.&lt;/p&gt;
&lt;p&gt;That might look something like this;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; cumulus.pub &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; ~/.ssh/authorized_keys
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;grep -q &apos;^[^#]*GatewayPorts&apos; /etc/ssh/sshd_config &amp;amp;&amp;amp; sed -i &apos;/^[^#]*GatewayPorts[[:space:]]no/c\GatewayPorts yes&apos; /etc/ssh/sshd_config || echo &apos;GatewayPorts yes&apos; &gt;&gt; /etc/ssh/sshd_config&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I would also recommend disabling &lt;code class=&quot;language-text&quot;&gt;ChallengeResponseAuthentication&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;PasswordAuthentication&lt;/code&gt; in OpenSSH;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;grep -q &apos;ChallengeResponseAuthentication&apos; /etc/ssh/sshd_config &amp;amp;&amp;amp; sed -i &apos;/^[^#]*ChallengeResponseAuthentication[[:space:]]yes.*/c\ChallengeResponseAuthentication no&apos; /etc/ssh/sshd_config || echo &apos;ChallengeResponseAuthentication no&apos; &gt;&gt; /etc/ssh/sshd_config&quot;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;sudo&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sh&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;grep -q &apos;^[^#]*PasswordAuthentication&apos; /etc/ssh/sshd_config &amp;amp;&amp;amp; sed -i &apos;/^[^#]*PasswordAuthentication[[:space:]]yes/c\PasswordAuthentication no&apos; /etc/ssh/sshd_config || echo &apos;PasswordAuthentication no&apos; &gt;&gt; /etc/ssh/sshd_config&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;local-server-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#local-server-setup&quot; aria-label=&quot;local server setup permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Local Server Setup&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Note: This assumes there is a folder &lt;code class=&quot;language-text&quot;&gt;certs/&lt;/code&gt; with &lt;code class=&quot;language-text&quot;&gt;fullchain.pem&lt;/code&gt; and &lt;code class=&quot;language-text&quot;&gt;privkey.pem&lt;/code&gt; files for the &quot;mysite.example.com&quot; domain and also that there is an OpenSSH private key for the remote host in a file named &lt;code class=&quot;language-text&quot;&gt;cumulus.privkey&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;docker-compose.yml&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;version: &apos;3.7&apos;
services:

  www:
    image: crccheck/hello-world
    restart: always
    expose:
      - &apos;8000&apos;

  proxy:
    depends_on:
      - www
    image: nginx:latest
    restart: always
    volumes:
      - &apos;./nginx.conf:/etc/nginx/nginx.conf&apos;
      - &apos;./certs/mysite.example.com:/etc/nginx/certs/mysite.example.com&apos;
    ports:
      - &apos;80:8080&apos;
      - &apos;443:8443&apos;

  autossh-forward-proxy-http-to-cumulus:
    image: jnovack/autossh:2.0.0
    environment:
      - SSH_MODE=-R
      - SSH_REMOTE_USER=root
      - SSH_REMOTE_HOST=123.123.123.123
      - SSH_REMOTE_PORT=22
      - SSH_BIND_IP=0.0.0.0
      - SSH_TUNNEL_PORT=80
      - SSH_TARGET_HOST=localhost
      - SSH_TARGET_PORT=80
    restart: always
    privileged: true
    network_mode: &quot;host&quot;
    volumes:
      - ./cumulus.privkey:/id_rsa

  autossh-forward-proxy-https-to-cumulus:
    image: jnovack/autossh:2.0.0
    environment:
      - SSH_MODE=-R
      - SSH_REMOTE_USER=root
      - SSH_REMOTE_HOST=123.123.123.123
      - SSH_REMOTE_PORT=22
      - SSH_BIND_IP=0.0.0.0
      - SSH_TUNNEL_PORT=443
      - SSH_TARGET_HOST=localhost
      - SSH_TARGET_PORT=443
    restart: always
    privileged: true
    network_mode: &quot;host&quot;
    volumes:
      - ./cumulus.privkey:/id_rsa&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;nginx.conf&lt;/strong&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;pid        /tmp/nginx.pid;
error_log  /var/log/nginx/error.log warn;

events {

}

http {
  client_body_temp_path /tmp/client_temp;
  proxy_temp_path       /tmp/proxy_temp_path;
  fastcgi_temp_path     /tmp/fastcgi_temp;
  uwsgi_temp_path       /tmp/uwsgi_temp;
  scgi_temp_path        /tmp/scgi_temp;

  client_max_body_size 200m;

  server {
    listen 8080;
    server_name mysite.example.com;

    rewrite ^/(.*)$ https://$host$request_uri? permanent; 
  }

  ssl_certificate     /etc/nginx/certs/$host/fullchain.pem;
  ssl_certificate_key /etc/nginx/certs/$host/privkey.pem;

  server {
    listen 8443 ssl;
    server_name mysite.example.com;

    location / {
      proxy_pass http://www:8000;

      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header X-Forwarded-Proto https;
    }

  }

}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id=&quot;networking-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#networking-setup&quot; aria-label=&quot;networking setup permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Networking Setup&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Note: These instructions are for Mikrotik routerOS, but something similar should be possible with most full-featured router/firewall hardware.&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;/ip dns static
add address=123.123.123.123 name=mysite.example.com ttl=5m
/ip firewall nat
add action=masquerade chain=srcnat comment=&quot;defconf: masquerade&quot; ipsec-policy=out,none out-interface-list=WAN
add action=dst-nat chain=dstnat dst-address=123.123.123.123 dst-port=80,443 protocol=tcp to-addresses=192.168.88.32
add action=src-nat chain=srcnat protocol=tcp src-address=192.168.88.32 src-port=80,443 to-addresses=123.123.123.123
add action=masquerade chain=srcnat dst-address=192.168.88.0/24 src-address=192.168.88.0/24
/ip route
add distance=1 dst-address=123.123.123.123/32 gateway=192.168.88.1&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c88659dc17ef04a63627bf26e42012f0/167b5/static_dns_entry.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.72254335260115%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAC4jAAAuIwF4pT92AAABwklEQVQ4y62UWY+iUBSE+f9/wiefXGFMTIhbIjBiHJG0W8cl2ho2UUB9r0mdDJ2ZHttMJ/NQOXCA71bde4Livkxg2zZ2ux36/T6KxSJKpRIqlYrUWq2GZrMJ0zRFhmFA0zTpS88wYBgWVFVHR29C+f5jgOFwiNVqhel0ivF4jMlkAsdxMHYcqbx3XVfeI9CyLAwGA6n5QpZlynPFsm2MRiNsNhtxud1u5ZqVixwPb1gul7hcLrjf77jdbk+lOO4LFouFQA6HA5IkwSmOcTweEUYxvChBmiRI01SeEfxMSt80Jep8PhdXWZYhjmO8vi7hBxE2bwF8z4PnedLPPyT8kRTtm4ZWq4VOp4PZbIbr9SofMmYYhggDX9xS7J/PZ9GnwHK5jEaj8Q6kQzpYr9cIgkAgWZZKzSN/BhNgvV6Xseh2uxI73ytGpENuw36/lx4Xo7tncIXzRKiu6++RCWLkKIrk5OmWkQk4nU5/7OVfh1IoFGSAq9WqzBtHw/d9GRkC6fijuOhHcWQEqKoq2u22RM4dJr/GhOJL+UH8ixS6+30Pc2CuRw6fSRwS2Ov1HgK/KoU/Ac7hI4dfdScOeRiE/S/gTzCcQAhxfgrKAAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Static DNS entry&quot;
        title=&quot;Static DNS entry&quot;
        src=&quot;/static/c88659dc17ef04a63627bf26e42012f0/1e043/static_dns_entry.png&quot;
        srcset=&quot;/static/c88659dc17ef04a63627bf26e42012f0/991de/static_dns_entry.png 173w,
/static/c88659dc17ef04a63627bf26e42012f0/e4d6b/static_dns_entry.png 345w,
/static/c88659dc17ef04a63627bf26e42012f0/1e043/static_dns_entry.png 690w,
/static/c88659dc17ef04a63627bf26e42012f0/167b5/static_dns_entry.png 776w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ca58cd5ec3ea57b8ebc1f5d48b714bad/2ed34/nat_rules.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 35.260115606936424%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAC4jAAAuIwF4pT92AAAA60lEQVQoz42PyYqEQBBE/f9/EQQvHr3Y44Z26V0EsV1xw10hhkwQ7J4ZmMMjMpKoqCrJFz5834eu6zAMA7Ztw7Is1ms2TfONa+c4DoIgYP98Cnw9TEi+EIiiCEmS4JW+UNc1U1UVa1mWPF+Qv8jzHFmWoSgKlGXBXtI0DUIINmma8iGaiaZpWO9FVwkRxzE/hLjykizLcF0X0zSh6zvM84xxHDEMA/Z955l267pi2zbW8zxxHAdD87IsnCHPhZ7ncQHdQLRt+0PvfObuGUlRFP4yFdKi67pfuRf+lSEkVVURhiH6vn878Kn/5RtsUwRVvkuRUQAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;NAT Rules&quot;
        title=&quot;NAT Rules&quot;
        src=&quot;/static/ca58cd5ec3ea57b8ebc1f5d48b714bad/1e043/nat_rules.png&quot;
        srcset=&quot;/static/ca58cd5ec3ea57b8ebc1f5d48b714bad/991de/nat_rules.png 173w,
/static/ca58cd5ec3ea57b8ebc1f5d48b714bad/e4d6b/nat_rules.png 345w,
/static/ca58cd5ec3ea57b8ebc1f5d48b714bad/1e043/nat_rules.png 690w,
/static/ca58cd5ec3ea57b8ebc1f5d48b714bad/e3189/nat_rules.png 1035w,
/static/ca58cd5ec3ea57b8ebc1f5d48b714bad/b1001/nat_rules.png 1380w,
/static/ca58cd5ec3ea57b8ebc1f5d48b714bad/2ed34/nat_rules.png 1776w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/22c92d34b9c69d7d2e50a7ef4c28fa86/d7542/dstnat_rule.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 356.6473988439306%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAABHCAYAAAAHk2dUAAAACXBIWXMAAC4jAAAuIwF4pT92AAAFxklEQVRYw52YV1PbQBSF9f//By+UDC0JHUK3wTg2oZpiAgHjQi8JMHMz33WORjYSsfxwx5J29+y5de86yBXW7fv3gpVKJctms7a4uGhLS0vh7+rqqq2srPgv45lMJvzGM99mZ2dtcnLSFua/WbCytmbFQhPw+PjYyuWyHRwcuBwdHfnv4eGhj29vb1ulUrHb21u7ubmx6+trF54lwfJqxjaLRV98cXFhV1dXVqvVrFqtWqPRsHq9bvV6LfzGuMAEImAkWM9t2P7evm1tbdn5+bnd3d35JMBOymW7qDbs7PLGbmMWRyVkODQ0aFNTU24H1EUdWCAXF+dWqzesWm/Y9fWVL9SYhG9sjoZnZ2cW9Pf329jYmM3NzTkgDJuTG1YuH1u1emlnp6e+iM2kdpQd75eXl26eYHR01BnOzMzYyclJCFgun1qpdGyVSnMiQHFgEjkqGBgYsOHh4RaVGw0Ab2xvr2GVSs29j6d//frlgGwA43b1kaCnp8dg+fnzZ7cDDNmxWq3Y1tamg+AsVGp+r7rHk+wZ2vDr16/ORF5m4eHhkQMJkDGNJ6kefPr0yaMclWVDFgCAmgQyIvshUjcWsK+vL9bLqL+zs+PBfvOfGGwB/PLli83Pz7uXBQibvb29EDDO+HES2hB1BXh/f2+np6eew9gUdTthFjIcHBy0hYWFMGxgKEAEwFQMR0ZGnF0UkLBQdcHDSQ6IZYjK09PTLSpjN4oFgDynVhnAqJcJC9XATm3YovL0zIyDiiHZsbu764CdeLkll2fn5ryc5/N5+/nzpw+Qq9ixWVzrH4IBwqaEGc4M1jfyVvh3BMAMldsZ/k9dTAQBfgOyZHx83G2o1GMnnLK/v9+xyi2pR2HAhioO7KZYTJMpXr6GhoY89aIMAcS7CuxUYYOXAVMcPj4+unOKxaLHIfZMBdjb29tyBOBVQFAZ4T0VICrDUCpTB1EVD+OU1CoDqAKLUx4eHpwhcdVVcSCXqYnt9RB2AOos6TqXyRQYUlwBVbXpmCFHqMoXNuQj3iV7AJWXOwYksCcmJjxboq0IZ0pX1QaViUUAYaiqge3SAIY2ROV2p2A3hU5qGxLYFIjoEcAAQa0jIHUcUhyizRIq05oRi6mPAB1SUZUBhFlXNpwaH7NMNms7/9RTl4DKOujjCmpiK5LZKFqptB/2Mc12ruFBTZGNA4wrrCFgvpBvqc7RONQmqVReXl62bDYTqgcgA9iSWEwT2F6xsd/a2pqzZLF6Q1KOd6p3J/FHUUaCTG7DcrmcFQrFkCFAOEWB3QnD8Foxv7xqudy6FwMWK/Wo1F11XyvLS166uL8JUKFBoKfOlJn5b7a0uOROkQ0BY5BAJ2NSAWbya/Zj80d4fuBdjEvadZUpiwsLYYGFjToH4rArG1IHKbBUnGhx4Ff3t1QFlhMPUYHVLQB2HPadVhuciQSwiwMkuHUF05UiKX9VTPw22g4Yvd4yAVC+63zWRu3ZQpoylgjYTKcTV5nJ+hZNt/bG0zMlCVD1kMBWQxkH9K6dS7IhTqGLhSHfKWVqnOIaqESGOqR0UGEXTY4rrMosbOw3+jiVlXoq9eqf22ufwLExmUXJS7Qhu+kI0E0+yX4qyB86hUlkii7gSVUnupFnShwgAzxTMFCXYqEOAtWjEv0W62UxjP67xDduWPTfiJ7V4D89Pfn7O5VhwmTZRM8seH5+tj9//rjw/vLy4iBsyjPyLpcBlHF1T4EBICx4fX21t7c3f+dXIDwjEIgFlJEVKjpjYKZTEcbMwbYyRWJgy2MKWtjCFI8zR0cD2YQwTmj5XS+OIWroPxrA5QRYIdwU9Avz379/+3ssQxaTRqhCULMrC7AbNkR4ly0BkQ0hEAuIOqgFYxiImZyDLQHmGxvy/KGXo3GnXpFN1FXoZoAG0iIsX4Bx1wM46mX1N7r7sZBnwAFVSjKPMSWBN+0ItwE8pmBmAhswSax1GupAij4rw/4C3eFhnELJvRgAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;dstnat Rule&quot;
        title=&quot;dstnat Rule&quot;
        src=&quot;/static/22c92d34b9c69d7d2e50a7ef4c28fa86/1e043/dstnat_rule.png&quot;
        srcset=&quot;/static/22c92d34b9c69d7d2e50a7ef4c28fa86/991de/dstnat_rule.png 173w,
/static/22c92d34b9c69d7d2e50a7ef4c28fa86/e4d6b/dstnat_rule.png 345w,
/static/22c92d34b9c69d7d2e50a7ef4c28fa86/1e043/dstnat_rule.png 690w,
/static/22c92d34b9c69d7d2e50a7ef4c28fa86/d7542/dstnat_rule.png 810w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6f99a8dbd5a6cf291f8553452dfc4357/d7542/srcnat_rule.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 356.6473988439306%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAABHCAYAAAAHk2dUAAAACXBIWXMAAC4jAAAuIwF4pT92AAAFt0lEQVRYw6WYV3PiSBSF+f//wy8OU87ZmHUGjBmYcQ5gY5JJzrnqbn1tH5XAgkXsQ5daaun0uel0tyKpzK79/p2x09NTSyQStrGxYZubm951Z2fHtre33ZVx+ouLi7a8vGzRaNSWlpZsZWXFPYvFVi2ynUxaNvMFmMvlLJ/P29nZmWsXFxfuen5+7sYPDg6sUqnY/f293d7eBrbI1k7c/mSz7uNyuWzNZtNqtZrd3NxYo9Gwer1u9XrNe8Y4H7ZarcAW2U3t2cnxie3v71upVPJmB+wyn7fyTcOuq7d2d9uyVg8gj+HExLjzx+rqqjP37u7OsaCVyyWr1RtWq8Pq6yONqQHG5Fh4fX1tkdHRUZufn7dYLOYAYaiXua9Wq3Z1dWWVatV9WP2+CoxGn+e4JzI9Pe1F7PLy0gMEjEAQhOti0QUGBowLxO9LLHMmj42N2eTkZJvJMKB/fHzsAJlZjL6CVPdAO1tkaGjIYDkzM+P8IAZElEAVi0U7OTlpm8xvcqc/PR/Ozc25PFSUAWQCfANLrvqw04dtafPr1y+X7ZgsHwIIAH4DTCnRLWXaAEdGRgKjDLvDw0OX7P0AeYCzs7O2trbmoixAzCUgAgxyflDzfIi5Anx4eLBCoeBqGJ9iciiG4+Pjtr6+7qUNDAVIAzAUw6mpKcfOD4gQSF2o724RDWSIyeiZ32T8Rg4CGDoomAygP8rkmTSwXx+2mbwSjTpQMaQ6jo6OHGA/UW6r5dVYzMl6Op12qsIAtYofVbe9wABhUtKMYEZ299KW+V4CYIbJnQz/y1xcBAGuEapkYWHB+VClx0wEBVHo1+S20kMY8KHEgdmUi2EqxcnXxMSEKz0/QwCJrhI7VNoQZcCUh09PTy442WzW5SH+DAU4PDzctgQQVUAwmSZ17hsQk2Eok9FBTCXCBCW0yQBKYAnK4+OjY0heDSQO1DKa2KmHsAMQxv+rlqkUGCKugEpt+mbIEir5woc8JLpUD6CKct+AJDZbMarFvxVhTRlIbTCZXAQQhlINfBcG0PMhJncGBb8pdUL7kMRGIPxLAAMktZaA0HmIOPg3S5jMxohcDL0EaJHymwwgzAby4fLCvMUTCTv8Nk/bDkzutnPoubeJ72Xt9PTE28doh0VSI7JBgEHC6gGmM+k2dfbnoSYJZfLW1pYlEnHPPAAZwJfayvWb2E6x8V8ymXQstXVDHCg57lHvfvIPUaZF4qk9S6VSlslkPYYAERQldj8MvWPF2taOpVK7Tgz4WKWHUg+0+9re2nTSxVlOgEoNEj10pUTX/rHNjU0XFPkQMAZJdComFGA8nbS/f/566wfRxbmU3UCVsrG+7gmsDjYEhDwcyIfoIAKL4vjFgavOb6EElhWPJoHVKQB2LPb9qg3BpEVgFwRIcpM6BEhnlG71KzFxp9FOQP/xlhcA5WWtz5qos1ooU8a6ArZaTbu6yjuTq9WbH2e7zpXQq5RugIVC2Q4Pz61YLHm/CoKAfmznuvkwl2va0VHdSqWqFQpXlsvlvV1E0AaqK0MtUq1WwwqFvPNLL2FVZelcHWiySq/RaH63RlffcQ9zKgvJ6+pDZtOfEjSxl+8kyD2DwktUiv/w3atCvEoJAmSAPuIg9davgc6mE776XRn6/y5hDics9t809bXBf35+dvc/TEa2eFk+UZ8PXl5e7O3tzTXu39/fHQiT0qf9qGUA5VydU2AACB98fHzY5+enu+cqEPo0CAQCyslKH60xMPP3GSNockXXxFbElLSwhSnR1kmBQLlfWJWKuypwgQwxg6vAFQT8SOOk8Pr66q6wpc/zQIZ8zKwkNErDrHyA3/AhjXv5EhD5EAKBgOggKx6MYSBmCg5sAeYZ4/R7RpkBmam9IpNoVyGxxZf6zefJF2Cc9QD2R1kVorOf1ARwqQuNPs9UBG7TTuM0wMKkZOYFJlClaIuiPNWipGeqsH8BdO9hsAzvNtAAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;srcnat Rule&quot;
        title=&quot;srcnat Rule&quot;
        src=&quot;/static/6f99a8dbd5a6cf291f8553452dfc4357/1e043/srcnat_rule.png&quot;
        srcset=&quot;/static/6f99a8dbd5a6cf291f8553452dfc4357/991de/srcnat_rule.png 173w,
/static/6f99a8dbd5a6cf291f8553452dfc4357/e4d6b/srcnat_rule.png 345w,
/static/6f99a8dbd5a6cf291f8553452dfc4357/1e043/srcnat_rule.png 690w,
/static/6f99a8dbd5a6cf291f8553452dfc4357/d7542/srcnat_rule.png 810w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/9cc74d2fde2c7bf98df8d9f50861dfc2/fe9f1/backup_route.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.77456647398844%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAC4jAAAuIwF4pT92AAABZ0lEQVQoz21Q2YrCQBCc//8TEUR8SAyKUTaY9cEjERW88UDXRJMYETywlmoYN8gOFF3V3VN0t3JdF/P5HJVKBblcDvl8XlAqlVAul2GapoCcva1WC47joFAowLZt0a77DdOwUbVMKM/zMB6PMZlMMJvN0Ov10O/3MRgM4Ps+ut3uv2Cfjp1OB47zhWazCcXPnHA6nYppmqa4XC4Ccq01z+psjKIIp9MJyvd8MVyv1wiCQAphGOJ4PAqP41g0eZIk7zo/s4dRa9aUHp3TrVYrMeeqo9EI2+1WctTcQNeHwyHa7TZ4rsViISfiuTabDZRhGHJcNu33eyRpIpHTcp3H44Hdbifm3IJT8TH/er3wfD5xv98l3m43qGq1ikajIRPEcYIgDHA4/IgBP/M+NCS4PtfWd7ter5LTNeZVsVhErVbDcrnE+XxGdIoQR7E06EOTZ/UnWNNcWZaFer3+ZxhFb2SNsvoT2dov2Y+NQ7IaAB8AAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Backup Route&quot;
        title=&quot;Backup Route&quot;
        src=&quot;/static/9cc74d2fde2c7bf98df8d9f50861dfc2/1e043/backup_route.png&quot;
        srcset=&quot;/static/9cc74d2fde2c7bf98df8d9f50861dfc2/991de/backup_route.png 173w,
/static/9cc74d2fde2c7bf98df8d9f50861dfc2/e4d6b/backup_route.png 345w,
/static/9cc74d2fde2c7bf98df8d9f50861dfc2/1e043/backup_route.png 690w,
/static/9cc74d2fde2c7bf98df8d9f50861dfc2/fe9f1/backup_route.png 883w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;</content:encoded><author>symbioquine@gmail.com</author></item><item><title><![CDATA[DIY Adapters for DeWalt Batteries]]></title><description><![CDATA[While I don't intend to make this blog reviews-focused, some products have the potential to be foundational to a wide range of alternative…]]></description><link>https://symbioquine.net//2021-12-03-dewalt-diy-adapters</link><guid isPermaLink="false">https://symbioquine.net//2021-12-03-dewalt-diy-adapters</guid><category><![CDATA[Batteries]]></category><pubDate>Fri, 03 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;While I don&apos;t intend to make this blog reviews-focused, some products have the potential to be foundational to a wide range of alternative DIY projects which I would like to help
make more viable.&lt;/p&gt;
&lt;p&gt;Many DIY projects require a simple reliable high-capacity power source. While dedicated or built-in batteries sometimes make sense, sourcing high-quality cells is often difficult.
Also, as the number of such devices with dedicated batteries grows, so does the effort needed to keep them all charged on a semi-regular basis. As such, small tool batteries -
like those for power tools by Makita, DeWalt, etc - make a compelling option. Here I will review 3 similar DeWalt adapters so other folks can hopefully just buy the one that best
suites their project best.&lt;/p&gt;
&lt;p&gt;There are probably at least a dozen of these adapters on Amazon, and even more listings on Ebay and AliExpress. I picked 2 of the ones with the most/best reviews and one that seemed
to be newly listed but which claimed to have over current/voltage/charge/heating protection.&lt;/p&gt;
&lt;p&gt;Since the adapters are so similar, I&apos;ll refer to them by the names Andy, Bart, and Curt.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Andy: &quot;Power Wheels Adaptor for dewalt 20V&quot; &lt;a href=&quot;https://www.amazon.com/dp/B09C5CXVDM&quot;&gt;https://www.amazon.com/dp/B09C5CXVDM&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;$12.58 - 4.6 stars with 1,114 ratings and 82 reviews&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Bart: &quot;Upgraded Battery Adapter for dewalt&quot; &lt;a href=&quot;https://www.amazon.com/dp/B08JLYPY4F&quot;&gt;https://www.amazon.com/dp/B08JLYPY4F&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;$8.09 - 4.5 stars with 3 ratings and zero reviews&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Curt: &quot;Battery Adapter for Dewalt 20V&quot; &lt;a href=&quot;https://www.amazon.com/dp/B08GWZ8LR6&quot;&gt;https://www.amazon.com/dp/B08GWZ8LR6&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;$10.58 - 4.6 stars with 498 ratings and 40 reviews&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/84e2d39ae133f6d966e62e8c33a5983c/3d0d4/overview.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 42.77456647398844%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAJABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAQL/xAAWAQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAAaeWT5uCA//EABkQAAMAAwAAAAAAAAAAAAAAAAECEAMSE//aAAgBAQABBQIO/MOdTkMaf//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABsQAAEEAwAAAAAAAAAAAAAAAAEAAhExICJR/9oACAEBAAY/AiS3bil1q8P/xAAaEAEAAgMBAAAAAAAAAAAAAAABAEEQETFx/9oACAEBAAE/IUajSBENOiNejyVi9n//2gAMAwEAAgADAAAAELgv/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/EJWv/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAR/9oACAECAQE/EE2f/8QAGRAAAwEBAQAAAAAAAAAAAAAAAAERMUHw/9oACAEBAAE/EJHmq2zPdNr+fBla3JnAzg0P/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Overview showing the adapters above from left to right&quot;
        title=&quot;Overview showing the adapters above from left to right&quot;
        src=&quot;/static/84e2d39ae133f6d966e62e8c33a5983c/15ec7/overview.jpg&quot;
        srcset=&quot;/static/84e2d39ae133f6d966e62e8c33a5983c/9ac50/overview.jpg 173w,
/static/84e2d39ae133f6d966e62e8c33a5983c/8d48c/overview.jpg 345w,
/static/84e2d39ae133f6d966e62e8c33a5983c/15ec7/overview.jpg 690w,
/static/84e2d39ae133f6d966e62e8c33a5983c/af659/overview.jpg 1035w,
/static/84e2d39ae133f6d966e62e8c33a5983c/89b2d/overview.jpg 1380w,
/static/84e2d39ae133f6d966e62e8c33a5983c/3d0d4/overview.jpg 2991w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;breakdown&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#breakdown&quot; aria-label=&quot;breakdown permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Breakdown&lt;/h2&gt;
&lt;h3 id=&quot;andy&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#andy&quot; aria-label=&quot;andy permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Andy&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/c38b9753e07a47b149eeb2cfa9184fa7/e5166/andy.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 117.34104046242774%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAXABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAMFBAb/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgD/2gAMAwEAAhADEAAAAWfSJvQIJqPMJpAf/8QAHBAAAwACAwEAAAAAAAAAAAAAAQIDAAQREhQj/9oACAEBAAEFAtqhwO6mDlp2PNCc1j86a/Y+d+Zp0T//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAbEAACAgMBAAAAAAAAAAAAAAAAAREhEDJBgf/aAAgBAQAGPwJJGxdjx6SmcIP/xAAaEAEBAQADAQAAAAAAAAAAAAABEQAhQYGh/9oACAEBAAE/IaFSF4cUa8cRXVS73k1OnfSyLLdKBWcON73/2gAMAwEAAgADAAAAEMcAgP/EABYRAAMAAAAAAAAAAAAAAAAAAAABEP/aAAgBAwEBPxAV/8QAGBEAAgMAAAAAAAAAAAAAAAAAAAEQETH/2gAIAQIBAT8QHpcf/8QAHRABAQEAAgIDAAAAAAAAAAAAAREAITFBkVFhgf/aAAgBAQABPxC6FzoVer60SFlUGe8xYbHaXdZfuPBxrSBHzuTOtVMFUeT8zYY/OayArHSu/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;andy&quot;
        title=&quot;andy&quot;
        src=&quot;/static/c38b9753e07a47b149eeb2cfa9184fa7/15ec7/andy.jpg&quot;
        srcset=&quot;/static/c38b9753e07a47b149eeb2cfa9184fa7/9ac50/andy.jpg 173w,
/static/c38b9753e07a47b149eeb2cfa9184fa7/8d48c/andy.jpg 345w,
/static/c38b9753e07a47b149eeb2cfa9184fa7/15ec7/andy.jpg 690w,
/static/c38b9753e07a47b149eeb2cfa9184fa7/af659/andy.jpg 1035w,
/static/c38b9753e07a47b149eeb2cfa9184fa7/e5166/andy.jpg 1200w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt; &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4da114d4d21762dd9d7189e63a3620e8/e5166/andy_internals.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 87.86127167630057%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAASABQDASIAAhEBAxEB/8QAGQABAAMBAQAAAAAAAAAAAAAAAAIDBAUG/8QAFQEBAQAAAAAAAAAAAAAAAAAAAQL/2gAMAwEAAhADEAAAAYTqxB6RzE3qzCXAf//EABwQAQACAgMBAAAAAAAAAAAAAAIAAQMEISIxMv/aAAgBAQABBQJNWhs8DKWXhU8vX+FFVTHXX//EABYRAQEBAAAAAAAAAAAAAAAAABEBIP/aAAgBAwEBPwGLj//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABsQAAICAwEAAAAAAAAAAAAAAAABETEQIWFx/9oACAEBAAY/AnvUkO+IlFoaH7isf//EABoQAQADAQEBAAAAAAAAAAAAAAEAESExYXH/2gAIAQEAAT8hMzClQOn1C+Hmk0olsq86NRV9M5I/UX8gGQJ//9oADAMBAAIAAwAAABCw6L7/xAAXEQEBAQEAAAAAAAAAAAAAAAABABAh/9oACAEDAQE/EAEryML/xAAWEQEBAQAAAAAAAAAAAAAAAAABABD/2gAIAQIBAT8QQZxv/8QAHBABAQACAwEBAAAAAAAAAAAAAREAITFBoVHx/9oACAEBAAE/EIPZEGoF9wGzLsiS/Llg4NaEcisQ8pa3B97ibOMbT/Az0YIprtFwMCV4Jn//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;andy internals&quot;
        title=&quot;andy internals&quot;
        src=&quot;/static/4da114d4d21762dd9d7189e63a3620e8/15ec7/andy_internals.jpg&quot;
        srcset=&quot;/static/4da114d4d21762dd9d7189e63a3620e8/9ac50/andy_internals.jpg 173w,
/static/4da114d4d21762dd9d7189e63a3620e8/8d48c/andy_internals.jpg 345w,
/static/4da114d4d21762dd9d7189e63a3620e8/15ec7/andy_internals.jpg 690w,
/static/4da114d4d21762dd9d7189e63a3620e8/af659/andy_internals.jpg 1035w,
/static/4da114d4d21762dd9d7189e63a3620e8/e5166/andy_internals.jpg 1200w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h4 id=&quot;pros&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pros&quot; aria-label=&quot;pros permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Attaches to battery securely, detaches only when battery release button is pressed, and appears to be making a solid electrical connection with battery terminals&lt;/li&gt;
&lt;li&gt;Plastic and mechanical portion is sturdy and fits together cleanly&lt;/li&gt;
&lt;li&gt;Has a very simple &quot;circuit board&quot; inside to hold the contacts - this could be convenient for some kinds of mods&lt;/li&gt;
&lt;li&gt;Wires are very well soldered to board&lt;/li&gt;
&lt;li&gt;200 C Fluorinated Ethylene Propylene (FEP) wire insulation&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cons&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cons&quot; aria-label=&quot;cons permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;No strain relief for wires&lt;/li&gt;
&lt;li&gt;Thin contacts&lt;/li&gt;
&lt;li&gt;Wire does not match description - actually is 14 awg not 12 awg&lt;/li&gt;
&lt;li&gt;Wire has a relatively low strand count and is pretty stiff - not ideal for applications which will experience frequent bending&lt;/li&gt;
&lt;li&gt;3g over advertised weight of 70g&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;bart&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#bart&quot; aria-label=&quot;bart permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Bart&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/4669ae42152866d0b34afb043cc6313d/e5166/bart.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 116.1849710982659%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAXABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAMEBQH/xAAVAQEBAAAAAAAAAAAAAAAAAAABAP/aAAwDAQACEAMQAAABtZslYttCHtAjWE//xAAdEAACAgIDAQAAAAAAAAAAAAACAwEEABITFCEj/9oACAEBAAEFArLJWPYZGKPkB57uL3K0fJlaCk6zIxY6B//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABwQAAICAgMAAAAAAAAAAAAAAAABEBIRMSFBYf/aAAgBAQAGPwJV2cpFtD8ltdxg/8QAGxABAAMBAQEBAAAAAAAAAAAAAQARITFhcYH/2gAIAQEAAT8hvBSg7ZtwBwnpcABdd+ynF/pM/wBj5A62YIY+yqe1P//aAAwDAQACAAMAAAAQpNjA/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPxAf/8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQIBAT8QKf/EAB4QAQADAAICAwAAAAAAAAAAAAEAESExUUGxccHh/9oACAEBAAE/EA5EWoC11vbBASlCHJ3VZkXZYGqkYfFmhW0aA722JmmHRBJyx5fUbklgefn8glZarM+z1HfC5JP/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;bart&quot;
        title=&quot;bart&quot;
        src=&quot;/static/4669ae42152866d0b34afb043cc6313d/15ec7/bart.jpg&quot;
        srcset=&quot;/static/4669ae42152866d0b34afb043cc6313d/9ac50/bart.jpg 173w,
/static/4669ae42152866d0b34afb043cc6313d/8d48c/bart.jpg 345w,
/static/4669ae42152866d0b34afb043cc6313d/15ec7/bart.jpg 690w,
/static/4669ae42152866d0b34afb043cc6313d/af659/bart.jpg 1035w,
/static/4669ae42152866d0b34afb043cc6313d/e5166/bart.jpg 1200w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt; &lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/e1f53b828aed78372cf37abb512f54e7/e5166/bart_internals.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 74.56647398843931%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMEAv/EABYBAQEBAAAAAAAAAAAAAAAAAAIAAf/aAAwDAQACEAMQAAAB0p0GVxMFf//EABoQAAMAAwEAAAAAAAAAAAAAAAECAwAEERL/2gAIAQEAAQUCpZwW2X6l/S0lQ5SbAyBC/wD/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAZEAACAwEAAAAAAAAAAAAAAAAAAREiMTL/2gAIAQEABj8CdVBiJaOUYYf/xAAcEAADAAIDAQAAAAAAAAAAAAAAAREhQTFhkaH/2gAIAQEAAT8hmbUwvSrJxcmKKdmC78GR70W+h9T2P//aAAwDAQACAAMAAAAQVA//xAAXEQADAQAAAAAAAAAAAAAAAAAAAREx/9oACAEDAQE/EKxYf//EABYRAQEBAAAAAAAAAAAAAAAAAAABMf/aAAgBAgEBPxBdf//EABsQAQEBAQEAAwAAAAAAAAAAAAERIQAxQVHh/9oACAEBAAE/ELrA2tZ+eEQ6IlilzspBITOjAtWiXe+XXwH0HtMdpp5nf//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;bart internals&quot;
        title=&quot;bart internals&quot;
        src=&quot;/static/e1f53b828aed78372cf37abb512f54e7/15ec7/bart_internals.jpg&quot;
        srcset=&quot;/static/e1f53b828aed78372cf37abb512f54e7/9ac50/bart_internals.jpg 173w,
/static/e1f53b828aed78372cf37abb512f54e7/8d48c/bart_internals.jpg 345w,
/static/e1f53b828aed78372cf37abb512f54e7/15ec7/bart_internals.jpg 690w,
/static/e1f53b828aed78372cf37abb512f54e7/af659/bart_internals.jpg 1035w,
/static/e1f53b828aed78372cf37abb512f54e7/e5166/bart_internals.jpg 1200w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h4 id=&quot;pros-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pros-1&quot; aria-label=&quot;pros 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Attaches to battery securely, detaches only when battery release button is pressed, and appears to be making a solid electrical connection with battery terminals&lt;/li&gt;
&lt;li&gt;Plastic and mechanical portion is sturdy and fits together cleanly&lt;/li&gt;
&lt;li&gt;Held together with nicer T9 screws compared with the philips screws found on Andy&lt;/li&gt;
&lt;li&gt;Slightly thicker contacts than Andy&lt;/li&gt;
&lt;li&gt;Right at advertised weight of 67g&lt;/li&gt;
&lt;li&gt;Actually has 12 gauge wire - as advertised&lt;/li&gt;
&lt;li&gt;Wires are directly soldered to the flat battery contacts - Could potentially increase overall ampacity&lt;/li&gt;
&lt;li&gt;200 C Fluorinated Ethylene Propylene (FEP) wire insulation&lt;/li&gt;
&lt;li&gt;Has some internal space that could be used in some projects to house additional small electronics&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cons-1&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cons-1&quot; aria-label=&quot;cons 1 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;No strain relief for wires - though they do feed through small holes in a way that may provide some protection compared to Andy&lt;/li&gt;
&lt;li&gt;Product images falsely advertise the adapter as having &quot;Multiple Protection&quot; (&quot;Over-current&quot;, &quot;Overvoltage&quot;, &quot;Over-Charge&quot;, and &quot;Over-Heating&quot;), but actually there is nothing in the adapter that would provide that&lt;/li&gt;
&lt;li&gt;Contacts are embedded in floating a piece of plastic and rely on the assembled case to capture them in the right position&lt;/li&gt;
&lt;li&gt;Wire has a relatively low strand count and is pretty stiff - not ideal for applications which will experience frequent bending&lt;/li&gt;
&lt;li&gt;I partially stripped two of the screws doing the investigation for this review - probably not really the fault of the adapter since they&apos;re not meant to be taken apart very many times&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;curt&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#curt&quot; aria-label=&quot;curt permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Curt&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/10b998d783fc5d5fe5ccda872c9d478e/e5166/curt.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 126.58959537572254%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAZABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAEDBAL/xAAXAQADAQAAAAAAAAAAAAAAAAABAgMA/9oADAMBAAIQAxAAAAG2K4hTazqLn5sDchh//8QAGhABAQACAwAAAAAAAAAAAAAAAQACEBEhMf/aAAgBAQABBQJ6MHEeZvIZnbr/xAAXEQADAQAAAAAAAAAAAAAAAAABEBEh/9oACAEDAQE/AQMVX//EABcRAAMBAAAAAAAAAAAAAAAAAAECEBH/2gAIAQIBAT8BZjsK7P/EABkQAAMAAwAAAAAAAAAAAAAAAAABECAhQf/aAAgBAQAGPwI04xXuP//EABsQAQEAAgMBAAAAAAAAAAAAAAEAITEQEUFR/9oACAEBAAE/IUT9wmpr9WFgWGauDsmhqHfsa4u7/9oADAMBAAIAAwAAABCcIvH/xAAXEQEBAQEAAAAAAAAAAAAAAAABABEh/9oACAEDAQE/EDephhlt/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARIf/aAAgBAgEBPxAmCOkK2DL/xAAfEAEAAwACAQUAAAAAAAAAAAABABEhMWFBUXGRsfD/2gAIAQEAAT8QUK1jvpCqS0dv2/MBbHIXNlVsRoBuivaaOmMhEdqCdE8sZNgu1PunCc0//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;curt&quot;
        title=&quot;curt&quot;
        src=&quot;/static/10b998d783fc5d5fe5ccda872c9d478e/15ec7/curt.jpg&quot;
        srcset=&quot;/static/10b998d783fc5d5fe5ccda872c9d478e/9ac50/curt.jpg 173w,
/static/10b998d783fc5d5fe5ccda872c9d478e/8d48c/curt.jpg 345w,
/static/10b998d783fc5d5fe5ccda872c9d478e/15ec7/curt.jpg 690w,
/static/10b998d783fc5d5fe5ccda872c9d478e/af659/curt.jpg 1035w,
/static/10b998d783fc5d5fe5ccda872c9d478e/e5166/curt.jpg 1200w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;h4 id=&quot;pros-2&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#pros-2&quot; aria-label=&quot;pros 2 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Pros&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Attaches to battery securely, detaches only when battery release button is pressed, and appears to be making a solid electrical connection with battery terminals&lt;/li&gt;
&lt;li&gt;Plastic and mechanical portion is sturdy and appears to be a single piece - not counting the contacts and wire&lt;/li&gt;
&lt;li&gt;Actually has 12 guage wire - as advertised&lt;/li&gt;
&lt;li&gt;Wire is a high strand count which should hold up better to frequent bending than the other adapters&lt;/li&gt;
&lt;li&gt;Wires are directly soldered to the flat battery contacts - Could potentially increase overall ampacity&lt;/li&gt;
&lt;li&gt;Wire insulation says 200 C and is nice and flexible/rubbery - might be silicone&lt;/li&gt;
&lt;li&gt;Has nice countersunk screw holes for attaching the adapter to things&lt;/li&gt;
&lt;li&gt;Lightest of the adapters at 56g&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;cons-2&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#cons-2&quot; aria-label=&quot;cons 2 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Cons&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;No strain relief for wires&lt;/li&gt;
&lt;li&gt;Thin contacts - similar to those on Andy but a little more fragile due to their smaller size and reduced contact area with the case of the adapter&lt;/li&gt;
&lt;li&gt;Contacts are a little loose and could potentially pull out of the adapter&lt;/li&gt;
&lt;li&gt;Product images could be misleading since they contradict the description and suggest the adapter comes with 14 awg wire (it is actually 12 awg - see &quot;pro&quot; above)&lt;/li&gt;
&lt;li&gt;6g over advertised weight of 50g&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;other-take-aways&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#other-take-aways&quot; aria-label=&quot;other take aways permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Other Take-aways&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;None of these adapters have a means of low voltage discharge cutoff - and &lt;a href=&quot;https://www.sevarg.net/2017/04/22/dewalt-20v-max-60ah-pack-teardown/&quot;&gt;neither do the batteries&lt;/a&gt; - so that will need to be part of any projects that use these adapters.&lt;/li&gt;
&lt;li&gt;None of these adapters have connections for the other balance leads so they can&apos;t be (safely) used for charging.&lt;/li&gt;
&lt;li&gt;None of these adapters are suitable for rough applications due to the lack of strain relief and wiring that will stand up to abuse.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#conclusion&quot; aria-label=&quot;conclusion permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Conclusion&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Andy is nice if you plan to mod the adapter and put additional electronics inside - especially for low-current applications or if you plan to redo the wiring anyway&lt;/li&gt;
&lt;li&gt;Bart is probably the best value and likely has the highest continuous current carrying capacity&lt;/li&gt;
&lt;li&gt;Curt is probably the best for applications where weight matters or where the wire will experience frequent bending&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; For most projects I would probably pick Bart.&lt;/p&gt;</content:encoded><author>symbioquine@gmail.com</author></item><item><title><![CDATA[Minimal git-based docker-compose deployments]]></title><description><![CDATA[Deployment automation infrastructure is perhaps one the most crowded domains. Many of the options out there address key facets of the domain…]]></description><link>https://symbioquine.net//2021-11-30-minimal-git-based-docker-compose-deployments</link><guid isPermaLink="false">https://symbioquine.net//2021-11-30-minimal-git-based-docker-compose-deployments</guid><category><![CDATA[Operational Excellence]]></category><category><![CDATA[Software Engineering]]></category><pubDate>Tue, 30 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Deployment automation infrastructure is perhaps one the most crowded domains. Many of the options out there address key facets of the domain such as testing, QA approvals, pipeline transparency,
rollback, zero-downtime rollouts, etc. There are also adjacent domains such as the DevOps space with tools like Puppet and Ansible.&lt;/p&gt;
&lt;p&gt;Those tools are great when you need them, but sometimes they introduce more complexity cost than they&apos;re worth.&lt;/p&gt;
&lt;p&gt;This recipe shows how to set up a minimal repository that will clone itself and run docker-compose on new pushes. It doesn&apos;t do a lot of the things those full-featured deployment tools do,
but it is pretty impressive what you &lt;em&gt;can&lt;/em&gt; get with a tiny git hook and a script in the right place.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note:&lt;/strong&gt; The pattern of calling a script in a repository from a git hook is a bit of a security risk in multi-user environments if write access on the repository are set too permissively. Please exercise caution.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id=&quot;prerequisites&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#prerequisites&quot; aria-label=&quot;prerequisites permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;If you&apos;re here, I&apos;m going to assume you already know how to set up SSH and are confident enough to customize this recipe for your needs.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;both-target-and-development-hosts&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#both-target-and-development-hosts&quot; aria-label=&quot;both target and development hosts permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Both Target and Development Hosts&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;git&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;target-host&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#target-host&quot; aria-label=&quot;target host permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Target Host&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;docker&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code class=&quot;language-text&quot;&gt;docker-compose&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;server-repository-setup-on-target-host&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#server-repository-setup-on-target-host&quot; aria-label=&quot;server repository setup on target host permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Server Repository Setup (on Target Host)&lt;/h2&gt;
&lt;p&gt;First we&apos;ll create a &lt;a href=&quot;https://git-scm.com/book/en/v2/Git-on-the-Server-Getting-Git-on-a-Server&quot;&gt;bare repository&lt;/a&gt; on the server - feel free to replace &quot;myproject&quot; with another name (It probably can&apos;t have spaces, but I haven&apos;t tested that.);&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ssh&lt;/span&gt; myuser@myserver
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; init &lt;span class=&quot;token parameter variable&quot;&gt;--bare&lt;/span&gt; /home/myuser/myproject.git&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Next we&apos;ll install the git hook in that bare repository;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&apos;[ -d &quot;/home/myuser/myproject&quot; ] &amp;amp;&amp;amp; /home/myuser/myproject/deploy.sh || git clone /home/myuser/myproject.git /home/myuser/myproject &amp;amp;&amp;amp; /home/myuser/myproject/deploy.sh&apos;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; /home/myuser/myproject.git/hooks/post-receive &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; +x /home/myuser/myproject.git/hooks/post-receive&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At this point, the repository can be pushed to and it will automatically clone itself and execute a &lt;code class=&quot;language-text&quot;&gt;deploy.sh&lt;/code&gt; script that must be placed at the root of the repository.&lt;/p&gt;
&lt;h2 id=&quot;client-repository-setup&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#client-repository-setup&quot; aria-label=&quot;client repository setup permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Client Repository Setup&lt;/h2&gt;
&lt;p&gt;Clone the repository and cd into it;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; clone myuser@myserver:/home/myuser/myproject.git myproject
&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; myproject&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Create the &lt;code class=&quot;language-text&quot;&gt;deploy.sh&lt;/code&gt; file with the following contents;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token shebang important&quot;&gt;#!/bin/bash&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-e&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; /home/myuser/myproject

&lt;span class=&quot;token assign-left variable&quot;&gt;STAGE&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$STAGE&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;plan&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    ./deploy.sh execute
    &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;$STAGE&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;execute&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;then&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;docker-compose&lt;/span&gt; up &lt;span class=&quot;token parameter variable&quot;&gt;-d&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;--build&lt;/span&gt; --remove-orphans
    &lt;span class=&quot;token builtin class-name&quot;&gt;exit&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fi&lt;/span&gt;

&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;GIT_DIR&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${GIT_DIR}&lt;/span&gt;
&lt;span class=&quot;token builtin class-name&quot;&gt;unset&lt;/span&gt; GIT_DIR
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; pull
./deploy.sh plan&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Create the &lt;code class=&quot;language-text&quot;&gt;docker-compose.yml&lt;/code&gt; file with the following contents;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;version: &lt;span class=&quot;token string&quot;&gt;&apos;3.7&apos;&lt;/span&gt;
services:

  hello-world:
    image: armhf/hello-world&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Mark the &lt;code class=&quot;language-text&quot;&gt;deploy.sh&lt;/code&gt; script as executable and commit/push the initial change;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;sh&quot;&gt;&lt;pre class=&quot;language-sh&quot;&gt;&lt;code class=&quot;language-sh&quot;&gt;&lt;span class=&quot;token function&quot;&gt;chmod&lt;/span&gt; +x deploy.sh 
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-A&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;git&lt;/span&gt; commit &lt;span class=&quot;token parameter variable&quot;&gt;-m&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Initial commit&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You should then see something like this;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;text&quot;&gt;&lt;pre class=&quot;language-text&quot;&gt;&lt;code class=&quot;language-text&quot;&gt;$ git commit -m &quot;Initial commit&quot;
[master (root-commit) f90ae92] Initial commit
 2 files changed, 28 insertions(+)
 create mode 100755 deploy.sh
 create mode 100644 docker-compose.yml
$ git push
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 12 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 530 bytes | 530.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
remote: Cloning into &apos;/home/myuser/myproject&apos;...
remote: done.
remote: pwd=/home/myuser/myproject
remote: GIT_DIR=.
remote: Already up to date.
remote: Creating network &quot;myproject_default&quot; with the default driver
remote: Pulling hello-world (armhf/hello-world:)...
remote: latest: Pulling from armhf/hello-world
remote: Digest: sha256:9701edc932223a66e49dd6c894a11db8c2cf4eccd1414f1ec105a623bf16b426
remote: Status: Downloaded newer image for armhf/hello-world:latest
remote: Creating myproject_hello-world_1 ... 
remote: Creating myproject_hello-world_1 ... done
To myserver:/home/myuser/myproject.git
 * [new branch]      master -&gt; master&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Congratulations, you&apos;ve now bootstrapped some minimal git-based deployment infrastructure. Use it wisely and build something cool!&lt;/p&gt;
&lt;h2 id=&quot;related-work&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#related-work&quot; aria-label=&quot;related work permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Related Work&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://kenfavors.com/code/using-git-to-deploy-code/&quot;&gt;https://kenfavors.com/code/using-git-to-deploy-code/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ricardoanderegg.com/posts/git-push-deployments-docker-tags/&quot;&gt;https://ricardoanderegg.com/posts/git-push-deployments-docker-tags/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><author>symbioquine@gmail.com</author></item><item><title><![CDATA[Off-grid lead acid battery charging]]></title><description><![CDATA[Recently I spent some time analyzing an off-grid system for some less technical family members. In trying to understand and explain why it…]]></description><link>https://symbioquine.net//2021-11-29-off-grid-lead-acid-battery-charging</link><guid isPermaLink="false">https://symbioquine.net//2021-11-29-off-grid-lead-acid-battery-charging</guid><category><![CDATA[Off-grid]]></category><category><![CDATA[Batteries]]></category><category><![CDATA[Solar]]></category><pubDate>Mon, 29 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Recently I spent some time analyzing an off-grid system for some less technical family members. In trying to understand and explain why it wasn&apos;t performing as they expected, I gained a greater appreciation for the nuances of lead acid (LA) battery charging characteristics and how they affect off-grid systems - especially those systems with insufficient solar capacity and those in less equatorial latitudes.&lt;/p&gt;
&lt;p&gt;There are many other articles describing the care and feeding of LA batteries (&lt;a href=&quot;https://www.sevarg.net/2018/04/08/off-grid-rv-lead-acid-maintenance-charging-failure-modes/&quot;&gt;here is one of my favorites&lt;/a&gt;) so I will try to go beyond just reiterating the conventional wisdom about LA battery life being inversely proportional to the depth of discharge (DoD). I&apos;m instead going to focus on one of the most important off-grid system characteristics which I think is both crucial and often neglected. That characteristic is &lt;strong&gt;charging time&lt;/strong&gt;. (&lt;em&gt;Actually, reputable battery manufacturers often &lt;strong&gt;do&lt;/strong&gt; provide specifications/guidance that are both detailed and tailored to their products, but for whatever reason that information - and the consequences thereof - doesn&apos;t seem to be well represented more generally.&lt;/em&gt;)&lt;/p&gt;
&lt;p&gt;All batteries have maximum rates at which they can safely charge. For LA batteries, the charging rates are usually specified by the manufacturer either in terms of a number of hours in bulk and absorption charging or in terms of a graph showing the state of charge (SoC) vs charging power and time. Regardless of how much solar capacity a system has, it will tend not to fully charge the batteries unless the system has long enough with sufficient power for charging from a given average DoD.&lt;/p&gt;
&lt;p&gt;You&apos;ve probably seen the characteristic charge curve of LA batteries where the charging amperage - and total power - tapers off dramatically before the batteries are full.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/b98eb2ab66154934f4329d766d842a58/f6f78/lead-acid-charging-amps.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 44.50867052023121%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAC4jAAAuIwF4pT92AAAA7klEQVQoz42SWW7DMAxEfYUi1Upqc+KkAXr/601BOpGXGE0+CILS8IlDezCe8G3DNsycT8YjcsH1/osyTlq/aFdhHGGwnmBc7CEXLhIccRd+nazC9rp9FtbgAm+FJiDkBKpFIfsH3wLtw7JxoQN9SuCx9qnWTUegJROGoxdlwnIdX5tc0PgXuLas+yNGOlfUnzPypW2m+sjy2orY5VbQ7jOs3ka1biPB+AOgX3qf69Ed2kBwkRFSUpjYlciTQB/1bTnTuDSUaa49J/0GfUKuFTEXhXKp+ttwbToZSU2s9yFlBE6gPJ+JRrSpjVoL8A8ygOiX5lqFkAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Graph of lead acid charging amperage from a 10% DoD&quot;
        title=&quot;Graph of lead acid charging amperage from a 10% DoD&quot;
        src=&quot;/static/b98eb2ab66154934f4329d766d842a58/1e043/lead-acid-charging-amps.png&quot;
        srcset=&quot;/static/b98eb2ab66154934f4329d766d842a58/991de/lead-acid-charging-amps.png 173w,
/static/b98eb2ab66154934f4329d766d842a58/e4d6b/lead-acid-charging-amps.png 345w,
/static/b98eb2ab66154934f4329d766d842a58/1e043/lead-acid-charging-amps.png 690w,
/static/b98eb2ab66154934f4329d766d842a58/f6f78/lead-acid-charging-amps.png 932w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The above graph is showing a Renogy 200 Ah AGM charging from a 10% DoD on 2021/07/05.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That roughly matches the manufacturer&apos;s charging characteristics graph.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/86fcb9da0c395c96d506710e92bad0ee/35751/renogy-200ah-agm-charge-characteristics.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 72.25433526011561%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA+wAAAPsAHpfpy9AAACNklEQVQoz6WSX2+bMBTF+f4fak+TurWibQgJwWCwDZj4L762YWqTkI1WmybtcT+de3VfzsPVOQlC6PHx8eHh4Xg8pmn68vLy+kGapgihPM8Ph0OWZUVRPD8/Z1mW5/lut3t/f7/f78n9P0hut9vlcvmzP1nXddub1vv9Huc4DMN5PGutjTEO4O3tbV3Xzay1vlyv4AAcxN+EefbzHEJYggdrhr6vKrR7zU7HoipLpdTttiYxRiXVyLm1kwMX5hhD8Na6YXCcjw3BqG1QS5q+yE7fvj49fX1Kv6eccyFEwhhVSlLa2g+csbLGw7E47k7oiMuioU3PyHBmfYfq0/OuzA5llvO+63uWMMakkpQ01hhxNjzNyP70pQtpN/ngg/cf42E7tl/CPM8xaqkwrhNKqFKqY0Qr5b0XeY6PVTFaJswcvAPwxsAmDUpukjIASCFQiRLOudZ66DtjrBnPeH+qKsqto9J4D+B9i0ldNQ1q0L4s87LYlUpbrRTvh6TFWEnZ4MoYzU4I1awlQ1CqVZO2UwCYwFsAC34Ks5sXGxcfZ61kXVUJZURrTdrGaJ0/pOWgwPuJslEootxnaltgc5ysGfkgRq7OoxAC1zi5XC4AsPz44bTeHTCEOM8RJmcZo5giwhs2EjZ2XNQ1eX3Z74vyhLDWZlmWrZ7Lslxv18lYZ6c5RgDnQ5gAjBCq70TXcUpbTNqmY91IhjPlUp7l1rDPlq7r+vcB4GqEKKWMdV3XXW+3f4v9y/wTKPsBps/BbYwAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Renogy 200 Ah AGM charge characteristics curves&quot;
        title=&quot;Renogy 200 Ah AGM charge characteristics curves&quot;
        src=&quot;/static/86fcb9da0c395c96d506710e92bad0ee/1e043/renogy-200ah-agm-charge-characteristics.png&quot;
        srcset=&quot;/static/86fcb9da0c395c96d506710e92bad0ee/991de/renogy-200ah-agm-charge-characteristics.png 173w,
/static/86fcb9da0c395c96d506710e92bad0ee/e4d6b/renogy-200ah-agm-charge-characteristics.png 345w,
/static/86fcb9da0c395c96d506710e92bad0ee/1e043/renogy-200ah-agm-charge-characteristics.png 690w,
/static/86fcb9da0c395c96d506710e92bad0ee/35751/renogy-200ah-agm-charge-characteristics.png 873w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;That charging curve is both a blessing and a curse. It means up to around 80% of the capacity can be regained at roughly the maximum charge rate - usually around 10-13% (of the 20 hour capacity rating) for FLA and closer to 20% for AGM. However, it also means that batteries are often cycled in a lower average SoC range than one might expect. For example, if you tend to use 30% of your battery capacity over night and only have 4 hours of charging time during the day, you will probably be cycling your batteries between 50% and 80% instead of 70% and 100% - even assuming the system has plenty of solar panels.&lt;/p&gt;
&lt;p&gt;There are a number of approaches to mitigating/solving this problem;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Shifting loads to the day time if/when there is excess solar capacity.&lt;/li&gt;
&lt;li&gt;Reducing usage such that - on average - there are enough hours of solar to charge when taking into account the battery charge curve.&lt;/li&gt;
&lt;li&gt;Performing a portion of the bulk charge using generator power in the morning before the &quot;solar charging day&quot; begins.
&lt;ul&gt;
&lt;li&gt;This yields the best value for the time spent running a generator since most generators are most efficient between 40% and 70% of their rated output.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Increasing the size of the LA battery bank.
&lt;ul&gt;
&lt;li&gt;This will allow the same amount of usage while staying at a higher SoC - which in turn means staying farther down the tail end of the charging curve.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Shifting loads to other battery technologies.
&lt;ul&gt;
&lt;li&gt;The cheap/easy version of this is similar to #1 where one just charges laptops/phones/etc during the day when there is excess solar capacity.&lt;/li&gt;
&lt;li&gt;This could also be a strategy for moving to another battery technology altogether. For example, by using a &lt;a href=&quot;https://www.amazon.com/dp/B085G72RQ2&quot;&gt;DC-DC charger&lt;/a&gt; configured to charge a secondary LiFePo4 battery bank to which loads and batteries are added incrementally.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Hopefully this post has served to highlight the importance of considering charging time as a key aspect of off-grid load management and battery sizing. May your systems be performant, long lasting, and boring.&lt;/p&gt;</content:encoded><author>symbioquine@gmail.com</author></item><item><title><![CDATA[Epever Solar Charge Controller Clocks Running Fast]]></title><description><![CDATA[As a follow-up to my previous post about off-grid solar monitoring via the ESP8266, I am writing this post to share what I've learned about…]]></description><link>https://symbioquine.net//2020-09-02-epever-scc-clocks-running-fast</link><guid isPermaLink="false">https://symbioquine.net//2020-09-02-epever-scc-clocks-running-fast</guid><category><![CDATA[Solar]]></category><category><![CDATA[Microcontrollers]]></category><category><![CDATA[Off-grid]]></category><pubDate>Thu, 02 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;As a follow-up to my &lt;a href=&quot;./2020-10-27-epever-esp-8266-controller&quot;&gt;previous post&lt;/a&gt; about off-grid solar monitoring via the ESP8266, I am writing this post to share what I&apos;ve learned about the clock behavior on the EPever (EPSolar) Tracer####AN line of MPPT solar charge controllers.&lt;/p&gt;
&lt;p&gt;I have four of these solar charge controllers running all the time each with an ESP8266 publishing metrics back to an InfluxDB instance. The Epever Tracer units provide several features which depend on date/time;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Time-of-day load control&lt;/li&gt;
&lt;li&gt;Cumulative energy generated/consumed stats&lt;/li&gt;
&lt;li&gt;Periodic equalization charging for lead-acid batteries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My loads are almost always switched on so the first feature is not particularly useful to me, but the other two are. These features make the most sense when the clock on the Epever unit is set accurately. The date/time can be viewed and set manually via the MT-50 external display, or by some other device over Modbus/RS485 - in my case the ESP8266.&lt;/p&gt;
&lt;p&gt;If the clock on the Epever units was accurate enough, there wouldn&apos;t be much point in automatically correcting it from the ESP8266 since it could just be set upon initial system setup with the MT-50. On the other hand, if the clock was inaccurate, it would be nice to automatically correct it using the time from an NTP server. To understand this, I started capturing a &quot;timeDrift&quot; metric calculated by subtracting the current NTP time from the current time reported by the Epever unit. This new metric showed that the Epever units were consistently running a little fast - ~4s/day.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/85803e71a3564c94884ca6a7d4137a02/cc418/time_drift_metric.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.17919075144509%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA+wAAAPsAHpfpy9AAAAy0lEQVQoz3WR6w6DIAxGfQhFoNBS8ILL3v/5lgbnkElyfpS0h36EYZrtI+DZIRvAh66y42S09UM5Kw3NxKhMoR4YlZganE8BMAy9zUrDRdk2aaudp5X5SHwkkeuhhtNRcpFjClsULaewR9pjVy4JR0nofQp8pHiIUxA/P8nnw2ZrEGmL8U8r3ORJn6tmCxBItNdSEl7Og3w9rErYal1ZGbCEuHL4TvSgjX/1zoA0cE7Ley0hOcvaHnX3/CpAskQgBPgWTuoGuh2RDOAHMj5MQ4zPMuQAAAAASUVORK5CYII=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Single time drift metric&quot;
        title=&quot;Single time drift metric&quot;
        src=&quot;/static/85803e71a3564c94884ca6a7d4137a02/1e043/time_drift_metric.png&quot;
        srcset=&quot;/static/85803e71a3564c94884ca6a7d4137a02/991de/time_drift_metric.png 173w,
/static/85803e71a3564c94884ca6a7d4137a02/e4d6b/time_drift_metric.png 345w,
/static/85803e71a3564c94884ca6a7d4137a02/1e043/time_drift_metric.png 690w,
/static/85803e71a3564c94884ca6a7d4137a02/e3189/time_drift_metric.png 1035w,
/static/85803e71a3564c94884ca6a7d4137a02/b1001/time_drift_metric.png 1380w,
/static/85803e71a3564c94884ca6a7d4137a02/cc418/time_drift_metric.png 1992w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I would also occasionally get nonsense datapoints where the Epever unit would briefly report a current date/time about 40 years in the past (approx. -1.26 * 10^9).&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/ca10c157f631b3fb2517390e11ec7885/cc418/clock_race_condition.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.17919075144509%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA+wAAAPsAHpfpy9AAAAfElEQVQoz82NUQrDMAxDc4fVtiwnHr3/JUcaKF1IPgeDR3hIMSpiPNTFOLg92lnzBHPZHupgK7vjQ/0leFaLY0WIURGDnU+JGK9l9eXy5L9Y3q/99bKHgOox2PlXgi5gFkQ1BqIOdn4nNt5eZQET3sD0Id0v/AEn+k/W9wdpjT5rJ2d+UwAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Clock race condition&quot;
        title=&quot;Clock race condition&quot;
        src=&quot;/static/ca10c157f631b3fb2517390e11ec7885/1e043/clock_race_condition.png&quot;
        srcset=&quot;/static/ca10c157f631b3fb2517390e11ec7885/991de/clock_race_condition.png 173w,
/static/ca10c157f631b3fb2517390e11ec7885/e4d6b/clock_race_condition.png 345w,
/static/ca10c157f631b3fb2517390e11ec7885/1e043/clock_race_condition.png 690w,
/static/ca10c157f631b3fb2517390e11ec7885/e3189/clock_race_condition.png 1035w,
/static/ca10c157f631b3fb2517390e11ec7885/b1001/clock_race_condition.png 1380w,
/static/ca10c157f631b3fb2517390e11ec7885/cc418/clock_race_condition.png 1992w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;I believe this to be due to some sort of race condition in how the Epever units read the time from their internal real-time-clock to report it back via Modbus. That hypothesis is reinforced by the observation that the erroneous datapoints tend to fluctuate by 60s and occasionally 3600s - suggesting that the race condition is occurring when a Modbus request for the time coincides with a minute or hour rolling over.&lt;/p&gt;
&lt;p&gt;With all those observations in hand, I ended up with some roughly Arduino compatible code that monitors and automatically corrects the Epever unit&apos;s clock when it becomes too far out of sync;&lt;/p&gt;
&lt;div class=&quot;gatsby-highlight&quot; data-language=&quot;c++&quot;&gt;&lt;pre class=&quot;language-c++&quot;&gt;&lt;code class=&quot;language-c++&quot;&gt;uint16_t pack_as_int(uint8_t v0, uint8_t v1) {
  uint16_t t = (v0 &amp;lt;&amp;lt; 8) | v1;
  return t;
}

uint32_t upper_int(uint16_t v) {
  return (v &amp;gt;&amp;gt; 8) &amp;amp; 0xFF;
}

uint32_t lower_int(uint16_t v) {
  return v &amp;amp; 0xFF;
}

bool readDevClock(time_t&amp;amp; devTime) {
  delay(250);
  uint8_t result = node-&amp;gt;readHoldingRegisters(0x9013, 0x3);
  if (result != ModbusMaster::ku8MBSuccess) {
    Serial.printf(&amp;quot;Failed to read date from charge controller: 0x%02X\n&amp;quot;, result);
    return false;
  }

  uint16_t min_and_sec = node-&amp;gt;getResponseBuffer(0x0);
  uint16_t day_and_hour = node-&amp;gt;getResponseBuffer(0x1);
  uint16_t year_and_month = node-&amp;gt;getResponseBuffer(0x2);

  struct tm devTm;

  devTm.tm_year = (upper_int(year_and_month) + 2000) - 1900;
  devTm.tm_mon = lower_int(year_and_month) - 1;
  devTm.tm_mday = upper_int(day_and_hour);
  devTm.tm_hour = lower_int(day_and_hour);
  devTm.tm_min = upper_int(min_and_sec);
  devTm.tm_sec = lower_int(min_and_sec);
  devTm.tm_isdst = -1;

  devTime = mktime(&amp;amp;devTm);

  if (devTime &amp;lt; -1260000000 &amp;amp;&amp;amp; devTime &amp;gt; -1270000000) {
    Serial.printf(&amp;quot;Received faulty date from charge controller: %04d-%02d-%02dT%02d:%02d:%02dZ\n&amp;quot;, devTm.tm_year + 1900, devTm.tm_mon + 1, devTm.tm_mday, devTm.tm_hour, devTm.tm_min, devTm.tm_sec);
    return false;
  }

  return true;
}

void syncClock() {
  time_t devTime0;
  time_t devTime1;

  // Don&amp;#39;t record metrics or adjust controller date/time if we can&amp;#39;t reliably tell
  // what time it has already set
  if (!readDevClock(devTime0) || !readDevClock(devTime1)) {
      return;
  }

  long int devTimeDelta = devTime0 - devTime1;

  if (devTimeDelta &amp;lt; -10 || devTimeDelta &amp;gt; 10) {
      metricBuffer-&amp;gt;appendf(&amp;quot;,devTimeDelta=%di&amp;quot;, devTimeDelta);
      return;
  }

  time_t currTime;
  time(&amp;amp;currTime);

  // Don&amp;#39;t record metrics or adjust controller date/time if somehow we didn&amp;#39;t
  // get the correct time from NTP
  if (currTime &amp;lt; 1000000000l) {
      return;
  }

  // Serial.printf(&amp;quot;Got date from charge controller: %d-%d-%dT%d:%d:%dZ (currTime = %ld, devTime = %ld, drift = %ld)\n&amp;quot;,
  //   devTm.tm_year + 1900, devTm.tm_mon + 1, devTm.tm_mday, devTm.tm_hour, devTm.tm_min, devTm.tm_sec, currTime, devTime, devTime - currTime);

  long int timeDrift = devTime1 - currTime;

  metricBuffer-&amp;gt;appendf(&amp;quot;,devTime=%di&amp;quot;, devTime1);
  metricBuffer-&amp;gt;appendf(&amp;quot;,currTime=%di&amp;quot;, currTime);
  metricBuffer-&amp;gt;appendf(&amp;quot;,timeDrift=%di&amp;quot;, timeDrift);

  struct tm *currTm = localtime(&amp;amp;currTime);

  // Only update controller date/time if it is more than an hour off or we&amp;#39;re in the first five minutes of the day
  bool shouldUpdate = abs(timeDrift) &amp;gt; 3600 || (abs(timeDrift) &amp;gt; 5 &amp;amp;&amp;amp; currTm-&amp;gt;tm_hour == 0 &amp;amp;&amp;amp; currTm-&amp;gt;tm_min &amp;lt;= 5);
  if (!shouldUpdate) {
      return;
  }

  node-&amp;gt;setTransmitBuffer(0, pack_as_int(currTm-&amp;gt;tm_min, currTm-&amp;gt;tm_sec));
  node-&amp;gt;setTransmitBuffer(1, pack_as_int(currTm-&amp;gt;tm_mday, currTm-&amp;gt;tm_hour));
  node-&amp;gt;setTransmitBuffer(2, pack_as_int((currTm-&amp;gt;tm_year + 1900) - 2000, currTm-&amp;gt;tm_mon + 1));

  uint8_t result = node-&amp;gt;writeMultipleRegisters(0x9013, 3);
  if (result == ModbusMaster::ku8MBSuccess) {
    Serial.printf(&amp;quot;Successfully set charge controller date/time.\n&amp;quot;);
  } else {
    Serial.printf(&amp;quot;Failed to set charge controller date/time: 0x%02X&amp;quot;, result);
  }
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which yields the following nice behavior where all four of my controllers are consistently drifting by about 4s per day and having their clocks updated approximately every other day at midnight;&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/a07e7864f198dc385fb41a95e3b5cf1a/9831c/clock_drift_with_correction.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 53.75722543352601%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA+wAAAPsAHpfpy9AAAB20lEQVQoz12SW3KbQBBF2YPDPLrnBQNIYCGQZkTACMkCCVtxvP/tJNjllCtV/XGr+/TXPQFlSLn4HPKRyZKlMlbHKReGsK/rF/aZCcOAcBEyQRkQwrmOQy44B0KBaftAwGqkjIdchBQkYEiRqZhQQMAfhAcizSkqKwGlKg81Q7FSCMrgepvE8lhpwhDsWqDIJIrYrnclcsiMfAh5YOq90bLQsHWlG3ykZK4RsyKuq2Fvmlxgso6qOtWYSXRPu6qtMompFg8Egqxt1pHqyujp0hzOvohlbrXd7fvRTS46FMo6v/a7R42HbXJ86VxblQazJCbCBI/tocnVy6ns56fz6EsrN5vi57Wbb36sdX/I876rfdXmch73x9d+6KuNEZWruYmC06W5d6u31/byq7/f/CaJzpdmfu/fZ3f18TS31dA999Xrsbj/7m9v/dSXVZFN917FJpivfjrV0+guoxtPu7Ypp8mNox9P1ThszxffHd31uZ6e6wUY3dBtT8PudvPS6EBEGqwFxgUDGZvVJgHGgTKIE7CpVXyTCW4igkpyoCHPHtN0ZRQH4BCEDAkFwnAxBARhS/tLnyhDhoDIQHwwC/CxEcuVi6VnVEaoiKOi3wT65pz4z7B/nv19/gNIYmEb7PyfMAAAAABJRU5ErkJggg==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Clock drift with correction&quot;
        title=&quot;Clock drift with correction&quot;
        src=&quot;/static/a07e7864f198dc385fb41a95e3b5cf1a/1e043/clock_drift_with_correction.png&quot;
        srcset=&quot;/static/a07e7864f198dc385fb41a95e3b5cf1a/991de/clock_drift_with_correction.png 173w,
/static/a07e7864f198dc385fb41a95e3b5cf1a/e4d6b/clock_drift_with_correction.png 345w,
/static/a07e7864f198dc385fb41a95e3b5cf1a/1e043/clock_drift_with_correction.png 690w,
/static/a07e7864f198dc385fb41a95e3b5cf1a/e3189/clock_drift_with_correction.png 1035w,
/static/a07e7864f198dc385fb41a95e3b5cf1a/b1001/clock_drift_with_correction.png 1380w,
/static/a07e7864f198dc385fb41a95e3b5cf1a/9831c/clock_drift_with_correction.png 1995w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;In the future I hope to share more of my learnings about monitoring/controlling cheap off-grid solar charge controllers - and perhaps a more complete firmware at some point too.&lt;/p&gt;
&lt;p&gt;Thanks for reading!&lt;/p&gt;</content:encoded><author>symbioquine@gmail.com</author></item><item><title><![CDATA[Pragmatic Minimal Infrastructure]]></title><description><![CDATA[A 2019 article by David Futcher appeared on Hacker News recently titled You Don’t Need All That Complex/Expensive/Distracting Infrastructure…]]></description><link>https://symbioquine.net//2020-03-14-pragmatic-minimal-infrastructure</link><guid isPermaLink="false">https://symbioquine.net//2020-03-14-pragmatic-minimal-infrastructure</guid><category><![CDATA[Operational Excellence]]></category><category><![CDATA[Software Engineering]]></category><pubDate>Sun, 14 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;A 2019 article by David Futcher &lt;a href=&quot;https://news.ycombinator.com/item?id=26423110&quot;&gt;appeared on Hacker News recently&lt;/a&gt; titled &lt;a href=&quot;https://blog.usejournal.com/you-dont-need-all-that-complex-expensive-distracting-infrastructure-a70dbe0dbccb&quot;&gt;You Don’t Need All That Complex/Expensive/Distracting Infrastructure&lt;/a&gt;.
The article is well written and makes a really important point about building to your actual requirements. It&apos;s hard to over-emphasize &lt;a href=&quot;https://martinfowler.com/bliki/Yagni.html&quot;&gt;YAGNI&lt;/a&gt; at all
levels of coding, software engineering, and operations. Critical thinking about the tradeoffs of increasing the upfront cost - whether in time, cash,
or complexity - is essential to reaching your audience faster and still having the flexibility/budget to respond to the ever-changing playing field.&lt;/p&gt;
&lt;p&gt;It&apos;s important to acknowledge which tradeoffs have recurring concrete costs and which have hypothetical costs. For instance, a typical high availability (HA)
configuration with a load balancer and multiple hosts incurs many costs. It incurs an up-front cost to configure the infrastructure and ongoing hardware/maintenance
costs. It also incurs a complexity cost of deploying to multiple hosts, reasoning about distributed systems, etc. Most of these costs don&apos;t buy you anything on a
day-to-day basis. Your machines will tend to be running smoothly and the network will tend to be stable - at least in the ways your HA setup protects against.
You might never pay the hypothetical cost of an availability zone outage or a host hardware failure.&lt;/p&gt;
&lt;p&gt;That said, you &lt;em&gt;will&lt;/em&gt; have to upgrade your hosts&apos; operating systems, deploy new versions of your software, and troubleshoot hard-to-reproduce behavior from your
system. All of these benefit from building out a little infrastructure.&lt;/p&gt;
&lt;p&gt;For the sake of simplicity let&apos;s assume you&apos;ve taken the argument against infrastructure deeply to heart and your startup&apos;s software is a gzipped folder with
all your web content and some sort of server binary. You &lt;code class=&quot;language-text&quot;&gt;scp&lt;/code&gt; it to your host, unpack, execute, and away you go. Life is good, there&apos;s no complicated container
systems, no build servers, no CD pipelines, no statefulness, just good old-fashioned operations - and presumably all the time in the world to find customers and
market your nascent business.&lt;/p&gt;
&lt;p&gt;You can even get most of the benefits naively associated with having more infrastructure. Need to upgrade a host or deploy your software? No problem, you get to make
the call about whether to risk downtime. You check your logs and try to guess whether any &quot;important customers&quot; are likely to try and use your site. If not, just go
ahead and bounce the host or kill your server process then start a new version. Otherwise, you can spin up a new host, get it set up correctly running your software,
then do a DNS flip and wait for the traffic to move to the new machine before stopping the old one.&lt;/p&gt;
&lt;p&gt;It may take a while and feel repetitive after the third or fourth time, but boring is good right?&lt;/p&gt;
&lt;p&gt;Well, yes and no. The minimalistic strategy I just described is great because it puts you in the driver&apos;s seat and hopefully delivering value to your customers faster.
Where it falls down is that it is an extreme local optimum on the cost vs external value graph. In my opinion, even considering a very small set of externalities can
both erode the advantages of the minimalistic strategy and help offset the costs of a more infrastructure-intensive strategy.&lt;/p&gt;
&lt;p&gt;One of the key aspects to consider is which kinds of growing pains you have control over and can predict/schedule. If your business venture is even a little successful,
you can be sure unforeseen bugs in your software will necessitate &quot;emergency&quot; code changes. You can also be sure you&apos;ll very quickly have enough web traffic that every
deployment will require that new-host/dns-flip strategy or else involve downtime affecting those existing/potential customers. What&apos;s harder to predict is where the
break-even point is on more complex infrastructure - especially since it can itself introduce unforeseen bugs. I would argue though, that paying the cost early on to
get an as-simple-as-possible no-downtime deployment mechanism is worthwhile.&lt;/p&gt;
&lt;p&gt;Should it be prioritized over the bare-minimum work to have a product or web-presence? Probably not. Should it be prioritized along with launching your first few features
or releases? Probably. Once you have any customers, traffic, or value; you have an obligation to respect others&apos; time and yours. In 2021 this means having very few
if any &quot;planned outages&quot; and definitely not having &quot;expected&quot; intermittent downtime for deployments. The tooling is becoming ubiquitous enough and customers discerning
enough that certain kinds of availability form a new baseline.&lt;/p&gt;
&lt;p&gt;Another aspect that should be considered is that there are ancillary benefits to early judicious infrastructure investment. If your business succeeds, your time spent
learning enough NGINX, Kubernetes, etc. to achieve that baseline availability will serve you well by providing foundational competency to evaluate future architectures.
If your business fails there&apos;s a good chance you can re-use the core configuration/scripts/learnings in your next endeavor.&lt;/p&gt;
&lt;p&gt;Finally, there&apos;s a sentiment case to be made for employing &quot;cool&quot; infrastructure that is a good fit for a given use-case and current growth. You probably don&apos;t need
and shouldn&apos;t build the fanciest multi-AZ autoscaling cluster with real-time machine-learning metrics/log analysis. But if a few scripts can give you a satisfying
minimal git-ops workflow for day-to-day deployments which makes you feel like your time is being well spent, it is likely worth it. Not to mention that your &quot;3 AM self&quot;
will thank you if deploying is easy, fast, and makes it hard to break things.&lt;/p&gt;
&lt;p&gt;In short, I too want to see more pragmatic business decisions as opposed to hype-driven over-engineering. However, those decisions need to take into account the
costs of not keeping up with the times.&lt;/p&gt;</content:encoded><author>symbioquine@gmail.com</author></item><item><title><![CDATA[EPever MPPT Solar Charge Controller Monitoring Hardware]]></title><description><![CDATA[Cheaper MPPT solar charge controllers have become ubiquitous. Many of these sport RS232 or RS485/Modbus ports
allowing the controllers to be…]]></description><link>https://symbioquine.net//2020-10-27-epever-esp-8266-controller</link><guid isPermaLink="false">https://symbioquine.net//2020-10-27-epever-esp-8266-controller</guid><category><![CDATA[Solar]]></category><category><![CDATA[Microcontrollers]]></category><category><![CDATA[Off-grid]]></category><pubDate>Tue, 27 Oct 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cheaper MPPT solar charge controllers have become ubiquitous. Many of these sport &lt;a href=&quot;https://en.wikipedia.org/wiki/RS-232&quot;&gt;RS232&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/RS-485&quot;&gt;RS485&lt;/a&gt;/&lt;a href=&quot;https://en.wikipedia.org/wiki/Modbus&quot;&gt;Modbus&lt;/a&gt; ports
allowing the controllers to be extended via proprietary hardware/software for advanced monitoring and control use-cases.&lt;/p&gt;
&lt;p&gt;Since this Spring I have been experimenting with the EPever (EPSolar) line of MPPT solar charge controllers which have RS485 interfaces and thought it would be worthwhile to share my
learnings in the hope they will be useful to others.&lt;/p&gt;
&lt;p&gt;This post will focus on the specific hardware I worked with, however the techniques are applicable to similar hardware.&lt;/p&gt;
&lt;p&gt;At the bottom of this post I have linked to similar projects and build logs - while there are many, I believe the content here adds materially in terms of discussing the downsides of using the Raspberry Pi for this application
and, for the esp8266 part of the build, showing the simpler wiring which results from choosing an RS485 adapter which does automatic flow control.&lt;/p&gt;
&lt;h2 id=&quot;the-first-pi&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-first-pi&quot; aria-label=&quot;the first pi permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The First Pi&lt;/h2&gt;
&lt;p&gt;Early in the Spring I was configuring a couple tiny independent solar systems - one to power networking hardware to get internet on an off-grid property from a local &lt;a href=&quot;https://en.wikipedia.org/wiki/Wireless_Internet_service_provider&quot;&gt;WISP&lt;/a&gt;
and another to provide incidental power for the construction phase of my skoolie conversion.&lt;/p&gt;
&lt;p&gt;Naturally, I wanted to capture data on the performance of these systems over time to help gain a more intuitive hands-on understanding of the power small &quot;renewable&quot; energy systems can provide. Also
I anticipate the data will be helpful in understanding the aging of the systems&apos; batteries - and possibly panels.&lt;/p&gt;
&lt;p&gt;My initial attempts involved a Raspberry Pi 3B and a RS485-to-RS232 adapter. I used a bit of python code and the excellent &lt;a href=&quot;https://github.com/Salamek/epsolar-tracer&quot;&gt;epsolar-tracer&lt;/a&gt; library to poll the
Tracer 3210AN and publishing metrics to &lt;a href=&quot;https://graphiteapp.org/&quot;&gt;Carbon/Graphite&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After a fairly steep learning curve with half-duplex RS485 adapters, I got that working and was pleased to have some day-over-day performance data.&lt;/p&gt;
&lt;p&gt;At this point my main complaint was that since only one device can connect to the RS485 port at a time, I couldn&apos;t have both remote monitoring and the handy &lt;a href=&quot;https://www.epsolarpv.com/product/55.html&quot;&gt;MT-50&lt;/a&gt; connected simultaneously.&lt;/p&gt;
&lt;h2 id=&quot;the-second-pi&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-second-pi&quot; aria-label=&quot;the second pi permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Second Pi&lt;/h2&gt;
&lt;p&gt;I set out to figure out how to get both the local display and remote monitoring working at the same time.&lt;/p&gt;
&lt;p&gt;Since the Raspberry Pi only has 1 hardware serial port, I got some &lt;a href=&quot;https://www.amazon.com/dp/B00LZV1G6K/&quot;&gt;USB serial adapters&lt;/a&gt; and decided to proxy requests from the MT-50 while still polling for data to publish to Carbon.&lt;/p&gt;
&lt;p&gt;Proxying the requests was unexpectedly complicated because I discovered that the MT-50 doesn&apos;t read data via the &lt;a href=&quot;http://www.solar-elektro.cz/data/dokumenty/1733_modbus_protocol.pdf&quot;&gt;documented modbus commands&lt;/a&gt;.
Most important of the undocumented commands the MT-50 uses is one for reading sparse/discontiguous registers which I have documented in &lt;a href=&quot;https://gist.github.com/symbioquine/95ba2abaf046c8e034b41e4cf3c334a9&quot;&gt;this gist&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With the protocol bits figured out and a nice watertight box, I had a passable system component which publishes metrics and still allows the local display to work.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/7e28122fe2d5b6daba704bcdedc5a5d6/72e01/second_pi.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 75.14450867052024%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABAAC/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAXCYCH2av//EABoQAAIDAQEAAAAAAAAAAAAAAAECABESIQT/2gAIAQEAAQUCE1Xo4Z0B8WjWv//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EAB4QAAEDBAMAAAAAAAAAAAAAAAABESECEDEyIlGh/9oACAEBAAY/AsC1cm8tq5tKL0Rg/8QAHRAAAgIBBQAAAAAAAAAAAAAAAREAITFBUWGRwf/aAAgBAQABPyFR6ctFzCMaojIsHBhJdhkuogKbJ//aAAwDAQACAAMAAAAQjw//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAVEQEBAAAAAAAAAAAAAAAAAAAQIf/aAAgBAgEBPxCn/8QAGxABAQEAAwEBAAAAAAAAAAAAAREAITFhUcH/2gAIAQEAAT8QdAcO0AzCGwEHQKN8uAeNMt1ESn+zTk3kgj400rq/ocb/2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;A Raspberry Pi in a watertight box with dual usb-serial and RS232-to-RS485 adapters&quot;
        title=&quot;A Raspberry Pi in a watertight box with dual usb-serial and RS232-to-RS485 adapters&quot;
        src=&quot;/static/7e28122fe2d5b6daba704bcdedc5a5d6/15ec7/second_pi.jpg&quot;
        srcset=&quot;/static/7e28122fe2d5b6daba704bcdedc5a5d6/9ac50/second_pi.jpg 173w,
/static/7e28122fe2d5b6daba704bcdedc5a5d6/8d48c/second_pi.jpg 345w,
/static/7e28122fe2d5b6daba704bcdedc5a5d6/15ec7/second_pi.jpg 690w,
/static/7e28122fe2d5b6daba704bcdedc5a5d6/72e01/second_pi.jpg 1024w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, there were still several issues with this strategy;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry Pi fail due to how quickly SD cards wear out and result in filesystem corruption - this is supposedly surmountable by limiting or completely disabling writes, but I haven&apos;t figured out how to do this while still
keep the ability to remotely push control-program/OS updates&lt;/li&gt;
&lt;li&gt;Raspberry Pi use quite a bit of energy - over 1 watt &lt;a href=&quot;http://www.pidramble.com/wiki/benchmarks/power-consumption&quot;&gt;even idle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The easiest way I found to securely publish to Carbon was via an SSH tunnel which adds unnecessary complexity and failure-points&lt;/li&gt;
&lt;li&gt;The Raspberry Pi is powered from the &apos;load&apos; output of the charge controller - meaning the Pi can&apos;t be used for toggling power to the load without also turning itself off&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;and-now-for-something-a-little-different&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#and-now-for-something-a-little-different&quot; aria-label=&quot;and now for something a little different permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;And Now for Something A Little Different&lt;/h2&gt;
&lt;p&gt;I decided to drop the Raspberry Pi and MT-50 in favor of a much simpler component built around the esp8266. This has a number of advantages;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Low power - I haven&apos;t spent much effort optimizing here, but it seems to be on the order of half a watt under load&lt;/li&gt;
&lt;li&gt;Reliability - there are many fewer points of failure without the complexities of a full OS/filesystem/etc while still supporting remote updates&lt;/li&gt;
&lt;li&gt;1 plug - since the esp8266 uses so little power, it can powered directly via the charge controller&apos;s RS485 port allowing for the unit to remain powered even when the charge controller load output is off&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maintaining the ssh tunnel from the esp8266 didn&apos;t seem practical so I switched to publishing the metrics to an authenticated InfluxDB endpoint over HTTPS.&lt;/p&gt;
&lt;h3 id=&quot;materials&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#materials&quot; aria-label=&quot;materials permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Materials&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/2b0d73af4c758816812455437f86e967/2b013/materials.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 58.95953757225434%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAMABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQBAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABc0leGyhX/8QAHBAAAgEFAQAAAAAAAAAAAAAAAQIDABAREiMx/9oACAEBAAEFAtSaMgVhJmzjsvn/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAcEAACAgIDAAAAAAAAAAAAAAABEQAQAiExcYH/2gAIAQEABj8CTI9i2zOKx6Nf/8QAGhABAAMBAQEAAAAAAAAAAAAAAQARITEQkf/aAAgBAQABPyEsId9Ns7sBTcyIbT9labybHh//2gAMAwEAAgADAAAAEGgP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQIBAT8QR//EAB4QAAIBAwUAAAAAAAAAAAAAAAERACExQVFhcZHB/9oACAEBAAE/EHhXVFt7DEDhDcGCjpuLNHlCx2DYzAGtY84TsF5//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Consumable materials used in this build&quot;
        title=&quot;Consumable materials used in this build&quot;
        src=&quot;/static/2b0d73af4c758816812455437f86e967/15ec7/materials.jpg&quot;
        srcset=&quot;/static/2b0d73af4c758816812455437f86e967/9ac50/materials.jpg 173w,
/static/2b0d73af4c758816812455437f86e967/8d48c/materials.jpg 345w,
/static/2b0d73af4c758816812455437f86e967/15ec7/materials.jpg 690w,
/static/2b0d73af4c758816812455437f86e967/af659/materials.jpg 1035w,
/static/2b0d73af4c758816812455437f86e967/89b2d/materials.jpg 1380w,
/static/2b0d73af4c758816812455437f86e967/2b013/materials.jpg 1700w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/dp/B081CSJV2V/&quot;&gt;ESP8266&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/dp/B082Y19KV9/&quot;&gt;Automatic Flow Control UART to RS485 Converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.amazon.com/dp/B07K7445H5/&quot;&gt;RJ45 Waterproof Coupler&lt;/a&gt; - There are probably more cost effective options and the threads are a little fragile on these&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.thermofisher.com/order/catalog/product/2118-0008&quot;&gt;Nalgene 2118-0008&lt;/a&gt; - Any convenient container should also work&lt;/li&gt;
&lt;li&gt;5 Female jumper wires&lt;/li&gt;
&lt;li&gt;~4 inches of ethernet cable with one end installed&lt;/li&gt;
&lt;li&gt;Ethernet cable to connect assembled device to the charge controller&lt;/li&gt;
&lt;li&gt;Solder&lt;/li&gt;
&lt;li&gt;Tools&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;the-build&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#the-build&quot; aria-label=&quot;the build permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;The Build&lt;/h3&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/590d0a621de6429fd181959e68927812/a878e/diagram_wiring.png&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.86705202312138%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAAuBAAALgQETQ8PIAAABy0lEQVQoz52STWsTURSG528ItmAbq7aMCFKLKYKrbAXFRRVRrKCoOJtijW4LSmIUxIL+A424kEKLrTUfghQRClbSztgGxYRGk2aSks7oZO59ZKZNg002euC9XM65POfl3KNIKdkWIB0HCgWoriPKZouoVJGmiSyXwbJwiiVfjbzCznAlbrmIK20P31KuO7Xte/pdmvFHEaLRMd6nU34DRQjJyorO4tI8+pcF1is/sPIGWWORx0+fcPfeGLGHUe7HIkQexDAyH7Gq37Cq3xk+f5qe7g4CgQ7C2nWo11Ecx2F6apLJiVckZqfJ6jp5c5WXySTBUIgjg/0EjwfZp/ayV+1lNjXB1/xnlrLLnDl7ClU9wEC/yuiI5rtWvNkt6waZ+U8YCxnqG7/9ws9KkdeJJG9TCeY+zPEmmWBqZgbTXPPrAsHFC0P0dO1m4FCA8M0bm0DvsH/ZWLZFbaOGaDO3diGFRLs2zIljBxlUu7ly+VLTYcvjrV93XbdFQohNh0JwbugkoaMqh/v2oGlXmw7/Wp2tBjtz7d7cGh1hf6CTrs5d3L4TbgL/JTxYw6VhGLyIPycef0baW5v/ATag/uxtm0JhlVwux1qp5AP/AG9Cva0VgU+2AAAAAElFTkSuQmCC&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Wiring diagram showing RS485 connection through adapter to ESP8266&quot;
        title=&quot;Wiring diagram showing RS485 connection through adapter to ESP8266&quot;
        src=&quot;/static/590d0a621de6429fd181959e68927812/1e043/diagram_wiring.png&quot;
        srcset=&quot;/static/590d0a621de6429fd181959e68927812/991de/diagram_wiring.png 173w,
/static/590d0a621de6429fd181959e68927812/e4d6b/diagram_wiring.png 345w,
/static/590d0a621de6429fd181959e68927812/1e043/diagram_wiring.png 690w,
/static/590d0a621de6429fd181959e68927812/e3189/diagram_wiring.png 1035w,
/static/590d0a621de6429fd181959e68927812/b1001/diagram_wiring.png 1380w,
/static/590d0a621de6429fd181959e68927812/a878e/diagram_wiring.png 2048w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Start by stripping about 2&quot; of the outer ethernet cable housing and removing the striped wires. The ground and +5v - brown and orange respectively - are left long while the RS485 A/B wires are trimmed short.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/6ec658c181f0a0e20235545aed2fbe53/40317/inner_cable_prep.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 41.61849710982659%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAIABQDASIAAhEBAxEB/8QAFwABAAMAAAAAAAAAAAAAAAAAAAIDBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAdGYWg//xAAYEAEAAwEAAAAAAAAAAAAAAAABAAMQEf/aAAgBAQABBQIqSHTP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGhAAAQUBAAAAAAAAAAAAAAAAAQACEDIzgf/aAAgBAQAGPwLVyuTyP//EABkQAQADAQEAAAAAAAAAAAAAAAEAESGBkf/aAAgBAQABPyFJofIGp4EDNbn/2gAMAwEAAgADAAAAEIv/AP/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABsQAQACAgMAAAAAAAAAAAAAAAEAESExUZGh/9oACAEBAAE/EFghbrI9GIHOslXUcoi5Z//Z&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Detail image showing preparation of the cable for connection to the RS485 adapter&quot;
        title=&quot;Detail image showing preparation of the cable for connection to the RS485 adapter&quot;
        src=&quot;/static/6ec658c181f0a0e20235545aed2fbe53/15ec7/inner_cable_prep.jpg&quot;
        srcset=&quot;/static/6ec658c181f0a0e20235545aed2fbe53/9ac50/inner_cable_prep.jpg 173w,
/static/6ec658c181f0a0e20235545aed2fbe53/8d48c/inner_cable_prep.jpg 345w,
/static/6ec658c181f0a0e20235545aed2fbe53/15ec7/inner_cable_prep.jpg 690w,
/static/6ec658c181f0a0e20235545aed2fbe53/af659/inner_cable_prep.jpg 1035w,
/static/6ec658c181f0a0e20235545aed2fbe53/89b2d/inner_cable_prep.jpg 1380w,
/static/6ec658c181f0a0e20235545aed2fbe53/40317/inner_cable_prep.jpg 2795w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Next the RS485 adapter is grounded.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/28b837c10e420a05db53e24b4bce4e4b/1cd85/wiring_rs485_adapter.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 70.52023121387285%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAByxW5E4aI/8QAGhAAAwADAQAAAAAAAAAAAAAAAAEDAhARE//aAAgBAQABBQK2VXXzoxT4ND1//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAHRAAAQIHAAAAAAAAAAAAAAAAEQABAhIgMTJRgf/aAAgBAQAGPwKRjCx1dZDtP//EABsQAQACAgMAAAAAAAAAAAAAAAEAERAhgZGx/9oACAEBAAE/Iad6Kz0lDa7mAaVecAibn//aAAwDAQACAAMAAAAQB/8A/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHBABAAICAwEAAAAAAAAAAAAAAQARIUFhcaGx/9oACAEBAAE/EG+H1V8u0cTVb+IA9l9Hd2qZA0FwwhR//9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Detail image showing one approach for grounding the RS485 adapter&quot;
        title=&quot;Detail image showing one approach for grounding the RS485 adapter&quot;
        src=&quot;/static/28b837c10e420a05db53e24b4bce4e4b/15ec7/wiring_rs485_adapter.jpg&quot;
        srcset=&quot;/static/28b837c10e420a05db53e24b4bce4e4b/9ac50/wiring_rs485_adapter.jpg 173w,
/static/28b837c10e420a05db53e24b4bce4e4b/8d48c/wiring_rs485_adapter.jpg 345w,
/static/28b837c10e420a05db53e24b4bce4e4b/15ec7/wiring_rs485_adapter.jpg 690w,
/static/28b837c10e420a05db53e24b4bce4e4b/af659/wiring_rs485_adapter.jpg 1035w,
/static/28b837c10e420a05db53e24b4bce4e4b/89b2d/wiring_rs485_adapter.jpg 1380w,
/static/28b837c10e420a05db53e24b4bce4e4b/1cd85/wiring_rs485_adapter.jpg 2058w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Then jumper wires are added to the adapter and the whole thing is assembled into the case.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/1f51e020e1b0fc4e5d5838b537f4bd2a/23d91/fully_assembled_cable_and_lid.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 50.28901734104046%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAKABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAa7qOQHD/8QAGhAAAgIDAAAAAAAAAAAAAAAAAAECEQMSQf/aAAgBAQABBQLXK51OxHGf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGhAAAgIDAAAAAAAAAAAAAAAAATEAAhEgQf/aAAgBAQAGPwI3NUhmci0//8QAGxAAAgMAAwAAAAAAAAAAAAAAAAERITEQQXH/2gAIAQEAAT8hXSVjqxWMe6S3wsaP/9oADAMBAAIAAwAAABAIL//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABwQAQACAgMBAAAAAAAAAAAAAAEAESExQXGRwf/aAAgBAQABPxAQlvCRulXrMGlTiKX4I5F/SazdNs//2Q==&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Image showing the assembled case lid, cable, adapter, and jumper wires&quot;
        title=&quot;Image showing the assembled case lid, cable, adapter, and jumper wires&quot;
        src=&quot;/static/1f51e020e1b0fc4e5d5838b537f4bd2a/15ec7/fully_assembled_cable_and_lid.jpg&quot;
        srcset=&quot;/static/1f51e020e1b0fc4e5d5838b537f4bd2a/9ac50/fully_assembled_cable_and_lid.jpg 173w,
/static/1f51e020e1b0fc4e5d5838b537f4bd2a/8d48c/fully_assembled_cable_and_lid.jpg 345w,
/static/1f51e020e1b0fc4e5d5838b537f4bd2a/15ec7/fully_assembled_cable_and_lid.jpg 690w,
/static/1f51e020e1b0fc4e5d5838b537f4bd2a/af659/fully_assembled_cable_and_lid.jpg 1035w,
/static/1f51e020e1b0fc4e5d5838b537f4bd2a/89b2d/fully_assembled_cable_and_lid.jpg 1380w,
/static/1f51e020e1b0fc4e5d5838b537f4bd2a/23d91/fully_assembled_cable_and_lid.jpg 3556w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;Here is the fully assembled unit connected and powered up.&lt;/p&gt;
&lt;p&gt;&lt;span
      class=&quot;gatsby-resp-image-wrapper&quot;
      style=&quot;position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 690px; &quot;
    &gt;
      &lt;a
    class=&quot;gatsby-resp-image-link&quot;
    href=&quot;/static/84aa1f68cb564bcf56af21a1dcf9bc1e/c293a/final_all_connected.jpg&quot;
    style=&quot;display: block&quot;
    target=&quot;_blank&quot;
    rel=&quot;noopener&quot;
  &gt;
    &lt;span
    class=&quot;gatsby-resp-image-background-image&quot;
    style=&quot;padding-bottom: 97.68786127167631%; position: relative; bottom: 0; left: 0; background-image: url(&apos;data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAUABQDASIAAhEBAxEB/8QAGQABAAIDAAAAAAAAAAAAAAAAAAMEAQIF/8QAFgEBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHeapJm85kWIgCz/8QAGRABAQADAQAAAAAAAAAAAAAAAgEAAxET/9oACAEBAAEFAkYctMDUd2LYs5e7BxN2T2Weyz//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/AR//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/AR//xAAcEAACAQUBAAAAAAAAAAAAAAAAASECERIxQSL/2gAIAQEABj8C15HGi7pMcBRHSFBSzhw//8QAHhABAAIBBAMAAAAAAAAAAAAAAQARITFBYZFRccH/2gAIAQEAAT8hsCiM5cxyAcJbQPuXOzo/Zgk3I8hC5qyU3nF1imp0n//aAAwDAQACAAMAAAAQkOiB/8QAFREBAQAAAAAAAAAAAAAAAAAAASD/2gAIAQMBAT8QGP/EABURAQEAAAAAAAAAAAAAAAAAAAEg/9oACAECAQE/EEj/xAAdEAEAAgMBAAMAAAAAAAAAAAABABEhMUGRUcHw/9oACAEBAAE/EAbc2ttjMF0XaQrYV7gmI0VKClLUPL9QumzTpo7yNoKAOTEdCzR3wi+FjVKfuI3b/Pmf/9k=&apos;); background-size: cover; display: block;&quot;
  &gt;&lt;/span&gt;
  &lt;img
        class=&quot;gatsby-resp-image-image&quot;
        alt=&quot;Image showing the fully assembled unit connected and powered up&quot;
        title=&quot;Image showing the fully assembled unit connected and powered up&quot;
        src=&quot;/static/84aa1f68cb564bcf56af21a1dcf9bc1e/15ec7/final_all_connected.jpg&quot;
        srcset=&quot;/static/84aa1f68cb564bcf56af21a1dcf9bc1e/9ac50/final_all_connected.jpg 173w,
/static/84aa1f68cb564bcf56af21a1dcf9bc1e/8d48c/final_all_connected.jpg 345w,
/static/84aa1f68cb564bcf56af21a1dcf9bc1e/15ec7/final_all_connected.jpg 690w,
/static/84aa1f68cb564bcf56af21a1dcf9bc1e/af659/final_all_connected.jpg 1035w,
/static/84aa1f68cb564bcf56af21a1dcf9bc1e/c293a/final_all_connected.jpg 1232w&quot;
        sizes=&quot;(max-width: 690px) 100vw, 690px&quot;
        style=&quot;width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;&quot;
        loading=&quot;lazy&quot;
        decoding=&quot;async&quot;
      /&gt;
  &lt;/a&gt;
    &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: for initial programming via USB it is necessary to disconnect the green (RX) wire from the esp8266. After initial programming, OTA updates can be used to avoid that step.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In a future post I will share some sample code and provide power usage benchmarks.&lt;/p&gt;
&lt;h2 id=&quot;related-work-of-others&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#related-work-of-others&quot; aria-label=&quot;related work of others permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Related Work of Others&lt;/h2&gt;
&lt;h3 id=&quot;raspberry-pi&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#raspberry-pi&quot; aria-label=&quot;raspberry pi permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;Raspberry Pi&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.rototron.info/raspberry-pi-solar-serial-rest-api-tutorial/&quot;&gt;https://www.rototron.info/raspberry-pi-solar-serial-rest-api-tutorial/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/a/57351990&quot;&gt;https://stackoverflow.com/a/57351990&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.navitron.org.uk/forum/index.php?topic=26987.0&quot;&gt;https://www.navitron.org.uk/forum/index.php?topic=26987.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://diysolarforum.com/threads/off-grid-solar-battery-monitoring-and-control-freeware.6662/&quot;&gt;https://diysolarforum.com/threads/off-grid-solar-battery-monitoring-and-control-freeware.6662/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;esp8266&quot; style=&quot;position:relative;&quot;&gt;&lt;a href=&quot;#esp8266&quot; aria-label=&quot;esp8266 permalink&quot; class=&quot;anchor before&quot;&gt;&lt;svg aria-hidden=&quot;true&quot; focusable=&quot;false&quot; height=&quot;16&quot; version=&quot;1.1&quot; viewBox=&quot;0 0 16 16&quot; width=&quot;16&quot;&gt;&lt;path fill-rule=&quot;evenodd&quot; d=&quot;M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z&quot;&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/a&gt;ESP8266&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.eevblog.com/forum/projects/nodemcu-esp8266-rs485-epever-solar-monitor-diy/&quot;&gt;https://www.eevblog.com/forum/projects/nodemcu-esp8266-rs485-epever-solar-monitor-diy/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hackster.io/tekk/cheap-mppt-controller-live-stats-on-mobile-3db5be&quot;&gt;https://www.hackster.io/tekk/cheap-mppt-controller-live-stats-on-mobile-3db5be&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.instructables.com/Wireless-Solar-Charge-Controller-Monitor-RS485-to-/&quot;&gt;https://www.instructables.com/Wireless-Solar-Charge-Controller-Monitor-RS485-to-/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</content:encoded><author>symbioquine@gmail.com</author></item></channel></rss>