<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
<channel>
<title>Albert Nisbet - RSS Feed</title>
<description>The blog of Albert Nisbet</description>
<link>https://albert.nz</link>
<lastBuildDate>Mon, 16 Mar 2026 09:59:33 GMT+0</lastBuildDate>
<item>
<title>Mt Barron summit from Goat Creek</title>
<description>Rugged semi-loop to the summit, going up Goat Creek and down scree</description>
<link>https://albert.nz/mt-barron/</link>
<guid isPermaLink="false">/mt-barron/</guid>
<pubDate>Fri, 31 Jan 2025 20:04:43 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>This semi-loop access Mt Barron up Goat Creek and down scree. Ironically, it entirely avoids the named “Mt Barron Route”. It’s one of the more technical tramps I’ve ever done, with a good proportion of the travel over rocks. There’s scrambling, rock hopping and descent down loose scree—with some of the best views in the park. All in all it’s a long and rewarding day on the feet!</p>
<p>The first section follows the pipeline prominently marked on Topo50 just south of Otira township. There’s a corresponding parking area on the east side of the road. The map and other trip reports are a bit misleading in terms of the first section of the route. The narrow pipeline reaches a large metal box then widens and proceeds to the creek intake. If you’re doing this trip, ensure you pick up the ribbon-marked track which turns off at this metal box to steeply climb the spur. We overshot and reached the creek before backtracking. At least it provided an opportunity to fill water supplies for the dry day ahead!</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_222435538.C1dGgQcm.webp" alt="The track above Goat Creek climbs steeply to the tops above the bushline" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_222435538.BwfivI46.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_222435538.CI57YOIC.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_222435538.BQtUOt5r.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_222435538.C1dGgQcm.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>The track above Goat Creek climbs steeply to the tops above the bushline</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02583.BJkDEgLd.webp" alt="Travel along the open tops, with Goat Hill visible on the opposite side of the valley" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02583.Dt9OZNMd.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02583.D95lN8kd.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02583.gl9iz3-u.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02583.BJkDEgLd.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Travel along the open tops, with Goat Hill visible on the opposite side of the valley</figcaption></figure>
<p>From the tops, we sidled around in the vague direction of Mt Barron summit. There’s no need to climb point 1629 or reach the ridge early.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_234805788.CGGtf4aD.webp" alt="Imposing view of Mt Barron during a snack break" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_234805788.BjDauoRI.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_234805788.QX8YeZNP.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_234805788.BcO_mPMn.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250131_234805788.CGGtf4aD.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Imposing view of Mt Barron during a snack break</figcaption></figure>
<p>Scree below the summit may be suitable for descent, but for our upwards route we chose to head up the more stable rock to the right. This reaches a narrow saddle.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02590.C0wCOMTO.webp" class="prose-custom-w-full" alt="View of the Otira Valley from the ridge below the summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02590.BMcp8U3q.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02590.DGq3SZIN.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02590.B88DAeir.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02590.C0wCOMTO.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>View of the Otira Valley from the ridge below the summit</figcaption></figure>
<p>We sidled behind a rock formation to reach the main saddle (visible at the top of the main scree chute in the earlier image). This side yielded a fresh view of Kellys Range. From the saddle it was a matter of carefully climbing between rocky outcrops to reach the low peak of Mt Barron.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_003447221.DbZ_4c6z.webp" alt="Final rocky climb towards the summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_003447221.C8QUgkBO.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_003447221.C5WGF_0g.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_003447221.BDy-CFSo.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_003447221.DbZ_4c6z.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Final rocky climb towards the summit</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_014636988.aK_Ktzog.webp" alt="View from the summit looking past the low peak and along the Otira River" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_014636988.DwGk7-Uy.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_014636988.D240yAl1.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_014636988.DWgaHJGm.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_014636988.aK_Ktzog.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>View from the summit looking past the low peak and along the Otira River</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02612.Dr7lgDDv.webp" alt="Departing the lower peak" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02612.l3Owz1ZU.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02612.BWjF9TXf.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02612.CsmcmYyq.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02612.Dr7lgDDv.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Departing the lower peak</figcaption></figure>
<p>The aim of the descent route was to follow the ridge over point 1569 then down the scree below point 1265. We made a mistake by picking up the ridge too soon—this would have been sensible in the opposite direction but really just lost us time. Instead the goal should be to get to the bottom of the basin south of the summit before picking up the ridge. This is where we ended up regardless, but there was a much more direct scree we could have ridden there from the low peak!</p>
<p>A benefit of our little detour was the chance to see and hear some elusive rock wrens nearby.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02615.DnG3dtwh.webp" alt="Rock wren spotted at a distance shortly into our descent. There were two calling to each other with thin whistling calls and lots of bobbing." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02615.B-awnqJQ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02615.DnG3dtwh.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Rock wren spotted at a distance shortly into our descent. There were two calling to each other with thin whistling calls and lots of bobbing.</figcaption></figure>
<p>We found our way to the ridge and enjoyed some easier travel as the tops step down towards our scree exit point.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02617-Pano.BaPEMOOg.webp" class="prose-custom-w-full" alt="View towards Arthurs Pass from the ridge" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02617-Pano.BpoMWQM-.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02617-Pano.DP_TNz7e.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02617-Pano.q7Z4nELn.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02617-Pano.BaPEMOOg.webp 2560w" style="aspect-ratio: 2.807;" data-aspect="2.807" sizes="auto" loading="lazy"><figcaption>View towards Arthurs Pass from the ridge</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_034656154.BSdS3fnm.webp" alt="Triangular stone implausibly wedged in a ravine" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_034656154.yKpffHr5.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_034656154.GaCOLJRY.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_034656154.CI8pFu-Q.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_034656154.BSdS3fnm.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Triangular stone implausibly wedged in a ravine</figcaption></figure>
<p>Our exit scree chute was the one below point 1265—we specifically took the northern branch visible on Topo50. We found the rock very loose (especially near the top), so care, patience and liberal spacing were needed to get all of us down without any concussions.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_054124951.CM9l_Ors.webp" alt="Lengthy scree descent below point 1265" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_054124951.DSN6_7Tw.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_054124951.AGq_qoLP.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_054124951.DRXnQthe.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250201_054124951.CM9l_Ors.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Lengthy scree descent below point 1265</figcaption></figure>
<p>I suspect the track is a more practical descent route, especially with a large group and/or tired knees. That said, the scree is certainly a more interesting option. At the base of the chute, a surprisingly straightforward downhill bush bash took us to the (incredibly solid and level) unsealed service road for the Otira rail tunnel, a short walk from the carpark where it joins SH73.</p>]]>
</content:encoded>
</item>
<item>
<title>Avalanche Peak to Crow Valley Route</title>
<description>Epic day trip up an iconic peak and down a big scree slope</description>
<link>https://albert.nz/avalanche-peak-crow/</link>
<guid isPermaLink="false">/avalanche-peak-crow/</guid>
<pubDate>Sat, 11 Jan 2025 20:30:50 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>The trip starts at Arthurs Pass village with an ascent of Avalanche Peak. Just like the <a href="/avalanche-peak">previous time</a> I did this section, the views get increasingly stunning as the track goes up.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_213839269.Bqd-_Pgp.webp" alt="Climbing Avalanche Peak" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_213839269.CiLHy73w.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_213839269.CCCZuCJA.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_213839269.RVMD9TbD.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_213839269.Bqd-_Pgp.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Climbing Avalanche Peak</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_215152743.Cp5V8rcl.webp" alt="The largest uropetala carovei I&#x27;ve seen, resting on the track" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_215152743.CWRcEZiy.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_215152743.DHPlRnwX.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_215152743.jUCCc80-.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_215152743.Cp5V8rcl.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>The largest uropetala carovei I've seen, resting on the track</figcaption></figure>
<p>Before long, the group made it to the (very busy) summit for a snack while looking out over the surrounding mountains.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02538.Ob4EeG5x.webp" alt="Snack break at the summit of Avalanche Peak" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02538.-VK8clOr.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02538.B0V5zJlb.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02538.BYzyG_rX.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02538.Ob4EeG5x.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Snack break at the summit of Avalanche Peak</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02539-Pano.D5wCWzMk.webp" class="prose-custom-w-full" alt="The view towards Arthurs Pass" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02539-Pano.CxSOvNT2.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02539-Pano.CLQYOZSI.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02539-Pano.DeyS85Xz.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02539-Pano.D5wCWzMk.webp 2560w" style="aspect-ratio: 2.105;" data-aspect="2.105" sizes="auto" loading="lazy"><figcaption>The view towards Arthurs Pass</figcaption></figure>
<p>From here the goal became to make it to the ridge leading towards the dramatic Mount Rolleston. To get there, we dropped off the summit to the south down some nice scree before looping back to the saddle just west of the summit.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_231105734.lgYhb77Y.webp" alt="Sidling to the saddle below the peak" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_231105734.DLKghp_k.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_231105734.CHfhqAGc.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_231105734.ZuiYv2Kf.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20250111_231105734.lgYhb77Y.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Sidling to the saddle below the peak</figcaption></figure>
<p>We followed the ridge towards the highlight of the trip: the lengthy scree descent into Crow Valley. The drop was further along the ridge than I expected but is quite obvious, with a sturdy pole at the top and (on this bluebird day) a clear line of sight right to the base of the valley.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02571.C70WycmF.webp" alt="Heading down the scree to Crow Valley" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02571.BVc3D_nP.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02571.Blz4cFMn.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02571.CsJHUd4S.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02571.C70WycmF.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>Heading down the scree to Crow Valley</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02577.BTCjZxrR.webp" class="prose-custom-w-full" alt="Looking down Crow Valley from the screen slope" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02577.KYRPkvFn.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02577.CMiD--Si.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02577.Ci1KMaLV.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02577.BTCjZxrR.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Looking down Crow Valley from the screen slope</figcaption></figure>
<p>We followed our noses down the valley, picking up track markers just above Crow Hut. This would make a great overnight stopover but we pressed on, exiting to Klondyke Corner on the same day.</p>]]>
</content:encoded>
</item>
<item>
<title>Paparoa Track to Croesus Knob</title>
<description>The first day of Paparoa Track to the stunning tops beyond Ces Clark Hut</description>
<link>https://albert.nz/paparoa-croesus-knob/</link>
<guid isPermaLink="false">/paparoa-croesus-knob/</guid>
<pubDate>Fri, 03 Jan 2025 19:58:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>During a stay in the nearby town of Blackball, we walked from the trailhead to the top of Croesus Knob and back, passing Ces Clark hut. This is the first day or so of the Paparoa Track, with a turn off at the saddle to reach Croesus Knob proper. It’s a decent distance for a day trip but the track is very well graded.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02503.DWkcZwfD.webp" alt="View over the bushline" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02503.DGiIMzti.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02503.D8dC0zgD.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02503.CTwkGfUF.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02503.DWkcZwfD.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>View over the bushline</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02522.DJ9JXKth.webp" alt="Looking north towards the rest of the Paparoa track" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02522.DeqWhi6K.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02522.DAYfVbPh.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02522.QlWvIgc3.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02522.DJ9JXKth.webp 2560w" style="aspect-ratio: 1.655;" data-aspect="1.655" sizes="auto" loading="lazy"><figcaption>Looking north towards the rest of the Paparoa track</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02523.CzLCqt4h.webp" class="prose-custom-w-full" alt="View from Croesus Knob summit looking south along the West Coast" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02523.Dz0HotbE.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02523.CDLbfe6S.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02523.BfoyTMLe.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02523.CzLCqt4h.webp 2560w" style="aspect-ratio: 1.600;" data-aspect="1.600" sizes="auto" loading="lazy"><figcaption>View from Croesus Knob summit looking south along the West Coast</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02529.jPwOJNPi.webp" alt="Starting the descent" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02529.BUkXyOcO.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02529.Cef5FSEa.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02529.myMgWU7Y.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02529.jPwOJNPi.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Starting the descent</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02532.DjZzY-1I.webp" class="prose-custom-w-full" alt="Vivid southern rātā in bloom" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02532.BGskBVvN.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02532.BN5YtOJq.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02532.DN81gh2s.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02532.DjZzY-1I.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Vivid southern rātā in bloom</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Kellys Hill</title>
<description>Accessible tops camping a short steep climb from Otira</description>
<link>https://albert.nz/kellys-hill/</link>
<guid isPermaLink="false">/kellys-hill/</guid>
<pubDate>Sat, 28 Dec 2024 01:51:31 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>The track up Kellys Hill is certainly steep, but one of the most direct ways to access the open tops near Otira. The trailhead is at a fairly large carpark at the intersection of Kellys Creek and State Highway 73. The creek is a great option for a post-hike swim, so I’d recommend packing a towel in summer.</p>
<p>My favourite part of the track is the approach to the tops, with spectacular shrubs and buttercup clinging to the hillside as the tree cover lessens and the view open out.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_034321823.XoEJ_-4_.webp" alt="Stunning hillside travel as the bush drops off, with Mt Cook buttercups lining the uphill side of the track" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_034321823.BkoIwAkt.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_034321823.Duf5WiTh.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_034321823.C8uA4LSr.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_034321823.XoEJ_-4_.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Stunning hillside travel as the bush drops off, with Mt Cook buttercups lining the uphill side of the track</figcaption></figure>
<p>Shortly past this point, the tops open up and Carroll Hut becomes visible. There were many visitors during the holiday season, with a full hut and families camping on the flat below the hut. We filled our bottles from the stream before continuing to our destination: the tops above the hut.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_042632166.DnyVjdLp.webp" class="prose-custom-w-full" alt="Carroll Hut, with Mt Barron rising into cloud opposite" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_042632166.C2A72-bL.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_042632166.DER9P4rH.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_042632166.B39rENmF.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_042632166.DnyVjdLp.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Carroll Hut, with Mt Barron rising into cloud opposite</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_220305672.DLjQAJZz.webp" alt="Hebe in flower. It seems to be thriving even above the bushline." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_220305672.BP6hCoir.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_220305672.BsbGopI0.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_220305672.B6EK40zc.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_220305672.DLjQAJZz.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Hebe in flower. It seems to be thriving even above the bushline.</figcaption></figure>
<p>There is good camping on the tops, but it took us a while to find a level spot among the rolling terrain.</p>
<p>We awoke to the grating sound of my favourite alpine alarm clock: nearby kea investigating our tents at 5:30am.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_164418785.BPgyi4kq.webp" class="prose-custom-w-full" alt="Dawn view facing Arthurs Pass, with the pass itself covered in low cloud" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_164418785.DECdFy5m.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_164418785.DJaDxwRr.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_164418785.X2BAD88M.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_164418785.BPgyi4kq.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Dawn view facing Arthurs Pass, with the pass itself covered in low cloud</figcaption></figure>
<p>Instead of going back to sleep, I climbed up Kellys Hill itself, to check out the radio station at one of the peaks and enjoy the views.</p>
<p>There is a poled route up to the radio station from the the main Kellys track. It branches of the track near the tarns.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_193406961.CpwAgT-n.webp" alt="Flies pollinating Mt Cook buttercup" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_193406961.DVd1Hqu-.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_193406961.GXzMEfP0.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_193406961.DFJXG6sR.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_193406961.CpwAgT-n.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Flies pollinating Mt Cook buttercup</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_194850063.CF_4Qvo_.webp" class="prose-custom-w-full" alt="View of Mt Alexander from Kellys Hill summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_194850063.Cd6odypQ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_194850063.D2ruCGsQ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_194850063.CUEIQCdw.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_194850063.CF_4Qvo_.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>View of Mt Alexander from Kellys Hill summit</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_235644683.Bz1cSt6Z.webp" alt="Southern rātā in bloom. We saw an orange-fronted kākāriki nearby." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_235644683.DwgXHKE4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_235644683.fQhvVZIy.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_235644683.BqrAGJK3.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241228_235644683.Bz1cSt6Z.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Southern rātā in bloom. We saw an orange-fronted kākāriki nearby.</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Mt Somers summit track</title>
<description>Loop track over Mt Somers with great views of the Alps</description>
<link>https://albert.nz/somers-summit/</link>
<guid isPermaLink="false">/somers-summit/</guid>
<pubDate>Fri, 11 Oct 2024 20:24:50 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>I have <a href="/somers">run around</a> Mt Somers before, but never previously made it to the summit. With a one-day weather window, I tackled the summit track with friends as part of a birthday trip.</p>
<p>The track was better marked than I thought, with poles present for the entire loop. This is despite the northern part of the route being entirely missing from Topo50. We completed the loop in clockwise direction which is better for getting to the summit quickly. A counter-clockwise attempt would be worth considering to ensure the steepest section (the northern section of the track, directly above the saddle) is an ascent.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_234959951.DZks9m4T.webp" class="prose-custom-w-full" alt="Icy ascent along the ridgeline to the summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_234959951.CyzVxqp_.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_234959951.CZM6uArG.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_234959951.DCGZ4mGU.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_234959951.DZks9m4T.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Icy ascent along the ridgeline to the summit</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_235010664.DDYPv0wC.webp" alt="View over the Canterbury plains from the summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_235010664.CzxNEAc4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_235010664.DjRQ0ZZ2.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_235010664.Dt65melo.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241011_235010664.DDYPv0wC.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>View over the Canterbury plains from the summit</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_013715273.DRHeEe21.webp" alt="Gentle descent on the other side of the summit with stunning views inland" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_013715273.DdXWZDh9.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_013715273.Dg4rtm0F.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_013715273.CZ2lvNV_.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_013715273.DRHeEe21.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Gentle descent on the other side of the summit with stunning views inland</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_014146263.t1qIeWyA.webp" class="prose-custom-w-full" alt="Preparing for the steep part of the descent" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_014146263.fJcUTpo4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_014146263.DhP2Cv3P.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_014146263.BRo0ktjg.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_014146263.t1qIeWyA.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Preparing for the steep part of the descent</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_025301133.EjFv_owO.webp" alt="Looking out towards the plains from where the summit descent meets the main track at the northern saddle" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_025301133.CMPkVE0s.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_025301133.Bmd9eYci.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_025301133.C_XknoeF.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20241012_025301133.EjFv_owO.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Looking out towards the plains from where the summit descent meets the main track at the northern saddle</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Granity Pass Hut and Mt Owen tops</title>
<description>Open tops with dramatic rock formations in Kahurangi National Park</description>
<link>https://albert.nz/granity-pass-hut/</link>
<guid isPermaLink="false">/granity-pass-hut/</guid>
<pubDate>Sat, 05 Oct 2024 21:00:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Mt Owen tops always stood out on topo maps, so when I had the opportunity for a two-night solo trip, this was right near the top of the list.</p>
<p>There are two ways up the hill from the Courthouse Flat trailhead: up the spur or via Blue Creek. I entered up the spur and exited by Blue Creek. The creek is muddier, more secluded and steeper than the spur but has some lovely bush. It also passes by interesting mining artifacts on the flat lower section, including the stamping battery marked on Topo50.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_015359156.MUwr0raQ.webp" alt="Bush-covered hills as far as the eye can see as the spur rises" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_015359156.CC6k0Fhb.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_015359156.DiwyfZzV.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_015359156.Cpv_0zf2.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_015359156.MUwr0raQ.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Bush-covered hills as far as the eye can see as the spur rises</figcaption></figure>
<p>It’s a steady uphill slog to the sidle around point 1335. The section ominously marked “Staircase” on the map is indeed steep but thankfully required no scrambling or climbing.</p>
<p>After this point it’s a straight shot to the hut. The last section of the approach is along a streambed.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_035806992.NEMZMy3e.webp" alt="The remains of the &#x22;Old propectors hut&#x22; marked on Topo50" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_035806992.NIH-T3Ti.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_035806992.CDX-BhSb.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_035806992.BZV2ezsk.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_035806992.NEMZMy3e.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The remains of the "Old propectors hut" marked on Topo50</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_041619143.DKkTCeUO.webp" alt="Dracophyllum in seed" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_041619143.BCau67sV.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_041619143.BKRmq2KB.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_041619143.Cxx7GGCe.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_041619143.DKkTCeUO.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Dracophyllum in seed</figcaption></figure>
<p>The hut is stunningly positioned in the open neck of Granity Pass with clear views up to the surrounding rock formations and tops.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_183831484.BkPaSC-a.webp" class="prose-custom-w-full" alt="Granity Pass Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_183831484.B_XkQVWH.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_183831484.CfIO8p-X.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_183831484.Byta8QE7.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_183831484.BkPaSC-a.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Granity Pass Hut</figcaption></figure>
<p>With two nights at the hut, I spent the next day exploring the open Mt Owen tops. This area is a bit of a playground for climbers and cavers at certain times of year. The scenery is astounding, with gnarly rock faces and disappearing streams. Sentinel Hill is just a short stroll above the hut and has excellent lines of sight to surrounding peaks. The path is not marked above the hut but is fairly well trodden.</p>
<p>I attempted Mt Owen summit during the day, but abandoned the effort due to the unsettling combination of ravines and late-season ice still settled around the place. I had little confidence that there was solid ground under any ice I stepped on!</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_212822122.BaxV_E2j.webp" class="prose-custom-w-full" alt="The ravines and rocky faces near the summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_212822122.B5JRv4IB.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_212822122.BRVbeqUx.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_212822122.JK_v4Bfg.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_212822122.BaxV_E2j.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The ravines and rocky faces near the summit</figcaption></figure>
<p>As consolation, a pair of heli-tourists who had parked up at the base of the rocky peak handed me half a bottle of wine on my way back down, which I enjoyed at the hut with a book and some snacks. The afternoon was sunny and calm. I didn’t see a soul until the following day.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_235846981.CiYpsmEu.webp" alt="Rock bivvy a 10 minute walk above hut. I stopped by for a swim." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_235846981.C6FEIQ_o.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_235846981.CZoiwwuh.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_235846981._NUfbaPS.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240929_235846981.CiYpsmEu.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Rock bivvy a 10 minute walk above hut. I stopped by for a swim.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_182738449.BntT9_fI.webp" alt="Hut weka" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_182738449.BVorFE9i.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_182738449.VjCz6Bea.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_182738449.BeJNRJDY.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_182738449.BntT9_fI.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Hut weka</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_211245154.0rblSUwC.webp" alt="Mining equipment near the stamping battery, not far from the trailhead" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_211245154.CVlRCbCd.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_211245154.C_UjIioc.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_211245154.DXm2L6lm.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240930_211245154.0rblSUwC.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Mining equipment near the stamping battery, not far from the trailhead</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>A video light which turns on whenever the webcam is enabled</title>
<description>Combining ESPHome, 3D printing and scripts to automatically provide video call lighting</description>
<link>https://albert.nz/automatic-camera-light/</link>
<guid isPermaLink="false">/automatic-camera-light/</guid>
<pubDate>Sun, 01 Sep 2024 08:12:13 GMT+0</pubDate>
<content:encoded>
<![CDATA[<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02464.BvCT7mdm.webp" alt="The completed setup with video light above the monitor" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02464.C8Y_qMoz.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02464.4icxjTiU.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02464.BzXhZ6ta.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02464.BvCT7mdm.webp 2560w" style="aspect-ratio: 1.708;" data-aspect="1.708" sizes="auto" loading="lazy"><figcaption>The completed setup with video light above the monitor</figcaption></figure>
<p>My home office lacks natural light, and this can lead to a fairly grainy and poorly-lit webcam feed. I’ve been considering upgrading the lighting setup with a key light but realised that, in theory, I could make my own camera light with parts and tools I already had:</p>
<ul>
<li>Decent-quality LED strip (the same 24V strip I once used for a <a href="/esp32-led-strip/">ceiling light</a>)</li>
<li>WiFi LED driver (the awesome ElectroDragon ESP32-C3 one I used in that project; it’s currently driving a 24V LED strip behind my desk as a bias light)</li>
<li>Some silicone LED strip conduit I purchased a while back but never ended up using</li>
<li>3D printer</li>
</ul>
<p>After some tinkering, I now have a VESA-mounted camera light which automatically turns on whenever an application starts the webcam. Here’s how I did it!</p>
<h1>Mounting the light</h1>
<p>The silicone conduit is flexible and from the get-go I imagined the light as a curve of the conduit which would “pop up” from behind the monitor. I also knew that I wanted it to attach to the monitor’s VESA mount so that I didn’t need to rely on adhesive (which so often leads to disappointment!).</p>
<p>I measured the conduit then whipped up a basic design in OpenSCAD, which I printed then iterated. The part is essentially an “adapter” which connects to half a VESA mount at one end and the conduit at the other. By printing two mirrored parts, I can mount both ends of the conduit to a VESA mount.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225252112.DZgrhaQk.webp" alt="Mounting hardware. Version 4 has a taller height than version 3 and better matches the monitor dimensions, but v3 was good enough that I kept using it for the final installation instead of wasting plastic." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225252112.DMVSpdAk.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225252112.B58i3D2h.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225252112.Doptthd-.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225252112.DZgrhaQk.webp 2560w" style="aspect-ratio: 1.151;" data-aspect="1.151" sizes="auto" loading="lazy"><figcaption>Mounting hardware. Version 4 has a taller height than version 3 and better matches the monitor dimensions, but v3 was good enough that I kept using it for the final installation instead of wasting plastic.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225523096.SrzYa7UG.webp" alt="Conduit installed in the mounts" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225523096.NZFddxcn.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225523096.Cs9gRgPQ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225523096.DSz1D44E.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_225523096.SrzYa7UG.webp 2560w" style="aspect-ratio: 0.859;" data-aspect="0.859" sizes="auto" loading="lazy"><figcaption>Conduit installed in the mounts</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_230821215.Cc9Aug6I.webp" alt="Mounts attached to the monitor. For this Dell monitor, the VESA plate is recessed in the screen&#x27;s casing. So I used a slightly janky setup of attaching the mounts on the outside of the plate using the 75mm mounting holes and some longer bolts. Still better than adhesive! The bias light strips (which do use adhesive) remain on the back panel." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_230821215.CdI6QSml.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_230821215.BzIUXV3m.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_230821215.1Ls-tvdx.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_230821215.Cc9Aug6I.webp 2560w" style="aspect-ratio: 1.002;" data-aspect="1.002" sizes="auto" loading="lazy"><figcaption>Mounts attached to the monitor. For this Dell monitor, the VESA plate is recessed in the screen's casing. So I used a slightly janky setup of attaching the mounts on the outside of the plate using the 75mm mounting holes and some longer bolts. Still better than adhesive! The bias light strips (which do use adhesive) remain on the back panel.</figcaption></figure>
<h1>Powering the light</h1>
<p>I’m using an ESP32-C3 driver board for the bias lighting behind my monitor. The new light will be in a similar place—it’s just pointing in the opposite direction (towards my face instead of the wall). I decided to drive the new LED strip from the same board. This works well as the board has four PWM output channels. They’re labelled “RGBW”, but that doesn’t mean they can only be used for coloured LED strips. Each temperature adjustable strip uses two channels (one for cold white, one for warm white), therefore I can wire the new strip to the currently unused “blue” and “white” pins. The common 24V anode on the strip will be soldered to the same “V” pin as the other strip, as both strips are the same voltage.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_234126920.D3DDsjvN.webp" alt="Two three-channel outputs coming from the five output pins on the board" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_234126920.Bkszi8nt.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_234126920.C3v9hmuH.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240824_234126920.D3DDsjvN.webp 1536w" style="aspect-ratio: 2.637;" data-aspect="2.637" sizes="auto" loading="lazy"><figcaption>Two three-channel outputs coming from the five output pins on the board</figcaption></figure>
<p>I also double-checked to ensure my power supply’s 24V rail would be able to drive the extra strip at max brightness. These strips max out at 8W/m, so the 40cm camera light will draw no more than 4W or so at max brightness on both channels. On top of the 10-15W already used by the bias light, this should be easily managed by the 24W supply.</p>
<p>Now, I updated the ESPHome config to make it aware of the second light on the “blue” and “white” pins:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">esphome:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">'esphome-desk-light'</span>
  <span class="hljs-attr">platformio_options:</span>
    <span class="hljs-attr">board_build.flash_mode:</span> <span class="hljs-string">dio</span>

<span class="hljs-attr">esp32:</span>
  <span class="hljs-attr">board:</span> <span class="hljs-string">esp32-c3-devkitm-1</span>
  <span class="hljs-attr">framework:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">arduino</span>
    <span class="hljs-attr">version:</span> <span class="hljs-string">latest</span>

<span class="hljs-attr">wifi:</span>
  <span class="hljs-attr">ssid:</span> <span class="hljs-type">!secret</span> <span class="hljs-string">wifi_ssid</span>
  <span class="hljs-attr">password:</span> <span class="hljs-type">!secret</span> <span class="hljs-string">wifi_password</span>

  <span class="hljs-comment"># Enable fallback hotspot (captive portal) in case wifi connection fails</span>
  <span class="hljs-attr">ap:</span>
    <span class="hljs-attr">ssid:</span> <span class="hljs-string">'Desk Light Fallback Hotspot'</span>
    <span class="hljs-attr">password:</span> <span class="hljs-type">!secret</span> <span class="hljs-string">wifi_ap_password</span>

<span class="hljs-attr">captive_portal:</span>

<span class="hljs-attr">web_server:</span>
  <span class="hljs-attr">port:</span> <span class="hljs-number">80</span>

<span class="hljs-attr">logger:</span>

<span class="hljs-attr">api:</span>
  <span class="hljs-comment"># Redacted</span>

<span class="hljs-attr">ota:</span>
  <span class="hljs-attr">platform:</span> <span class="hljs-string">esphome</span>

<span class="hljs-attr">light:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">cwww</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">'Desk bias light'</span>
    <span class="hljs-attr">cold_white:</span> <span class="hljs-string">redpin</span>
    <span class="hljs-attr">warm_white:</span> <span class="hljs-string">greenpin</span>
    <span class="hljs-attr">cold_white_color_temperature:</span> <span class="hljs-number">6000 </span><span class="hljs-string">K</span>
    <span class="hljs-attr">warm_white_color_temperature:</span> <span class="hljs-number">2700 </span><span class="hljs-string">K</span>
    <span class="hljs-attr">constant_brightness:</span> <span class="hljs-literal">true</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">cwww</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">'Desk camera light'</span>
    <span class="hljs-attr">cold_white:</span> <span class="hljs-string">bluepin</span>
    <span class="hljs-attr">warm_white:</span> <span class="hljs-string">whitepin</span>
    <span class="hljs-attr">cold_white_color_temperature:</span> <span class="hljs-number">6000 </span><span class="hljs-string">K</span>
    <span class="hljs-attr">warm_white_color_temperature:</span> <span class="hljs-number">2700 </span><span class="hljs-string">K</span>
    <span class="hljs-attr">constant_brightness:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">output:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">ledc</span>
    <span class="hljs-attr">pin:</span> <span class="hljs-string">GPIO5</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">redpin</span>
    <span class="hljs-attr">frequency:</span> <span class="hljs-string">19531Hz</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">ledc</span>
    <span class="hljs-attr">pin:</span> <span class="hljs-string">GPIO8</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">greenpin</span>
    <span class="hljs-attr">frequency:</span> <span class="hljs-string">19531Hz</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">ledc</span>
    <span class="hljs-attr">pin:</span> <span class="hljs-string">GPIO4</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">bluepin</span>
    <span class="hljs-attr">frequency:</span> <span class="hljs-string">19531Hz</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">ledc</span>
    <span class="hljs-attr">pin:</span> <span class="hljs-string">GPIO3</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">whitepin</span>
    <span class="hljs-attr">frequency:</span> <span class="hljs-string">19531Hz</span>
</code></pre>
<p>And everything magically appears in Home Assistant!</p>
<h1>Turning the light on with the Mac webcam</h1>
<p>At this point, I have a sweet camera light that I can turn on using Home Assistant, just like my other lights. But a smart home’s only smart if it’s automated, and in this case the automation is clear: when my laptop’s camera is in use, I want the light to turn on; when the camera is not in use, the light should turn off.</p>
<p>In MacOS, there are a few commands that can help achieve this. The following ones work on MacOS Sonoma 14.5, but tweaks may well be needed for different versions.</p>
<p>To detect when the camera is off on on, I can use the long-running command:</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">exec</span> <span class="hljs-built_in">log</span> stream --predicate <span class="hljs-string">'process == "appleh13camerad" &#x26;&#x26; (eventMessage CONTAINS "PowerOnCamera" || eventMessage CONTAINS "PowerOffCamera")'</span> --style compact --color none
</code></pre>
<p>I should be able to hook into the events I need now, but it’s important that I only toggle the light when I’m actually at my desk! To work this out, I can check to see whether the laptop dock is connected:</p>
<pre><code class="hljs language-bash">system_profiler SPThunderboltDataType | grep -q <span class="hljs-string">"UID: 0x014AFA4DCC5947BE"</span>
</code></pre>
<p>This will get the Thunderbolt devices connected to the laptop and fail if my dock’s specific UID is not present.</p>
<p>Finally, I need to actually toggle the light from the command line. Because I enabled <code>web_server</code> in ESPHome, I can just use cURL to POST the relevant endpoint on the light:</p>
<pre><code class="hljs language-bash">curl -X POST <span class="hljs-string">'http://esphome-desk-bias-light.local/light/desk_camera_light/turn_on'</span>
curl -X POST <span class="hljs-string">'http://esphome-desk-bias-light.local/light/desk_camera_light/turn_off'</span>
</code></pre>
<h2>Putting it all together</h2>
<p>Much of this is inspired by an <a href="https://apple.stackexchange.com/questions/424789/can-i-trigger-a-homekit-scene-when-my-laptops-camera-is-turned-on-or-off">AskDifferent</a> question which used a similar solution for a “do not disturb” light outside a room.</p>
<p>Using the commands above, the following script waits for relevant events, checks whether the dock is attached, then requests the relevant state from the light.</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/bin/bash</span>

<span class="hljs-built_in">exec</span> <span class="hljs-built_in">log</span> stream --predicate <span class="hljs-string">'process == "appleh13camerad" &#x26;&#x26; (eventMessage CONTAINS "PowerOnCamera" || eventMessage CONTAINS "PowerOffCamera")'</span> --style compact --color none |
  /usr/bin/grep -vE --line-buffered <span class="hljs-string">'^Filter|^Timestamp'</span> | <span class="hljs-comment"># Remove the useless "header" row in output</span>
  <span class="hljs-built_in">tee</span> /dev/stdout | <span class="hljs-comment"># Display matching events</span>
  /usr/bin/sed -Eu <span class="hljs-string">'s/.*(PowerOnCamera|PowerOffCamera).*/\1/'</span> | <span class="hljs-comment"># Extract the relevant word from the event</span>
  <span class="hljs-keyword">while</span> <span class="hljs-built_in">read</span> -r event; <span class="hljs-keyword">do</span> <span class="hljs-comment"># Store that word in the $event variable</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Camera <span class="hljs-variable">$event</span>"</span>

    system_profiler SPThunderboltDataType | grep -q <span class="hljs-string">"UID: 0x014AFA4DCC5947BE"</span> <span class="hljs-comment"># Check to see whether Thunderbolt Dock attached</span>
    OFFICE_DOCK_ATTACHED=$?  <span class="hljs-comment"># Capture the exit status of the previous command (0 if success)</span>

    <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$OFFICE_DOCK_ATTACHED</span>"</span> -eq 0 ]; <span class="hljs-keyword">then</span>
      <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$event</span>"</span> = <span class="hljs-string">"PowerOnCamera"</span> ]; <span class="hljs-keyword">then</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"Lamp on"</span>
        curl -X POST <span class="hljs-string">'http://esphome-desk-bias-light.local/light/desk_camera_light/turn_on'</span> &#x26;
      <span class="hljs-keyword">else</span>
        <span class="hljs-built_in">echo</span> <span class="hljs-string">"Lamp off"</span>
        curl -X POST <span class="hljs-string">'http://esphome-desk-bias-light.local/light/desk_camera_light/turn_off'</span> &#x26;
      <span class="hljs-keyword">fi</span>
    <span class="hljs-keyword">fi</span>
  <span class="hljs-keyword">done</span>
</code></pre>
<p>I haven’t tried this yet for Windows or Linux, but I’m sure there’s an approach which works similarly on each of those operating systems. Let me know if you have any ideas as to how it can be done!</p>
<h1>The result</h1>
<p>The light works just as I imagined: when any application starts using the camera, a request is fired off to enable the light. Here’s the behaviour switching between Google Meet and Photo Booth:</p>
<iframe src="https://www.youtube.com/embed/kJ2qLW6nl0o" frameborder="0" class="video" allowfullscreen></iframe>
<h1>Further improvements</h1>
<p>While I’m happy that the automation is possible, there are a few things that could be better about this setup:</p>
<ul>
<li><strong>There’s some delay in turning the light on.</strong> The HTTP web server on ESPHome has fairly poor performance. I’m not sure whether this is the fault of ESPHome or just an ESP32-C3 hardware limitation. ESPHome’s native API—the one used by Home Assistant to talk to ESPHome devices—is a much more lightweight setup based on protocol buffers and encryption. In theory I could work out the exact command to send to the device over TCP, either by replaying the payload or building it up using the <a href="https://github.com/esphome/esphome/blob/dev/esphome/components/api/api.proto">official definitions</a>. I opted for HTTP as it’s easier and lets me use cURL to remove the need to install specific dependencies on a work laptop.</li>
<li><strong>The light could be brighter.</strong> This amount of lighting helps to fill out some shadows but it’s not sufficient to greatly reduce camera noise. There are many ways that this could be improved, including using more lights or more transparent conduit. The most tempting solution is to completely swap out this conduit-based light with an <a href="https://www.dpreview.com/news/2291236447/video-how-to-upcycle-old-tvs-and-monitors-into-a-natural-looking-light-source">old screen which has been converted to diffuse a set of bright LEDs</a>. I have a broken monitor lying around which might be perfect for this purpose. This may well be an upcoming project.</li>
</ul>]]>
</content:encoded>
</item>
<item>
<title>Building a translation app using htmx and CloudFlare Workers AI</title>
<description>I rewrote Obfuscator with a focus on simplicity</description>
<link>https://albert.nz/htmx-cloudflare-ai/</link>
<guid isPermaLink="false">/htmx-cloudflare-ai/</guid>
<pubDate>Sat, 10 Aug 2024 11:15:15 GMT+0</pubDate>
<content:encoded>
<![CDATA[<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/image-1.W9cPnSnT.webp" alt="Screenshot of Obfuscator" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/image-1.E5v0XdVR.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/image-1.W9cPnSnT.webp 768w" style="aspect-ratio: 1.055;" data-aspect="1.055" sizes="auto" loading="lazy"><figcaption>Screenshot of Obfuscator</figcaption></figure>
<p>I’ve been recently reading about tools like <a href="https://htmx.org/">htmx</a> and <a href="https://turbo.hotwired.dev/">Hotwire Turbo</a>. These frameworks rely on sending HTML, not JSON, over the wire, with a focus on graceful degradation and reduced custom client-side JavaScript code. The simplicity is tantalising!</p>
<p>To learn more about this tech, I re-wrote my Obfuscator project using htmx. You can find the latest incarnation <a href="https://obfuscator.albert.nz">here</a>, with source code on <a href="https://github.com/albertnis/obfuscator-htmx">GitHub</a>. The functionality of Obfuscator remains the same as ever: it translates some user-entered text through multiple languages in sequence, returning the result in the original language.</p>
<h2>Then and now</h2>
<p>The <a href="/obfuscator">first version</a> of Obfuscator was written in PHP and used the Bing Translate API, but I had no server to host it on an ongoing basis. The <a href="/serverless-side-rendering">second version</a> was a React/Redux setup which ran on AWS Lambda plus AWS Machine Translation. It had 174 runtime dependencies for what basically amounts to a simple form with five inputs and a button.</p>
<p>The latest verion is hosted on CloudFlare Pages, my preferred static host these days. There’s no framework or additional third-party dependencies to be seen here—just htmx at runtime, plus TypeScript and CloudFlare’s CLI for development (thankfully, the CLI wraps all the build shenanigans for backend code). I enjoyed writing a fairly stripped-back frontend in vanilla HTML. As for the backend: it’s amazing how painless shipping serverless backend code has become with the likes of Netlify and CloudFlare Pages Functions, compared to when I first wrote Obfuscator in 2016. I was even able use CloudFlare for translation using the <a href="https://developers.cloudflare.com/workers-ai/models/m2m100-1.2b/">m2m100</a> model which is surfaced as part of CloudFlare Workers AI. It’s certainly slower and less accurate than Amazon’s offering, but is much cheaper with a perpetual free tier. Besides, accurate translation was never really the point!</p>
<h2>Diving into htmx</h2>
<p>One of the cool things about the new Obfuscator is that there is no custom frontend JavaScript. The page is basically just a form with some <code>data-hx-</code> attributes added to indicate desired form submission behaviour to htmx. Here’s a compacted version of the page:</p>
<pre><code class="hljs language-html"><span class="hljs-meta">&#x3C;!doctype <span class="hljs-keyword">html</span>></span>
<span class="hljs-tag">&#x3C;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>></span>
	<span class="hljs-tag">&#x3C;<span class="hljs-name">head</span>></span>
		<span class="hljs-tag">&#x3C;<span class="hljs-name">title</span>></span>Obfuscator<span class="hljs-tag">&#x3C;/<span class="hljs-name">title</span>></span>
		<span class="hljs-comment">&#x3C;!-- Other head fields (omitted) --></span>
		<span class="hljs-tag">&#x3C;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"htmx-config"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">'{"responseHandling": [{"code":".*", "swap": true}]}'</span> /></span>
	<span class="hljs-tag">&#x3C;/<span class="hljs-name">head</span>></span>
	<span class="hljs-tag">&#x3C;<span class="hljs-name">body</span>></span>
		<span class="hljs-tag">&#x3C;<span class="hljs-name">form</span>
			<span class="hljs-attr">action</span>=<span class="hljs-string">"/obfuscation"</span>
			<span class="hljs-attr">method</span>=<span class="hljs-string">"get"</span>
			<span class="hljs-attr">data-hx-get</span>=<span class="hljs-string">"/obfuscation"</span>
			<span class="hljs-attr">data-hx-indicator</span>=<span class="hljs-string">"#indicator"</span>
			<span class="hljs-attr">data-hx-disabled-elt</span>=<span class="hljs-string">"#form-fields"</span>
			<span class="hljs-attr">data-hx-select</span>=<span class="hljs-string">"#result"</span>
			<span class="hljs-attr">data-hx-target</span>=<span class="hljs-string">"#result"</span>
			<span class="hljs-attr">data-hx-swap</span>=<span class="hljs-string">"outerHTML"</span>
			<span class="hljs-attr">class</span>=<span class="hljs-string">"form"</span>
		></span>
			<span class="hljs-tag">&#x3C;<span class="hljs-name">fieldset</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"form-fields"</span>></span>
				<span class="hljs-comment">&#x3C;!-- Fields in here (omitted) --></span>
				<span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"block-submit"</span>></span>
					<span class="hljs-tag">&#x3C;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"button"</span>></span>
						<span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"indicator"</span> <span class="hljs-attr">aria-hidden</span>=<span class="hljs-string">"true"</span>></span>
							<span class="hljs-tag">&#x3C;<span class="hljs-name">svg</span> <span class="hljs-attr">viewBox</span>=<span class="hljs-string">"-10 -10 20 20"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"loading-spinner"</span>></span><span class="hljs-tag">&#x3C;/<span class="hljs-name">svg</span>></span>
						<span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span>
						<span class="hljs-tag">&#x3C;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"button-content"</span>></span>Obfuscate<span class="hljs-tag">&#x3C;/<span class="hljs-name">span</span>></span>
					<span class="hljs-tag">&#x3C;/<span class="hljs-name">button</span>></span>
				<span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span>
			<span class="hljs-tag">&#x3C;/<span class="hljs-name">fieldset</span>></span>
		<span class="hljs-tag">&#x3C;/<span class="hljs-name">form</span>></span>

		<span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"result"</span>></span><span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span>
	<span class="hljs-tag">&#x3C;/<span class="hljs-name">body</span>></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">html</span>></span>
</code></pre>
<p>The non-htmx form attributes are correct here: the form submits to the <code>/obfuscation</code> endpoint using GET, which will return a full HTML page which looks a bit like:</p>
<pre><code class="hljs language-html"><span class="hljs-meta">&#x3C;!doctype <span class="hljs-keyword">html</span>></span>
<span class="hljs-tag">&#x3C;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>></span>
	<span class="hljs-tag">&#x3C;<span class="hljs-name">head</span>></span>
		<span class="hljs-tag">&#x3C;<span class="hljs-name">title</span>></span>Obfuscator<span class="hljs-tag">&#x3C;/<span class="hljs-name">title</span>></span>
		<span class="hljs-comment">&#x3C;!-- Other head fields (omitted) --></span>
	<span class="hljs-tag">&#x3C;/<span class="hljs-name">head</span>></span>
	<span class="hljs-tag">&#x3C;<span class="hljs-name">body</span>></span>
		<span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"result"</span>></span>
			<span class="hljs-tag">&#x3C;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"result-content"</span>></span>
				<span class="hljs-tag">&#x3C;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"result-label"</span>></span>The result:<span class="hljs-tag">&#x3C;/<span class="hljs-name">span</span>></span>
				<span class="hljs-tag">&#x3C;<span class="hljs-name">details</span>></span>
					<span class="hljs-tag">&#x3C;<span class="hljs-name">summary</span>></span>The entrance<span class="hljs-tag">&#x3C;/<span class="hljs-name">summary</span>></span>
					<span class="hljs-tag">&#x3C;<span class="hljs-name">ol</span>></span>
						<span class="hljs-tag">&#x3C;<span class="hljs-name">li</span>></span><span class="hljs-tag">&#x3C;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"result-lang"</span>></span>English<span class="hljs-tag">&#x3C;/<span class="hljs-name">span</span>></span> input<span class="hljs-tag">&#x3C;/<span class="hljs-name">li</span>></span>
						<span class="hljs-tag">&#x3C;<span class="hljs-name">li</span>></span><span class="hljs-tag">&#x3C;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"result-lang"</span>></span>Chinese<span class="hljs-tag">&#x3C;/<span class="hljs-name">span</span>></span> 入口<span class="hljs-tag">&#x3C;/<span class="hljs-name">li</span>></span>
						<span class="hljs-tag">&#x3C;<span class="hljs-name">li</span>></span><span class="hljs-tag">&#x3C;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"result-lang"</span>></span>Russian<span class="hljs-tag">&#x3C;/<span class="hljs-name">span</span>></span> Вход<span class="hljs-tag">&#x3C;/<span class="hljs-name">li</span>></span>
						<span class="hljs-tag">&#x3C;<span class="hljs-name">li</span>></span><span class="hljs-tag">&#x3C;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"result-lang"</span>></span>Hebrew<span class="hljs-tag">&#x3C;/<span class="hljs-name">span</span>></span> הכניסה<span class="hljs-tag">&#x3C;/<span class="hljs-name">li</span>></span>
						<span class="hljs-tag">&#x3C;<span class="hljs-name">li</span>></span>
							<span class="hljs-tag">&#x3C;<span class="hljs-name">b</span>></span><span class="hljs-tag">&#x3C;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"result-lang"</span>></span>English<span class="hljs-tag">&#x3C;/<span class="hljs-name">span</span>></span> The entrance<span class="hljs-tag">&#x3C;/<span class="hljs-name">b</span>></span>
						<span class="hljs-tag">&#x3C;/<span class="hljs-name">li</span>></span>
					<span class="hljs-tag">&#x3C;/<span class="hljs-name">ol</span>></span>
				<span class="hljs-tag">&#x3C;/<span class="hljs-name">details</span>></span>
			<span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span>
		<span class="hljs-tag">&#x3C;/<span class="hljs-name">div</span>></span>
		<span class="hljs-tag">&#x3C;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>></span>Do another Obfuscation<span class="hljs-tag">&#x3C;/<span class="hljs-name">a</span>></span>
	<span class="hljs-tag">&#x3C;/<span class="hljs-name">body</span>></span>
<span class="hljs-tag">&#x3C;/<span class="hljs-name">html</span>></span>
</code></pre>
<p>That’s a good baseline, and means that the page gracefully degrades in the absence of JavaScript. When htmx is available, however, it does its magic of replacing a full browser form submission with an HTTP call which happens in the background, making the page feel smoother. Once the call is completed, it “swaps” the source page with the response.</p>
<p>This “swapping” can occur in many ways. It can replace the source page entirely, just a part of it, or even disparate elements across it! The behaviour is configured by the various attributes on the submitting element, in this case the form:</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">form</span>
	<span class="hljs-attr">action</span>=<span class="hljs-string">"/obfuscation"</span>
	<span class="hljs-attr">method</span>=<span class="hljs-string">"get"</span>
	<span class="hljs-attr">data-hx-get</span>=<span class="hljs-string">"/obfuscation"</span>
	<span class="hljs-attr">data-hx-indicator</span>=<span class="hljs-string">"#indicator"</span>
	<span class="hljs-attr">data-hx-disabled-elt</span>=<span class="hljs-string">"#form-fields"</span>
	<span class="hljs-attr">data-hx-select</span>=<span class="hljs-string">"#result"</span>
	<span class="hljs-attr">data-hx-target</span>=<span class="hljs-string">"#result"</span>
	<span class="hljs-attr">data-hx-swap</span>=<span class="hljs-string">"outerHTML"</span>
	<span class="hljs-attr">class</span>=<span class="hljs-string">"form"</span>
></span><span class="hljs-tag">&#x3C;/<span class="hljs-name">form</span>></span>
</code></pre>
<ul>
<li><code>data-hx-get=“/obfuscation”</code> re-iterates the <code>action</code> and <code>method</code> attributes, as in this case htmx should still GET the <code>/obfuscation</code> endpoint. Unlike the browser, htmx will do this request in the background when the form is submitted.</li>
<li><code>data-hx-indicator=“#indicator”</code> tells htmx that the element with ID <code>indicator</code> is the loading indicator here; htmx will add the <code>.htmx-request</code> class to this element while the request is in flight. Styling possibilities ensue.</li>
<li><code>data-hx-disabled-elt=“#form-fields”</code>: htmx will mark this element as <code>disabled</code> while the request is in flight. Putting all the fields in a single <code>fieldset</code> is a clean way to disable them all using one parent element.</li>
<li><code>data-hx-select=“#result”</code> means htmx will only look at the <code>#result</code> element in the response when swapping out the target.</li>
<li><code>data-hx-target=“#result”</code> tells htmx that the <code>#result</code> element on this page should swapped with the selected element in the response.</li>
<li><code>data-hx-swap=“outerHTML”</code> ensures the entire <code>#result</code> element is replaced with that in the response.</li>
</ul>
<p>In all these cases, the <code>data-</code> prefix is optional and I could use <code>hx-</code> directly, but I prefer <code>data-</code> for being able to quickly spot custom attributes in HTML.</p>
<p>I like this approach for its graceful fallback. It reminds me a bit of <a href="https://turbo.hotwired.dev/handbook/frames">Turbo Frames</a>, which encourages sending full pages which are surgically selected and swapped.</p>
<p>The last piece of the puzzle is the meta element at the top:</p>
<pre><code class="hljs language-html"><span class="hljs-tag">&#x3C;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"htmx-config"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">'{"responseHandling": [{"code":".*", "swap": true}]}'</span> /></span>
</code></pre>
<p>This configures htmx to perform its swapping even if the response is an error—I want errors to be visible to the user! If the input starts with “!!!error” it will trigger an “artificial” 400 error to demonstrate the behaviour.</p>
<h2>What’s next</h2>
<p>Obfuscator probably won’t need much maintenance for the next few years, especially now that there are so few dependencies to update. I’d love to internationalise it someday, or play around with a template engine that isn’t just strings. As for htmx: I’m impressed with its simplicity for basic applications like this and I’m keen to try it for something (a little) more complicated.</p>]]>
</content:encoded>
</item>
<item>
<title>Welcome Flat Hut and Hot Pools</title>
<description>South Westland walk to some of the best natural hot pools in New Zealand</description>
<link>https://albert.nz/welcome-flat/</link>
<guid isPermaLink="false">/welcome-flat/</guid>
<pubDate>Sun, 02 Jun 2024 17:35:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Welcome Flat is one of the most stunning spots on the West Coast, and possibly in the entire country. In a 2.5-hour drive and a few hours’ walk into the bush, walkers are rewarded with a well-equipped hut with exceptional natural hot pools adjacent. Our group had booked two nights in the hut to afford a hut day and made our way over on Friday night from Christchurch and Dunedin</p>
<p>The trail is well-maintained and relatively straightforward for a West Coast track—but it’s still a West Coast track, with a couple of short rock-hopping sections but thankfully not much steep climbing. Experienced hikers or trail runners will take less than the DOC time of seven hours, but inexperienced hikers will find things more challenging; this can lead to a long day on the legs!</p>
<p>We were subjected to an absolute deluge while attempting to reach the hut, and side streams became uncrossable. Architect Creek Hut provided a great place to rest up for a couple of hours until the rain and waterways subsided. If you’re attempting this, make sure to allow extra time for such inclement weather, which is fairly common in the area. DOC is quite good at communicating with those who have booked the hut and on this weekend were offering booking refunds, even though the track stayed open.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_010450138.DbD_6jzp.webp" alt="Bridge over a torrential Architect Creek" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_010450138.CepGpNO8.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_010450138.BqRXg8st.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_010450138.BpK7bTAj.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_010450138.DbD_6jzp.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Bridge over a torrential Architect Creek</figcaption></figure>
<p>Our day in to the hut saw us walking through lush and very damp bush. It was particularly impressive to look up to the steep rocky hills on either side of the valley, upon which waterfalls materialised in the heavy rain.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012134440.DxlMjK0H.webp" alt="A swollen side creek rushes to join the Copland River below" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012134440.PgHDDpri.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012134440.BW3nU-cK.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012134440.Rak0QuIU.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012134440.DxlMjK0H.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>A swollen side creek rushes to join the Copland River below</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012303760.DyuuC4tT.webp" alt="I relocated this stick insect off the track after nearly trampling it" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012303760.CD4rrtJX.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012303760.DNuWLup0.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012303760.CgShC_of.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_012303760.DyuuC4tT.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>I relocated this stick insect off the track after nearly trampling it</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_032751285.CrNPYMfi.webp" alt="Looking down the Copland Valley from Shiels Creek" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_032751285.BXMstkez.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_032751285.eI08Z18g.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_032751285.ixVrbFB-.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_032751285.CrNPYMfi.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Looking down the Copland Valley from Shiels Creek</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_014008200.DQTS52co.webp" alt="Mushroom, possibly Armillaria novae-zelandiae" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_014008200.CGA8kDyL.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_014008200.j0WOE9Zj.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_014008200.v_7_68qP.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240601_014008200.DQTS52co.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Mushroom, possibly Armillaria novae-zelandiae</figcaption></figure>
<p>The hut is generously-sized, with four sleeping rooms upstairs plus the Sierra Room downstairs—a four-bunk room which can only be booked with sole occupancy. The downstairs fire didn’t seem particularly effective but with enough people, the space warmed up a little. The most impressive feature of the hut is the flush toilets which are available all year round.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_203324167.Dpwtkm3q.webp" alt="Welcome Flat Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_203324167.uXlqZcsv.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_203324167.WI5721u8.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_203324167.CMZzyVdE.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_203324167.Dpwtkm3q.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Welcome Flat Hut</figcaption></figure>
<p>I was particularly excited to check out the hot pools, which I had heard much about. The pools are just a short walk from the hut along a 50-metre track. A small shelter near the pools provides a place to change and hang dry clothes.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_202851391.PANO.Dvi62085.webp" alt="The Welcome Flat hot pools, with views of the imposing Sierra Range" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_202851391.PANO.Dz-Y3A2m.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_202851391.PANO.fqD7hshl.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_202851391.PANO.C3axLowL.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240602_202851391.PANO.Dvi62085.webp 2560w" style="aspect-ratio: 3.097;" data-aspect="3.097" sizes="auto" loading="lazy"><figcaption>The Welcome Flat hot pools, with views of the imposing Sierra Range</figcaption></figure>
<p>The pools are absolutely stunning, and I proceeded to spend about seven hours in them the following day. The hot water, dramatic view and cold air truly is a winning combination. In this single trip, Welcome Flat became one of my favourite places in the country and I’m already planning a return.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240603_002645641.lv4PVuZE.webp" alt="Dog-shaped rock in the river" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240603_002645641.lHn51ih4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240603_002645641.DCPIE5S7.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240603_002645641.DxwoE1ML.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240603_002645641.lv4PVuZE.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Dog-shaped rock in the river</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Isthmus Peak</title>
<description>Mountain walk right between Lakes Hāwea and Wānaka, with panoramic views over both</description>
<link>https://albert.nz/isthmus-peak/</link>
<guid isPermaLink="false">/isthmus-peak/</guid>
<pubDate>Sat, 20 Apr 2024 00:04:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030034442.D4zFMoKz.webp" alt="Post and crowd at the summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030034442.BIHmDudD.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030034442.B4II7TJJ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030034442.B98vyw6O.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030034442.D4zFMoKz.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Post and crowd at the summit</figcaption></figure>
<p>Isthmus Peak track is accessible from State Highway 6 just a few minutes’ drive north of Hāwea. There are multiple car parks opposite the trailhead. On the blissfully calm autumn day we visited, the track seemed only moderately busy. I imagine it must be heaving in the summer months, though presumably not to the level of Roys Peak.</p>
<p>Apart from a short bush-clad start, the route is almost entirely 4WD track through private farmland. We visited during the roar season and got to see a couple of stags fairly close to the track. They’re much tamer than deer I’ve experienced in the backcountry as they’re farmed for tourists who hunt them on site. This is a good reason to stay on the track!</p>
<p>After many switchbacks, the track straightens and levels somewhat for a beautiful tops approach to the summit.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_031827163.YfLEGD7z.webp" alt="Approach to the summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_031827163.D7hATwAL.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_031827163.DPnMs-RF.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_031827163.FHpXYiCF.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_031827163.YfLEGD7z.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Approach to the summit</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_014848258.CqptFTd3.webp" alt="Dramatic rock and tussock scenery along the tops" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_014848258.P-EujQ8u.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_014848258.DG-pXmm0.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_014848258.BKREuhXA.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_014848258.CqptFTd3.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Dramatic rock and tussock scenery along the tops</figcaption></figure>
<p>The view from the top is truly panoramic, with great sight-lines over both Lake Hāwea and Lake Wānaka. Pride of place to the north is Mt Albert which sits above <a href="/albert-burn-hut-track">Albert Burn</a>. We got a glimpse of the snowy peaks in Mt Aspiring National Park, too.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_021938070.1wjWgxHn.webp" class="prose-custom-w-full" alt="Looking north from the summit over Lake Wānaka towards Mt Albert and the Makarora River" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_021938070.CC5zxYaF.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_021938070.BkVIfcnm.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_021938070.DfuZl4m4.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_021938070.1wjWgxHn.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Looking north from the summit over Lake Wānaka towards Mt Albert and the Makarora River</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030050994.DmGgujaH.webp" class="prose-custom-w-full" alt="Looking south from the summit. The small forested island is Mou Waho, which I have added to my list of places I want to camp!" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030050994.BdwHgkZk.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030050994.C_E1iLMQ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030050994.DYA5voBt.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_030050994.DmGgujaH.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Looking south from the summit. The small forested island is Mou Waho, which I have added to my list of places I want to camp!</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_041100577.6wSmC7Vx.webp" alt="View over Lake Hāwea on the descent" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_041100577.CoPNyHxx.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_041100577.DeQ5LdUE.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_041100577.DhhAvDGy.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240420_041100577.6wSmC7Vx.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>View over Lake Hāwea on the descent</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Little Mt Peel</title>
<description>South Canterbury peak near Geraldine with views over the plains</description>
<link>https://albert.nz/little-mt-peel/</link>
<guid isPermaLink="false">/little-mt-peel/</guid>
<pubDate>Sun, 04 Feb 2024 21:54:06 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>We picked one of the hottest days of summer to take on this peak. The first half of the ascent along Deer Spur is in lush bush, before the track emerges into lower scrub. This second half was much more exposed, and hotter as a result.</p>
<figure><img alt="Little Mt Peel summit with shelter just below" src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000004043.BOjePfvD.webp" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000004043.Y_xGsTmS.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000004043.BUo4PyPy.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000004043.Bme8Rg0o.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000004043.BOjePfvD.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Little Mt Peel summit with shelter just below</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000253772.BEpBAGiP.webp" alt="Approaching the shelter" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000253772.CFXMmJaS.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000253772.DjtIs8-l.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000253772.DgS8Dz7T.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240205_000253772.BEpBAGiP.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Approaching the shelter</figcaption></figure>
<p>After having a lovely picnic in the shade of the summit shelter, we decided to go back the way we came. There is the option of forming a loop with South Ridge, but this route looked more exposed and rocky.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240204_234620276.DZA8RkJq.webp" alt="Nearby grass fire which developed (and was extinguished) while we were above the bushline" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240204_234620276.C8Rz_Xp_.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240204_234620276.UCvCskPk.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240204_234620276.Bc2lH-PG.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20240204_234620276.DZA8RkJq.webp 2560w" style="aspect-ratio: 0.751;" data-aspect="0.751" sizes="auto" loading="lazy"><figcaption>Nearby grass fire which developed (and was extinguished) while we were above the bushline</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Making 3D-printed geometric pots with code</title>
<description>vasestl combines Rust and trigonometry to create striking STL pots from scratch</description>
<link>https://albert.nz/vasestl/</link>
<guid isPermaLink="false">/vasestl/</guid>
<pubDate>Thu, 04 Jan 2024 23:35:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0569.BcVhmecB.webp" alt="Dual diagonal triangle waves. Printed in clear PETG and pictured with an N&#x27;Joy pothos." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0569.BEdC18T6.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0569.BzxqJvtu.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0569.irB7lOc7.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0569.BcVhmecB.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Dual diagonal triangle waves. Printed in clear PETG and pictured with an N'Joy pothos.</figcaption></figure>
<p>I was recently inspired by some impressive 3D-printed geometric vase designs and considered how I could replicate a similar effect for some decorative pot covers. It struck me that many of these designs could be described by a mathematical function. I love the simplicity of “printing” a 3D function, but I was unsure as to how I could achieve this in practice.</p>
<p>There are a <a href="https://3dprinting.stackexchange.com/questions/18189/how-can-i-convert-a-mathematically-defined-shape-into-a-3d-printable-g-code-file">few solutions</a> I have seen proposed for tackling this kind of thing:</p>
<ol>
<li><strong>Use an off-the-shelf graphical CAD program supporting functions.</strong> There are tools that support defining surfaces as functions. SideFX Houdini and Full Control Designer are mentioned. The disadvantage here is that I would need to learn these specific workflows and be at the mercy of their limitations.</li>
<li><strong>Work out how to do it in OpenSCAD.</strong> The <code>polyhedron</code> feature, combined with a lot of iteration, could be used. While I love OpenSCAD for basic parts, I suspect this approach would be very slow and cumbersome to output a complicated shape with the resolution I desire.</li>
<li><strong>Code it from scratch.</strong> I chose this option as it would give full flexibility, maximum speed and let me learn a bit more about STL files along the way.</li>
</ol>
<h1>How to make a pot with code</h1>
<p>Enter my solution: <a href="https://github.com/albertnis/vasestl">vasestl</a>. I opted to use Rust for its blazing-fast performance, and because I have recently been learning the basics of Rust through the Advent of Code challenges. On reflection, I doubt the performance advantages were necessary because the program can build tens of thousands of triangles in a matter of milliseconds.</p>
<h2>Compute points</h2>
<p>The first step is to come up with an equation describing the shape. I decided to use polar co-ordinates for this process, which makes it easier to define periodic shapes that will wrap around the pot seamlessly. At each increment of height (<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span>) and angle (<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>θ</mi></mrow><annotation encoding="application/x-tex">\theta</annotation></semantics></math></span>), the distance from the origin (<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>r</mi></mrow><annotation encoding="application/x-tex">r</annotation></semantics></math></span>) can be defined—which yields a point in 3D space. One of the simplest options is a cylinder, which would have a mathematical definition like this:</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>r</mi><mo stretchy="false">(</mo><mi>θ</mi><mo separator="true">,</mo><mi>z</mi><mo stretchy="false">)</mo><mo>=</mo><mn>30</mn></mrow><annotation encoding="application/x-tex">r(\theta, z) = 30
</annotation></semantics></math></span>
<p>With four iterations of z and 14 iterations of θ, the corresponding point cloud for this equation would look something like:</p>
<p><svg style="margin: auto" viewBox="-54 -35.764571353075624 108 179.529" width="108" height="179.529"><circle cx="30.000" cy="0.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="27.029" cy="3.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="18.705" cy="6.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="6.676" cy="7.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="7.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="6.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="3.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="-30.000" cy="0.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="-3.369" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="-6.071" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="-7.570" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="6.676" cy="-7.570" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="18.705" cy="-6.071" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="27.029" cy="-3.369" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="30.000" cy="36.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="27.029" cy="39.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="18.705" cy="42.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="6.676" cy="43.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="43.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="42.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="39.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="-30.000" cy="36.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="32.631" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="29.929" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="28.430" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="6.676" cy="28.430" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="18.705" cy="29.929" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="27.029" cy="32.631" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="30.000" cy="72.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="27.029" cy="75.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="18.705" cy="78.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="6.676" cy="79.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="79.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="78.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="75.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="-30.000" cy="72.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="68.631" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="65.929" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="64.430" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="6.676" cy="64.430" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="18.705" cy="65.929" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="27.029" cy="68.631" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="30.000" cy="108.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="27.029" cy="111.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="18.705" cy="114.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="6.676" cy="115.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="115.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="114.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="111.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="-30.000" cy="108.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="104.631" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="101.929" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="100.430" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="6.676" cy="100.430" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="18.705" cy="101.929" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="27.029" cy="104.631" opacity="0.5" fill="currentColor" r="4"></circle></svg></p>
<p>Adding a cool swept sine pattern to this shape is as simple as incorporating the angle and offsetting it with changing z-height:</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>r</mi><mo stretchy="false">(</mo><mi>θ</mi><mo separator="true">,</mo><mi>z</mi><mo stretchy="false">)</mo><mo>=</mo><mn>31</mn><mo>+</mo><mi>s</mi><mi>i</mi><mi>n</mi><mo stretchy="false">(</mo><mn>12</mn><mi>θ</mi><mo>+</mo><mi>z</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">r(\theta, z) = 31 + sin(12\theta + z)
</annotation></semantics></math></span>
<p>In the Rust code, we could consider the base cylindrical shape and the decorative “ripple” separately, representing it as something like:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">r</span>(t: <span class="hljs-type">f32</span>, z: <span class="hljs-type">f32</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">f32</span> {
    <span class="hljs-keyword">const</span> CYLINDER_RADIUS: <span class="hljs-type">f32</span> = <span class="hljs-number">30.0</span>;

    <span class="hljs-comment">// "Base": Just the cylinder, in this case</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">base</span> = CYLINDER_RADIUS;

    <span class="hljs-comment">// "Decoration": A sine wave to add a rippled texture</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">decoration</span> = <span class="hljs-number">1.0</span> + <span class="hljs-type">f32</span>::<span class="hljs-title function_ invoke__">sin</span>(<span class="hljs-number">12.0</span>*t + z);

    <span class="hljs-comment">// Return the sum of these components</span>
    base + decoration
}
</code></pre>
<p>Where this can get a bit painful is for non-circular shapes, such as squares and rectangles. These need to use a polar representation of the shape. For example, a square pot shape of side length <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>s</mi></mrow><annotation encoding="application/x-tex">s</annotation></semantics></math></span> would be defined as:</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>r</mi><mo stretchy="false">(</mo><mi>θ</mi><mo separator="true">,</mo><mi>z</mi><mo stretchy="false">)</mo><mo>=</mo><mrow><mo fence="true">{</mo><mtable rowspacing="0.36em" columnalign="left left" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi mathvariant="normal">∣</mi><mi>s</mi><mi>cosec</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>θ</mi><mo stretchy="false">)</mo><mi mathvariant="normal">∣</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext>if </mtext><mi>π</mi><mi mathvariant="normal">/</mi><mn>4</mn><mo>&#x3C;</mo><mi>θ</mi><mo>&#x3C;</mo><mn>3</mn><mi>π</mi><mi mathvariant="normal">/</mi><mn>4</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi mathvariant="normal">∣</mi><mi>s</mi><mi>cosec</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>θ</mi><mo stretchy="false">)</mo><mi mathvariant="normal">∣</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mtext>if </mtext><mn>5</mn><mi>π</mi><mi mathvariant="normal">/</mi><mn>4</mn><mo>&#x3C;</mo><mi>θ</mi><mo>&#x3C;</mo><mn>7</mn><mi>π</mi><mi mathvariant="normal">/</mi><mn>4</mn></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mi mathvariant="normal">∣</mi><mi>s</mi><mi>sec</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>θ</mi><mo stretchy="false">)</mo><mi mathvariant="normal">∣</mi></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="false"><mtext>otherwise</mtext></mstyle></mtd></mtr></mtable></mrow></mrow><annotation encoding="application/x-tex">r(\theta, z) = \begin{cases}
   |s\cosec(\theta)| &#x26;\text{if } \pi/4 &#x3C; \theta &#x3C; 3\pi/4 \\
   |s\cosec(\theta)| &#x26;\text{if } 5\pi/4 &#x3C; \theta &#x3C; 7\pi/4 \\
   |s\sec(\theta)| &#x26;\text{otherwise}
\end{cases}
</annotation></semantics></math></span>
<p>Awkward, right? In code it’s at least a tad clearer to build these shapes up piece by piece. For example, here is the definition of a pot which sweeps from a circular bottom to a square top. Once this “base shape” is defined, it is easy to add any decorative elements just like before:</p>
<pre><code class="hljs language-rust"><span class="hljs-keyword">fn</span> <span class="hljs-title function_">r</span>(t: <span class="hljs-type">f32</span>, z: <span class="hljs-type">f32</span>) <span class="hljs-punctuation">-></span> <span class="hljs-type">f32</span> {
    <span class="hljs-comment">// Pot is circle ø88 at bottom; square 104×104 at top</span>

    <span class="hljs-keyword">const</span> SQUARE_SIZE: <span class="hljs-type">f32</span> = <span class="hljs-number">52.0</span>; <span class="hljs-comment">// Half the width (minimum "radius") of square</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">square</span> = <span class="hljs-keyword">if</span> (PI * <span class="hljs-number">0.25</span>..PI * <span class="hljs-number">0.75</span>).<span class="hljs-title function_ invoke__">contains</span>(&#x26;t) || (PI * <span class="hljs-number">1.25</span>..PI * <span class="hljs-number">1.75</span>).<span class="hljs-title function_ invoke__">contains</span>(&#x26;t) {
        <span class="hljs-type">f32</span>::<span class="hljs-title function_ invoke__">abs</span>(SQUARE_SIZE / <span class="hljs-type">f32</span>::<span class="hljs-title function_ invoke__">sin</span>(t))
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-type">f32</span>::<span class="hljs-title function_ invoke__">abs</span>(SQUARE_SIZE / <span class="hljs-type">f32</span>::<span class="hljs-title function_ invoke__">cos</span>(t))
    };

    <span class="hljs-keyword">const</span> CIRCLE: <span class="hljs-type">f32</span> = <span class="hljs-number">44.0</span>; <span class="hljs-comment">// Radius of circle</span>

    <span class="hljs-comment">// Ease sinusoidally between circle base and square top</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">z_ease</span> = -(<span class="hljs-type">f32</span>::<span class="hljs-title function_ invoke__">cos</span>(PI * z) - <span class="hljs-number">1.0</span>) / <span class="hljs-number">2.0</span>;
    <span class="hljs-keyword">let</span> <span class="hljs-variable">base</span> = CIRCLE * (<span class="hljs-number">1.0</span> - z_ease) + square * z_ease;

    <span class="hljs-comment">// "Decoration": A sine wave to add a rippled texture</span>
    <span class="hljs-keyword">let</span> <span class="hljs-variable">decoration</span> = <span class="hljs-type">f32</span>::<span class="hljs-title function_ invoke__">sin</span>((t + <span class="hljs-number">0.5</span> * z) * <span class="hljs-number">80.0</span>) * <span class="hljs-number">1.0</span>;

    <span class="hljs-comment">// Combine and return</span>
    base + decoration
}
</code></pre>
<p>This yields the following shape:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0537.gTn0wtUb.webp" alt="Single swept sine wave. Printed in grey PLA and pictured with a purple inch plant." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0537.C0A49-xN.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0537.CXwdjCQO.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0537.B_28d3cb.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0537.gTn0wtUb.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Single swept sine wave. Printed in grey PLA and pictured with a purple inch plant.</figcaption></figure>
<p>It is possible to also build rectangles using a similar strategy. See the <a href="https://github.com/albertnis/vasestl/blob/main/examples">examples in the repository</a>.</p>
<h2>Convert the points to triangles</h2>
<p>After computing the points at every increment of <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span> and <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>θ</mi></mrow><annotation encoding="application/x-tex">\theta</annotation></semantics></math></span>, the code has a point cloud where every point lies on the surface of the pot shape. This needs to be converted to a series of triangles which will define the surface which can later be printed.</p>
<p>Because the points were computed at discrete increments of <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>z</mi></mrow><annotation encoding="application/x-tex">z</annotation></semantics></math></span>, the array of points is effectively a series of vertically-stacked “rings” of points, where each “ring” has the same number of points (determined by the increment of <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>θ</mi></mrow><annotation encoding="application/x-tex">\theta</annotation></semantics></math></span>). Building triangles is a matter of iterating through the rings and connecting adjacent points to form squares, which are then stored as two triangles. Additionally, the normal vector of each triangle is determined, which can be computed using a cross product. This normal is later embedded in the STL file to indicate which side is the “outside” of the surface.</p>
<figure>
<div style="display: flex; justify-content: center;">
<svg viewBox="-76 -37.31748562369074 152 215.529" style="max-height: 215px; height: 100%; width: 100%;"><circle cx="36.000" cy="0.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="32.435" cy="4.043" opacity="1" fill="currentColor" r="4"></circle><circle cx="22.446" cy="7.285" opacity="1" fill="currentColor" r="4"></circle><circle cx="8.011" cy="9.084" opacity="1" fill="currentColor" r="4"></circle><circle cx="-8.011" cy="9.084" opacity="1" fill="currentColor" r="4"></circle><circle cx="-22.446" cy="7.285" opacity="1" fill="currentColor" r="4"></circle><circle cx="-32.435" cy="4.043" opacity="1" fill="currentColor" r="4"></circle><circle cx="-36.000" cy="0.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="-32.435" cy="-4.043" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-22.446" cy="-7.285" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-8.011" cy="-9.084" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="8.011" cy="-9.084" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="22.446" cy="-7.285" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="32.435" cy="-4.043" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="36.000" cy="36.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="32.435" cy="40.043" opacity="1" fill="currentColor" r="4"></circle><circle cx="22.446" cy="43.285" opacity="1" fill="currentColor" r="4"></circle><circle cx="8.011" cy="45.084" opacity="1" fill="currentColor" r="4"></circle><circle cx="-8.011" cy="45.084" opacity="1" fill="currentColor" r="4"></circle><circle cx="-22.446" cy="43.285" opacity="1" fill="currentColor" r="4"></circle><circle cx="-32.435" cy="40.043" opacity="1" fill="currentColor" r="4"></circle><circle cx="-36.000" cy="36.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="-32.435" cy="31.957" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-22.446" cy="28.715" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-8.011" cy="26.916" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="8.011" cy="26.916" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="22.446" cy="28.715" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="32.435" cy="31.957" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="45.000" cy="72.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="40.544" cy="77.053" opacity="1" fill="currentColor" r="4"></circle><circle cx="28.057" cy="81.106" opacity="1" fill="#8b5cf6" r="4"></circle><circle cx="10.013" cy="83.355" opacity="1" fill="#8b5cf6" r="4"></circle><circle cx="-10.013" cy="83.355" opacity="1" fill="currentColor" r="4"></circle><circle cx="-28.057" cy="81.106" opacity="1" fill="currentColor" r="4"></circle><circle cx="-40.544" cy="77.053" opacity="1" fill="currentColor" r="4"></circle><circle cx="-45.000" cy="72.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="-40.544" cy="66.947" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-28.057" cy="62.894" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-10.013" cy="60.645" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="10.013" cy="60.645" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="28.057" cy="62.894" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="40.544" cy="66.947" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="52.000" cy="108.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="46.850" cy="113.839" opacity="1" fill="currentColor" r="4"></circle><circle cx="32.421" cy="118.522" opacity="1" fill="#8b5cf6" r="4"></circle><circle cx="11.571" cy="121.121" opacity="1" fill="#f97316" r="4"></circle><circle cx="-11.571" cy="121.121" opacity="1" fill="currentColor" r="4"></circle><circle cx="-32.421" cy="118.522" opacity="1" fill="currentColor" r="4"></circle><circle cx="-46.850" cy="113.839" opacity="1" fill="currentColor" r="4"></circle><circle cx="-52.000" cy="108.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="-46.850" cy="102.161" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-32.421" cy="97.478" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-11.571" cy="94.879" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="11.571" cy="94.879" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="32.421" cy="97.478" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="46.850" cy="102.161" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="30.000" cy="144.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="27.029" cy="147.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="18.705" cy="150.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="6.676" cy="151.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="151.570" opacity="1" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="150.071" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="147.369" opacity="1" fill="currentColor" r="4"></circle><circle cx="-30.000" cy="144.000" opacity="1" fill="currentColor" r="4"></circle><circle cx="-27.029" cy="140.631" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-18.705" cy="137.929" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="-6.676" cy="136.430" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="6.676" cy="136.430" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="18.705" cy="137.929" opacity="0.5" fill="currentColor" r="4"></circle><circle cx="27.029" cy="140.631" opacity="0.5" fill="currentColor" r="4"></circle></svg>
<svg style="max-height: 215px; height: 100%; width: 100%;" viewBox="-20 -20 215 215">
  <path d="M0 0 H 175 V 175 H 0 V 0 L 175 175" stroke="currentColor" fill="transparent" stroke-width="2"></path>
  <circle cx="0" cy="0" r="8" fill="#8b5cf6"></circle>
  <circle cx="175" cy="0" r="8" fill="#8b5cf6"></circle>
  <circle cx="0" cy="175" r="8" fill="#f97316"></circle>
  <circle cx="175" cy="175" r="8" fill="#8b5cf6"></circle>
</svg>
</div>
<figcaption>Illustration of deriving two triangles from a selected point highlighted in orange. Certain adjacent points (purple) are considered as a square which is then bisected.</figcaption>
</figure>
<h2>Build an STL file</h2>
<p>The STL file itself was surprisingly simple to build. I opted to use the binary variant of the file for its more efficient structure. After a mostly-empty header, the data for each triangle is simply stuffed into the file bytes. Each triangle is defined by 12 floating point numbers (x, y, z coordinates of each corner, plus a 3D normal vector) and Rust’s <code>to_le_bytes()</code> function made the data dumping trivial, if <a href="https://github.com/albertnis/vasestl/blob/main/src/stl.rs#L24">verbose</a>.</p>
<h1>Printing the pots</h1>
<p>In part, this whole project started an excuse to play around with vase mode. Vase mode is offered by many 3D slicers and enabled the printing of a single-walled part with a continuous spiralling layer, as opposed to the more typical discrete layers used in conventional prints. This process leads to a fairly flimsy part, but saves on filament and is perfect for aesthetic pot and vase covers.</p>
<p>In Cura, enabling this mode is as simple as checking the “Spiralize outer contours” box. I’ve had great success using this feature with PLA and PETG.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0538.X8plC8GV.webp" alt="Dual swept sine waves. Printed in grey PLA and pictured with a spider plant." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0538.D8l2ixgV.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0538.C-s9jE0-.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0538.CUVDxKS6.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0538.X8plC8GV.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Dual swept sine waves. Printed in grey PLA and pictured with a spider plant.</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Travers Peak</title>
<description>Easily-accessed summit near Lewis Pass with broad views of the surrounding ranges</description>
<link>https://albert.nz/travers-peak/</link>
<guid isPermaLink="false">/travers-peak/</guid>
<pubDate>Tue, 02 Jan 2024 22:44:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Inspired by a recent trip to <a data-sveltekit-reload="" href="/avalanche-peak">Avalanche Peak</a>, I sought out another peak for a day trip. Travers Peak checked most of the boxes. It’s similar to the Avalanche Peak track in many ways but it is unmarked on the tops, with expansive views across the mountain ranges surrounding Lewis Pass.</p>
<p>Access is easy—park at Deer Valley campsite then go straight up Foley’s Track to the bushline. After this, it’s a matter of following the ridge to the summit. There are a couple of particularly steep sections, but cairns and the occasional pole help to navigate. We made it to the summit in just over two hours. If you’re doing this trip, be wary of the steep dropoffs to the south of the final ridge climb.</p>
<p>The peak itself has a few nice nooks to sit down and enjoy some snacks while enjoying the stunning view. It’s amazing how open the tops are around here. Traversing the Libretto Range—or at least spending a night camping up here—could be a fun challenge.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02073.58KIaQUP.webp" alt="Looking north from the summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02073.Dy8IBmPX.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02073.BXfBwqsj.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02073.oWQvRi__.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02073.58KIaQUP.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Looking north from the summit</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02074.ojbH34oS.webp" alt="Starting the descent, looking southwest" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02074.CyLC0Bru.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02074.CnqMePbk.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02074.DfANeMXE.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02074.ojbH34oS.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>Starting the descent, looking southwest</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0568.DZbDSa5f.webp" alt="Silvery moss and ferns among the beech forest near the bushline" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0568.B2n5KnnA.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0568.CwJQztzv.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0568.BKV2C9ca.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0568.DZbDSa5f.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Silvery moss and ferns among the beech forest near the bushline</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Avalanche Peak</title>
<description>Popular alpine peak above Arthurs Pass village</description>
<link>https://albert.nz/avalanche-peak/</link>
<guid isPermaLink="false">/avalanche-peak/</guid>
<pubDate>Fri, 22 Dec 2023 20:30:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Avalanche Peak is an amazingly accessible mountain about two hours’ drive from Christchurch. There are two routes to access the summit: Avalanche Peak Track and Scotts Track. We ascended on Avalanche Peak Track and saved the more gentle Scotts Track for the descent. Both tracks are steep but well-marked and quite manageable.</p>
<p>If you’re tackling this trip, make sure to bring plenty of water and keep an ear out for kea.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02018.C-cm6_ro.webp" alt="Catching a breath on the climb to the summit. Bealey River is visible at the base of the valley." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02018.BhSJ9Xql.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02018.DLGHdT01.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02018.C7oF2OHk.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02018.C-cm6_ro.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Catching a breath on the climb to the summit. Bealey River is visible at the base of the valley.</figcaption></figure>
<p>The two tracks meet just shy of the peak. This final section is a rocky ridge with stunning views all around.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02037-Pano.CT-7UDPS.webp" class="prose-custom-w-full" alt="Dramatic vista from the summit. A hiker can be seen admiring the view. Far below, the highway winds up towards Arthur&#x27;s Pass proper." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02037-Pano.QI1OGm6W.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02037-Pano.CFcw-uQb.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02037-Pano.n13Mosdk.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02037-Pano.CT-7UDPS.webp 2560w" style="aspect-ratio: 2.874;" data-aspect="2.874" sizes="auto" loading="lazy"><figcaption>Dramatic vista from the summit. A hiker can be seen admiring the view. Far below, the highway winds up towards Arthur's Pass proper.</figcaption></figure>
<p>As we left the ridge and commenced our descent, I was distracted by a roving gang of young kea that landed nearby. Now I think of it, maybe they were the ones being distracted by me. It’s always hard to tell with these intelligent creatures.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02060.CHTCHDDq.webp" alt="Curious juvenile kea that got dangerously close to my camera" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02060.3hXD2hkq.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02060.B3k9nGdx.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02060.BENxn5fq.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02060.CHTCHDDq.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Curious juvenile kea that got dangerously close to my camera</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02070.BD7K0JV6.webp" alt="Water break on the cloudy descent. Devils Punchbowl Falls are just visible on the other side of the valley." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02070.BaSffOwr.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02070.ClKtHqs9.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02070.84fvTu76.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC02070.BD7K0JV6.webp 2560w" style="aspect-ratio: 1.000;" data-aspect="1.000" sizes="auto" loading="lazy"><figcaption>Water break on the cloudy descent. Devils Punchbowl Falls are just visible on the other side of the valley.</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Ice Lake</title>
<description>Epic alpine lake in the Butler Range accessed from Whataroa, West Coast</description>
<link>https://albert.nz/ice-lake/</link>
<guid isPermaLink="false">/ice-lake/</guid>
<pubDate>Fri, 20 Oct 2023 19:35:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Ice Lake is an epic alpine lake accessed up the Whataroa and Butler Rivers on the West Coast. It’s fairly remote, wedged right up against the Main Divide, but still accessible on a two-night tramp. Ice Lake had been on my radar for several years so I was excited to be part of a group tackling the hike up the river!</p>
<h2>Day one</h2>
<p>With our sights set on Butler Junction Hut, we set off from the carpark adjacent to the Whataroa River. The track proved easy-going until the main swingbridge at the Perth River Track junction. Beyond that, the track turns to follow the Whataroa River valley. There are a couple of large undulations, then things become increasingly slow-going and technical as the hut approaches. Thankfully, overgrown vegetation had been recently cleared on some of the track sections near the hut. Still, it took us over nine hours to reach the hut—longer than DOC’s eight-hour estimate.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01942.DwOoSXhd.webp" alt="Butler Range viewed from near the start of the trail. Ice Lake is just on the other side. So close!" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01942.07iy7iP7.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01942.BbCkO9RC.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01942.BjyKo8i5.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01942.DwOoSXhd.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>Butler Range viewed from near the start of the trail. Ice Lake is just on the other side. So close!</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01955.BclV224w.webp" alt="Predator-proof door on the drawbridge over the Whataroa River near the Perth River Track junction" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01955.DGXBA6MG.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01955.lE58MltL.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01955.D1zXEmIe.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01955.BclV224w.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>Predator-proof door on the drawbridge over the Whataroa River near the Perth River Track junction</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0404.QX5ID64C.webp" alt="Tackling windfall with a Silky saw" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0404.f23FFN5l.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0404.BAHoJL7K.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0404.W8vV_dAs.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0404.QX5ID64C.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Tackling windfall with a Silky saw</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01961.CWk3aoDR.webp" alt="We encountered a pair of kea on the high section of track between Scotties Beach and Barrowman Flat" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01961.Bus1qSBw.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01961.CiQbdBZm.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01961.BWXa687Z.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01961.CWk3aoDR.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>We encountered a pair of kea on the high section of track between Scotties Beach and Barrowman Flat</figcaption></figure>
<p>Butler Junction Hut is perched above a bend in the Whataroa River and comes into sight with a decent amount of walking remaining. With the hut intentions book only going back to about March 2023, it was hard to get a good idea of visitation rates. It looked like there had been about one visit per month since the start of the book, but I imagine that the rate would likely increase in summer.</p>
<h2>Day two</h2>
<p>The mission was simple: get to Ice Lake. In line with DOC times, the Butler River Track took us about four hours to the lake, with Top Butler Hut at the halfway mark. This hut is in a dramatic clearing, surrounded by imposing mountain peaks. There is a notable river crossing of the Butler River South Branch near Top Butler Hut. The area hadn’t received any rain for about a week, so this crossing was easily manageable.</p>
<p>Like the later parts of day one, the Butler River track was undulating and slow-going, with a fair amount of windfall. There were some fascinating rock faces and cave-like areas along the way, as well as a spicy section of ongaonga overgrowth.</p>
<p>The track emerges from the bush for the last 30 minutes of the approach to the lake. Eventually, we crested a ridge to see Ice Lake in all its glory.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01978.CgQJy6_6.webp" alt="Ice Lake shore" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01978.Cnup_ScF.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01978.DjrqHE18.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01978.DMhb32zU.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01978.CgQJy6_6.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>Ice Lake shore</figcaption></figure>
<p>The colour of the water is striking, with a light cloudy colour imparted by the rock dust delivered by the glaciers beyond the head of the lake. We sat near the lake for over an hour, soaking in the view and making some drinks with the nearby ice (it’s called Ice Lake so the ice must be worth a try, right?).</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01988-Pano.BDu3zmNk.webp" alt="Ice Lake with Butler Range towering high above" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01988-Pano.dbIp_dYb.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01988-Pano.CYzRRg2r.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01988-Pano.-G4Eltf_.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01988-Pano.BDu3zmNk.webp 2560w" style="aspect-ratio: 2.087;" data-aspect="2.087" sizes="auto" loading="lazy"><figcaption>Ice Lake with Butler Range towering high above</figcaption></figure>
<p>Another four hours of walking brought us back to Butler Junction Hut, where I was delighted to take off my boots for the day. Thankfully we had several hours of daylight left—plenty of time for a dip in the river and a sit in the sun.</p>
<h2>Day three</h2>
<p>Up bright and early, day three was simply a matter of retracing our steps to get back to the cars and then to Christchurch.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0415.6fJQi9T7.webp" alt="Stunning gorge views alongside the track" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0415.mmw5Y6DQ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0415.DfBgUd22.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0415.CHRxxtGw.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_0415.6fJQi9T7.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Stunning gorge views alongside the track</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Mt Fyffe Hut</title>
<description>Accessible Kaikōura hut with brilliant east-coast views from the ridgeline</description>
<link>https://albert.nz/mt-fyffe-hut/</link>
<guid isPermaLink="false">/mt-fyffe-hut/</guid>
<pubDate>Fri, 13 Oct 2023 04:59:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>What a marvellous hut to visit as an overnighter from Christchurch! The day of our descent was incredibly windy and had us abandoning an attempt to summit Mt Fyffe after getting sandblasted on the way up from the hut. It wasn’t all unpleasant, however: we got to witness an amazing sunset climbing up to the hut, followed by an even more amazing sunrise in the morning. It’s breathtaking to see Kaikōura Peninsula from this angle.</p>
<p>The track to the hut is easy-going and could be safely walked at night by flashlight. Note that the hut is popular and had scarce water in the tank when we visited. Pack in plenty of water, and either arrive early to claim a bunk or bring a tent as backup.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01889.0Zr6Us0P.webp" alt="Sunset over the Kaikōura Ranges" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01889.Dhxku9Bo.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01889.ZyP4iFQV.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01889.DrlaqlCg.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01889.0Zr6Us0P.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Sunset over the Kaikōura Ranges</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01897.BN-rgem0.webp" alt="Looking down towards Kōwhai River" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01897.C0jBnFxf.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01897.Bs8S-pcl.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01897.cWt646uC.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01897.BN-rgem0.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>Looking down towards Kōwhai River</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01922.3VB5K0y_.webp" alt="Complex cloud patterns illuminated by sunset" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01922.Bnonv_4o.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01922.DRHk6_zr.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01922.D09d2GOi.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01922.3VB5K0y_.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Complex cloud patterns illuminated by sunset</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01929-HDR.B8h5ab6A.webp" alt="Glorious sunrise over Kaikōura Peninsula, viewed from near the hut" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01929-HDR.D_jHGwmk.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01929-HDR.ApdEI6Co.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01929-HDR.CQqrAkif.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01929-HDR.B8h5ab6A.webp 2560w" style="aspect-ratio: 1.690;" data-aspect="1.690" sizes="auto" loading="lazy"><figcaption>Glorious sunrise over Kaikōura Peninsula, viewed from near the hut</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01932.CAjiTwFP.webp" alt="Sunrise on the range to the northwest, looking towards Mt Saunders" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01932.caNftUD-.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01932.D_Kj21cA.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01932.CMz7L8g8.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01932.CAjiTwFP.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Sunrise on the range to the northwest, looking towards Mt Saunders</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Extending brightness controls to external monitors, in 15 lines of code</title>
<description>Harnessing Hyprland to brighten/dim whichever monitor I'm working on</description>
<link>https://albert.nz/hyprland-brightness/</link>
<guid isPermaLink="false">/hyprland-brightness/</guid>
<pubDate>Thu, 14 Sep 2023 09:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>I change the brightness quite often on my displays, trying to keep it as dim as possible for my environment as it changes throughout the day. For external displays, I fiddle with the monitor’s physical buttons and on-screen display to dial in the brightness; for the built-in display I use the brightness buttons on the keyboard. That’s how it’s meant to work, right?</p>
<p>I have been using the impressive <a href="https://hyprland.org/">Hyprland</a> compositor on my Linux laptop for the last month or so, and it occurred to me recently that I had all the ingredients for a better setup: a setup where the laptop’s brightness keys would just change the brightness of whichever monitor I was using in that moment.</p>
<h2>The ingredients</h2>
<p>To change the brightness of the internal display, I use <a href="https://gitlab.com/cameronnemo/brillo">brillo</a> because it supports smoothed brightness adjustments. This is the command I previously had bound to the brightness up key in Hyprland’s config:</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Increase the brightness 8% over 150ms</span>
brillo -u 150000 -A 8
</code></pre>
<p>To increase the brightness of the external display, <a href="https://github.com/rockowitz/ddcutil">ddcutil</a> is perfect as it uses DDC/CI to change the display’s paramters over I²C (magic!):</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Increase the brightness of the first external monitor by 8%</span>
ddcutil --display=1 setvcp 10 + 8
</code></pre>
<p>What I really wanted to do was run the relevant command for the currently focused monitor (ie, the one where the cursor is). Here’s where Hyprland comes in:</p>
<pre><code class="hljs language-bash"><span class="hljs-comment"># Get a list of monitors including focused state and ID, in JSON format</span>
hyprctl monitors -j
</code></pre>
<p>The (truncated) output of this command is:</p>
<pre><code class="hljs language-js">[
	{
		<span class="hljs-string">"id"</span>: <span class="hljs-number">0</span>,
		<span class="hljs-string">"name"</span>: <span class="hljs-string">"eDP-1"</span>, <span class="hljs-comment">// The internal monitor</span>
		<span class="hljs-string">"focused"</span>: <span class="hljs-literal">false</span>,
		...
	},
	{
		<span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>,
		<span class="hljs-string">"name"</span>: <span class="hljs-string">"DP-2"</span>, <span class="hljs-comment">// My one external monitor</span>
		<span class="hljs-string">"focused"</span>: <span class="hljs-literal">true</span>,
        ...
	}
]
</code></pre>
<p>Using a bit of <code>jq</code>, I plucked out the ID and name of the focused monitor:</p>
<pre><code class="hljs language-bash">focused_name=$(hyprctl monitors -j | jq -r <span class="hljs-string">'.[] | select(.focused == true) | .name'</span>)
focused_id=$(hyprctl monitors -j | jq -r <span class="hljs-string">'.[] | select(.focused == true) | .id'</span>)
</code></pre>
<h2>Putting it together</h2>
<p>Now it’s just a matter of getting the current monitor and running the correct command based on its name:</p>
<pre><code class="hljs language-bash"><span class="hljs-meta">#!/usr/bin/env sh</span>

<span class="hljs-comment"># Accept an arg '+' or '-'</span>
direction=<span class="hljs-variable">$1</span>

<span class="hljs-comment"># Get monitor info</span>
monitor_data=$(hyprctl monitors -j)
focused_name=$(<span class="hljs-built_in">echo</span> <span class="hljs-variable">$monitor_data</span> | jq -r <span class="hljs-string">'.[] | select(.focused == true) | .name'</span>)

<span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$focused_name</span>"</span> == <span class="hljs-string">"eDP-1"</span> ]; <span class="hljs-keyword">then</span>
    <span class="hljs-comment"># Internal display is focused -> use brillo</span>
    <span class="hljs-keyword">if</span> [ <span class="hljs-string">"<span class="hljs-variable">$direction</span>"</span> == <span class="hljs-string">"-"</span> ]; <span class="hljs-keyword">then</span>
        brillo -u 150000 -U 8
    <span class="hljs-keyword">else</span>
        brillo -u 150000 -A 8
    <span class="hljs-keyword">fi</span>
<span class="hljs-keyword">else</span>
    <span class="hljs-comment"># External display is focused -> use ddcutil</span>
    <span class="hljs-comment"># But *which* external display?</span>
    focused_id=$(<span class="hljs-built_in">echo</span> <span class="hljs-variable">$monitor_data</span> | jq -r <span class="hljs-string">'.[] | select(.focused == true) | .id'</span>)
    ddcutil --display=<span class="hljs-variable">$focused_id</span> setvcp 10 <span class="hljs-variable">$direction</span> 8
<span class="hljs-keyword">fi</span>
</code></pre>
<p>I named this script <code>hypr_brightness.sh</code> and mapped it to my brightness keys in my <code>hyprland.conf</code> a bit like the following:</p>
<pre><code class="hljs language-conf">binde = , XF86MonBrightnessDown, exec, /path/to/hypr_brightness.sh -
binde = , XF86MonBrightnessUp, exec, /path/to/hypr_brightness.sh +
</code></pre>
<p>Since implementing this change on my personal device, I’ve found myself confused on other laptops when the external monitor brightness does not change when I use the brightness keys. That’s just how intuitive it feels.</p>
<p>You can find the script at <a href="https://github.com/albertnis/hypr-brightness">albertnis/hypr-brightness</a> on GitHub. The version there is a bit more refined, with argument validation and some optimisations.</p>]]>
</content:encoded>
</item>
<item>
<title>Buckland Peaks Hut</title>
<description>Hillside hut in the Westport region, with easy access to the rugged Buckland Peaks</description>
<link>https://albert.nz/buckland-peaks-hut/</link>
<guid isPermaLink="false">/buckland-peaks-hut/</guid>
<pubDate>Tue, 13 Jun 2023 00:40:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Approaching the end of a period of unemployment, I went hunting for a rewarding solo weeknight trip. Buckland Peaks Hut fit the bill perfectly: not too difficult and a good chance to be up a hill in the West Coast during a period of calm weather.</p>
<p>The track to Buckland Peaks Hut starts at the carpark situated at the end of Bucklands Peak Road<a id="footnote-1-from" href="#footnote-1-to">*</a>. The first 5.5 kilometres of hiking follows the private section of the road, crossing farmland to reach the base of the hills. Note that this section is accessible on foot only, as the landowner has disallowed bicycle and vehicle travel. The track is well-marked through intersecting farm tracks, and with some tactical jogging along the way I reached the hills in a bit under an hour. At this point the track crosses a fenceline then follows a spur steadily up to the bushline.</p>
<p>Just above the bushline is a short stretch of tops travel to a saddle, at which point the poled route drops down to the hut which is about a 10 minute walk down rocky terrain. Reaching the hut from the carpark is noted by DOC as taking five hours, but I found it doable in just over 3.5 hours. Your mileage may vary, but getting up to the hut and back down would be quite possible as a daytrip.</p>
<p>After refilling water bottles at the hut, I ascended back up to the saddle with a cooker to eat soup while watching the sunset. The view out to the coast was glorious on this pristine winter’s evening. With barely a breeze in the air, I enjoyed the peaceful moment.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01603.DNkvC38z.webp" alt="Buckland Peaks Hut as viewed from the saddle. The mouth of the Buller River is in the distance, framed between hills." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01603.D2iI2p0f.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01603.B6lyHGTO.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01603.DdbVQt2Y.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01603.DNkvC38z.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Buckland Peaks Hut as viewed from the saddle. The mouth of the Buller River is in the distance, framed between hills.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01633-HDR.CJYfP591.webp" alt="View of the West Coast from the saddle" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01633-HDR.Cp3olrC4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01633-HDR.qdlN4yOl.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01633-HDR.mryFyZmU.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01633-HDR.CJYfP591.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>View of the West Coast from the saddle</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01636-HDR.DrYt8RLj.webp" alt="Preparing soup with a view" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01636-HDR.cVHXUvPi.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01636-HDR.Do-blT_0.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01636-HDR.RvD7-9bV.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01636-HDR.DrYt8RLj.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Preparing soup with a view</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01653.CfzcCLFq.webp" alt="Stars over Buckland Peaks after dusk" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01653.DED2FADO.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01653.Ip1-87Rm.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01653.B9WPL5h8.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01653.CfzcCLFq.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>Stars over Buckland Peaks after dusk</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01666.BOsxbEJn.webp" alt="Both Magellanic Clouds and a satellite over Paparoa Range" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01666.Dd54KZqe.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01666.CFUDJSf1.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01666.CP8U2kPi.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01666.BOsxbEJn.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Both Magellanic Clouds and a satellite over Paparoa Range</figcaption></figure>
<p>The hut is weathertight and double-glazed, and it retained its heat well into the evening. That said, it lacks a fireplace and modern insulation, leading to a chilly morning after being woken by a mouse in the ceiling.</p>
<p>Judging by the intentions book, it is common to walk up to Buckland Peaks themselves on the second day, before descending in the afternoon. I walked along the ridge to get a better view of the peaks but decided not to venture any further, to allow myself plenty of time to drive back to Christchurch. It’s epic country up here, and somewhere I’d love to come back to one day for more exploration.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01687-Pano.C4tQ8NDy.webp" alt="Buckland Peaks in the morning. The access ridge is visible on the left." class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01687-Pano.yCdv-N_o.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01687-Pano.CFjGs9-r.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01687-Pano.w-yAHcjP.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01687-Pano.C4tQ8NDy.webp 2560w" style="aspect-ratio: 3.902;" data-aspect="3.902" sizes="auto" loading="lazy"><figcaption>Buckland Peaks in the morning. The access ridge is visible on the left.</figcaption></figure>
<p>​<a id="footnote-1-to" href="#footnote-1-from">*</a> The naming I experienced along this track was weirdly inconsistent. The hut is called “Buckland Peaks Hut”. The peaks are called “Buckland Peaks”. But, somehow, the road is called “Bucklands Peak Road” and there are plenty of signs talking about “Buckland Peak Hut” and other permutations. It seems like nobody could remember the correct name—or that there are definitely multiple peaks.</p>]]>
</content:encoded>
</item>
<item>
<title>Building a Vite GPX plugin to enable rich maps with one import</title>
<description>I developed a custom Vite plugin to import GPX files for interactive maps and more</description>
<link>https://albert.nz/vite-plugin-gpx/</link>
<guid isPermaLink="false">/vite-plugin-gpx/</guid>
<pubDate>Tue, 23 May 2023 07:17:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>I love posting adventures on my blog. When I go on a hike, I track the route on my watch and upload the resulting GPX file to Strava. That’s fine, but what I really want is to display the route on my own website alongside photos and a full trip report. In this post, I will walk through how I achieved this on my SvelteKit site using a custom Vite plugin. The concepts are easily applicable to any site using Rollup or Vite, and hopefully are helpful for anybody hoping to deal with GPX file inputs on the web.</p>
<p>Spoiler alert: here’s the end result! By simply adding a GPX file to the <code>routes</code> key in the frontmatter of a Markdown blog post, I can see an interactive map and elevation profile. Here is the example for the <a href="/top-hope-hut">Top Hope Hut</a> adventure.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">title:</span> <span class="hljs-string">Top</span> <span class="hljs-string">Hope</span> <span class="hljs-string">Hut</span>
<span class="hljs-attr">date:</span> <span class="hljs-number">2022-09-24T19:42:00</span><span class="hljs-string">+1200</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">Meandering</span> <span class="hljs-string">river</span> <span class="hljs-string">valley</span> <span class="hljs-string">track</span> <span class="hljs-string">up</span> <span class="hljs-string">Hope</span> <span class="hljs-string">Valley</span> <span class="hljs-string">near</span> <span class="hljs-string">Lewis</span> <span class="hljs-string">Pass</span>
<span class="hljs-attr">tags:</span> [<span class="hljs-string">adventures</span>]
<span class="hljs-attr">routes:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">./Top_Hope_Hope_Kiwi_.gpx</span>
</code></pre>
<figcaption>The frontmatter</figcaption>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot.0X3-xxaL.webp" alt="Screenshot showing the top of the rendered page" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot.CHsiMQZQ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot.0X3-xxaL.webp 768w" style="aspect-ratio: 0.907;" data-aspect="0.907" sizes="auto" loading="lazy"><figcaption>Screenshot showing the top of the rendered page</figcaption></figure>
<p>Let’s jump in and see how this is achieved, or <a href="https://github.com/albertnis/albert.nz/tree/f0cf77fe327c37dff6bcdc2bf21364586650a815/src/plugins/vite-plugin-gpx">check out the code in GitHub</a>.</p>
<h1>Desired features</h1>
<p>The first step for tackling this problem was to identify the features I wanted from this functionality. Here are some features I want to replicate from Strava:</p>
<ul>
<li>View the route on an interactive map</li>
<li>View an elevation profile</li>
<li>See metadata:
<ul>
<li>Elapsed time</li>
<li>Total distance</li>
<li>Gross elevation gain</li>
</ul>
</li>
</ul>
<p>And here are some features I desire beyond Strava’s functionality:</p>
<ul>
<li>Ability for anybody to download original GPX file</li>
<li>View overnight stops along the route (most of my blogged adventures are multi-day hikes and the stops usually correspond to notable huts or campsites)</li>
</ul>
<h1>Setting up the Vite plugin</h1>
<p>Before tackling most of these features, a Vite plugin needs to be set up.</p>
<h2>Create a minimal plugin</h2>
<p>A Vite plugin is simple in that it’s a function that is directly referenced by config. That is great for type-safety and ease of getting started. To create one, I simply created the following stub in a <code>src/plugins/vite-plugin-gpx/index.ts</code> file:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">Plugin</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">gpxPlugin</span>(<span class="hljs-params"></span>): <span class="hljs-title class_">Plugin</span> {
	<span class="hljs-keyword">return</span> {
		<span class="hljs-attr">name</span>: <span class="hljs-string">'vite-plugin-gpx'</span>
	}
}
</code></pre>
<p>Loading this stub plugin is as easy as adding a reference into <code>vite.config.ts</code> or equivalent:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">UserConfig</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>
<span class="hljs-keyword">import</span> { gpxPlugin } <span class="hljs-keyword">from</span> <span class="hljs-string">'./src/plugins/vite-plugin-gpx'</span>

<span class="hljs-keyword">const</span> <span class="hljs-attr">config</span>: <span class="hljs-title class_">UserConfig</span> = {
	<span class="hljs-comment">// ...</span>
	<span class="hljs-attr">plugins</span>: [
		<span class="hljs-comment">// ...</span>
		<span class="hljs-title function_">gpxPlugin</span>()
	]
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> config
</code></pre>
<p>I find it awesome that Vite supports type-safety all the way from the config file. If <code>gpxPlugin</code> didn’t properly implement <code>Plugin</code>, we would see errors in the IDE well before we built any output. This code “works” great but doesn’t achieve anything!</p>
<h2>Enabling importing of GPX files</h2>
<p>Let’s go over what this Vite plugin will let us achieve. Specifically, we want to be able to import the GPX file directly. The return value of the import will be an object with the data we’re interested in.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> routeData <span class="hljs-keyword">from</span> <span class="hljs-string">'./my-route.gpx'</span>

<span class="hljs-variable language_">console</span>.<span class="hljs-title function_">log</span>(routeData)

<span class="hljs-comment">// Object { fileName: "my-route.gpx", ... }</span>
</code></pre>
<p>Initially, we want the output object to just have a <code>fileName</code> property, just to prove that we can get some output from the plugin. We’ll make the output more useful in subsequent steps.</p>
<p>Let’s add a <code>load</code> function to our <code>gpxPlugin</code>.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> { basename } <span class="hljs-keyword">from</span> <span class="hljs-string">'node:path'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">Plugin</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">ViteGpxPluginOutput</span> {
	<span class="hljs-attr">fileName</span>: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">gpxPlugin</span>(<span class="hljs-params"></span>): <span class="hljs-title class_">Plugin</span> {
	<span class="hljs-keyword">return</span> {
		<span class="hljs-attr">name</span>: <span class="hljs-string">'vite-plugin-gpx'</span>,
		<span class="hljs-title function_">load</span>(<span class="hljs-params">id</span>) {
			<span class="hljs-keyword">if</span> (!<span class="hljs-regexp">/\.gpx$/</span>.<span class="hljs-title function_">test</span>(id)) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>

			<span class="hljs-keyword">const</span> srcURL = <span class="hljs-keyword">new</span> <span class="hljs-title function_">URL</span>(id, <span class="hljs-string">'file://'</span>)
			<span class="hljs-keyword">const</span> <span class="hljs-attr">pluginOutput</span>: <span class="hljs-title class_">ViteGpxPluginOutput</span> = {
				<span class="hljs-attr">fileName</span>: <span class="hljs-title function_">basename</span>(srcURL.<span class="hljs-property">pathname</span>)
			}

			<span class="hljs-keyword">return</span> {
				<span class="hljs-attr">code</span>: <span class="hljs-string">`export default <span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify(pluginOutput)}</span>`</span>
			}
		}
	}
}
</code></pre>
<p>The <code>load</code> function is called whenever <code>import</code> is invoked, with the absolute path of the imported file present in the <code>id</code> parameter. In this example, we do a few things:</p>
<ol>
<li>If the file does not end with <code>.gpx</code>, ignore it!</li>
<li>Get the name of the imported file</li>
<li>Wrap the name with an object matching the output interface defined as <code>ViteGpxPluginOutput</code></li>
<li>Return a plugin output object. The <code>code</code> key is set to a string containing JavaScript code which exports the output object, in string form.</li>
</ol>
<p>The last part is particularly wild to me. <strong>We are effectively transforming our GPX file into a JavaScript file</strong>! Importing a GPX file will “trick” the compiler into thinking it has imported whatever JavaScript file contents this function returns.</p>
<h2>Enabling type-safe imports</h2>
<p>Running the import example above should work well now. But there’s a problem:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-error.Cs_oMzvg.webp" alt="Screenshot of Visual Studio Code showing import errors" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-error.DLjs3uzI.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-error.Cs_oMzvg.webp 768w" style="aspect-ratio: 3.429;" data-aspect="3.429" sizes="auto" loading="lazy"><figcaption>Screenshot of Visual Studio Code showing import errors</figcaption></figure>
<p>The imports fail with the error <code>Cannot find module ‘./my-route.gpx’ or its corresponding type declarations.</code> even though the file does exist. What’s possibly even worse is that the return type of the import is <code>any</code>, as demonstrated by <code>asyncRouteData</code> having an <code>any</code> type.</p>
<p>This can be easily fixed with a TypeScript module declaration. In my case, I put it in <code>src/plugins/vite-plugin-gpx/ambient.d.ts</code>. The main thing is to ensure the file matches an <code>include</code> rule from <code>tsconfig.json</code>.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">declare</span> <span class="hljs-variable language_">module</span> <span class="hljs-string">'*.gpx'</span> {
	<span class="hljs-keyword">const</span> <span class="hljs-attr">output</span>: <span class="hljs-keyword">import</span>(<span class="hljs-string">'./index'</span>).<span class="hljs-property">ViteGpxPluginOutput</span>
	<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> output
}
</code></pre>
<p>With this file in place, import errors are gone and we see beautiful, beautiful autocomplete on the imported variable! Even import paths will autocomplete <code>.gpx</code> files now.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-import-success.DCWFEccn.webp" alt="Sweet IntelliSense" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-import-success.De9_TuDa.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-import-success.DCWFEccn.webp 768w" style="aspect-ratio: 2.927;" data-aspect="2.927" sizes="auto" loading="lazy"><figcaption>Sweet IntelliSense</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-import-suggest.Bt4mQsxe.webp" alt="Visual Studio Code recognises GPX files as import-worthy now" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-import-suggest.3UvNhUWS.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-import-suggest.Bt4mQsxe.webp 768w" style="aspect-ratio: 2.697;" data-aspect="2.697" sizes="auto" loading="lazy"><figcaption>Visual Studio Code recognises GPX files as import-worthy now</figcaption></figure>
<p>Now the plugin is fully set up and type-safe. By adding to our <code>pluginOutput</code>, we can return more data about the file. Time to incorporate the first feature.</p>
<h2>Enabling downloading of the original file</h2>
<p>The first and most trivial feature for the plugin is to enable the download of the original GPX file. Frustratingly, this feature is withheld by Strava; I personally love the idea of letting people download GPX files as it can help with navigation for their own travels.</p>
<p>The main aim here is to instruct Vite to either render or serve the file then obtain the hosted path for the file. I was inspired by Jonas Kruckenberg’s work on the <a href="https://github.com/JonasKruckenberg/imagetools"><code>imagetools</code></a> library and implemented a simplified version of imagetools’ <a href="https://github.com/JonasKruckenberg/imagetools/blob/82a810b78c3d5b7b44a1dfa0518c397c76f23522/packages/vite/src/index.ts">Vite plugin definition</a>. I’ll place the code below and then walk through it.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> { basename } <span class="hljs-keyword">from</span> <span class="hljs-string">'node:path'</span>
<span class="hljs-keyword">import</span> { readFile } <span class="hljs-keyword">from</span> <span class="hljs-string">'node:fs/promises'</span>
<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">Plugin</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'vite'</span>

<span class="hljs-keyword">export</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">ViteGpxPluginOutput</span> {
	<span class="hljs-attr">filePath</span>: <span class="hljs-built_in">string</span>
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">gpxPlugin</span>(<span class="hljs-params"></span>): <span class="hljs-title class_">Plugin</span> {
	<span class="hljs-keyword">let</span> <span class="hljs-attr">basePath</span>: <span class="hljs-built_in">string</span>
	<span class="hljs-keyword">const</span> gpxPaths = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Map</span>()
	<span class="hljs-keyword">return</span> {
		<span class="hljs-attr">name</span>: <span class="hljs-string">'vite-plugin-gpx'</span>,
		<span class="hljs-title function_">configResolved</span>(<span class="hljs-params">cfg</span>) {
			<span class="hljs-keyword">const</span> viteConfig = cfg
			basePath = (viteConfig.<span class="hljs-property">base</span>?.<span class="hljs-title function_">replace</span>(<span class="hljs-regexp">/\/$/</span>, <span class="hljs-string">''</span>) || <span class="hljs-string">''</span>) + <span class="hljs-string">'/@gpx/'</span>
		},
		<span class="hljs-keyword">async</span> <span class="hljs-title function_">load</span>(<span class="hljs-params">id</span>) {
			<span class="hljs-keyword">if</span> (!<span class="hljs-regexp">/\.gpx$/</span>.<span class="hljs-title function_">test</span>(id)) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>

			<span class="hljs-keyword">const</span> srcURL = <span class="hljs-keyword">new</span> <span class="hljs-title function_">URL</span>(id, <span class="hljs-string">'file://'</span>)
			<span class="hljs-keyword">const</span> fileContents = <span class="hljs-keyword">await</span> <span class="hljs-title function_">readFile</span>(<span class="hljs-built_in">decodeURIComponent</span>(srcURL.<span class="hljs-property">pathname</span>))

			gpxPaths.<span class="hljs-title function_">set</span>(<span class="hljs-title function_">basename</span>(srcURL.<span class="hljs-property">pathname</span>), id)

			<span class="hljs-keyword">let</span> <span class="hljs-attr">src</span>: <span class="hljs-built_in">string</span>
			<span class="hljs-keyword">if</span> (!<span class="hljs-variable language_">this</span>.<span class="hljs-property">meta</span>.<span class="hljs-property">watchMode</span>) {
				<span class="hljs-keyword">const</span> handle = <span class="hljs-variable language_">this</span>.<span class="hljs-title function_">emitFile</span>({
					<span class="hljs-attr">name</span>: <span class="hljs-title function_">basename</span>(srcURL.<span class="hljs-property">pathname</span>),
					<span class="hljs-attr">source</span>: fileContents,
					<span class="hljs-attr">type</span>: <span class="hljs-string">'asset'</span>
				})

				src = <span class="hljs-string">`__VITE_ASSET__<span class="hljs-subst">${handle}</span>__`</span>
			} <span class="hljs-keyword">else</span> {
				src = basePath + <span class="hljs-title function_">basename</span>(srcURL.<span class="hljs-property">pathname</span>)
			}

			<span class="hljs-keyword">const</span> <span class="hljs-attr">pluginOutput</span>: <span class="hljs-title class_">ViteGpxPluginOutput</span> = {
				<span class="hljs-attr">filePath</span>: src
			}

			<span class="hljs-keyword">return</span> {
				<span class="hljs-attr">code</span>: <span class="hljs-string">`export default <span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify(pluginOutput)}</span>`</span>
			}
		},
		<span class="hljs-title function_">configureServer</span>(<span class="hljs-params">server</span>) {
			server.<span class="hljs-property">middlewares</span>.<span class="hljs-title function_">use</span>(<span class="hljs-keyword">async</span> (req, res, next) => {
				<span class="hljs-keyword">if</span> (req.<span class="hljs-property">url</span>?.<span class="hljs-title function_">startsWith</span>(basePath)) {
					<span class="hljs-keyword">const</span> [, id] = req.<span class="hljs-property">url</span>.<span class="hljs-title function_">split</span>(basePath)

					<span class="hljs-keyword">const</span> gpxPath = gpxPaths.<span class="hljs-title function_">get</span>(id)

					<span class="hljs-keyword">if</span> (!gpxPath)
						<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Error</span>(
							<span class="hljs-string">`gpx cannot find GPX file with id "<span class="hljs-subst">${id}</span>" this is likely an internal error. Files are <span class="hljs-subst">${<span class="hljs-built_in">JSON</span>.stringify(
								gpxPaths
							)}</span>`</span>
						)

					res.<span class="hljs-title function_">setHeader</span>(<span class="hljs-string">'Content-Type'</span>, <span class="hljs-string">'application/gpx+xml'</span>)
					res.<span class="hljs-title function_">setHeader</span>(<span class="hljs-string">'Cache-Control'</span>, <span class="hljs-string">'max-age=360000'</span>)
					<span class="hljs-keyword">const</span> buffer = <span class="hljs-keyword">await</span> <span class="hljs-title function_">readFile</span>(gpxPath)
					<span class="hljs-keyword">const</span> contents = buffer.<span class="hljs-title function_">toString</span>()
					<span class="hljs-keyword">return</span> res.<span class="hljs-title function_">end</span>(contents)
				}

				<span class="hljs-title function_">next</span>()
			})
		}
	}
}
</code></pre>
<p>There’s a lot going on here. But at its core you should still able to see the <code>return</code> statement within the <code>load</code> function. Here’s what is happening:</p>
<ol>
<li>When Vite is started and “sees” the plugin, <code>configResolved</code> is called. The <code>cfg</code> argument contains a bunch of configuration and context about Vite. In this case, we use it to get base path information and store it for later use.</li>
<li>Whenever we import a file, the <code>load</code> function is called, just like before. It has a bit more functionality now, though. First, it reads the file using Node’s <code>readFile</code>. It then needs to ensure this GPX data is hosted somewhere before returning the path at which it is hosted. This is achieved quite differently depending on whether Vite is running with a dev server or as a build command:
<ul>
<li>In build mode, the file contents are registered with Vite using the <code>this.emitFile</code> function. The output path looks like <code>__VITE_ASSET_{uniqueid}__</code>. Behind the scenes, Vite will replace this magic path with the path at which it has stored the emitted file. (I can’t find much documentation on this process—reach out if you know more).</li>
<li>In dev server mode, a path is generated for each loaded gpx file with the form <code>/@gpx/{filename}</code>. The plugin also registers each loaded gpx file in the <code>gpxPaths</code> map. To ensure the files actually exist in this location, the <code>configureServer</code> function is used to register a sever middleware which intercepts requests to <code>/@gpx/*</code> paths. If the request is for a loaded GPX file, the file contents are read and returned.</li>
</ul>
</li>
</ol>
<p>Now, when a GPX file is imported, there is easy access to the file path—perfect for a download link or button.</p>
<h1>Extracting data from the GPX file</h1>
<p>It’s time to delve into the file contents and extract some data. It might be time to refactor the output into its own function:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">export</span> <span class="hljs-keyword">function</span> <span class="hljs-title function_">gpxPlugin</span>(<span class="hljs-params"></span>): <span class="hljs-title class_">Plugin</span> {
	<span class="hljs-comment">// ...</span>
	<span class="hljs-keyword">return</span> {
		<span class="hljs-comment">// ...</span>
		<span class="hljs-keyword">async</span> <span class="hljs-title function_">load</span>(<span class="hljs-params">id</span>) {
			<span class="hljs-comment">// ...</span>
			<span class="hljs-keyword">const</span> pluginOutput = <span class="hljs-title function_">gpxDataToOutput</span>(fileContents.<span class="hljs-title function_">toString</span>(), src)
			<span class="hljs-comment">// ...</span>
		}
	}
	<span class="hljs-comment">// ...</span>
}

<span class="hljs-keyword">const</span> gpxDataToOutput = (<span class="hljs-attr">gpxData</span>: <span class="hljs-built_in">string</span>, <span class="hljs-attr">path</span>: <span class="hljs-built_in">string</span>): <span class="hljs-function"><span class="hljs-params">ViteGpxPluginOutput</span> =></span> {
	<span class="hljs-keyword">return</span> {
		<span class="hljs-attr">filePath</span>: path
	}
}
</code></pre>
<p>The rest of the code in this section will be within the scope of this <code>gpxDataToOutput</code> function, unless specified otherwise.</p>
<h2>Understanding GPX</h2>
<p>A GPX file is an XML file with a schema. The most useful thing to do with it in this plugin is to parse it into an GeoJSON format. This makes it easier to work with in JavaScript. Besides, most mapping libraries will accept GeoJSON as input. The <a href="https://www.npmjs.com/package/@xmldom/xmldom"><code>@xmldom/xmldom</code></a> and <a href="https://www.npmjs.com/package/@mapbox/togeojson"><code>@mapbox/togeojson</code></a> libraries work well here.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> geojson <span class="hljs-keyword">from</span> <span class="hljs-string">'@mapbox/togeojson'</span>
<span class="hljs-keyword">import</span> { <span class="hljs-title class_">DOMParser</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'@xmldom/xmldom'</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">const</span> gpxXmlDocument = <span class="hljs-keyword">new</span> <span class="hljs-title class_">DOMParser</span>().<span class="hljs-title function_">parseFromString</span>(gpxData)
<span class="hljs-keyword">const</span> gj = geojson.<span class="hljs-title function_">gpx</span>(gpxXmlDocument)
</code></pre>
<p>It’s worth noting that the <code>@mapbox/togeojson</code> library, while reliable, is quite old and has no type definitions. Thankfully, the GeoJSON output from the library is externally standardised so we can simply use a separate library for typing and apply a module declaration. I achieved this by creating a <code>src/@mapbox/ambient.d.ts</code> file with the following declaration which uses the <code>geojson</code> library to provide typing:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">declare</span> <span class="hljs-variable language_">module</span> <span class="hljs-string">'@mapbox/togeojson'</span> {
	<span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">GeoJSON</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'geojson'</span>

	<span class="hljs-keyword">interface</span> <span class="hljs-title class_">MapboxToGeoJson</span> {
		<span class="hljs-attr">gpx</span>: <span class="hljs-function">(<span class="hljs-params">data: Document</span>) =></span> <span class="hljs-title class_">GeoJSON</span>
	}

	<span class="hljs-keyword">const</span> <span class="hljs-attr">mapboxToGeoJson</span>: <span class="hljs-title class_">MapboxToGeoJson</span>

	<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> mapboxToGeoJson
}
</code></pre>
<p>Once we have converted to a GeoJSON value stored in <code>gj</code>, we can evaluate the contents:</p>
<pre><code class="hljs language-js">{
	<span class="hljs-string">"type"</span>: <span class="hljs-string">"FeatureCollection"</span>,
	<span class="hljs-string">"features"</span>: [
		{
			<span class="hljs-string">"type"</span>: <span class="hljs-string">"Feature"</span>,
			<span class="hljs-string">"properties"</span>: {},
			<span class="hljs-string">"geometry"</span>: {
				<span class="hljs-string">"type"</span>: <span class="hljs-string">"LineString"</span>,
				<span class="hljs-string">"coordinates"</span>: [
					[<span class="hljs-number">171.96891</span>, -<span class="hljs-number">41.855557</span>, <span class="hljs-number">653</span>],
					[<span class="hljs-number">171.968706</span>, -<span class="hljs-number">41.855599</span>, <span class="hljs-number">653</span>],
					[<span class="hljs-number">171.968748</span>, -<span class="hljs-number">41.855728</span>, <span class="hljs-number">653</span>],
					[<span class="hljs-number">171.968787</span>, -<span class="hljs-number">41.855936</span>, <span class="hljs-number">654</span>], <span class="hljs-comment">// ...</span>
				]
			}
		}
	]
}
</code></pre>
<p>That’s a GeoJSON! Each co-ordinate is displayed as <code>[latitude, longitude, elevation]</code>.</p>
<h2>Data compression</h2>
<p>It might be tempting to surface our path and elevation data at this point by simply returning:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">return</span> {
	<span class="hljs-attr">filePath</span>: path,
	<span class="hljs-attr">geoJson</span>: gj
}
</code></pre>
<p>Before doing that, consider file size! Everything that we return here will have to travel across the wire at some point, and GPX files can be huge—often tens of megabytes for long trips. That includes position, heart rate and timestamp data taken every second or two. Without intervention, most of this data will end up in our GeoJSON file as metadata. Compressed server responses will help to some extent, but we have a prime candidate for optimisation here. Let’s see how this can be achieved for path and elevation data.</p>
<h2>Path data</h2>
<p>First, the plugin calculates the target number of datapoints for the route. The input count is raised to the power of 0.7 (a number I settled on after much experimentation) to obtain the desired number of coordinates in the output data. This means that longer trips are compressed more aggressively. A short route with 1,000 points will be downsampled to about 125 points, while a 30,000-point adventure will result in around 1,361 points in the output.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">const</span> coordinatesDataCount = feature.<span class="hljs-property">geometry</span>.<span class="hljs-property">coordinates</span>.<span class="hljs-property">length</span>

<span class="hljs-keyword">const</span> targetPathDataCount = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">pow</span>(coordinatesDataCount, <span class="hljs-number">0.7</span>)
<span class="hljs-keyword">const</span> pathSamplingPeriod = <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">floor</span>(coordinatesDataCount / targetPathDataCount)
<span class="hljs-keyword">const</span> downSampledCoordinates = <span class="hljs-title function_">downSampleArray</span>(feature.<span class="hljs-property">geometry</span>.<span class="hljs-property">coordinates</span>, pathSamplingPeriod)
</code></pre>
<p>Downsampling occurs by only taking values spaced <code>pathSamplingPeriod</code> apart:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">const</span> downSampleArray = &#x3C;T>(<span class="hljs-attr">input</span>: T[], <span class="hljs-attr">period</span>: <span class="hljs-built_in">number</span>): T[] => {
	<span class="hljs-keyword">if</span> (period &#x3C; <span class="hljs-number">1</span> || period % <span class="hljs-number">1</span> != <span class="hljs-number">0</span>) {
		<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">TypeError</span>(<span class="hljs-string">'Period must be an integer greater than or equal to 1'</span>)
	}

	<span class="hljs-keyword">if</span> (period === <span class="hljs-number">1</span>) {
		<span class="hljs-comment">// Return a copy of input</span>
		<span class="hljs-keyword">return</span> [...input]
	}

	<span class="hljs-keyword">const</span> <span class="hljs-attr">output</span>: T[] = []

	<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &#x3C; input.<span class="hljs-property">length</span>; i += period) {
		output.<span class="hljs-title function_">push</span>(input[i])
	}

	<span class="hljs-keyword">return</span> output
}
</code></pre>
<p>After downsampling the coordinates, we can exclude elevation data from the GeoJSON as this will be handled separately. Removing this data will decrease the filesize further by a third.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> <span class="hljs-keyword">type</span> { <span class="hljs-title class_">Geometry</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">'geojson'</span>

<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">const</span> <span class="hljs-attr">downSampledGeometry</span>: <span class="hljs-title class_">Geometry</span> = {
	...feature.<span class="hljs-property">geometry</span>,
	<span class="hljs-attr">coordinates</span>: downSampledCoordinates.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">c</span>) =></span> [c[<span class="hljs-number">0</span>], c[<span class="hljs-number">1</span>]])
}
</code></pre>
<h2>Elevation data</h2>
<p>I used a similar method for elevation data, but with a different approach to obtaining a count target. Here I want the target to be at least 900, as this is approximately the number of pixels my elevation graph takes up at its maximum width.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">const</span> targetElevationDataCount = <span class="hljs-number">900</span>
<span class="hljs-keyword">const</span> elevationSamplingPeriod =
	coordinatesDataCount &#x3C; targetElevationDataCount
		? <span class="hljs-number">1</span> <span class="hljs-comment">// Use all the datapoints if there are fewer</span>
		: <span class="hljs-title class_">Math</span>.<span class="hljs-title function_">floor</span>(coordinatesDataCount / targetElevationDataCount) <span class="hljs-comment">// Downsample</span>
<span class="hljs-keyword">const</span> downSampledGeometryForElevation = <span class="hljs-title function_">downSampleArray</span>(
	feature.<span class="hljs-property">geometry</span>.<span class="hljs-property">coordinates</span>,
	elevationSamplingPeriod
)
</code></pre>
<p>The final step is to extract just a list of elevations from the GeoJSON:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">const</span> downSampledElevations = downSampledGeometryForElevation.<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">g</span>) =></span> g[<span class="hljs-number">2</span>])
</code></pre>
<p>We could downsample elevation at the same rate as position data but generally speaking there are fewer elevation points required, so this saves space overall.</p>
<h2>Bringing it together</h2>
<p>With some changes to the ouput interface, we can set the ouput of <code>gpxDataToOutput</code> function to look a bit like the following:</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">return</span> {
	<span class="hljs-attr">elevationData</span>: {
		downSampledElevations,
		<span class="hljs-attr">samplingPeriod</span>: elevationSamplingPeriod
	},
	<span class="hljs-attr">pathData</span>: {
		<span class="hljs-attr">geoJson</span>: <span class="hljs-title function_">buildGeoJSONFromGeometry</span>(downSampledGeometry),
		<span class="hljs-attr">samplingPeriod</span>: pathSamplingPeriod
	},
	<span class="hljs-attr">metadata</span>: {
		<span class="hljs-attr">gpxFilePath</span>: path
	}
}
</code></pre>
<p><code>buildGeoJSONFromGeometry</code> simply reconstructs a minimal GeoJSON to wrap geometry. In this case it’s used to strip out any of the extra bloat that may have been in the original GeoJSON.</p>
<p>It’s coming together! This is enough information to show path data and basic elevation data.</p>
<h1>Computing metadata</h1>
<h2>Distance</h2>
<p>There are two purposes for computing the distance of the path:</p>
<ol>
<li>Display the total distance of the route</li>
<li>Display an accurate elevation graph</li>
</ol>
<p>The first item is probably obvious, but the interaction between distance and elevation graph is particularly interesting. You see, an elevation graph typically has <em>distance</em> as the x-axis, not <em>data point index</em>. Currently we only have a list of elevations and no distance data for correlation.</p>
<p>To provide this distance data, a list of cumulative distances is required. This enables us to map an index to a distance.</p>
<pre><code class="hljs language-ts"><span class="hljs-comment">/**
 * Compute cumulative distances along a path
 * <span class="hljs-doctag">@param</span> input List of coordinate arrays representing a path
 * <span class="hljs-doctag">@returns</span> Array of distances. Each distance is the distance along the path from the start of the path to that point.
 */</span>
<span class="hljs-keyword">const</span> computeCumulutiveDistanceMetres = (<span class="hljs-attr">input</span>: <span class="hljs-title class_">Position</span>[]): <span class="hljs-built_in">number</span>[] => {
	<span class="hljs-keyword">let</span> distanceMetres = <span class="hljs-number">0</span>
	<span class="hljs-keyword">const</span> distancesMetres = [<span class="hljs-number">0</span>]

	<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &#x3C; input.<span class="hljs-property">length</span>; i++) {
		distanceMetres += <span class="hljs-title function_">haversineDistanceMetres</span>(
			input[i] <span class="hljs-keyword">as</span> [<span class="hljs-built_in">number</span>, <span class="hljs-built_in">number</span>],
			input[i - <span class="hljs-number">1</span>] <span class="hljs-keyword">as</span> [<span class="hljs-built_in">number</span>, <span class="hljs-built_in">number</span>]
		)
		distancesMetres.<span class="hljs-title function_">push</span>(distanceMetres)
	}

	<span class="hljs-keyword">return</span> distancesMetres
}
</code></pre>
<p>The distance between each successive pair of co-ordinates is computed using the <a href="https://en.wikipedia.org/wiki/Haversine_formula">Haversine formula</a> then added to the previous distance and appended to the array.</p>
<p>The total distance is simple the last item in the cumulative distance array.</p>
<h2>Elevation gain</h2>
<p>Gross elevation gain is a useful metric for assessing how hilly a route was. Conceptually it’s the total cumulative upwards travel. In practice, it can be a bit nuanced to compute given that elevation data can be imprecise and noisy at times. I implemented a fairly simple version which includes some downsampling and a threshold for elevation gain. I don’t think it’s perfect, but it gives a fairly good estimate.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">const</span> computeCumulativeElevationGainMetres = (<span class="hljs-attr">input</span>: <span class="hljs-title class_">Position</span>[]): <span class="hljs-function"><span class="hljs-params">number</span> =></span> {
	<span class="hljs-keyword">let</span> vertGain = <span class="hljs-number">0</span>
	<span class="hljs-keyword">const</span> gainThreshold = <span class="hljs-number">2</span>
	<span class="hljs-keyword">let</span> currentBaseline = input[<span class="hljs-number">0</span>][<span class="hljs-number">2</span>]

	<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">0</span>; i &#x3C; input.<span class="hljs-property">length</span>; i += <span class="hljs-number">3</span>) {
		<span class="hljs-keyword">const</span> alt = input[i][<span class="hljs-number">2</span>]
		<span class="hljs-keyword">const</span> climb = alt - currentBaseline

		<span class="hljs-keyword">if</span> (climb >= gainThreshold) {
			<span class="hljs-comment">// We have gone up an appreciable amount</span>
			currentBaseline = alt
			vertGain += climb
		} <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (climb &#x3C;= -gainThreshold) {
			<span class="hljs-comment">// We have gone down an appreciable amount</span>
			currentBaseline = alt
		}
	}

	<span class="hljs-keyword">return</span> vertGain
}
</code></pre>
<h2>Time</h2>
<p>There are two key times to capture from an activity: duration and start time.</p>
<p>Start time is easy. Most of the time the GeoJSON feature will contain a “time” which can be used. Just in case, I fall back to the first coordinate time.</p>
<p>Times in GeoJSON are represented in ISO format, so liberal use of <code>parseISO</code> from <code>date-fns</code> is appropriate here.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> { parseISO } <span class="hljs-keyword">from</span> <span class="hljs-string">'date-fns'</span>

<span class="hljs-comment">/**
 * Calculate the start time from a feature
 * <span class="hljs-doctag">@param</span> input Feature to use for calculating start time
 * <span class="hljs-doctag">@returns</span> Date object representing the feature's `time` property, or the first `coordTime` if there is no such property
 */</span>
<span class="hljs-keyword">const</span> computeStartTime = (<span class="hljs-attr">input</span>: <span class="hljs-title class_">Feature</span>): <span class="hljs-title class_">Date</span> | <span class="hljs-function"><span class="hljs-params">null</span> =></span> {
	<span class="hljs-keyword">const</span> startTime = input.<span class="hljs-property">properties</span>?.<span class="hljs-property">time</span>

	<span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> startTime === <span class="hljs-string">'string'</span>) {
		<span class="hljs-keyword">return</span> <span class="hljs-title function_">parseISO</span>(startTime)
	}

	<span class="hljs-keyword">const</span> times = input.<span class="hljs-property">properties</span>?.<span class="hljs-property">coordTimes</span>

	<span class="hljs-keyword">if</span> (<span class="hljs-title class_">Array</span>.<span class="hljs-title function_">isArray</span>(times)) {
		<span class="hljs-keyword">return</span> <span class="hljs-title function_">parseISO</span>(times[<span class="hljs-number">0</span>])
	}

	<span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
}
</code></pre>
<p>Elapsed duration can be calculated by taking the difference between the first and last coordinate time. The <code>date-fns</code> library has some very helpful methods and types to assist with this. The timestamps appear in the <code>properties</code> key of the GeoJSON. I’m unsure whether this is standard or arbitrary behaviour in <code>@mapbox/togeojson</code>.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> { intervalToDuration, parseISO } <span class="hljs-keyword">from</span> <span class="hljs-string">'date-fns'</span>

<span class="hljs-comment">/**
 * Calculate the duration from the `coordTime`s stored in a feature's property
 * <span class="hljs-doctag">@param</span> input Feature to use for calculating duration
 * <span class="hljs-doctag">@returns</span> Duration between first and last `coordTime` in the feature
 */</span>
<span class="hljs-keyword">const</span> computeDuration = (<span class="hljs-attr">input</span>: <span class="hljs-title class_">Feature</span>): <span class="hljs-title class_">Duration</span> | <span class="hljs-function"><span class="hljs-params">null</span> =></span> {
	<span class="hljs-keyword">const</span> times = input.<span class="hljs-property">properties</span>?.<span class="hljs-property">coordTimes</span>
	<span class="hljs-keyword">if</span> (times == <span class="hljs-literal">null</span> || !<span class="hljs-title class_">Array</span>.<span class="hljs-title function_">isArray</span>(times)) {
		<span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
	}

	<span class="hljs-keyword">const</span> start = <span class="hljs-title function_">parseISO</span>(times[<span class="hljs-number">0</span>])
	<span class="hljs-keyword">const</span> end = <span class="hljs-title function_">parseISO</span>(times[times.<span class="hljs-property">length</span> - <span class="hljs-number">1</span>])

	<span class="hljs-keyword">return</span> <span class="hljs-title function_">intervalToDuration</span>({ start, end })
}
</code></pre>
<h2>Breaks</h2>
<p>Many of the trips I track are multi-day hikes (or “tramps” as they’re lovingly known by New Zealanders). It is nice to know where the huts and campsites were along the route. Manually entering the locations of these spots is probably the most foolproof way of doing this, but I opted for an alternative, lazy, method. I simply extract the locations where there are no GPX entries for over four hours. This tends to be a good indicator of where I paused my tracking device overnight—and a great proxy for hut and campsite locations.</p>
<pre><code class="hljs language-ts"><span class="hljs-keyword">import</span> { differenceInHours, parseISO } <span class="hljs-keyword">from</span> <span class="hljs-string">'date-fns'</span>

<span class="hljs-comment">/**
 * Compute the locations of breaks exceeding four hours within a feature
 * <span class="hljs-doctag">@param</span> input Feature with coordTimes data used to evaluate breaks
 * <span class="hljs-doctag">@returns</span> Array of indexes where each index corresponds to a coordinate after which there were no coordinates recorded for at least four hours
 */</span>
<span class="hljs-keyword">const</span> computeBreakIndices = (<span class="hljs-attr">input</span>: <span class="hljs-title class_">Feature</span>): <span class="hljs-built_in">number</span>[] => {
	<span class="hljs-comment">// Self-contained sampling rate to accelerate calcs</span>
	<span class="hljs-comment">// Can ususally set this high unless there are lots of stops</span>
	<span class="hljs-keyword">const</span> samplingRate = <span class="hljs-number">30</span>

	<span class="hljs-keyword">const</span> times = input.<span class="hljs-property">properties</span>?.<span class="hljs-property">coordTimes</span>

	<span class="hljs-keyword">if</span> (!<span class="hljs-title class_">Array</span>.<span class="hljs-title function_">isArray</span>(times)) {
		<span class="hljs-keyword">return</span> []
	}

	<span class="hljs-comment">// Get the date objects for a subsampled set of timestamps</span>
	<span class="hljs-keyword">const</span> parsedTimes = times.<span class="hljs-title function_">filter</span>(<span class="hljs-function">(<span class="hljs-params">_, i</span>) =></span> i % samplingRate === <span class="hljs-number">0</span>).<span class="hljs-title function_">map</span>(<span class="hljs-function">(<span class="hljs-params">t</span>) =></span> <span class="hljs-title function_">parseISO</span>(t))

	<span class="hljs-keyword">const</span> indices = []
	<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> i = <span class="hljs-number">1</span>; i &#x3C; parsedTimes.<span class="hljs-property">length</span>; i++) {
		<span class="hljs-keyword">if</span> (<span class="hljs-title function_">differenceInHours</span>(parsedTimes[i], parsedTimes[i - <span class="hljs-number">1</span>]) > <span class="hljs-number">4</span>) {
			indices.<span class="hljs-title function_">push</span>((i - <span class="hljs-number">1</span>) * samplingRate)
		}
	}
	<span class="hljs-keyword">return</span> indices
}
</code></pre>
<h1>Displaying data</h1>
<p>Now that all the data and metadata has been computed, it’s time to put this to use! Let’s import a gpx file like before and see what we’re dealing with:</p>
<pre><code class="hljs language-js"><span class="hljs-keyword">import</span> geoData <span class="hljs-keyword">from</span> <span class="hljs-string">'../top-hope-hut/Top_Hope_Hope_Kiwi_.gpx'</span>
</code></pre>
<p>Inspecting or logging <code>geoData</code> shows that has the following value:</p>
<pre><code class="hljs language-js">{
	<span class="hljs-string">"elevationData"</span>: {
		<span class="hljs-string">"downSampledElevations"</span>: [
			<span class="hljs-number">602</span>,
			<span class="hljs-number">602</span>,
			<span class="hljs-number">602</span>,
			<span class="hljs-number">603</span> <span class="hljs-comment">// ... [907 items total]</span>
		],
		<span class="hljs-string">"elevationGainMetres"</span>: <span class="hljs-number">1073</span>,
		<span class="hljs-string">"samplingPeriod"</span>: <span class="hljs-number">54</span>
	},
	<span class="hljs-string">"metadata"</span>: {
		<span class="hljs-string">"gpxFilePath"</span>: <span class="hljs-string">"/@gpx/Top_Hope_Hope_Kiwi_.gpx"</span>,
		<span class="hljs-string">"breakIndices"</span>: [<span class="hljs-number">23940</span>, <span class="hljs-number">34560</span>],
		<span class="hljs-string">"duration"</span>: {
			<span class="hljs-string">"years"</span>: <span class="hljs-number">0</span>,
			<span class="hljs-string">"months"</span>: <span class="hljs-number">0</span>,
			<span class="hljs-string">"days"</span>: <span class="hljs-number">2</span>,
			<span class="hljs-string">"hours"</span>: <span class="hljs-number">3</span>,
			<span class="hljs-string">"minutes"</span>: <span class="hljs-number">21</span>,
			<span class="hljs-string">"seconds"</span>: <span class="hljs-number">45</span>
		},
		<span class="hljs-string">"startTime"</span>: <span class="hljs-string">"2022-09-23T21:34:06.000Z"</span>,
		<span class="hljs-string">"distanceMetres"</span>: <span class="hljs-number">57605.837060918944</span>
	},
	<span class="hljs-string">"pathData"</span>: {
		<span class="hljs-string">"geoJson"</span>: {
			<span class="hljs-string">"type"</span>: <span class="hljs-string">"FeatureCollection"</span>,
			<span class="hljs-string">"features"</span>: [
				{
					<span class="hljs-string">"type"</span>: <span class="hljs-string">"Feature"</span>,
					<span class="hljs-string">"properties"</span>: {},
					<span class="hljs-string">"geometry"</span>: {
						<span class="hljs-string">"type"</span>: <span class="hljs-string">"LineString"</span>,
						<span class="hljs-string">"coordinates"</span>: [
							[<span class="hljs-number">172.382772</span>, -<span class="hljs-number">42.584167</span>],
							[<span class="hljs-number">172.38245</span>, -<span class="hljs-number">42.58434</span>],
							[<span class="hljs-number">172.382203</span>, -<span class="hljs-number">42.584338</span>],
							[<span class="hljs-number">172.381928</span>, -<span class="hljs-number">42.584328</span>],
							[<span class="hljs-number">172.381573</span>, -<span class="hljs-number">42.584365</span>] <span class="hljs-comment">// ... [1958 items total]</span>
						]
					}
				}
			]
		},
		<span class="hljs-string">"cumulativeDistancesMetres"</span>: [
			<span class="hljs-number">0</span>,
			<span class="hljs-number">36.508624548783956</span>,
			<span class="hljs-number">61.228052816957145</span>,
			<span class="hljs-number">88.76666712805523</span>,
			<span class="hljs-number">124.48251406266579</span> <span class="hljs-comment">// ... [1958 items total]</span>
		],
		<span class="hljs-string">"samplingPeriod"</span>: <span class="hljs-number">25</span>
	}
}
</code></pre>
<p>We can see all the geo information is stored there as we expect! Elevation data has a sparser sampling rate than point data and both sampling rates are present. Plus, the object gives us a <code>gpxFilePath</code> which can be used to download the original file. Here the file starts with <code>/@gpx/</code> because the example output is from a local server.</p>
<p>Displaying the file is now a matter of writing some code that takes this data and renders something with it. It could be plain old JavaScript, a React component, or just about anything. For my Sveltekit blog, I created a <code>&#x3C;MapGroup></code> Svelte component that renders an elevation graph using SVG and a map using MapBox. The details are a bit beyond this article, but suffice it to say that I can use the component with the following code snippet:</p>
<pre><code class="hljs language-svelte">&#x3C;script>
	import MapGroup from '$lib/components/MapGroup.svelte'
	import geoData from '../top-hope-hut/Top_Hope_Hope_Kiwi_.gpx'
&#x3C;/script>

&#x3C;figure>
	&#x3C;MapGroup geo={geoData} />
	&#x3C;figcaption>Example of GPX-derived data being loaded via map component&#x3C;/figcaption>
&#x3C;/figure>
</code></pre>
<p>For my blog, I check the blog post for the <code>routes</code> key, then import every GPX file reference and insert a component similar to the above. <a href="https://github.com/albertnis/albert.nz/blob/f0cf77fe327c37dff6bcdc2bf21364586650a815/src/routes/(main)/%5Bslug%5D/%2Bpage.ts">See how it works on GitHub</a>.</p>
<h2>Graceful degradation in the absence of JavaScript</h2>
<p>An early version of my site’s GPX integration used lazy loading for the entire map group, consisting of elevation graph, metadata display and map. But it’s always worth considering how much can be rendered without JavaScript present in the browser at all. In my case, I could easily pre-render or server-side-render the SVG elevation graph, as well as the metadata block.</p>
<pre><code class="hljs language-svelte">&#x3C;script lang="ts">
	import type { ViteGpxPluginOutput } from '../../plugins/vite-plugin-gpx/types'
	import type { ComponentType, SvelteComponentTyped } from 'svelte'
	import { onMount } from 'svelte'
	import ElevationGraph from '$lib/components/ElevationGraph.svelte'
	import MapMetadata from '$lib/components/MapMetadata.svelte'
	import MapLoading from '$lib/components/MapLoading.svelte'

	export let geo: ViteGpxPluginOutput
	let hoveredIndex: number | undefined = undefined
	let mapComponent: ComponentType&#x3C;SvelteComponentTyped> | undefined

	onMount(async () => {
		mapComponent = (await import('$lib/components/Map.svelte')).default
	})
&#x3C;/script>

&#x3C;svelte:head>
	&#x3C;noscript>
		&#x3C;style>
			.maploading {
				display: none;
			}
		&#x3C;/style>
	&#x3C;/noscript>
&#x3C;/svelte:head>

&#x3C;ElevationGraph {...elevationGraphPropsGoHere} />

&#x3C;MapMetadata {...metadataPropsGoHere} />

{#if mapComponent == null}
	&#x3C;div class="maploading">
		&#x3C;MapLoading />
	&#x3C;/div>
{:else}
	&#x3C;svelte:component this={mapComponent} {...otherMapPropsGoHere} />
{/if}
</code></pre>
<p>Here, the map component itself is lazy-loaded (and will be hidden if there is no JavaScript on the client) but elevation graph and metadata will be present regardless of whether the code is running on server or client. The next level would be to fall back to Mapbox’s image API to render an image instead of an interactive map in the absence of JavaScript!</p>
<h1>Conclusion</h1>
<p>In building a Vite GPX plugin, I set out to move a small slice of Strava to my personal site. A Vite plugin achieves this in a fairly elegant way, abstracting the messy work away from application code which simply imports it. With creative downsampling it is possible to minimise file sizes while retaining the full GPX file for download.</p>
<p>The next step might be to make some improvements to the performance of the loader, given that it can take 2-3 minutes to build the entire site. There are also plenty of options for enabling configuration which I will explore if I ever publish <code>vite-plugin-gpx</code> as a standalone library.</p>
<p>I hope you have enjoyed this walkthrough of building a Vite GPX plugin! You can find <a href="https://github.com/albertnis/albert.nz/tree/f0cf77fe327c37dff6bcdc2bf21364586650a815/src/plugins/vite-plugin-gpx">the full source code for the plugin on GitHub</a>.</p>]]>
</content:encoded>
</item>
<item>
<title>Configuring LD2410 mmWave sensors in the browser</title>
<description>I built LD2410 Configurator, a web-based tool for configuring these tiny yet powerful motion sensors</description>
<link>https://albert.nz/ld2410-configurator/</link>
<guid isPermaLink="false">/ld2410-configurator/</guid>
<pubDate>Tue, 16 May 2023 00:12:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>I recently stumbled upon the LD2410 mmWave sensor manufactured by HiLink. These little devices use radar to measure distance and detect motion—and they’re a hell of a lot more sensitive than the more traditional PIR sensors, being able to detect the breathing motion of a stationary human from several metres away. The DFRobot SEN0395 is a similar mmWave sensor and has gained some popularity recently. I’m hoping the LD2410 can deliver most of the rock-solid reliability I’ve experienced from the SEN0395 but at a tenth of the cost and a third of the size.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ld2410c.Cg0NYrDj.webp" alt="LD2410C sensor connected to serial adapter" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ld2410c.C5Fv_ORZ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ld2410c.eJwBxGJT.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ld2410c.BRseRK3q.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ld2410c.Cg0NYrDj.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>LD2410C sensor connected to serial adapter</figcaption></figure>
<p>The LD2410 uses a serial bus for configuration and for detailed output from the sensor. The configuration in particular is quite powerful here: with the right tweaking of sensitivity values, the sensor can be set to only detect motion within certain distance ranges. For example, you could configure it to trigger when something is between 3 and 4.5 metres of the sensor.</p>
<p>So how can this configuration be achieved? HiLink provides a first-party tool but it only runs on Windows and is closed source. That’s why I have recently developed and released an alternative!</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot.CZkbY8Mr.webp" alt="Screenshot of LD2410 Configurator" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot.C4-BaN7A.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot.CZkbY8Mr.webp 768w" style="aspect-ratio: 1.524;" data-aspect="1.524" sizes="auto" loading="lazy"><figcaption>Screenshot of LD2410 Configurator</figcaption></figure>
<p>LD2410 Configurator is my first foray into the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Serial_API">WebSerial API</a>. It was very interesting to be able to dabble with serial functionality right within the web browser. Using SvelteKit and Cloudflare Pages, it was quite easy to build and deploy this simple app. The most interesting learning was how to use custom Svelte stores for basic reactive features; I used one to implement an event-based serial data store that other components could subscribe to.</p>
<p>Check out the LD2410 Configurator at <a href="https://ld2410.albert.nz">ld2410.albert.nz</a> or view the <a href="https://github.com/albertnis/ld2410-configurator">source code</a>!</p>]]>
</content:encoded>
</item>
<item>
<title>Stewart Island / Rakiura North West Circuit</title>
<description>Remote 10-day hike around the coast of New Zealand's third island</description>
<link>https://albert.nz/north-west-circuit/</link>
<guid isPermaLink="false">/north-west-circuit/</guid>
<pubDate>Thu, 16 Mar 2023 22:32:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Stewart Island’s North West Circuit track provides a walk like no other. Even a speedy hiker will take 7 days to complete this loop. Those with plenty of spare time may combine it with the Southern Circuit for a 12-16 day doozy. In the 10 days we took to complete the one circuit, we trekked through undulating damp bush, traversed remote beaches and experienced all types of weather within the span of a single day. It’s a rewarding challenge that for many people will push the limits of their self-sufficiency.</p>
<h2>Flora and fauna</h2>
<p>The birdlife is plentiful in this part of the country. Hours of walking were set to the sounds of clusters of bellbirds calling in unison, kakariki chattering (though rarely seen) and the cheeps of tomtits and riflemen. Kiwi were around, too; I saw no fewer than seven during the trip. They’re audible early and late in the day making horrendously shrill cries to each other, but we saw a few alongside the track in daylight, too. They’re faster, bigger, and more oblivious than I ever expected, often just about colliding with a walker before turning around and zooming off in the other direction.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01405.C-PsJj9T.webp" alt="Stewart Island kiwi / tokoeka on the track shortly before the Ruggedy Mountain saddle crossing" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01405.DzkRwVxy.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01405.B09NP1km.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01405.DA7GjS1R.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01405.C-PsJj9T.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Stewart Island kiwi / tokoeka on the track shortly before the Ruggedy Mountain saddle crossing</figcaption></figure>
<p>Robins were numerous in the sheltered low-lying stretch between Mason Bay and Freshwater River.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01532.CJ8XDTDB.webp" alt="Friendly and vocal Stewart Island Robin near Freshwater Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01532.KnwL8p5l.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01532.L_ipf17s.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01532.D9ppTWDq.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01532.CJ8XDTDB.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Friendly and vocal Stewart Island Robin near Freshwater Hut</figcaption></figure>
<p>There’s a varied range of plants around the place. Unfortunately the huge deer population is successful at continually suppressing new broadleaf growth, so there are large stretches of forest consisting of ferns, mature broadleaf trees, and nothing in between. I suppose hunters enjoy the good sight lines afforded by this distorted ecosystem.</p>
<p>The forest is incredibly damp from the large amount of precipitation received on Stewart Island. Windfall and dead trees rot quickly and give rise to new plant life.</p>
<figure>
  <img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01299.D4B1fC9O.webp" alt="Native orchid Earina autumnalis was often seen growing draped over windfall and rotting trees. It smells strongly like cheap air freshener and we often caught a whiff of it before we saw it." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01299.B22KKsf-.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01299.D4WM6RY3.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01299.C5nDd1Im.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01299.D4B1fC9O.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
  <figcaption>Native orchid <i>Earina autumnalis</i> was often seen growing draped over windfall and rotting trees. It smells strongly like cheap air freshener and we often caught a whiff of it before we saw it.</figcaption>
</figure>
<p>We saw some bright orange and blue fungi, too.</p>
<figure>
  <img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01422.DuIpg2KQ.webp" alt="Tiny snail, possibly a carnivorous Powelliphanta variety, on a large orange fungus" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01422.BL4Skjre.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01422.8VAw0CRl.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01422.DdvQGbjy.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01422.DuIpg2KQ.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
  <figcaption>Tiny snail, possibly a carnivorous <i>Powelliphanta</i> variety, on a large orange fungus</figcaption>
</figure>
<h2>Landscape</h2>
<p>The vast and pristine beaches did a great job of making me feel very small. It’s a humbling wilderness.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01492.DYfns58G.webp" alt="Three hikers on Mason beach. The prominent rock becomes impassable at high tide; the high tide route does not look fun. This photo was taken about two hours after a 2.7m high tide. Green marram grass is present on the foredunes on the right." class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01492.Blg0Vb18.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01492.CNWDxVsw.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01492.BXLOe0q_.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01492.DYfns58G.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Three hikers on Mason beach. The prominent rock becomes impassable at high tide; the high tide route does not look fun. This photo was taken about two hours after a 2.7m high tide. Green marram grass is present on the foredunes on the right.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01335.BCgTiojK.webp" alt="Solo hiker crossing Smoky Beach" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01335.B03xIaVr.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01335.TcbmTBy5.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01335.BGhF2ReY.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01335.BCgTiojK.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Solo hiker crossing Smoky Beach</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01385-Pano.BFrRkXoR.webp" alt="Looking west to East Ruggedy Beach and the Rugged Islands with the Ruggedy Mountains visible in the distance along with approaching rain" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01385-Pano.BjpufqXu.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01385-Pano.Bsi9FeT1.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01385-Pano.B0JGyhgU.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01385-Pano.BFrRkXoR.webp 2560w" style="aspect-ratio: 3.288;" data-aspect="3.288" sizes="auto" loading="lazy"><figcaption>Looking west to East Ruggedy Beach and the Rugged Islands with the Ruggedy Mountains visible in the distance along with approaching rain</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01391-Pano.DgL9xSXN.webp" alt="West Ruggedy Beach with Codfish Island / Whenua Hou visible in the background" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01391-Pano.DH62O5W2.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01391-Pano.DTsbFAyf.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01391-Pano.JAo4OWie.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01391-Pano.DgL9xSXN.webp 2560w" style="aspect-ratio: 2.341;" data-aspect="2.341" sizes="auto" loading="lazy"><figcaption>West Ruggedy Beach with Codfish Island / Whenua Hou visible in the background</figcaption></figure>
<p>Possibly my favourite terrain feature was the incredibly tall sand dune adjacent to Big Hellfire Hut. I was not prepared for a hut at 200m elevation to have a beach on its doorstep.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01434-HDR.CLtpwSoL.webp" alt="Sunset from the top of the Hellfire sand dune with Codfish Island / Whenua Hou visible in the background" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01434-HDR.BsyAR1kf.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01434-HDR.SoLGsH-J.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01434-HDR.JFwZ3LZD.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01434-HDR.CLtpwSoL.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>Sunset from the top of the Hellfire sand dune with Codfish Island / Whenua Hou visible in the background</figcaption></figure>
<h1>What to expect if you’re doing the North West Circuit</h1>
<p>Here’s a bit more information if you’d like to learn more about doing this tramp.</p>
<h2>North West Circuit itinerary</h2>
<p>Counter-clockwise is the standard direction for the North West Circuit but you can totally do it the other way, too.</p>
<p>The standard track description includes all 10 huts on the circuit, which would take 11 days. If you’re staying at all huts, the amount of walking per day will vary from 3 hours to 7 hours. Note that the first and last (Port William and North Arm huts) are Great Walk huts on the Rakiura Track, meaning they book out early and cost more money than the $5 standard huts on the rest of the track. It’s common to skip these huts to avoid the bustle and make it to the cheaper huts more quickly. If you do this, be aware that you will need to walk from Freshwater Hut to Oban in one day, which has a DOC-provided time of 10-11 hours. I’d recommend getting a water taxi for this leg! See the transport section below. Some experienced hikers we encountered also skipped Yankee River Hut, Long Harry Hut or Mason Bay Hut to reduce the amount of days.</p>
<p>We took 10 days and skipped the Great Walk huts only. This meant we had an additional rest day, which we spent at Mason Bay Hut.</p>
<h2>Transport to and on the island</h2>
<p>Transport to Rakiura / Stewart Island is via boat or plane. The boat is operated by <a href="https://www.realnz.com/en/experiences/ferry-services/stewart-island-ferry-services/">realnz</a> and costs $99 each way from Bluff. I’d agree with their website that it is quite an experience; the Foveaux Strait lives up to its name and even on a calm day those prone to seasickness (like myself) will be reaching for the sick bag during the hour-long crossing. Get on the boat mentally prepared and with an empty stomach—or catch the plane. The <a href="https://www.stewartislandflights.co.nz/">flight</a> is $240 return from Invercargill. It takes less than 30 minutes but is limited to 15kg baggage allowance.</p>
<p>Water taxi services are available within the island. For $70 per person, they will shuttle you between Bungaree Hut and Oban, or Freshwater Hut and Oban. We used a taxi to skip our last day from Freshwater Hut to Oban—the taxi arrives at the landing adjacent to the hut at high tide then slaloms down Freshwater River at impressive speed, reaching Oban in about 45 minutes. You need at least three people to charter a boat (or get lucky and book onto an already-chartered boat) and you’ll need to book ahead. Freshwater Hut has no cellphone reception and you’re not guaranteed any reception from Rocky Mountain, either. There are two operators who sail to Freshwater Hut: <a href="https://rakiura.nz/">Rakiura Adventure</a> and <a href="https://www.rakiuracharters.co.nz/">Rakiura Charters</a>.</p>
<h2>Huts</h2>
<p>Apart from the Great Walk huts, all huts are standard. You can use $5 hut tickets or a hut pass. There is alternatively a North West Circuit Pass which I am told is cheaper than individual tickets. There’s little information online about this pass but you should enquire at the DOC visitor centre.</p>
<p>The standard huts are quite generously sized. Most have 10-12 bunk beds with the exceptions being Mason Bay Hut (20 bunks) and Freshwater Hut (16 bunks). They all have at least two sleeping areas, with the exception of Long Harry Hut and Big Hellfire Hut which are single rooms with a “marae-style” sleeping area.</p>
<p>The northern huts (Bungaree, Christmas Village, Yankee River and Long Harry) have cell reception in or near to the hut.</p>
<p>Long Harry hut is dramatically positioned above the beach with a stunning view to the north. It would have been my favourite if it wasn’t for the sandfly leakage—the window mesh is too coarse and sandflies just crawl through.</p>
<p>Many of the huts have great rivers nearby to bathe in. Being somewhat clean makes a big difference on such a long hike.</p>
<h2>People</h2>
<p>The circuit was far busier than I expected. We walked it in March as a group of three and didn’t have a single hut to ourselves. We heard from some circuit hikers a day behind us that they experienced full huts and were forced to hut skip as a result. If you’re doing this trip in summer or autumn and are looking forward to 10 days without seeing another soul, adjust your expectations!</p>
<p>A few of the circuit walkers we encountered had just finished Te Araroa southbound. Early autumn may be a common time for such walkers as they would have started TA in late spring and taken about four months to complete it.</p>
<p>Solo and duo circuit hikers are common because it’s hard to organise a group for a 10-day tramp on an island. That said, the hut books did have reports of groups up to six people in size. You might find that you form an ad-hoc group with people you encounter along the way, if they’re pleasant company!</p>
<p>Some of the huts (at least Bungaree, Yankee River and East Ruggedy) can be used by hunters as the corresponding hunting blocks do not have dedicated hunters’ huts. They’re only allowed to use half the bunks in these huts but in some cases will unpack their stuff throughout the hut, leading to some tension with trampers. The friendlier hunters will offer hot water, good conversation and maybe even meat.</p>
<p>Mason Bay Hut is commonly used by wealthy groups who charter a helicopter in, stay a few nights in the $5 hut, then walk the three hours to Freshwater to catch a water taxi out. Needless to say, this can cause tension with circuit walkers who arrive after seven days of walking and dehydrated meals, looking for a bunk and perhaps some solidarity. It was certainly jarring for me to walk into a party of six drinking wine and gorging on delicious-looking hamburgers.</p>
<h2>Tidal considerations</h2>
<p>There are several parts of the trip that are difficult in high tide. There may be other parts we missed out of sheer luck, but here are the two we noticed:</p>
<ol>
<li>The section east of East Ruggedy Beach. There’s no high tide track here. This one nearly caught us out but the tide wasn’t quite high enough to cut us off.</li>
<li>The rock in the Mason Bay section. It’s described on the DOC webpage. There is a high tide route but it looks difficult—DOC says it adds an hour of walking.</li>
</ol>
<p>For Mason Bay tides, the guidance from the DOC visitor centre was to subtract two hours from the official LINZ Oban tide predictions. For Mason Bay, aim to avoid this time by two hours either side.</p>
<h2>Track condition</h2>
<p>The track was well marked—much better than I expected. When we did the track there were many sections between Long Harry Hut and Mason Beach that were overgrown and had significant windfall. DOC told us there hasn’t been much maintenance in the last couple of years due to COVID-19 but that they were expecting to get maintenance teams in there over winter 2023.</p>
<h2>Side trips</h2>
<h3>Mt Anglem / Hananui</h3>
<p>We skipped the side trip to Mt Anglem / Hananui from Christmas Village Hut. According to our DOC informant, it’s a stunning view on a clear day but generally you can’t tell whether it’s a nice day until you’re 80% of the way up the mountain. It wasn’t worth the gamble for us.</p>
<h3>East Ruggedy Beach lookout</h3>
<p>There’s a lookout signposted just before the descent to East Ruggedy Beach. It’s only 50m from the main track and well worth it.</p>
<h3>Hellfire tops</h3>
<p>A local staying at Big Hellfire Hut informed us there are some great views to be had along the ridge south of the hut. He suggested departing the track by a few metres to reach the various false peaks along the ridge. We found the views from the track were good enough!</p>
<h3>Big Sandhill</h3>
<p>Definitely worth the 90-minute return trip from Mason Bay Hut. There are nice views from up there and it’s easy to squeeze into a day even if you’re walking on to Freshwater Hut. Playing on the dunes is fun, too.</p>
<h3>Rocky Mountain</h3>
<p>2-3 hours return from Freshwater Hut, the top of Rocky Mountain has a grand view back towards Mason Bay, and even towards Oban—or so we were told. As we climbed the hill, the weather closed in and it was cold, wet and windy at the top. Probably only worth doing in good weather. Some say you can get cell reception at the top (theoretically great for booking or rescheduling a water taxi from the hut) but on this stormy day we had neither the patience nor the line of sight to find it.</p>
<h2>Weather</h2>
<p>The weather was all over the place. Most days would go from cloud to rain to sun and back to rain again—in the span of an hour. We had warm sunny times and cold blustery southerlies. Forecasts were good enough to predict storm systems passing through, but don’t rely on them for day-to-day weather. Pack for all conditions!</p>]]>
</content:encoded>
</item>
<item>
<title>Mt Curtis Hut</title>
<description>Modern bushline hut with a hard-to-find trail near Inangahua, West Coast</description>
<link>https://albert.nz/mt-curtis-hut/</link>
<guid isPermaLink="false">/mt-curtis-hut/</guid>
<pubDate>Sun, 22 Jan 2023 02:58:15 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>During an idle evening of scrolling topo maps I discovered a hut situated up Mt Curtis on the West Coast. Mt Curtis Hut is publicly accessible but is not a DOC hut; rather, it is maintained by a community group. This group rebuilt the hut in 2012 with some of the funding coming from DOC. There is remarkably little information about this hut on the internet and the track to the hut is unmapped near private land with no obvious trailhead or signage. As I attempted to interpret the terse trip reports that are available, I became increasingly intrigued by this well-kept secret. I had to go! So I planned an overnighter in this rugged part of the country.</p>
<p>What follows is hopefully a more complete description of getting to and from Mt Curtis Hut based on our experience. It is up-to-date as at time of writing.</p>
<h2>How to get to Mt Curtis Hut</h2>
<p>Access is from the bridge which crosses Dee Creek, just East of Inangahua. There’s a decent-sized layby on the West end of the bridge for parking. Cross to the Eastern side of the bridge and descend to the river on the true right. You’ll have to jump a fence to get down to the river from the road. The next step is to follow Dee Creek for a couple of kilometres—there are no markings in this section and travel is through farmland along a paper road which follows the river.</p>
<p>Eventually the river plunges into forest. Continue following the river until you reach point 1515277E 5364463N on the TL bank.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230121_214620031.DwiaedTs.webp" alt="The wasp bait station at point 1515277E 5364463N. Turn right here to look for the track." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230121_214620031.BZTqPCoR.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230121_214620031.S8usHYMa.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230121_214620031.DOuyyAvi.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230121_214620031.DwiaedTs.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The wasp bait station at point 1515277E 5364463N. Turn right here to look for the track.</figcaption></figure>
<p>At this point there exists a marked trail but you need to head directly away from the river to find it. Look for orange and pink ribbons visible from the parallel side creek a few metres to the south of the main river. If you struggle to find it, consider continuing 100-300m further up the main river then bush-bashing south to intercept the track while it is close to the river.</p>
<p>The track runs vaguely parallel to the river at first, tracking the top of the bluffs marked on Topo50. It then moves to follow the spur past Pt 489 and all the way up to the hut, becoming increasingly well-marked along the way. We encountered a wasp-infested section of beech about halfway along the trail—watch out for that if you’re doing this tramp in the summer months.</p>
<h2>How we got to Mt Curtis Hut</h2>
<p>This, of course, was not the way we went up. Only after coming back down do I have the privilege of describing the actual track to you.</p>
<p>With such concealed marking and little information, we completely missed the turnoff for the track, heading up the river for another few hours. Dee Creek develops gorge sections higher up, leading to beautiful sights, slow travel and steep bush-bashing to bypass unnavigable parts.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_230917376.mH4JyT9o.webp" alt="Taking in the stunning West Coast jungle as we follow a side stream for a bit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_230917376.DBIlTF6e.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_230917376.BT-aytqJ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_230917376.C3obIt9c.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_230917376.mH4JyT9o.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Taking in the stunning West Coast jungle as we follow a side stream for a bit</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_234600242.C9SUvPVo.webp" alt="One of the first Gorge sections, which was easily wadeable. They got worse. A lot worse." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_234600242.BrfJ4cUU.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_234600242.sXYFlcpY.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_234600242.DkYiKECR.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20230120_234600242.C9SUvPVo.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>One of the first Gorge sections, which was easily wadeable. They got worse. A lot worse.</figcaption></figure>
<p>Eventually the river rose out of a gorge to meet the bank on the TL. We took this opportunity to depart the river to head in the direction of the hut. Fully prepared to bash the remaining two kilometres to the hut, we hedged our bets by detouring to the spur—a decision inspired by recalling a vague report of a marked track up one of the spurs in the area. After pushing through scrub for a while, we reached the spur and found an orange track marker! The going got steep shortly afterwards and it was amazing to experience the shift from mentally-exhausting bush-bashing to physically-exhausting climbing.</p>
<h2>The hut itself</h2>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01104.CJv5IZVz.webp" alt="Mt Curtis Hut on a claggy morning" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01104.CcyNE3FI.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01104.Bt02lBp0.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01104.ISrOIGaa.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01104.CJv5IZVz.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Mt Curtis Hut on a claggy morning</figcaption></figure>
<p>Even before we could collapse on the hut deck, we were greeted by a weka scurrying through the adjacent flax plantation. (An entire family of curious weka meandered past while I was on the toilet in the morning, much to my surprise.)</p>
<p>Mt Curtis Hut is a modern-but-basic four-bunker with a spare fifth mattress. The hut book indicates about a dozen visits per year. There’s a fairly well-stocked pantry here with a few pots and pans. It’s double-glazed and we certainly had no need for the fire on this clear summer night. The hut sits just below the bushline and the view is largely obscured by trees. Just five minutes’ walk up the hill (quite doable in sandals) is a small knoll which has unobstructed views to the West. Mt Curtis summit itself is also easily accessible but might be a more appropriate side trip for a two-night stay.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01090-HDR-Pano.eLfmgf-B.webp" alt="Sunset over the Paparoa Range" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01090-HDR-Pano.BAqYaCop.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01090-HDR-Pano.B_LTeIFs.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01090-HDR-Pano.D5M4RWB5.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01090-HDR-Pano.eLfmgf-B.webp 2560w" style="aspect-ratio: 3.780;" data-aspect="3.780" sizes="auto" loading="lazy"><figcaption>Sunset over the Paparoa Range</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01098.NGMfvDZC.webp" alt="Milky Way over Mt Wynn further down the Victoria Range" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01098.ZtuiuCn6.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01098.DwbYB78Z.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01098.Bt2-hSNy.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01098.NGMfvDZC.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>Milky Way over Mt Wynn further down the Victoria Range</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01099.CTvWswRW.webp" alt="The hut at night with light pollution visible from Westport" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01099.CfX_TyIX.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01099.CeA4bKIZ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01099.d505-1Nu.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01099.CTvWswRW.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>The hut at night with light pollution visible from Westport</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01096.CtZy6AOj.webp" alt="Paparoa Range after dusk" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01096.DutgcAYe.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01096.sw45ez5I.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01096.BVG5i569.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC01096.CtZy6AOj.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Paparoa Range after dusk</figcaption></figure>
<h2>The return</h2>
<p>Descending the marked track was straightforward enough experience (despite a wasp sting) and one we had been looking forward to since the previous day. We expected to have missed an obvious turnoff but once we got to the bottom of the track the markers thinned then stopped well out of sight of Dee Creek. In the end, then, we didn’t have any reason to beat ourselves up for missing the track on the way up.</p>
<p>Our misguided trip in to the hut took 6½ hours. Out, it had taken just 3½.</p>
<h2>Paying for Mt Curtis Hut</h2>
<p>This is not a DOC hut so hut passes and tickets do not apply. Payments towards the hut can instead be made to the following account which was displayed on a sign in the hut.</p>
<pre><code class="hljs language-txt">Mount Curtis Community Hut
38-9012-0653480-00
</code></pre>
<p>Also listed in the hut are details to contact some of the people involved in its maintenance. Contact me if you would like those.</p>]]>
</content:encoded>
</item>
<item>
<title>Edwards, Otehake, and Hawdon Valleys</title>
<description>These three valleys in Arthur's Pass are connected by Tarn Col and straddle the border between Canterbury and the West Coast</description>
<link>https://albert.nz/edwards-otehake-hawdon/</link>
<guid isPermaLink="false">/edwards-otehake-hawdon/</guid>
<pubDate>Fri, 11 Nov 2022 04:14:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>The Edwards-Hawdon route is a popular Arthur’s Pass outing. We decided to add a side trip to the secluded Otehake Hut for our overnight adventure.</p>
<h1>Day 1</h1>
<p>After an early start and a disappointingly closed Sheffield pie shop, we got walking from Greyneys Shelter. It was an easy going three-and-a-bit hours up to Edwards Hut, where we stopped for a sit-down and some lunch.</p>
<p>Beyond the hut the trail continues all the way to Taruahuna Pass, with the only addition being overgrown scrub and multiple small river crossings. The increasingly open surroundings really show off the surrounding mountains.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00823.DM2Bspfs.webp" alt="Attractive scrub in the upper Edwards Valley beyond Edwards Hut" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00823.NkCBEuPC.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00823.C-gcJ8Fv.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00823.8hW1u6BP.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00823.DM2Bspfs.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Attractive scrub in the upper Edwards Valley beyond Edwards Hut</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00832.BhuUCu36.webp" alt="Rockfall near Taruahuna Pass" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00832.iIyRjpa9.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00832.DakAEsRx.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00832.i-ZoY5V9.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00832.BhuUCu36.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Rockfall near Taruahuna Pass</figcaption></figure>
<p>At the pass, travel converts to rock-hopping then sliding down loose banks towards Otehake River.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00837-Pano.Buwan15B.webp" alt="Looking down Otehake West Branch Valley from Taruahuna Saddle" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00837-Pano.DF4l5-Tc.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00837-Pano.BWI-COLP.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00837-Pano.BV6_vHvr.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00837-Pano.Buwan15B.webp 2560w" style="aspect-ratio: 3.721;" data-aspect="3.721" sizes="auto" loading="lazy"><figcaption>Looking down Otehake West Branch Valley from Taruahuna Saddle</figcaption></figure>
<p>The track markers peel off to Tarn Col; to get down the valley, we picked a route that brought us out on the true right of the river as the terrain levels out at the bottom of the rockfall. From this point we could see a track marker leading us to the left. The marked track crosses all the way to the left of the valley, sidling through forest above the bluffs before descending at the main river junction. This is the route we took. Apparently it is also possible to simply follow the river between the bluffs when levels are low.</p>
<p>The track on the West Coast side of Taruahuna Pass is starkly different to the Canterbury side. The forest is more dense and markers harder to spot—there is a mix between orange DOC markers and white Permolat markers. Despite the slower travel we found our way across the river and on to Otehake Hut, where we prepared a pleasant cheeseboard before dinner.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_175434484.lVxlGZTY.webp" alt="Getting ready to leave Otehake Hut. The hut is fairly standard and only receives a handful of parties each year. The group before us had been there about a month prior." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_175434484.DOrv_fdu.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_175434484.CxB1STKc.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_175434484.BjfqgUed.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_175434484.lVxlGZTY.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Getting ready to leave Otehake Hut. The hut is fairly standard and only receives a handful of parties each year. The group before us had been there about a month prior.</figcaption></figure>
<h1>Day 2</h1>
<p>Allowing 10 hours for our exit day through Hawdon Valley, we got up nice and early. Backtracking to Taruahuna saddle was slow and steady; I found the steep climb up to the saddle easier than the descent. At the saddle we were greeted by a delightful sight: a pair of rock wren hopping between the rocks and calling to each other.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00862._WHhvXDb.webp" alt="Rock wren at Taruahuna Saddle" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00862.CPhcBFDX.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00862._WHhvXDb.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Rock wren at Taruahuna Saddle</figcaption></figure>
<p>We turned our attention to the Tarn Col ascent. The face is an imposing wall of rock and in the clouded visibility we largely missed the poles marking the ascent route through vegetation, instead deciding to climb a scree face. As a scree novice, this was definitely out of my comfort zone—though possibly good practice for times when such an ascent would be necessary. For clarity, here’s a summary of the various routes which I devised after the trip:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00833.CtXHZiCg.webp" alt="Approximate summary of the proper Tarn Col routes--including our mistaken route. The upwards route is poled." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00833.DYd2o-l-.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00833.Be4p4Tbm.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00833.BlV1R2MG.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00833.CtXHZiCg.webp 2560w" style="aspect-ratio: 2.341;" data-aspect="2.341" sizes="auto" loading="lazy"><figcaption>Approximate summary of the proper Tarn Col routes--including our mistaken route. The upwards route is poled.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_215949664.BKZFAdrK.webp" alt="View (or lack thereof) from Tarn Col" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_215949664.NBgXNlcz.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_215949664.Cb8G9VA9.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_215949664.Cu42nFjp.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_215949664.BKZFAdrK.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>View (or lack thereof) from Tarn Col</figcaption></figure>
<p>During breaks in the cloud, we glimpsed sights of the otherwordly tops up here. The rocky tussock atmosphere reminded me a lot of Routeburn, a track I also completed in damp grey weather.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00867.LwK2ld1U.webp" alt="The tarn at the top of Tarn Col (is it called &#x22;Tarn Col Tarn&#x22;? &#x22;Col Tarn&#x22;?)" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00867.DT3cuLQ7.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00867.BMF9CnKd.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00867.BeLzj0gM.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00867.LwK2ld1U.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>The tarn at the top of Tarn Col (is it called "Tarn Col Tarn"? "Col Tarn"?)</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_222251654.D7zbisZR.webp" alt="Avalanche debris snow cover over the stream draining from Tarn Col Tarn" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_222251654.DOK_LGEQ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_222251654.DoMA3QMS.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_222251654.BgiRnuQp.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221111_222251654.D7zbisZR.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Avalanche debris snow cover over the stream draining from Tarn Col Tarn</figcaption></figure>
<p>The route is poled all the way down to the intersection with Otehake River East Branch. Follow that river upstream until the marked turn off towards Walker Pass.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00868.Bg7wZzKs.webp" alt="Descending the stream gorge from Tarn Col, with Pt 1742 looming large in the dramatic cloud" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00868.BDrnwS6-.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00868.Ch993Qo0.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00868.dpPiDq3y.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00868.Bg7wZzKs.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Descending the stream gorge from Tarn Col, with Pt 1742 looming large in the dramatic cloud</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00869.B-NZhXBq.webp" alt="Tarn near Walker Pass with Blackball Ridge visible in the skyline" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00869.B9dPt78G.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00869.DYZsP716.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00869.CjbH1EDS.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC00869.B-NZhXBq.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Tarn near Walker Pass with Blackball Ridge visible in the skyline</figcaption></figure>
<p>With surprisingly fast travel through Walker Pass and out Hawdon Valley, we made it out safely after a 9-hour day.</p>]]>
</content:encoded>
</item>
<item>
<title>Albert Burn Hut</title>
<description>Remote hut reachable by boating across Lake Wānaka or inland via the Matukituki Valley</description>
<link>https://albert.nz/albert-burn-hut-track/</link>
<guid isPermaLink="false">/albert-burn-hut-track/</guid>
<pubDate>Sat, 15 Oct 2022 02:14:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Albert Burn Hut is the only hut in the entire country named Albert. This seemed like an excellent opportunity to make it a birthday destination. There are a couple of ways to approach the valley but the easiest is surely to paddle just over 2km across the lake from Boundary Creek Campsite then walk the remaining 10km. If you’re going out this way, make sure to plan the trip for calm weather.</p>
<h1>Day 1</h1>
<p>With packrafts inflated, we paddled across the northern tip of the lake. It was an absolute bluebird day and a lot of fun on the water. We made it across in about 30 minutes.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221014_220239057.BCsbWmwB.webp" alt="Rafting across Lake Wānaka on a bluebird Saturday" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221014_220239057.RPf06ugQ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221014_220239057.BKR-kWqO.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221014_220239057.lG669gy1.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221014_220239057.BCsbWmwB.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Rafting across Lake Wānaka on a bluebird Saturday</figcaption></figure>
<p>One of the benefits of this trip is that the paddling leg is adjacent to the carpark. This means no need to carry rafts! Kayaks would work even better here. We stashed the raft amidst a manuka forest just north of the Albert Burn inlet.</p>
<p>Walking up the track was a fairly straightforward affair. We zigzagged across the water following the river outlet’s twists and turns, eventually picking up the marked track on the true left of the river just downstream of the narrow gorge section. The track climbs up the bank a wee bit before dropping down for a crossing in the middle of the gorge. On this clear day the river was waist deep here, so we locked arms and made it across safely. This is definitely not a crossing to be attempted at rainy times.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_002430395.abo2VKBV.webp" alt="One of the Gorge sections of the Albert Burn" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_002430395.BiLnwNm7.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_002430395.Dc13b91J.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_002430395.Bb44bxoo.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_002430395.abo2VKBV.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>One of the Gorge sections of the Albert Burn</figcaption></figure>
<p>Continuing along the track on the other side of the river, it became increasingly clear that this track is not well-frequented. Pieces of fallen beech debris were scattered everywhere and we shoved many branches out of the way as we walked. After about a kilometre, the track opens up to a beautiful wide river valley. We spotted numerous waterfalls coming down from the steep rocky hills lining the valley as we meandered up the river.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_013719717.Dr2Ylaqv.webp" alt="Stunning riverbed travel up the Albert Burn beyond the Gorge" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_013719717.Daiv0Bmk.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_013719717.WPzXssjz.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_013719717.DbViW0zZ.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_013719717.Dr2Ylaqv.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Stunning riverbed travel up the Albert Burn beyond the Gorge</figcaption></figure>
<p>The hut sits above a large clearing, a bit further up a hill than I thought. It’s a surprisingly nice hut for one which receives about a dozen parties each year: built in 1995 and having two levels of four bunks each, with lovely evening sun and only minor sandfly leakage.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_193643539.DhALqXuE.webp" alt="Albert Burn Hut on the hillside" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_193643539.B9sOCgPx.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_193643539.Mth8jdGJ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_193643539.n5nuRcTq.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_193643539.DhALqXuE.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Albert Burn Hut on the hillside</figcaption></figure>
<p>The valley is really wide at this point and we took the opportunity to check out the dramatic waterfall on the opposite side of the valley. It made for a sublime spot for some swimming, frisbee throwing, and refilling of water bottles. This was absolutely a moment to remember.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_194304090.CdyfTkoq.webp" alt="Beautiful 146m waterfall cascading down the cliffs opposite the hut. The basin at the base forms a delightful swimming hole." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_194304090.BteHlBgu.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_194304090.3M_9i3DQ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_194304090.CFSowInm.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_194304090.CdyfTkoq.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Beautiful 146m waterfall cascading down the cliffs opposite the hut. The basin at the base forms a delightful swimming hole.</figcaption></figure>
<h1>Day 2</h1>
<p>With some showers overnight, I was worried that the river would be swollen. It was reassuring to see many of the waterfalls, presumably fed by snow melt, were actually lower than the previous evening. We crossed the river without incident.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_202619754.CmmDaloO.webp" alt="One of the many steep, rocky waterfalls draining into the Albert Burn" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_202619754.ChVE1jMY.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_202619754.DNwH8mS8.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_202619754.CfvPrDqi.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20221015_202619754.CmmDaloO.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>One of the many steep, rocky waterfalls draining into the Albert Burn</figcaption></figure>
<p>What became somewhat more troubling was the ever-increasing wind as we approach the lake shore. A stiff northerly—stronger than forecast—was whipping down from the Makarora Valley. With minimal delay, we loaded up with sugar and carbs, inflated the rafts, then set out back to the campsite. It took more than an hour to get back in the inefficient and highly blowable little packrafts—over twice the time it took in the other direction. With tired arms, but thankful to have made it back safely, we regrouped at the carpark and prepared for the trip home.</p>]]>
</content:encoded>
</item>
<item>
<title>Top Hope Hut</title>
<description>Meandering river valley track up Hope Valley near Lewis Pass</description>
<link>https://albert.nz/top-hope-hut/</link>
<guid isPermaLink="false">/top-hope-hut/</guid>
<pubDate>Sat, 24 Sep 2022 07:42:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Top Hope Hut has been refurbished in the last couple of years—now is an excellent time to check out this classic but very tidy old hut near Lewis Pass. We combined it with a second night at the large (and popular) Hope Kiwi Lodge. For a summer trip it would be awesome to spend the second night at Lake Man before exiting through either Doubtful Range or Doubtful Valley.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_031810327.CqV5hnbL.webp" alt="Looking up Hope Valley shortly before reaching St Jacob&#x27;s Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_031810327.Bzpceenw.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_031810327.CRQB1Vf0.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_031810327.DItTI_Nx.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_031810327.CqV5hnbL.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Looking up Hope Valley shortly before reaching St Jacob's Hut</figcaption></figure>
<p>About halfway between St Jacob’s Hut and Top Hope Hut, the track peters out on the true right of the river. It looks like undermining from the river is eating away at the track. We wasted a fair amount of time trying to find the track higher up on the bank, as marked on Topo50. But it turned out to be easier to just hop into the river and walk the final 30 minutes of the approach with wet feet.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_224748952.enoL0Hhr.webp" alt="Top Hope Hut itself" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_224748952.2Vttlttl.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_224748952.B3mEHQoX.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_224748952.CkBTGR-B.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_224748952.enoL0Hhr.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Top Hope Hut itself</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_233129186.vL8mm733.webp" alt="The mighty Tevas performed spectacularly for the half hour of river crossings after leaving the hut on our way out" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_233129186.WhKQnJuM.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_233129186.B_57vjjK.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_233129186.dBq4dzGy.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220924_233129186.vL8mm733.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The mighty Tevas performed spectacularly for the half hour of river crossings after leaving the hut on our way out</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_195823380.dT7lmncS.webp" alt="Hope Kiwi Lodge with its lovely open surroundings. I went here for my first ever tramp; this is the first time I have revisited a hut!" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_195823380.BBVH8Zph.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_195823380.C20PL4Mn.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_195823380.BTZqWAQ3.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_195823380.dT7lmncS.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Hope Kiwi Lodge with its lovely open surroundings. I went here for my first ever tramp; this is the first time I have revisited a hut!</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_202633523.f3ehhQlU.webp" alt="The bridged gorge section of the Hope River was surprisingly pretty" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_202633523.pz9R64ML.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_202633523.BXvm-vgw.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_202633523.DjbU_oZN.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220925_202633523.f3ehhQlU.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The bridged gorge section of the Hope River was surprisingly pretty</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Driving a high-quality LED strip with ESPHome and ESP32-C3</title>
<description>Combining the new ElectroDragon control board with COB LEDs</description>
<link>https://albert.nz/esp32-led-strip/</link>
<guid isPermaLink="false">/esp32-led-strip/</guid>
<pubDate>Thu, 26 May 2022 02:11:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>It’s been over a year since I’ve taken on a new LED strip project. This is probably because of the rock-solid reliability demonstrated by the strips I already have installed. These are the “individually-addressable” style of strip, with RGBW colour. I drive them with the <a href="/esphome-wled-migration">WLED firmware</a> running on the excellent <a href="https://www.electrodragon.com/product/esp-led-strip-board/">ESP8266 control boards</a> from ElectroDragon (I’ve written about these boards <a href="/voice-activated-lighting-hardware">before</a>).</p>
<p>Colour strips are great for decoration, but there are better options for illuminating a room with high-quality light. I stumbled upon some nice <a href="https://www.aliexpress.com/item/1005001614814078.html">temperature-adjustable white LED strips</a> on AliExpress recently. Unlike individually-addressable strips where control is done via a signal line, these strips are what I like to call “direct drive”, where the input voltage is applied directly to the LED strip and brightness is changed via current control or PWM.</p>
<p>ESP8266 boards aren’t ideally suited to driving these strips directly because their PWM implementation is in software, not hardware. This can cause flickering. But I was recently overjoyed to notice ElectroDragon is now selling an <a href="https://www.electrodragon.com/product/esp-led-strip-board/">ESP32-based version of the driver board</a>. The ESP32 has the notable benefit of hardware PWM—perfect for LED strips that require direct voltage!</p>
<p>Armed with this knowledge and a bunch of components, I set out to install my new temperature-adjustable ceiling light.</p>
<h1>Hardware</h1>
<h1>The LED strip</h1>
<p>I’m using a 24V two-channel white COB LED strip. One channel is warm white, and one channel is cold white. I went with this strip for a few reasons:</p>
<ul>
<li>This is for ceiling lighting; I don’t need colour and I don’t need individually-addressable LEDs.</li>
<li>I enjoy the idea of having controllable colour temperature so I can change it during the day or connect it to Flux for automatic temperature changing over the course of the day.</li>
<li>The CRI of these strips is excellent at Ra>90. For comparison: the white channel of my colour SK6812 strips is generally rated at Ra>70. This all means the COB strip gives off a much fuller-feeling light that is more suitable to illuminate a room.</li>
<li>24V is a high enough voltage that I can run over two metres of the strip without having to wire up extra power lines to intermediate points along the strip (something I had to do with my 5V addressable strips).</li>
<li>The COB (chip on board) design of this particular strip is incredibly bright at over 700 lumens per metre.</li>
<li>With an insane 640 LEDs per metre, the strip basically has all the diffusion it needs built right in. This is less of a concern given I will be bouncing the light off the ceiling.</li>
</ul>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_212245352.DpW-r3Y3.webp" alt="The LEDs on the strip are inside that yellow stripe along the middle. You can see the solder pads for cold white and warm white channels, plus the common 24V anode attached to the red wire. The strip came with a four-pin connector with three wires. I got so confused about how the wires connected to the pins that I desoldered the factory connector and used these much more sensible three-wire JST connectors I had floating around." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_212245352.DrTFI8_m.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_212245352.DyiqJgRC.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_212245352.DiBaIlEH.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_212245352.DpW-r3Y3.webp 2560w" style="aspect-ratio: 4.898;" data-aspect="4.898" sizes="auto" loading="lazy"><figcaption>The LEDs on the strip are inside that yellow stripe along the middle. You can see the solder pads for cold white and warm white channels, plus the common 24V anode attached to the red wire. The strip came with a four-pin connector with three wires. I got so confused about how the wires connected to the pins that I desoldered the factory connector and used these much more sensible three-wire JST connectors I had floating around.</figcaption></figure>
<p>You’ll need a 24V power supply to use with these. I bought a cheap supply from BTF Lighting, the same seller as the LED strip. I would recommend a more reputable 24V supply, perhaps one of those more traditional “plastic brick” style ones.</p>
<h1>The driver</h1>
<p>I have loved working with ElectroDragon’s ESP8266 boards in previous projects. It’s a basic LED strip controller with built-in MOSFETs and voltage regulation—configurable to drive addressable LED strips like the SK6812 or “direct” LED strips by sending PWM signals to the MOSFET pins.</p>
<p>A big downside of the ESP8266 is its lack of hardware PWM. This means a direct drive LED strip (like the one I’m using in this project) would be more likely to flicker as the microcontroller juggles PWM signalling, WiFi connection, and other tasks with competing priorities. That’s simply not an issue with the new ESP32 boards. PWM is managed using what are called <a href="https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-reference/peripherals/ledc.html">LEDC channels</a>: just specify a PWM frequency and it will be taken care of in hardware. At 7USD a pop for the upgraded version, more people should know about these delightful boards.</p>
<p>To configure the board’s hardware in this project, I set both power jumpers to the “5-27V” setting. This means that I can use the 24V power supply required for the strip as input and power will be passed straight through. At the output side, I set the jumpers to R (as opposed to GND) and G (as opposed to IO2). This is the configuration for driving those “direct” LED strips via the MOSFETs.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_034245456.ypeUoP4B.webp" alt="The ElectroDragon ESP LED strip control board alongside my USB FTDI adapter" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_034245456.B6SLz4B7.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_034245456.DdDMVilM.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_034245456.BptTMVY_.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220525_034245456.ypeUoP4B.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The ElectroDragon ESP LED strip control board alongside my USB FTDI adapter</figcaption></figure>
<p>The board comes with screw terminals for the power connections. I was happy to see that unlike previous revisions, the power input terminals are labelled on the silkscreen. Like on my other projects with the board, once I was happy with my configuration, I soldered the input wires, output wires and jumper settings in place.</p>
<h1>Software</h1>
<p>There are two options here: the flexible <a href="https://esphome.io">ESPHome</a>, or the more LED-focused <a href="https://kno.wled.ge/">WLED</a>, which now supports two-channel white strips. I went with ESPHome, mostly because WLED doesn’t have support for ESP32-C3 (read on for more about that) but also because ESPHome has supported this kind of strip for much longer.</p>
<p>The end result I want is a temperature-controllable light entity in Home Assistant. Fortunately, ESPHome has the <a href="https://esphome.io/components/light/cwww.html"><code>cwww</code> light platform</a> which is exactly for this application. The <a href="https://esphome.io/components/output/ledc.html"><code>ledc</code> output component</a> lets me use that glorious hardware PWM, too.</p>
<p>The configuration I went with looks something like this:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">esphome:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">ceiling_light</span>
  <span class="hljs-attr">platformio_options:</span>
    <span class="hljs-attr">board_build.flash_mode:</span> <span class="hljs-string">dio</span>

<span class="hljs-attr">esp32:</span>
  <span class="hljs-attr">board:</span> <span class="hljs-string">esp32-c3-devkitm-1</span>
  <span class="hljs-attr">framework:</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">arduino</span>
    <span class="hljs-attr">version:</span> <span class="hljs-string">latest</span>

<span class="hljs-attr">wifi:</span>
  <span class="hljs-attr">ssid:</span> <span class="hljs-type">!secret</span> <span class="hljs-string">wifi_ssid</span>
  <span class="hljs-attr">password:</span> <span class="hljs-type">!secret</span> <span class="hljs-string">wifi_password</span>

  <span class="hljs-comment"># Enable fallback hotspot (captive portal) in case wifi connection fails</span>
  <span class="hljs-attr">ap:</span>
    <span class="hljs-attr">ssid:</span> <span class="hljs-string">'Ceiling Light Fallback Hotspot'</span>
    <span class="hljs-attr">password:</span> <span class="hljs-type">!secret</span> <span class="hljs-string">wifi_ap_password</span>

<span class="hljs-attr">logger:</span>

<span class="hljs-attr">api:</span>

<span class="hljs-attr">ota:</span>

<span class="hljs-attr">light:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">cwww</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">'Ceiling light'</span>
    <span class="hljs-attr">cold_white:</span> <span class="hljs-string">greenpin</span>
    <span class="hljs-attr">warm_white:</span> <span class="hljs-string">redpin</span>
    <span class="hljs-attr">cold_white_color_temperature:</span> <span class="hljs-number">6000 </span><span class="hljs-string">K</span>
    <span class="hljs-attr">warm_white_color_temperature:</span> <span class="hljs-number">2700 </span><span class="hljs-string">K</span> <span class="hljs-comment"># From the strip's datasheet but pretty sure it's more like 3000K in reality</span>
    <span class="hljs-attr">constant_brightness:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">output:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">ledc</span>
    <span class="hljs-attr">pin:</span> <span class="hljs-string">GPIO5</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">greenpin</span>
    <span class="hljs-attr">frequency:</span> <span class="hljs-string">19531Hz</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">ledc</span>
    <span class="hljs-attr">pin:</span> <span class="hljs-string">GPIO8</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">redpin</span>
    <span class="hljs-attr">frequency:</span> <span class="hljs-string">19531Hz</span>
  <span class="hljs-comment"># - platform: ledc</span>
  <span class="hljs-comment">#   pin: GPIO4</span>
  <span class="hljs-comment">#   id: bluepin</span>
  <span class="hljs-comment">#   frequency: 19531Hz</span>
  <span class="hljs-comment"># - platform: ledc</span>
  <span class="hljs-comment">#   pin: GPIO3</span>
  <span class="hljs-comment">#   id: whitepin</span>
  <span class="hljs-comment">#   frequency: 19531Hz</span>
</code></pre>
<p>There are some important bits in here:</p>
<ul>
<li><code>board: esp32-c3-devkitm-1</code> signifies that this isn’t an ESP32 but rather the related-by-name ESP32-C3. The ESP32-C3 is less powerful but contains many of the ESP32’s key features. It’s also much newer than the ESP32 and runs on the completely different RISC-V architecture. The small package size of the ESP32-C3 means it can be used by manufacturers as a drop-in replacement for the ESP8266 on boards like the ElectroDragon one, and I can only see the popularity of this chip skyrocketing.</li>
<li><code>board_build.flash_mode: dio</code> is absolutely required for flashing to work successfully. Without this line, the board will default to QIO mode which resulted in boot loops on my ESP32-C3 board.</li>
<li><code>frequency: 19531Hz</code> is taken from the table of <a href="https://esphome.io/components/output/ledc.html#recommended-frequencies">recommended frequencies published in the ESPHome documentation</a>. Initially I tried 1220Hz as this results in the most steps available for buttery transitions. Unfortunately, my power supply produced a loud whining sound and some flickering at that frequency. So I switched to the lowest recommended frequency above my hearing limit of about 18kHz; 19.5kHz it was! A worthwhile change for the eliminated flickering and noise—and I don’t notice a difference in transition smoothness. A lower recommended frequency may work well for you if you have a better quality supply.</li>
</ul>
<h3>A note on pins</h3>
<p>I’ve included the other pins on the board, commented out, for reference purposes. This took some sleuthing as ElectroDragon originally didn’t publish the pin numbers for the ESP32 variant of this board. Here’s a table of what I found:</p>



































<table><thead><tr><th>Output channel</th><th><a href="https://w.electrodragon.com/w/ESP_LED_Board_HDK">Pin (ESP8266 variant)</a></th><th>Pin (ESP32-C3 variant)</th></tr></thead><tbody><tr><td>G</td><td>GPIO13</td><td>GPIO5</td></tr><tr><td>R</td><td>GPIO15</td><td>GPIO8</td></tr><tr><td>B</td><td>GPIO12</td><td>GPIO4</td></tr><tr><td>W</td><td>GPIO14</td><td>GPIO3</td></tr><tr><td>Signal (SK6812 or similar)</td><td>GPIO2</td><td>GPIO10</td></tr></tbody></table>
<h2>Flashing the board</h2>
<p>I used the ESPHome dashboard interface to enter and export the config. From the dashboard, find the device you’re flashing and select Install -> Manual download -> Modern format. It should compile and download a <code>bin</code> file which can be flashed to the ESP.</p>
<blockquote>
<p>The documentation for “Modern format” is entirely absent but after much pain and learning about bootloaders I can confirm this is the option you want. I believe “modern” generates an image including the bootloader, which makes it really simple to flash to the <code>0x0</code> offset. “Legacy” exports an image for tools which provide their own bootloader.</p>
</blockquote>
<p>Connect the ESP board to a USB FTDI adapter. My FTDI adapter is based on the CP2102 and includes a 3.3V output. Connect GND->GND, RX->TX, TX->RX and 3V3->3V3. The ElectroDragon documentation recommends using 5V->5V instead of 3V3->3V3 (I think this is what I have done on previous projects) but either should work.</p>
<p>I flashed the board on Linux using <a href="https://github.com/espressif/esptool">esptool</a>.</p>
<blockquote>
<p>Before each <code>esptool</code> command, hold down the <code>IO0</code> button on the ESP board while plugging the FTDI adapter into the computer. This signals the ESP to boot in flash mode.</p>
</blockquote>
<p>First, erase the contents of the flash:</p>
<pre><code class="hljs language-bash">python -m esptool -p /dev/ttyUSB0 -b 460800 --before default_reset --after hard_reset --chip esp32c3 erase_flash
</code></pre>
<p>Now replug the board and flash the firmware:</p>
<pre><code class="hljs language-bash">python -m esptool -p /dev/ttyUSB0 -b 460800 --before default_reset --after hard_reset --chip esp32c3 write_flash 0x0 firmware-factory.bin
</code></pre>
<p>Easy as! The device should eventually appear as online in the ESPHome dashboard. Over-the-air flashing will be possible now.</p>
<blockquote>
<p>For troubleshooting, I found <a href="https://github.com/npat-efault/picocom">picocom</a> a useful tool to monitor serial output over USB:</p>
<pre><code class="hljs language-bash">picocom -b 115200 /dev/ttyUSB0
</code></pre>
</blockquote>
<h1>The result</h1>
<p>It’s simple and it works. Introducing my new ceiling light! I’m really impressed by the quality of the light. While it’s fun being able to generate rainbows from lights like the SK6812, there’s something really beautiful about a natural, bright, high-CRI white. I’ll be sticking to this kind of strip whenever I just want illumination, not decoration, from a lighting project.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220526_045405691.MP.BC7H_Qej.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220526_045405691.MP.Do_EUi_q.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220526_045405691.MP.CFhQgJ_c.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220526_045405691.MP.BN9N5YLj.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220526_045405691.MP.BC7H_Qej.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220529_104832975.MP.BnQpbEKd.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220529_104832975.MP.CbZKvzZM.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220529_104832975.MP.CfwND-ku.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220529_104832975.MP.BnQpbEKd.webp 1536w" style="aspect-ratio: 0.909;" data-aspect="0.909" sizes="auto" loading="lazy">]]>
</content:encoded>
</item>
<item>
<title>Mataketake Hut</title>
<description>Majestic hut with a view in South West Coast</description>
<link>https://albert.nz/mataketake-hut/</link>
<guid isPermaLink="false">/mataketake-hut/</guid>
<pubDate>Fri, 22 Apr 2022 02:52:22 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>This recently-built hut has been on the to-do list for a few months now, ever since a friend recommended it while we were walking up <a href="/taipo">Taipo Valley</a>. Mataketake Hut sits high on the range of the same name, accessed from the Haast to Paringa Cattle Track north of Haast, a 6.5-hour drive from Christchurch.</p>
<p>There is a small network of tracks around the Mataketake Range. Confusingly, they’re not all on topomaps and the cattle track is closed south of Maori Saddle. We decided to go to and from the hut via the Mica Mine tops route to maximise our chance of getting great views. Going up via Maori Saddle Hut would have the benefit of forming a longer loop and opening up a side trip to Lake Law.</p>
<p>With a sketchy-but-passable weather forecast for ANZAC weekend, we committed to the trip and set off!</p>
<h1>Day one</h1>
<p>After the long drive, it was under an hour’s walk to Blue River Hut. What a great feeling to stretch the legs before arriving at this historic, cosy, building for the night! We even got a clear sky overnight. A noisy group of three kea zoomed overhead while we took some photos.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09590.DDlmgssL.webp" alt="Posing for a photo outside Blue River (Blowfly) Hut on an unexpectedly clear evening" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09590.D1PzriMo.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09590.Bd7OCQ2d.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09590.v2AvW7QJ.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09590.DDlmgssL.webp 2560w" style="aspect-ratio: 0.668;" data-aspect="0.668" sizes="auto" loading="lazy"><figcaption>Posing for a photo outside Blue River (Blowfly) Hut on an unexpectedly clear evening</figcaption></figure>
<p>By night the top-bunkers among us discovered a downside of this hut: the low ceilings made it comically easy to bump limbs and heads during the night.</p>
<h1>Day two</h1>
<p>With the skies still clear against the odds, we were keen to get up the hill! The track was generally straightforward, fast travel the whole way to the bushline, becoming more of a grind at the top. A highlight of this section was seeing many kererū flying through the bush and hearing the calls of kākā and kea mix across the forested valley.</p>
<p>Before we knew it, we were out into openness with the sky still clear. Beautiful views of the surrounding ranges emerged, only to be swallowed up by low cloud as we approached Mataketake Hut.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09610.D6xXxzHb.webp" alt="Proceeding along the tops with clear skies to the left and low cloud to the right" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09610.B8JgPHQQ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09610.Db5d1OUc.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09610.BkWd15Ri.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09610.D6xXxzHb.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Proceeding along the tops with clear skies to the left and low cloud to the right</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09618.Cu0FKEyU.webp" alt="Mataketake Hut spotted!" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09618.0ihFrrUM.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09618.Z08e6tEe.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09618.CRTiLaui.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09618.Cu0FKEyU.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>Mataketake Hut spotted!</figcaption></figure>
<h1>Day three</h1>
<p>As a personal first, I had planned for an entire hut day on this trip. This was based on the nice things I had heard about the hut. Hopefully it would increase our chance of getting good weather and having the building to ourselves. Turns out neither of those things occurred during the first night at the hut. Sunday proved to be an excellent day to be indoors, with a storm bring plenty of rain and wind. Plus, over the course of a cosy day in the hut filled with board-gaming, tarn-swimming, and book-reading, we realised we would have a clear evening and sole occupancy in the second night.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09621.BcnEqpFB.webp" alt="Improvising some kindling for the fire by cutting larger pieces. There wasn&#x27;t much firewood but we somehow got the fire raging." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09621.C1IyTE3Q.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09621.CHqOcAnF.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09621.DEfSsxlR.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09621.BcnEqpFB.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Improvising some kindling for the fire by cutting larger pieces. There wasn't much firewood but we somehow got the fire raging.</figcaption></figure>
<p>As sunset approached and the cloud layer lifted, it became clear that this hut’s situation is truly special.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09649-HDR-Pano.DTUmCDeG.webp" alt="Sunset as viewed from the hut" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09649-HDR-Pano.CD19lXJx.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09649-HDR-Pano.DPHhp9Kh.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09649-HDR-Pano.Ra6YqsYp.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09649-HDR-Pano.DTUmCDeG.webp 2560w" style="aspect-ratio: 2.365;" data-aspect="2.365" sizes="auto" loading="lazy"><figcaption>Sunset as viewed from the hut</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09685.BdjLQoUA.webp" alt="Watching shooting stars in the dying light of dusk" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09685.9HhxN1NO.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09685.B3doIxoG.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09685.CYK5qf0K.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09685.BdjLQoUA.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Watching shooting stars in the dying light of dusk</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09678.kfMZrFBH.webp" alt="Mataketake Hut after sundown" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09678.B7RGcMOH.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09678.B9u1BOWD.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09678.CFvevrPj.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09678.kfMZrFBH.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Mataketake Hut after sundown</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09691.DsHvJgEV.webp" alt="Mountains to the east of the range" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09691.DobHyd_G.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09691.D7Pr-oEb.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09691.Dvfg_V9V.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09691.DsHvJgEV.webp 2560w" style="aspect-ratio: 0.668;" data-aspect="0.668" sizes="auto" loading="lazy"><figcaption>Mountains to the east of the range</figcaption></figure>
<h1>Day four</h1>
<p>We awoke at dawn. This seemed fitting for ANZAC day and meant we would get back to Christchurch earlier. With a crisp breeze behind us and the day dawning in front of us, we packed up and headed out before sunrise. The views were distractingly stunning.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09693.BUnG-bzN.webp" alt="Dawn breaks on ANZAC day, revealing excellent weather for our early departure" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09693.DgDWAfaE.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09693.RaLPxrSn.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09693.DvmnIChy.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09693.BUnG-bzN.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Dawn breaks on ANZAC day, revealing excellent weather for our early departure</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09719.DKOCFfDQ.webp" alt="Looking out from the tops before descending back into the bush" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09719.DCsnvdeS.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09719.BkX2yHRt.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09719.BqmB7SV9.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09719.DKOCFfDQ.webp 2560w" style="aspect-ratio: 3.000;" data-aspect="3.000" sizes="auto" loading="lazy"><figcaption>Looking out from the tops before descending back into the bush</figcaption></figure>
<p>We made it out from the hut in 4.5 hours, a fair bit faster than the 8 hours estimated by DOC. Before we knew it we were back in Christchurch and dreaming about getting back to the hills.</p>]]>
</content:encoded>
</item>
<item>
<title>Pfeifer Biv and Otehake Hot Pools</title>
<description>Stunning mountain hut and natural hot spring in northern Arthur's Pass</description>
<link>https://albert.nz/pfeifer-biv-otahake-hot-pools/</link>
<guid isPermaLink="false">/pfeifer-biv-otahake-hot-pools/</guid>
<pubDate>Fri, 15 Apr 2022 00:32:22 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Easter weekend of 2022 presented a great opportunity to head into the hills near Christchurch. A traverse of Arthur’s Pass, from Aickens to Hawdon Valley via Otehake Valley, was lined up. Before we knew it, the holiday was upon us and it was time to hike! A last-minute route change had us detouring to the dramatically-located Pfeifer Biv on the first day.</p>
<p>Access to the Mt Pfeifer tops is via a stream up to Waharoa Saddle. If you’re going up there, make sure to stick to the left branch and you should have no trouble reaching the saddle.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09522.C775dSsw.webp" alt="Travelling up the stream from Morrisons Footbridge was slippery at times but overall good going" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09522.V_hseFk2.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09522.K5YAWhe8.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09522.BBV3Xyo7.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09522.C775dSsw.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Travelling up the stream from Morrisons Footbridge was slippery at times but overall good going</figcaption></figure>
<p>The saddle was rewarding with views of the Pfeifer tops and provided a great place to top up on snacks before the steepest part of the climb.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09534.CoBjEXiM.webp" alt="The track becomes steeper beyond the saddle, heading up the ridge towards Mt Pfeifer" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09534.BP7M_Gnz.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09534.VoynZMZp.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09534.BOhreEkk.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09534.CoBjEXiM.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The track becomes steeper beyond the saddle, heading up the ridge towards Mt Pfeifer</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09543.CAHB3YqB.webp" alt="The open tops around Mt Pfeifer, with the summit concealed by cloud" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09543.cedcnBAb.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09543.BNDtqxv6.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09543.PXGsPZRW.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09543.CAHB3YqB.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The open tops around Mt Pfeifer, with the summit concealed by cloud</figcaption></figure>
<p>Beyond the bushline, the track fades. There are a couple of large basins to cross before reaching the small hut that is Pfeifer Biv. We had originally intended to proceed beyond the biv but quickly realised we were (a) running out of daylight hours, and (b) quite looking forward to a rest after walking up a mountain. This cosy biv sleeps two but was perfect for all six of us to sit in to cook up some dinner before dispersing to tents. Thankfully, water from the nearby tarns didn’t seem to be particularly poisonous.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09555.NIrVCv_E.webp" alt="Mt Pfeifer Biv at dusk" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09555.DkOt5lh4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09555.DZuG2IaL.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09555.BCvzwAXb.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09555.NIrVCv_E.webp 2560w" style="aspect-ratio: 1.778;" data-aspect="1.778" sizes="auto" loading="lazy"><figcaption>Mt Pfeifer Biv at dusk</figcaption></figure>
<p>A cool, rainy morning broke and saw us heading down the mountain towards Lake Kaurapataka. Will expertly navigated us to our exit route: a scree chute marked as a stream on Topo250, a few hundred metres northeast of the biv beginning around the 1120m contour. This descent provided me with my first experiences in both bush-bashing and scree-skiing as we lost over 500m of altitude in a kilometre.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_204710461.MP.Cp0av0km.webp" alt="Chief Navigator Will assessing the route down an exposed spine as we descend into thicker scrub" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_204710461.MP.v0Bx4gD4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_204710461.MP.Cp6BgfqQ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_204710461.MP.BqWn1e8E.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_204710461.MP.Cp0av0km.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Chief Navigator Will assessing the route down an exposed spine as we descend into thicker scrub</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_213041011.C_ByP7a4.webp" alt="Dramatic backdrop to one of the sections of scree. It&#x27;s not captured well here but the sodden beech forest reflected the clouds, taking on a shimmering silver sheen." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_213041011.D1VDDm4g.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_213041011.Bo1QVLsP.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_213041011.Douv7NRn.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220415_213041011.C_ByP7a4.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Dramatic backdrop to one of the sections of scree. It's not captured well here but the sodden beech forest reflected the clouds, taking on a shimmering silver sheen.</figcaption></figure>
<p>After reaching the bottom of the not-stream, we followed Pfeifer creek out to Lake Kaurapataka track. This track is very fast travel until near the end of the lake when it becomes surprisingly undulating, a situation which worsens until the track eventually joins the Otehake River further south. The distance and elevation changes are definitely concealed by the contours on the map.</p>
<p>Eventually, though, we were out of the forest and walking along the river to the hot pools. These are situated on a riverbed between two campsites. There are some excellent swimming holes nearby, too.</p>
<p>The hot pools are delightful and most of us agreed these were the best natural pools we had experienced. All six of us could fit in them; I even found a spot sheltered from the rain by a protruding log. With a perfect temperature, neck-high water, and few hotspots, I can certainly recommend these pools to anybody willing to walk the 5-6 hours to get there from the road.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09568.DcOkMRlP.webp" alt="Dinner time beside the hot pools" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09568.CQVxWowe.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09568.RjkVPStb.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09568.DkJ5D1Fw.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09568.DcOkMRlP.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Dinner time beside the hot pools</figcaption></figure>
<p>With the delayed itinerary, we decided to split the party: Will and Hayley would speed onwards to complete the traverse, while the four others would head back to Morrisons Footbridge (the easy way—that is, not going over a sizeable mountain). While it was a shame to miss the views of Tarn Col in brilliant weather, I was happy to return home before nightfall and in one piece, with plenty of Arthur’s Pass secrets remaining for me to explore another day.</p>]]>
</content:encoded>
</item>
<item>
<title>Camping at Lake Minchin</title>
<description>Hike to a remote lake near the Casey-Binser track and Poulter valley in Arthur's Pass, Canterbury</description>
<link>https://albert.nz/lake-minchin/</link>
<guid isPermaLink="false">/lake-minchin/</guid>
<pubDate>Fri, 14 Jan 2022 05:42:22 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>I have been overdue for a solo trip for a while now, and what’s better than finding a remote lake to camp beside? Without too much additional thought, I packed my tent and headed for Lake Minchin.</p>
<p>The track consists of three parts: a hilly section over Casey Saddle, a flat bike track along the Poulter riverbed between Casey Hut and Poulter Hut, and finally a gentle uphill approach to the lake.</p>
<h1>Day one</h1>
<p>A mere 200 metres in to the track, I tripped and almost fell into a bush of Matagouri. Not the most elegant start.</p>
<p>The following 15 or so kilometres to Casey hut has a couple of surprisingly steep ups and downs: don’t underestimate the change in altitude between 550m and almost 900m! On the downhill approaching Casey hut I felt my IT band starting to play up. With 12km of walking remaining, this was not a great sign.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220113_220904969.OJ4-GedO.webp" alt="Dense beech forest somewhere near Casey Saddle, including this large trunk with something resembling a tumour" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220113_220904969.B8AoYpYv.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220113_220904969.CcHJ8xW2.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220113_220904969.CCSZXsw1.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220113_220904969.OJ4-GedO.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Dense beech forest somewhere near Casey Saddle, including this large trunk with something resembling a tumour</figcaption></figure>
<p>Before I knew it, I was back on the flat in the Poulter Valley, and the pain vanished. It’s quick travel up the track all the way to Poulter Hut.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_025632071.B9qcMxW7.webp" alt="Beelining across the Poulter River to reach Poulter Hut. The hut becomes visible at quite a distance but I kept GPS handy just in case." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_025632071.BJOxIcgh.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_025632071.BJpCx5Ah.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_025632071.W_GUavFQ.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_025632071.B9qcMxW7.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Beelining across the Poulter River to reach Poulter Hut. The hut becomes visible at quite a distance but I kept GPS handy just in case.</figcaption></figure>
<p>Nearing the hut, I heard a vaguely familiar “kekekekekek” sound and looked up to see a kārearea (NZ falcon) chasing a kāhu (harrier hawk). The falcon wasn’t flying much faster than the hawk and would take a while to catch up to it, performing a dive bomb then repeating the whole process. They carried on like this for a good few minutes; it was majestic to watch.</p>
<p>Poulter Hut looked very tidy. I stopped for a snack and filled out the intentions book before resuming up to the lake. It would be quite nice to come back here another day.</p>
<p>It didn’t take long to cover the ground to Lake Minchin. The track meets the Lake at its southern point and continues around to the north. This northern shore is where I set up camp on some nice spongy ground.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_042300159.BT8_igZW.webp" alt="Lake Minchin comes into sight. The view unfortunately includes a flock of Canada Geese, a common pest in otherwise pristine areas like this." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_042300159.BPdL5enM.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_042300159.BOEnpRc5.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_042300159.CeCN4Ejj.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_042300159.BT8_igZW.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Lake Minchin comes into sight. The view unfortunately includes a flock of Canada Geese, a common pest in otherwise pristine areas like this.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183113285.MP.Deukvn-S.webp" alt="Reflections in the lake" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183113285.MP.BH5PZodS.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183113285.MP.rjUz_G9e.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183113285.MP.gmTQXAZn.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183113285.MP.Deukvn-S.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Reflections in the lake</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183230862.DABZAPkl.webp" alt="Camp, with Pt. 1546 looming in the distance. Not pictured: the friendly hoard of sandflies following me around." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183230862.UNsaeyqP.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183230862.i6m4PWH5.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183230862.BKoN0QDi.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_183230862.DABZAPkl.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Camp, with Pt. 1546 looming in the distance. Not pictured: the friendly hoard of sandflies following me around.</figcaption></figure>
<p>I should pause here to make a couple of points that I wish I had known before camping here:</p>
<ul>
<li>The sandflies can be vicious. They sounded like rain on my tent. If you’re going here, bring DEET and/or your favourite long clothing. I’m personally quite sensitive to insects and I found their presence made it a fair bit harder to relax and enjoy the view!</li>
<li>More importantly, with rivers running low I struggled to find a clean water source near the lake. There was a creek I had topped up at halfway between Poulter Hut and the lake, but I really should have taken that opportunity to fill up my bottles.</li>
</ul>
<p>Nevertheless, this spot was truly beautiful and ticked the boxes I had set out to tick. I felt truly physically isolated from anyone else and completely in the middle of nature. Most people don’t get to feel that feeling very often!</p>
<h1>Day two</h1>
<p>Slightly thirsty and plagued by the gradually-awakening sandflies, my plan in the morning was simple: wake up, pack up, and hurry up to Poulter Hut for breakfast. It became quickly apparent I had aggravated injuries to both my IT band and ankle; walking was pretty uncomfortable. But there’s not much that can be done in these situations. I scoffed some painkillers and focused on moving. This would be a day to get over and done with.</p>
<p>I couldn’t help but stop just a half hour later when I spotted a kea chilling on a trunk off the side of the track. It seemed very interested in me as I watched it. A second kea appeared behind me on the track and started hopping closer while screeching. Next thing I knew, both of them were dive-bombing me as I escaped towards the hut. I’ve never seen such odd behaviour from these birds!</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_193935533.BN9xrHJg.webp" alt="One of the two irritated kea that chased me down the track" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_193935533.CTxDaT2e.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_193935533.0ol09mpw.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_193935533.BOvhTNzM.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_193935533.BN9xrHJg.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>One of the two irritated kea that chased me down the track</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_200153631.MP.Dz7dBDYr.webp" alt="Poulter Hut catching the morning rays" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_200153631.MP.45FDK3pd.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_200153631.MP.sJlmiSW3.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_200153631.MP.OVHOsrlt.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220114_200153631.MP.Dz7dBDYr.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Poulter Hut catching the morning rays</figcaption></figure>
<p>After filling up on porridge and some much-needed water, I was back out on the riverbed. My two kea friends (frenemies?) emerged from the forest and circled me for a surreal few minutes, screeching the whole time.</p>
<p>A day of backtracking proceeded. I saw a human for the first time in 23 hours somewhere just beyond the Casey Hut turnoff. Eventually I reached Andrews Shelter, clutching a sore leg but thankful for the weekend adventure that had been.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220115_022817123.MP.C7fwNpg_.webp" alt="The steep (and leg-breaking) final descent to Andrews Shelter" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220115_022817123.MP.Do4jiR_s.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220115_022817123.MP.BLt-YPEl.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220115_022817123.MP.DU_JZkKh.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20220115_022817123.MP.C7fwNpg_.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The steep (and leg-breaking) final descent to Andrews Shelter</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Kirwans Hut</title>
<description>Steady and scenic trail near Reefton on the West Coast</description>
<link>https://albert.nz/kirwans-hut/</link>
<guid isPermaLink="false">/kirwans-hut/</guid>
<pubDate>Fri, 31 Dec 2021 00:29:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>This beautiful trail ascends through lush West Coast jungle to reach a hut with a grand view of the surrounding mountain ranges. As a former mining track, the gradient is remarkably steady (the elevation plot looks like a triangle). A highlight of the route was a bridge which implausibly emerges from a man-made tunnel.</p>
<p><strong>Update</strong>: I considered revisiting this track in 2024, but unfortunately a rockfall near the bridge has closed the track we took. The hut is still accessible from the east, from Gannons Road via Montgomerie Hut.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_005046913.Cl6bW1jk.webp" alt="The bridge which emerges from a tunnel" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_005046913.CRxa_VPn.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_005046913.Cuh8mTZh.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_005046913.C9DDno4H.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_005046913.Cl6bW1jk.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>The bridge which emerges from a tunnel</figcaption></figure>
<p>The hut is fairly modern and warm, but we had a hard time getting the coal burner started on a surprisingly cold New Year’s Eve.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081655503.MP.B8Xly9eH.webp" alt="Kirwans Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081655503.MP.vLe3nygC.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081655503.MP.DNaMgDCS.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081655503.MP.DLIQlbJu.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081655503.MP.B8Xly9eH.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Kirwans Hut</figcaption></figure>
<figure><img alt="The clouds part to give a glimpse of Lake Te Anau" src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081750945.BDCHJ8wu.webp" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081750945.DoOmfTME.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081750945.C82gMXOI.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081750945.xc4NkNkV.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211231_081750945.BDCHJ8wu.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The clouds part to give a glimpse of Lake Te Anau</figcaption></figure>
<p>On our outwards journey, we attempted to locate “Fiery Cross Gold Mine”, as present on the Topo50 map. We found neither mine nor track, despite our attempts to ascend Caples Creek.</p>]]>
</content:encoded>
</item>
<item>
<title>Running the Kepler Track</title>
<description>A gorgeous Great Walk completed in a day</description>
<link>https://albert.nz/kepler/</link>
<guid isPermaLink="false">/kepler/</guid>
<pubDate>Fri, 26 Nov 2021 17:31:50 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>This hugely popular Great Walk is a great challenge to tackle in a day. We ran it counter-clockwise. The first half has some nice high sections before ducking into the bush for a pretty but monotonous second half.</p>
<p>I hear the views from the ridgeline parts are expansive and some of the best in New Zealand. Unfortunately we received low cloud and only received brief glimpses of the surroundings.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_193703777.BlEBgunM.webp" alt="The clouds part to give a glimpse of Lake Te Anau" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_193703777.DKrpsb_u.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_193703777.dTxENu6d.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_193703777.nmiN9Ope.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_193703777.BlEBgunM.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The clouds part to give a glimpse of Lake Te Anau</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213900451.CG_jcYgS.webp" alt="Speeding down the misty descent towards Iris Burn Hut, our halfway stop" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213900451.BS2b5n7D.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213900451.ByqSR8KX.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213900451.BSX3fOxd.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213900451.CG_jcYgS.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Speeding down the misty descent towards Iris Burn Hut, our halfway stop</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213903819.CJ2aXkEk.webp" alt="Beautiful waterfall near Iris Burn Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213903819.N5Mm4SRH.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213903819.BA9gCW8C.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213903819.BRsyClol.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20211126_213903819.CJ2aXkEk.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Beautiful waterfall near Iris Burn Hut</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Mt Brown Hut</title>
<description>Popular West Coast hut with magnificent views near Hokitika</description>
<link>https://albert.nz/mt-brown-hut/</link>
<guid isPermaLink="false">/mt-brown-hut/</guid>
<pubDate>Fri, 08 Oct 2021 22:38:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Mt Brown Hut is a classic overnight trip near Hokitika, to a hut renowned for its views and character. It’s about a 3.5-hour drive from my home in Christchurch. Another 3.5 hours is the time it took us to complete the short but sharp 4km climb from the carpark.</p>
<p>During the climb through forest, increasing glimpses of Lake Kaniere are offered. By the time we reached the bushline we were surrounded by dense cloud which, thankfully, started clearing soon after.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08903.CJSo-GEQ.webp" alt="Glimpse of Lake Kaniere from the densely-forested track" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08903.uyywiffJ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08903.BgtBrRvh.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08903.Dx3zA_wE.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08903.CJSo-GEQ.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Glimpse of Lake Kaniere from the densely-forested track</figcaption></figure>
<p>Upon reaching the hut, we instantly recognised it as the building pictured on the information sign in just about every DOC hut in the country. Ironically, the hut is not actually a DOC hut, being managed by the Kokatahi Tramping Club.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08925.DqHKbCmY.webp" alt="Mt Brown Hut with Newton Range in the background" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08925.BfCZeqIp.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08925.y6WRYfPD.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08925.CqnrrkA4.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08925.DqHKbCmY.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>Mt Brown Hut with Newton Range in the background</figcaption></figure>
<p>Mt Brown Hut is a cosy hut with only four bunks and a fireplace. Its small size belies its high popularity so I would recommend bringing a tent if you are heading up here, especially on summer weekends. The hut sits directly adjacent to a lip which drops off steeply to reveal gorgeous views out to the coast. Views are even more stunning from the hill just to the south of the hut. It was exciting to watch these views improve as the cloud cleared into the evening.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08997.C6UdMq4K.webp" alt="View towards the coast as the clouds clear for sunset. Kokatahi River is prominent." class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08997.yBX-V_fb.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08997.kjMB9ugr.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08997.B3uvMGEt.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08997.C6UdMq4K.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>View towards the coast as the clouds clear for sunset. Kokatahi River is prominent.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08956.CkMzU0Ml.webp" alt="Distant hillside, likely Sam Knob in in Toaroha Range, viewed from Mt Brown" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08956.D9ySCEGp.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08956.CPxQIhVV.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08956.ChrcfQvj.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08956.CkMzU0Ml.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Distant hillside, likely Sam Knob in in Toaroha Range, viewed from Mt Brown</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09000.Cnpu-0IR.webp" alt="Moon and a planet after dusk. The lingering sunlight illuminates the rain below the distant clouds." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09000.Djfdw9Vq.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09000.C0Ip-0ei.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09000.CaQcIAIC.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09000.Cnpu-0IR.webp 2560w" style="aspect-ratio: 0.500;" data-aspect="0.500" sizes="auto" loading="lazy"><figcaption>Moon and a planet after dusk. The lingering sunlight illuminates the rain below the distant clouds.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09019.Do_yUzzV.webp" alt="Stargazing at night" class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09019.dUxXFwz4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09019.CJxzfT7i.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09019.DNIjKOHf.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09019.Do_yUzzV.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Stargazing at night</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Paddy's Track</title>
<description>Part of Mt Robert loop up Pourangahau / Mt Robert near Lake Rotoiti and St Arnaud</description>
<link>https://albert.nz/pourangahau/</link>
<guid isPermaLink="false">/pourangahau/</guid>
<pubDate>Fri, 23 Jul 2021 23:14:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>A popular hill walk near St Arnaud is the Mt Robert loop which takes walkers up Pourangahau / Mt Robert a few kilometres southeast of the town. The 9km loop is comprised of two tracks: Paddy’s Track, a gentle trail passing Bushline Hut; and the lower section of Pinchgut Track which consists of steep switchbacks to the roadend carpark. We wanted a less steep trail so opted to take Paddy’s Track up to Relax shelter near the summit and return the same way (11km total).</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08548.C84I4Zff.webp" alt="The trail climbs up high above the lake, granting panoramic views across the entire region" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08548.B6eN51Vz.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08548.zshlrP0M.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08548.CLMK2n4h.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08548.C84I4Zff.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The trail climbs up high above the lake, granting panoramic views across the entire region</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08542.CAC7Q6w2.webp" alt="The party passes over a saddle near Bushline Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08542.3d8hnXwd.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08542.Ccb9uOgt.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08542.bIW6p-Qy.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08542.CAC7Q6w2.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The party passes over a saddle near Bushline Hut</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08561.CtxfoccQ.webp" alt="A walker crests the hill near Bushedge Shelter, about to tackle the steep downhill of Pinchgut Track" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08561.B5IiZdrh.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08561.DYzdoWT_.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08561.CtIH_k84.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08561.CtxfoccQ.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>A walker crests the hill near Bushedge Shelter, about to tackle the steep downhill of Pinchgut Track</figcaption></figure>
<figure>
  <img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08537.YUCqgZIl.webp" alt="Needle ice was present along much of the track. It&#x27;s the first time I&#x27;ve experienced it." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08537.DbqCwHLm.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08537.DtSSZ4ig.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08537.kLIAK4Id.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08537.YUCqgZIl.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
  <figcaption><a href="https://en.wikipedia.org/wiki/Needle_ice">Needle ice</a> was present along much of the track. It’s the first time I’ve experienced it.</figcaption>
</figure>
<h1>Back in St Arnaud</h1>
<p>We were only in town for a couple of nights but enjoyed heading down to Lake Rotoiti which was a short walk from our accomodation. I already want to come back to this region, possibly for some fast-packing in summer - there are so many interesting longer trails to explore.</p>
<figure>
  <img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08487.DJmrAHIN.webp" alt="Meeting the local longfin eels (tuna kuwharuwharu)" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08487.DhTUi_JL.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08487.CRkQBkRW.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08487.yQZTYh7B.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08487.DJmrAHIN.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
  <figcaption>Meeting the local longfin eels (tuna kuwharuwharu). They are friendly and squishy. I researched more about these creatures and learnt <a href="https://en.wikipedia.org/wiki/New_Zealand_longfin_eel">they can live to 60 years old before swimming to Tonga to breed</a>.</figcaption>
</figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08513.DmbfNXUD.webp" alt="A night walk didn&#x27;t reveal any kiwi, but a narrow layer of dense fog settled over the lake making for dramatic sights" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08513.BGvl2Qte.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08513.BWSYJl2z.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08513.BPjL2xUK.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08513.DmbfNXUD.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>A night walk didn't reveal any kiwi, but a narrow layer of dense fog settled over the lake making for dramatic sights</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Taipo Track to Julia Hut and hot pools</title>
<description>Up the river with the Ngatea Tramping Club</description>
<link>https://albert.nz/taipo/</link>
<guid isPermaLink="false">/taipo/</guid>
<pubDate>Fri, 02 Jul 2021 03:30:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Julia Hut is a 25-kilometre walk up the Taipo Valley, to the West of Arthur’s Pass.</p>
<h2>Day 1</h2>
<p>To maximise our hot pool time on day 2, we arrived late on Friday and walked to Dillons Hut in the dark. The first 2.5km is 4WD track which we tackled without too much difficulty in a Prado. We got out and started walking when it got too muddy to proceed in the vehicle, arriving at the hut around 9:30pm.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08215.VIW_64Vd.webp" alt="Nighttime crossing of the river just before Dillons Homestead Hut." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08215.BTM8L_ju.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08215.Cc1Y_iJj.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08215.Dd_WF_xc.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08215.VIW_64Vd.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Nighttime crossing of the river just before Dillons Homestead Hut.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08225.Bl-NNLzY.webp" alt="Tree outside Dillon Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08225.dIlLvpVw.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08225.DR4UiPXD.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08225.Db1IrY4O.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08225.Bl-NNLzY.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>Tree outside Dillon Hut</figcaption></figure>
<h2>Day 2</h2>
<p>We set off the next day, now able to see the valley views all around us as we proceeded to Mid Taipo Hut and beyond. There is a three-wire bridge incorrectly labelled as a cableway on Topo50. Following the markers when approaching the bridge from downstream will lead you onto the flood track. If possible I’d recommend staying on the riverbank all the way to the bridge.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08233.DlRKaGJu.webp" alt="Lining up a photo spot beside the river. The azure blue colour of the river was striking. The three-wire bridge can be seen in the distance." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08233.OaC-AdJy.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08233.DVCcL1Kj.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08233.B35ukQCh.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08233.DlRKaGJu.webp 2560w" style="aspect-ratio: 0.589;" data-aspect="0.589" sizes="auto" loading="lazy"><figcaption>Lining up a photo spot beside the river. The azure blue colour of the river was striking. The three-wire bridge can be seen in the distance.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08254.Sbz6bHDI.webp" alt="Crossing the river at the bridge between Mid Taipo and Julia Huts" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08254.C0JVbiYL.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08254.CSvkBkaM.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08254.C5oMwuer.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08254.Sbz6bHDI.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Crossing the river at the bridge between Mid Taipo and Julia Huts</figcaption></figure>
<p>Shortly after arriving at Julia Hut, it was time to find the hot pools. Follow the track beyond the hut down to the stream and then head about 400m futher downstream. We found some shallow pools directly adjacent to the river on the true right. With the assistance of some spades and pots left by past groups, we dug some shallow pools big enough for six of us to sit half-submerged. The combination of dark skies, hot water and cold air made for a surreal stargazing experience.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08271.DcsGODKi.webp" alt="Stargazing on the balcony at Julia Hut. Grevilles Cone (centre) and Campbell Range (right) can be seen in the background - not to mention the Large Magellanic Cloud." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08271.CDccMCtN.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08271.BpZwHJcf.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08271.IlPvEH62.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08271.DcsGODKi.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Stargazing on the balcony at Julia Hut. Grevilles Cone (centre) and Campbell Range (right) can be seen in the background - not to mention the Large Magellanic Cloud.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08273.ObXSBgXf.webp" alt="Reading in the dawn light" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08273.BOWqFTSR.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08273.CxuLmkPm.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08273.BafxUDXG.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08273.ObXSBgXf.webp 2560w" style="aspect-ratio: 0.754;" data-aspect="0.754" sizes="auto" loading="lazy"><figcaption>Reading in the dawn light</figcaption></figure>
<h2>Day 3</h2>
<p>The next day we got up early and retraced our steps 23km back to the car.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08277.By-hfk8l.webp" alt="The group ready to depart Julia Hut on a frosty morning" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08277.CowrR6bK.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08277.BlkfIApW.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08277.v6NCvW-j.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08277.By-hfk8l.webp 2560w" style="aspect-ratio: 1.574;" data-aspect="1.574" sizes="auto" loading="lazy"><figcaption>The group ready to depart Julia Hut on a frosty morning</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08290.Cpp3q2Qa.webp" alt="Waiting for party members to cross one by one" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08290.CN2ASiuv.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08290.NmwTo3bM.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08290.GDktBol7.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08290.Cpp3q2Qa.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Waiting for party members to cross one by one</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08292.CiDHQNKX.webp" alt="Crossing at Micks Creek" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08292.C4Mz7qAn.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08292.dJBTytWS.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08292.C_vttfRP.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08292.CiDHQNKX.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Crossing at Micks Creek</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08312.lYsv0F2L.webp" alt="The group proceeds down Taipo Valley beyond Mid Taipo Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08312.DzSOeOB_.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08312.aXcrzUzB.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08312.DcixWwzO.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08312.lYsv0F2L.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The group proceeds down Taipo Valley beyond Mid Taipo Hut</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08321.CgcKS5-k.webp" alt="Crossing the lengthy three-wire bridge between Mid Taipo and Dillon Huts" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08321.DwX0EG-l.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08321.CvMjB1Az.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08321.DY70Sls9.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08321.CgcKS5-k.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Crossing the lengthy three-wire bridge between Mid Taipo and Dillon Huts</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08329.CD8Y1YJq.webp" alt="Some interesting goldmining equipment near Dillons Homestead Hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08329.JgPonLBN.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08329.ChyR8p5C.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08329.CSz1--Gt.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08329.CD8Y1YJq.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Some interesting goldmining equipment near Dillons Homestead Hut</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Aotea Track</title>
<description>Three-day tramp on Great Barrier Island visiting Kaiaraara Hut, Mt Heale Hut and Kaitoke hot springs</description>
<link>https://albert.nz/aotea/</link>
<guid isPermaLink="false">/aotea/</guid>
<pubDate>Sun, 20 Jun 2021 22:21:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>During a sibling trip to Great Barrier Island, we took on the Aotea Track. The track is a loop around the centre of the island with many options for entering and exiting from either the north or south. Three days is ample for this route and made for a leisurely tramp. It was seriously nice having the Kaitoke hot springs lined up for the last day; I’d thoroughly recommend exiting on this trail. For a more strenuous trip, consider exploring more side tracks during the three days. For an overnight excursion, make sure you overnight at Mt Heale Hut - it’s the nicer hut!</p>
<h2>Day 1</h2>
<p>We started at the Forest Road trailhead, oppositing the Te Ahumata Track on a saddle near Whagaparapara. We were planning to finish the tramp 2km down the road at the Kaitoke Hot Springs Track. So I dropped the others off at the saddle, parked at the Hot Springs carpark, then ran back along the road.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07960.C7vzMYSf.webp" alt="Setting off along Forest Road. Much of the first day was like this - 12km of travel along 4WD tracks. Take the Kiwiriki Track if you have more daylight hours than us!" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07960.n2nj8xiC.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07960.BX8dVxxR.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07960.DCRsgpeR.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07960.C7vzMYSf.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Setting off along Forest Road. Much of the first day was like this - 12km of travel along 4WD tracks. Take the Kiwiriki Track if you have more daylight hours than us!</figcaption></figure>
<p>We had lunch at a picnic table at the junction between Forest Road, Kiwiriki Track, and Maungapiko Track. The Maungopiko Track proved to be a delightful excursion. Just a few hundred metres and some rock scrambling leads to a beautiful view out from the centre of the island all the way to the western coast and Little Barrier Island beyond.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07974.BtUKv9GZ.webp" alt="Great Barrier Forest viewed from Maungapiko summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07974.JEh6hwNH.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07974.DmwR_d_M.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07974.BgQQTsT4.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07974.BtUKv9GZ.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Great Barrier Forest viewed from Maungapiko summit</figcaption></figure>
<p>A couple of kilometres before reaching Kaiaraara Hut, there is a lovely short nature walk to some incredibly large kauri trees - probably the biggest I have seen in my life.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07983.BcKjQP0m.webp" alt="Huge kauri tree" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07983.0zlY0wQM.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07983.C2CckPLm.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07983.DHSmteqj.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07983.BcKjQP0m.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Huge kauri tree</figcaption></figure>
<p>There was light left in the day so a few of us took an excursion to Bush’s Beach. It’s about 1.5km each way and well worth it. I’m sure this would be a lovely spot to relax in summer.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210621_042420032.MP.VnDKCbS8.webp" alt="A couple we met at the hut were also walking down to Bush&#x27;s Beach to get back to their boat anchored in the bay" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210621_042420032.MP.dDKTtksp.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210621_042420032.MP.6cuPHG1p.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210621_042420032.MP.BH-AbHwQ.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210621_042420032.MP.VnDKCbS8.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>A couple we met at the hut were also walking down to Bush's Beach to get back to their boat anchored in the bay</figcaption></figure>
<h2>Day 2</h2>
<p>There are two ways from Kaiaraara Hut to Mt Heale Hut: the Kaiaraara Track (which is the one DOC marks on the Aotea Track map) or the South Fork Track. We were leaning towards the South Fork Track as it has less vertical gain. This decision was confirmed by chatting to a ranger who arrived at the hut in the morning - he said it is one of his favourite tracks on the island!</p>
<p>The track follows close to Kaiaraara Stream and is poorly marked for this distance. This isn’t a big deal, though; just make sure to find the track at the stream fork about 600m after the track first hits the stream. A couple of us simply walked along the stream for this stretch. Some members of our party were less enthusiastic about wet feet.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07996.D7pxKvCP.webp" alt="Carefully crossing the river. I got knee-deep to capture the moment." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07996.DQV--hY3.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07996.CLclohrx.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07996.C2NVRJDU.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07996.D7pxKvCP.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>Carefully crossing the river. I got knee-deep to capture the moment.</figcaption></figure>
<p>The remainder of the track is a straightforward and increasingly beautiful ascent up to Mt Heale Hut. See if you can spot the hut on the other side of the valley as the track climbs. Thankfully, the views keep on getting better all the way to the hut. From the balcony, the vista looks down the bush-clad valley out to the sea, with Little Barrier Island perfectly framed.</p>
<p>We arrived at the hut early, around 2pm, just in time for a late lunch and reading on the deck until the sun went down and the winter cold forced us inside. Sunsets don’t get much better than this.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08049.D17-6nL0.webp" alt="Sunset as viewed from the Mt Heale Hut balcony" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08049.EH6P7w9x.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08049.z2JOMMAS.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08049.W0S7qjgp.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08049.D17-6nL0.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Sunset as viewed from the Mt Heale Hut balcony</figcaption></figure>
<h2>Day 3</h2>
<p>We woke up at 6am to climb Mt Hobson / Hirakimata. Unlike the Kaiaraara Track, the South Fork Track hadn’t taken us up to the summit and we were really keen to watch sunrise from the tallest peak on the entire island! So we set off in the dawn light.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08064.D0Kp-Zf_.webp" alt="Dawn view from the hut balcony" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08064.DCNobDyy.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08064.CG2ioph_.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08064.Cr-GEbX_.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08064.D0Kp-Zf_.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Dawn view from the hut balcony</figcaption></figure>
<p>The climb up is short and steep - I was grateful for the very nice stairs all the way to the top.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08070.BYNZTxf7.webp" alt="The first light brushes Mt Heale below us. The town of Claris sits beneath the fog to the left." class="prose-custom-w-full" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08070.DCicHXoA.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08070.CkNVbkWC.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08070.D1_I2HDX.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08070.BYNZTxf7.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>The first light brushes Mt Heale below us. The town of Claris sits beneath the fog to the left.</figcaption></figure>
<p>Exiting was straightforward; it’s a gentle downhill most of the way from the hut to the hot springs carpark. The hot pools themselves were delightful. There is one large pool, then more along a stream which stretches up the hill beyond. On advice from a local, we waded up the stream to a deep pool 50 metres along. The water was lukewarm with hot mud lining the streambed. While far from a spa, I really enjoyed this first natural hot pools experience and I would certainly recommend it for the last day of the trip.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08152.C0pFo30v.webp" alt="The upper stream at the Kaitoke hot springs. Our deep pool was the far one. Quite magical!" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08152.Bi-B2VZ3.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08152.DosJfdSf.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08152.CmK8vTJG.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08152.C0pFo30v.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>The upper stream at the Kaitoke hot springs. Our deep pool was the far one. Quite magical!</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>St James Walkway to Boyle Flat Hut</title>
<description>Southernmost day of St James Walkway in Lewis Pass, to and from Boyle Flat Hut</description>
<link>https://albert.nz/boyle-flat/</link>
<guid isPermaLink="false">/boyle-flat/</guid>
<pubDate>Sun, 06 Jun 2021 02:14:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Heavy flooding in mid Canterbury had closed Wharfedale Track (our plan A), so we had to quickly plan an overnighter. We headed a couple of hours north to Lewis Pass in hopes of avoiding flooding. St James Walkway is a 66km trail so we opted to just do the Southernmost leg, to and from Boyle Flat Hut.</p>
<p>A big benefit of this section of trail is that the three major river crossings are bridged. It’s clear the waters were higher than usual.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07473.YUpVC6QQ.webp" alt="First crossing of Boyle River" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07473.I4Sy4uBz.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07473.CKFpWWJ1.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07473.r22zUF2A.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07473.YUpVC6QQ.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>First crossing of Boyle River</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07484.BHd0TGux.webp" alt="Second crossing of Boyle River" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07484.s4menrjy.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07484.DeoFIfwi.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07484.C6zc9FTZ.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07484.BHd0TGux.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Second crossing of Boyle River</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07490.C1yxvm_e.webp" alt="Many friendly Robins were encountered. So too were Pīwakawaka, which followed us along sizeable sections of the trail." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07490.6hiKQUmU.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07490.DdPEMbK9.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07490.1VKs-zHn.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07490.C1yxvm_e.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Many friendly Robins were encountered. So too were Pīwakawaka, which followed us along sizeable sections of the trail.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07494.CYKqbUGC.webp" alt="Valley views while approaching Boyle Flat Hut. The hut can be seen in the distance on the other side of the river." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07494.BDzJKMvr.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07494.CuUGSAR5.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07494.BykI6eOQ.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07494.CYKqbUGC.webp 2560w" style="aspect-ratio: 1.600;" data-aspect="1.600" sizes="auto" loading="lazy"><figcaption>Valley views while approaching Boyle Flat Hut. The hut can be seen in the distance on the other side of the river.</figcaption></figure>
<p>A highlight of the hut were the new memory foam mattresses—a clear upgrade from the old ones—which made for a great night’s sleep.</p>
<h1>Appendix: Fungi</h1>
<p>Here are some neat fungi which we spotted beside the trail.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07482.i7ty4WX-.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07482.C5YzCm7k.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07482.C-NDYTc7.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07482.DESbr_Q6.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07482.i7ty4WX-.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07485.6ckBYuco.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07485.B2UqjVut.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07485.B2GelC9x.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07485.5f2Fh1C3.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07485.6ckBYuco.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07506.BtMbC9Pp.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07506.DrCklIZo.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07506.CP9mmOow.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07506.CNaJ6N62.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07506.BtMbC9Pp.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07516.1tHB9whw.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07516.CmiMiTsP.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07516.BgDk97C8.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07516.BBeG2aF-.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07516.1tHB9whw.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">]]>
</content:encoded>
</item>
<item>
<title>Pinchgut Track</title>
<description>Canterbury's most accessible tramp?</description>
<link>https://albert.nz/pinchgut/</link>
<guid isPermaLink="false">/pinchgut/</guid>
<pubDate>Fri, 07 May 2021 00:52:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Pinchgut Track is a 5km walk near Rangiora leading to Pinchgut Hut. It’s a short, easy track which is popular with families. After tramping this one I realised I barely took any photos.</p>
<p>The track starts with a river crossing right near the carpark. We did the walk in autumn and the crossing was trivial. As usual, though, check forecasts—especially if walking with young ones.</p>
<p>It’s also worth noting that the hut gets busy! I’d highly recommend our route—leave early, drop bags at the hut, then do the 12km loop in the hills with daypacks. It makes the excursion nicely challenging and means you can claim a bunk early.</p>]]>
</content:encoded>
</item>
<item>
<title>The Great Kauri Run - Classic</title>
<description>Scenic running event in the hills of Coromandel</description>
<link>https://albert.nz/kauri-run/</link>
<guid isPermaLink="false">/kauri-run/</guid>
<pubDate>Sat, 01 May 2021 00:52:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>This 30km run starts and finshes in Coromandel Town. It travels along Harray Track the up Flays Road through private property, linking up with the ridgeline track which travels to Kaipawa Summit before dropping back down Success Track to the town.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210430_231256790.C5b58eBA.webp" alt="Looking west from the ridgeline trail. This was my first trip to the region; the scenery reminds me of a warmer, busier Marlborough Sounds." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210430_231256790.CUBlJWEY.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210430_231256790.PC7yA8ql.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210430_231256790.BkJ0Ncgv.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210430_231256790.C5b58eBA.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Looking west from the ridgeline trail. This was my first trip to the region; the scenery reminds me of a warmer, busier Marlborough Sounds.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_014935918.CiocM8Mb.webp" alt="The finish line in town. A great feeling to complete my first trail running event!" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_014935918.CzulW9sS.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_014935918.C4RtyKgE.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_014935918.gubpae3k.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_014935918.CiocM8Mb.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The finish line in town. A great feeling to complete my first trail running event!</figcaption></figure>
<p>The run was harder than expected but I managed to beat my four hour goal. Highlight: making a fair few overtakes in the 5km of technical flat trail between Flays Road and the ridgeline. Lowlight: the cramps, which begun 5km before the finish line and didn’t relent for hours!</p>
<h1>Back in Coromandel Town</h1>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_235242179.MP.DS2yWBaD.webp" alt="We went to Stu&#x27;s Pig Farm on The 309 Road nearby. An interesting place but I wouldn&#x27;t go back." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_235242179.MP.bKoyuaW-.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_235242179.MP.DVRRdiAM.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_235242179.MP.UjEdnrjE.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210501_235242179.MP.DS2yWBaD.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>We went to Stu's Pig Farm on The 309 Road nearby. An interesting place but I wouldn't go back.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210502_011125098.D8UkqqNw.webp" alt="Driving Creek Railway provided great views and offbeat entertainment" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210502_011125098.BH_5pNym.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210502_011125098.D1RqQI4p.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210502_011125098.BT2VXU-4.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210502_011125098.D8UkqqNw.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Driving Creek Railway provided great views and offbeat entertainment</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Hooker Valley Track</title>
<description>Popular track to Hooker Lake with views of Mt Cook / Aoraki</description>
<link>https://albert.nz/hooker-valley/</link>
<guid isPermaLink="false">/hooker-valley/</guid>
<pubDate>Sun, 25 Apr 2021 00:52:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Possibly the most popular walk in the area, Hooker Valley Track provided us with an easy afternoon walk after heavy rain confined us to the campsite shelter all morning. Compared to <a href="/mueller">Mueller Hut Route</a>, which we had completed the day before, Hooker Valley Track felt like a gentle highway. There is even a toilet block about halfway to the lake.</p>
<p>The weather was far from perfect but there’s no denying it’s a walk with beautiful surroundings.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07241.BZ9SRHes.webp" alt="Looking up to Mt Sefton. An ominous shelf of ice is present and so too is Sefton Bivouac, merely a few red pixels in this image (click for higher resolution)." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07241.Coe2X8By.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07241.D3jH-UR-.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07241.CymHIhOO.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07241.BZ9SRHes.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Looking up to Mt Sefton. An ominous shelf of ice is present and so too is Sefton Bivouac, merely a few red pixels in this image (click for higher resolution).</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07243.CcM7WzNN.webp" alt="The view from the end of the track. Aoraki was frustratingly obscured by cloud most of the time we were there; I&#x27;m told this is quite typical." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07243.BY9VlKXq.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07243.CyCPBPNz.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07243.C2OzUs86.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07243.CcM7WzNN.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The view from the end of the track. Aoraki was frustratingly obscured by cloud most of the time we were there; I'm told this is quite typical.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07248.CK6GOMNF.webp" alt="The cloud was clearing as we exited along the trail and Aoraki became almost fully visible" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07248.DDOHmmqf.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07248.C03d3osc.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07248.DnmCqGvz.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07248.CK6GOMNF.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The cloud was clearing as we exited along the trail and Aoraki became almost fully visible</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Mueller Hut Route</title>
<description>Steep track near Aoraki Village with mountain views</description>
<link>https://albert.nz/mueller/</link>
<guid isPermaLink="false">/mueller/</guid>
<pubDate>Sat, 24 Apr 2021 00:52:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Mueller Hut Route is one of the most beautiful walks I have done. As the hut was booked out, we camped at White Horse Hill Campsite (which is at the base of the track) and went up to the hut and back within a day. What makes this route awesome is the proximity to the campsite, combined with short distance, large vertical gain and mild track conditions in good weather. It feels like climbing to the top of the world—no mountaineering experience needed!</p>
<blockquote>
<p>Check the weather! It seemed pretty doable on the sunny Saturday we attempted it, but the very next day the weather packed in; crampons were necessary on the route and we wouldn’t have been able to do it in such conditions.</p>
</blockquote>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07097.PM8wVlIR.webp" alt="Sealy Tarns viewed from above, with Hooker Lake in the background" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07097.Bo1uFZtW.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07097.CdYkbPxJ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07097.CKkX-ZVj.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07097.PM8wVlIR.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Sealy Tarns viewed from above, with Hooker Lake in the background</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07124.DH1FaifU.webp" alt="The last steep section of the track becomes rocky before rounding the corner" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07124.Cpoc8Cip.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07124.dLl2BVpM.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07124.D3TC-54A.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07124.DH1FaifU.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The last steep section of the track becomes rocky before rounding the corner</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07152.CSOSG9v9.webp" alt="Approach to Mueller Hut. One of my tramping companions had brought a longboard up, because why not?" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07152.DiVQG5L0.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07152.CyBuueX2.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07152.Buujj-Ma.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07152.CSOSG9v9.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Approach to Mueller Hut. One of my tramping companions had brought a longboard up, because why not?</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07218.DEI7aOgy.webp" alt="Hooker Valley during the descent, with Aoraki taking pride of place at the head of the valley. The Mueller Glacier moraine wall sits in front." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07218.DJ4XJdJy.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07218.8ciKH-ji.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07218.B70Gi47k.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07218.DEI7aOgy.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>Hooker Valley during the descent, with Aoraki taking pride of place at the head of the valley. The Mueller Glacier moraine wall sits in front.</figcaption></figure>
<h1>Other things</h1>
<p>We ventured up the <a href="/hooker-valley">Hooker Valley Track</a> the following day.</p>
<p>There was enough clear sky at night to get some nightscape photos.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07041.BL1KY2C5.webp" alt="We stopped shortly before Aoraki to take some photos across Lake Pukaki. The weather forecast was ominous; sure enough, we arrived at the campsite around 10pm to pouring rain." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07041.BRJilJ-Y.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07041.C7Lyxcud.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07041.BXMGUCSM.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07041.BL1KY2C5.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>We stopped shortly before Aoraki to take some photos across Lake Pukaki. The weather forecast was ominous; sure enough, we arrived at the campsite around 10pm to pouring rain.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07223.gEMSoMVm.webp" alt="Mt Sefton viewed from the campsite. This night was clear and windy." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07223.7O_lf7Fw.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07223.DicsJGOJ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07223.BLqJGUbX.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07223.gEMSoMVm.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Mt Sefton viewed from the campsite. This night was clear and windy.</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Waiopehu Track</title>
<description>Cloudy tramp in the north-eastern Tararua Range</description>
<link>https://albert.nz/waiopehu/</link>
<guid isPermaLink="false">/waiopehu/</guid>
<pubDate>Sun, 04 Apr 2021 03:30:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>The walk to Waiopehu Hut is a straightforward but steady climb up a ridgeline. Lovely dense forest surrounds the track to the hut, which sits near the bushline. The hut ostensibly has remarkable views out to Levin and the coast—surely a great introduction to tramping for the member of our party who was new to the activity. Of course, we received dense low cloud the whole time we were up there.</p>
<p>I’d like to come back here in better weather and potentially create a longer loop with Gable End Ridge.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06970.3Jag9iPU.webp" alt="The best view we got from the track, out towards Levin. After this point, we rose into the cloud." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06970.BEsGi4y7.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06970.DQqr_uzd.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06970.BPZV2Yjb.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06970.3Jag9iPU.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The best view we got from the track, out towards Levin. After this point, we rose into the cloud.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06987.COvggtop.webp" alt="Waiopehu Hut in the morning" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06987.CXGQ97Ep.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06987.CyizZpoa.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06987.owUcMTkY.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06987.COvggtop.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Waiopehu Hut in the morning</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06988.ClQ95l_s.webp" alt="Descending down the trail into foggy forest, shortly after the hut" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06988.CvGIL3Ly.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06988.DHDz7uGA.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06988.Hu6Dc5jX.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06988.ClQ95l_s.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Descending down the trail into foggy forest, shortly after the hut</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06991.CLMp4Nbh.webp" alt="Spiderweb alongside the track" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06991.Cpsnr_sT.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06991.DAmTaKMh.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06991.CAaQ6xDR.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06991.CLMp4Nbh.webp 2560w" style="aspect-ratio: 1.600;" data-aspect="1.600" sizes="auto" loading="lazy"><figcaption>Spiderweb alongside the track</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07004.Db0uYxkL.webp" alt="Marlborough Sounds while flying out of Wellington the evening after the tramp" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07004.CN6tzXNO.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07004.By79q-v2.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07004.DkewKs6A.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07004.Db0uYxkL.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>Marlborough Sounds while flying out of Wellington the evening after the tramp</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Running the Routeburn Track</title>
<description>Completing one of New Zealand's most famous walks in a day</description>
<link>https://albert.nz/routeburn/</link>
<guid isPermaLink="false">/routeburn/</guid>
<pubDate>Fri, 26 Mar 2021 04:52:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>To celebrate Chris’ birthday a group of 17 took on Routeburn. The 32km trail is the shortest of New Zealand’s Great Walks and one of the best-known. A large group makes logistics easier: we booked a shuttle to take us from Queenstown to The Divide and had some non-runners willing to meet us at the other end.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_180133482.NIGHT.BkFiyygs.webp" alt="We had an early start from Queenstown as it&#x27;s almost 4 hours to drive to The Divide" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_180133482.NIGHT.Baj7yiqZ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_180133482.NIGHT.BwOad7Ch.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_180133482.NIGHT.C0aDuPRV.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_180133482.NIGHT.BkFiyygs.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>We had an early start from Queenstown as it's almost 4 hours to drive to The Divide</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG20210326105232.ElWg3Q8T.webp" alt="The group gets ready to set off from The Divide" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG20210326105232.uv3ftDlU.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG20210326105232.fFeu9i2z.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG20210326105232.DVAmz9sV.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG20210326105232.ElWg3Q8T.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The group gets ready to set off from The Divide</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_234246598.CZqZb60Y.webp" alt="Lake MacKenzie" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_234246598.CmpAesp3.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_234246598.DkMsnb63.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_234246598.gv2t861a.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210325_234246598.CZqZb60Y.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Lake MacKenzie</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_002250382.jozTo0SO.webp" alt="Livingstone and Earl Mountain Ranges viewed from above Lake MacKenzie. The clouds added much drama to the scenery." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_002250382.DstgB7xU.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_002250382.xEcympUB.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_002250382.BwJRgRCI.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_002250382.jozTo0SO.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Livingstone and Earl Mountain Ranges viewed from above Lake MacKenzie. The clouds added much drama to the scenery.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_011945011.MP.75kE0L_r.webp" alt="Tarns just beyond Harris Saddle Shelter" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_011945011.MP.BUrXobZh.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_011945011.MP.B8iaP0kG.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_011945011.MP.D4HEaY0w.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_011945011.MP.75kE0L_r.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Tarns just beyond Harris Saddle Shelter</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_023524067.DgVs_M76.webp" alt="Turqoise waters of Route Burn" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_023524067.Cg3gjoNy.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_023524067.B9KmnuCS.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_023524067.De3EjyTn.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_023524067.DgVs_M76.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Turqoise waters of Route Burn</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_050620422.MP.CGrO_F_7.webp" alt="One of a few kea near Routeburn Shelter" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_050620422.MP.GUlg6MlO.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_050620422.MP.BXMRxQHS.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_050620422.MP.FMpWSoWi.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210326_050620422.MP.CGrO_F_7.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>One of a few kea near Routeburn Shelter</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Cass-Lagoon Saddle Track</title>
<description>Beautiful overnight walk in Arthur's Pass</description>
<link>https://albert.nz/cass-lagoon/</link>
<guid isPermaLink="false">/cass-lagoon/</guid>
<pubDate>Fri, 19 Mar 2021 21:14:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>We dropped a car at the western carpark then drove to the eastern one, tackling the walk clockwise. With the popularity of State Highway 73, hitchhiking is a real alternative here to get between carparks. A couple of other Cass-Lagooners happened to arrive at the western carpark at the same time as us. They were intending to walk to the road to hitchhike but were able to get a ride with us.</p>
<p>If running the track, the counterclockwise direction would probably make more sense. This way the steepest ascent is done early and gradual downhill is maximised.</p>
<h1>Day 1</h1>
<p>Most of the day is a gradual climb up Cass River. The scenery gets dramatic at Cass Saddle.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210320_000918572.CqRt-33J.webp" alt="Looking back down the track while approaching Cass Saddle" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210320_000918572.CTBi7-pl.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210320_000918572.D5u6xB9i.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210320_000918572.Bm5CzoM6.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210320_000918572.CqRt-33J.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Looking back down the track while approaching Cass Saddle</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09087-Pano.pWkpjcjI.webp" alt="Just over Cass Saddle, the beech was covered in this mossy stuff - quite atmospheric in combination with the low cloud" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09087-Pano.B2aTgfvn.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09087-Pano.BlHaoaey.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09087-Pano.Dr85Y_di.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09087-Pano.pWkpjcjI.webp 2560w" style="aspect-ratio: 2.162;" data-aspect="2.162" sizes="auto" loading="lazy"><figcaption>Just over Cass Saddle, the beech was covered in this mossy stuff - quite atmospheric in combination with the low cloud</figcaption></figure>
<p>Hamilton Hut is halfway along the track. It’s nicely located on a bank set back from Hamilton Creek. We were the first group at the hut, arriving around 3pm. This way we were able to make the most of the sunshine hours and great weather by reading in the sun and throwing a frisbee around.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09093.DezLkNv_.webp" alt="The riverside plain in front of Hamilton Hut. The ground is more level close to the hut and made for an excellent spot to throw a frisbee." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09093.Enmdk3_w.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09093.BQLmUxqZ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09093.CEIln7F4.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09093.DezLkNv_.webp 2560w" style="aspect-ratio: 1.600;" data-aspect="1.600" sizes="auto" loading="lazy"><figcaption>The riverside plain in front of Hamilton Hut. The ground is more level close to the hut and made for an excellent spot to throw a frisbee.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09108.CLkBXJSL.webp" alt="The night allowed for some good astrophotography before the cloud rolled in. Visible is Hamilton Hut and the bank upon which it sits." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09108.BFxrpFNF.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09108.5w4OZ9BS.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09108.BGNUDPMW.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09108.CLkBXJSL.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The night allowed for some good astrophotography before the cloud rolled in. Visible is Hamilton Hut and the bank upon which it sits.</figcaption></figure>
<h1>Day 2</h1>
<p>We set off up Harper River as the sun rose above the hills.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09113.BQJsOAA9.webp" alt="Hamilton Hut and its surroundings in the morning" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09113.BQZHHlu1.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09113.Cm3KhT9_.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09113.CWx3S2DZ.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09113.BQJsOAA9.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Hamilton Hut and its surroundings in the morning</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09119.BcXBEpuf.webp" alt="I only had my landscape lens with me for this trip but robins are so tame that it served adequately for bird photography. This particular individual was quite fascinated with the toilet marker." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09119.BlqetXQT.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09119.Dj0AkE8l.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09119.BcXBEpuf.webp 1536w" style="aspect-ratio: 1.000;" data-aspect="1.000" sizes="auto" loading="lazy"><figcaption>I only had my landscape lens with me for this trip but robins are so tame that it served adequately for bird photography. This particular individual was quite fascinated with the toilet marker.</figcaption></figure>
<p>The second day was a bit harder than the first and more interesting too. We made our way up the river sections with help from cairns. It’s lovely scenery, with dense beech forest and river junctions all over the place.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09128.C8Rq0TTt.webp" alt="We found a comfortable spot for lunch, perched high on a riverbank overlooking a clearing. There was this moss that made for the most pleasant cushion. We watched some others pass us while we ate." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09128.DoxQ4bRe.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09128.DN-HxV_z.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09128.BdffT98-.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09128.C8Rq0TTt.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>We found a comfortable spot for lunch, perched high on a riverbank overlooking a clearing. There was this moss that made for the most pleasant cushion. We watched some others pass us while we ate.</figcaption></figure>
<p>Beyond A-Frame Hut, the vegetation falls away to reveal what must be some of the best views in the area.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09135.BF1mraBH.webp" alt="Magnificent views from Lagoon Saddle" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09135.B2-WMsrx.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09135.BFDEHPDI.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09135.DKvM8LWL.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09135.BF1mraBH.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Magnificent views from Lagoon Saddle</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Mt Oxford and Ryde Falls Tracks</title>
<description>Epic run along the Mt Oxford tops, a short drive from Christchurch</description>
<link>https://albert.nz/mt-oxford/</link>
<guid isPermaLink="false">/mt-oxford/</guid>
<pubDate>Sat, 13 Mar 2021 03:30:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>This counterclockwise loop made for a great trail run about a half-marathon in length. The uphills and downhills are fairly steep but worth it for about a kilometre of good travel along the subalpine tops. I would recommend the side trip to Ryde Falls, especially on a warm day. A longer and even hillier daytrip could be created by making a detour to Wharfedale Hut.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_002054778.CyHhvPTW.webp" alt="Arriving at the summit of Mt Oxford. The Canterbury Plains are laid out in front of the hill making for an expansive view across to Banks Peninsula and the coast." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_002054778.9PYzPc2D.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_002054778.DkNWfJmp.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_002054778.CsB7wYXf.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_002054778.CyHhvPTW.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Arriving at the summit of Mt Oxford. The Canterbury Plains are laid out in front of the hill making for an expansive view across to Banks Peninsula and the coast.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_011706689.CWvzHrtD.webp" alt="The best part of the run is the tops travel: it&#x27;s good trail with panoramic views. The descent becomes steeper after dipping below the bushline." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_011706689.ABXRy2Ly.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_011706689.CvNw_OSp.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_011706689.C7mFB-Mv.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_011706689.CWvzHrtD.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The best part of the run is the tops travel: it's good trail with panoramic views. The descent becomes steeper after dipping below the bushline.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_022211835.M6IvKKZ9.webp" alt="Ryde Falls. The detour doesn&#x27;t add much distance to the total and it was lovely to cool our feet in the pool at the falls&#x27; base." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_022211835.BOM9Gq0C.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_022211835.DX-I7JMs.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_022211835.DGHM5-il.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210313_022211835.M6IvKKZ9.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Ryde Falls. The detour doesn't add much distance to the total and it was lovely to cool our feet in the pool at the falls' base.</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Mt Somers Track</title>
<description>Mountainous trail run in Mid Canterbury</description>
<link>https://albert.nz/somers/</link>
<guid isPermaLink="false">/somers/</guid>
<pubDate>Sat, 06 Mar 2021 22:30:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>This track around Mt Somers / Te Kiekie is a popular tramp. Huts were booked out so I decided to tackle this one as a meaty 25km run with almost 2km of vertical gain. There are a few ways to approach this track. To shorten the distance and position a steep ascent at the beginning, I started at the Sharplin Falls carpark to the east, following the trail clockwise all around the mountain. For a shorter, steeper option consider traversing the mountain via the summit - a trail runner I encountered assured me there is a northern summit track. A longer run could be created by including the Rhyolite and Woolshed Creek tracks, with the option to commence at the Woolshed Creek carpark in the west.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_202522095.CyiIPdQZ.webp" alt="The first clearing during the initial ascent. The entire south face had beautiful views out across the plains." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_202522095.CpVuRTN5.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_202522095.D6u3SvIe.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_202522095.ChaOslWw.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_202522095.CyiIPdQZ.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The first clearing during the initial ascent. The entire south face had beautiful views out across the plains.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_223342535.MP.BecsNCfF.webp" alt="Beautiful rock formations and forest around the west of the mount. I think that&#x27;s Two Thumb Range visible in the distance." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_223342535.MP.BnOP_uef.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_223342535.MP.CFVTQaQX.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_223342535.MP.CA30e70b.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_223342535.MP.BecsNCfF.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Beautiful rock formations and forest around the west of the mount. I think that's Two Thumb Range visible in the distance.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_225259551.B1C6l6zE.webp" alt="I took a snack break at this bus stop. Alas, no bus arrived." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_225259551.DuFHMT72.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_225259551.gG8fsWvp.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_225259551.Bjpv12Bx.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_225259551.B1C6l6zE.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>I took a snack break at this bus stop. Alas, no bus arrived.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_234711605.MP.BSllGe6I.webp" alt="A steady uphill slog after Woolshed Creek Hut takes you to a saddle north of the mountain" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_234711605.MP.ClRzYz3D.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_234711605.MP.o022Rvqp.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_234711605.MP.Y21CBj73.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210306_234711605.MP.BSllGe6I.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>A steady uphill slog after Woolshed Creek Hut takes you to a saddle north of the mountain</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210307_004150063.MP.JhV4im6J.webp" alt="Another view of the plains before the track dips down below the bushline to reach Pinnacles Hut. There were a bunch of fires in the distance." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210307_004150063.MP.CQgFNvK3.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210307_004150063.MP.CV_9PHJc.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210307_004150063.MP.1_U3Te-T.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210307_004150063.MP.JhV4im6J.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Another view of the plains before the track dips down below the bushline to reach Pinnacles Hut. There were a bunch of fires in the distance.</figcaption></figure>
<p>I was not mentally prepared for the last few kilometres of the trail. The climb to Duke Knob near the carpark looks innocent enough on the map but really took it out of me. It was a luxurious feeling to relax on a camping chair once I got back to the car, despite the feasting sandflies. My legs were cramping all the way home.</p>]]>
</content:encoded>
</item>
<item>
<title>Kaituna Valley to Mt Herbert / Te Ahu Patiki</title>
<description>The tallest peak on Banks Penninsula, accessed from the south</description>
<link>https://albert.nz/kaituna/</link>
<guid isPermaLink="false">/kaituna/</guid>
<pubDate>Sat, 27 Feb 2021 02:14:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Mt Herbert / Te Ahu Patiki is commonly accessed from the north, from either Charteris Bay or Diamond Harbour. We decided to approach from the south, leaving the car at the Parkinsons Road carpark in Kaituna Valley and headed up the Kaituna Valley Packhorse Hut Track.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_214259866.QRFUx8RR.webp" alt="Packhorse Hut in the low cloud. Good to stop for a snack and to top up the water supplies." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_214259866.8kStLr5b.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_214259866.5EIGvGto.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_214259866.CaH8Dk7H.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_214259866.QRFUx8RR.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Packhorse Hut in the low cloud. Good to stop for a snack and to top up the water supplies.</figcaption></figure>
<p>We were worried that the cloud would mean no views. But as we climbed higher along the steep (and surprisingly lush) Te Ara Pātaka Walkway, it was revealed that the cloud was simply a low layer appearing to sit across the entire mid Canterbury region.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_221636699.Bcg6qGCz.webp" alt="Climbing above the cloud to the south of Mt Bradley (Te Ara Pātaka Walkway)" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_221636699.DuH3SDAt.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_221636699.B-7DfMKB.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_221636699.xsG1aVMH.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_221636699.Bcg6qGCz.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Climbing above the cloud to the south of Mt Bradley (Te Ara Pātaka Walkway)</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_230634856.iMOhkKVd.webp" alt="Mt Herbert summit. Ethereal views above the cloud all the way to the alps!" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_230634856.DMX66bcr.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_230634856.DuGZKe9Q.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_230634856.C_moY5RM.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210226_230634856.iMOhkKVd.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Mt Herbert summit. Ethereal views above the cloud all the way to the alps!</figcaption></figure>
<p>To form a bit of a loop, we returned to Packhorse Hut along Faulkner Track on the other side of Mt Bradley.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210227_003507757.PEuZGKvb.webp" alt="Desceding back along Kaituna Valley Packhorse Hut Track to the car" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210227_003507757.CARbTfY8.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210227_003507757.B-RgGjzQ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210227_003507757.CBL0aRnS.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210227_003507757.PEuZGKvb.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Desceding back along Kaituna Valley Packhorse Hut Track to the car</figcaption></figure>
<p>I would definitely recommend this for a day walk or run in the region. For a faster, fuller loop with some road running, consider continuing past Mt Herbert summit and decsending Monument Track and Kaituna Valley Road—that’s what I would try next time.</p>]]>
</content:encoded>
</item>
<item>
<title>Edwards Hut Track</title>
<description>Approachable overnight tramp in Arthur's Pass</description>
<link>https://albert.nz/edwards-hut/</link>
<guid isPermaLink="false">/edwards-hut/</guid>
<pubDate>Sat, 20 Feb 2021 02:14:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>This fairly straightforward track follows Edwards River to the hut. Start at Greyneys Shelter, use the railway underpass to access Bealey River then look for a crossing towards Edwards Hut Track. Allow plenty of time for a swim in the river if you’re doing this one in warm weather.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08989.CqZoExEM.webp" alt="The group heads up Edwards River" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08989.DpoEEh4G.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08989.D2CEpTiG.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08989.D1fXhyNs.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08989.CqZoExEM.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The group heads up Edwards River</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09009.UhvkX7YU.webp" alt="One of many perfect swimming spots along the river" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09009.Bcx1J30z.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09009.BsscCf7T.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09009.BrGp5nzw.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09009.UhvkX7YU.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>One of many perfect swimming spots along the river</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09016.B89hOx_b.webp" alt="A curious robin" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09016.hGeHyTZq.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09016.Doet1KCM.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09016.DR8K4U2L.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09016.B89hOx_b.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>A curious robin</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09044.orK0MjQe.webp" alt="Scenic bend in the river" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09044.CAuVnD83.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09044.Aa-SwVKa.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09044.BjYtb5fX.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09044.orK0MjQe.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Scenic bend in the river</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09070.CP3XgB8J.webp" alt="Polar Range at night" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09070.BMIKN4iI.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09070.CpkzxEM4.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09070.CCcgq26U.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09070.CP3XgB8J.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Polar Range at night</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09074.C04w5qja.webp" alt="20m waterfall viewed from the track" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09074.bNewfbCT.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09074.TzLRw2OE.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09074.oCM-3m0l.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC09074.C04w5qja.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>20m waterfall viewed from the track</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Running the WUU2K marathon route for Wellington Anniversary Day</title>
<description>In my final week in Wellington, I pay my respects by breaking my body</description>
<link>https://albert.nz/wuu2k/</link>
<guid isPermaLink="false">/wuu2k/</guid>
<pubDate>Mon, 25 Jan 2021 00:52:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Everything was lined up:</p>
<ul>
<li>I had less than a week until my move to Christchurch</li>
<li>The weather was perfect</li>
<li>I was fit enough to run a marathon, maybe</li>
<li>It was Wellington Anniversary Day</li>
<li>I had told a bunch of people I would do this before leaving Wellington</li>
</ul>
<p>I had more or less experienced each individual section of the WUU2K route over the years so nothing would be unfamiliar. I opted for the marathon route as it’s a bit more sensible than the ultra (a more sensible route and also more sensible for my body).</p>
<p>Without overthinking it, then, the night before the run I asked my flatmate to give me a ride to the start line. He kindly obliged and before I knew it I was on the trail!</p>
<p>I mentally broke down the trail into four sections.</p>
<h1>Section 1: Skyline Walkway (Runway?)</h1>
<p>My favourite stretch of the run! After a quick walk to Mt Kaukau it’s lovely running along the undulating Skyline Walkway. In worse weather this track gets some awful wind, so be prepared for that.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_211348840.xfhzbHLE.webp" alt="Mt Kaukau / Tarikākā summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_211348840.BI-2mkLL.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_211348840.wQtK0TMF.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_211348840.B_I9Xw_T.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_211348840.xfhzbHLE.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Mt Kaukau / Tarikākā summit</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_224644105.CkufEPDT.webp" alt="Makara Peak summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_224644105.Dcnzq3PP.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_224644105.CzkrhaK-.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_224644105.Co4YMHj7.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_224644105.CkufEPDT.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Makara Peak summit</figcaption></figure>
<h1>Section 2: Makara Peak to Wrights Hill</h1>
<p>Now for the best bush trail on the entire route. A solid downhill along a 4WD track then on to Lazy Fern downhill bike track. This was the dodgiest part of the marathon; during the event I’m guessing they close this track but in my case I had to remain vigilant for any bikes approaching from behind. Thankfully it’s blissful smooth trail and pleasant to get done quickly.</p>
<p>Then it’s up Salvation Track, a gentle uphill which is quite runnable. Cross the road near the top of the hill to continue along the trail.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_235537985.mu_2Xz-2.webp" alt="Wrights Hill summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_235537985.TQW4FHzA.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_235537985.e14Bbyp-.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_235537985.b2vg5-yZ.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210124_235537985.mu_2Xz-2.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Wrights Hill summit</figcaption></figure>
<h1>Section 3: Zealandia fenceline</h1>
<p>A simple but steep 4WD section to the turbine. The legs were really starting to feel it now and I began to fade a bit.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_003531680.Dk_FNjag.webp" alt="Brooklyn wind turbine" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_003531680.GMVqSoNG.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_003531680.Djd7zvzo.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_003531680.CtNOIdPc.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_003531680.Dk_FNjag.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>Brooklyn wind turbine</figcaption></figure>
<h1>Section 3: Tip Track and Island Bay</h1>
<p>Backtracking from the turbine but now on the other side of the road, the trail is good going all the way to Hawkins Hill. It was still slow travel on weary legs.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_020640991.2u3JzBQD.webp" alt="Near Hawkins Hill summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_020640991.DLvGe1fa.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_020640991.CgZz02rV.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_020640991.Bo-NFErc.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_020640991.2u3JzBQD.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Near Hawkins Hill summit</figcaption></figure>
<p>The tip track heads down to Happy Valley from here and is easy cardio-wise—but like a jackhammer on the knees.</p>
<p>Just before the Berhampore Golf Course there’s a compact but confusing mess of trails known as Tawatawa Reserve. I became lost here despite knowing I wanted to head to the golf course, despite having visted this area the week before and despite colour-coded trail maps throughout the park.</p>
<p>The official route from The Parade up to Mt Albert Road is also unclear. It appears the route heads directly up the hill. I think I found the intended track but it was so overgrown as to be unnavigable. I opted for the longer left track instead.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_025539117.BG1OnsOK.webp" alt="Mt Albert summit" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_025539117.CiBdMYqW.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_025539117.B4pqmzn6.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_025539117.DuPdT1h9.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_025539117.BG1OnsOK.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Mt Albert summit</figcaption></figure>
<h1>Section 4: Southern Walkway</h1>
<p>I was familiar with this one. Mostly just follow the signs, but pay attention to the shimmy past the zoo: there’s no need to go up to Truby King park so just get onto the hill road as soon as possible. Further along, take the detour along the left of the velodrome, then it’s straight up to the summit. This entire section I wasn’t doing much running. Better nutrition probably would have helped.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_035450114.CzEw1Spw.webp" alt="Mt Victoria summit. I collapsed in this spot and stayed here for a good 15 minutes." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_035450114.By9BOWzm.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_035450114.CfNof5AR.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_035450114.BXU52nww.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210125_035450114.CzEw1Spw.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Mt Victoria summit. I collapsed in this spot and stayed here for a good 15 minutes.</figcaption></figure>
<p>I struggled to walk for the rest of the day, but the sense of achievement more than made up for it. How cool is it that this marathon route is almost entirely on good-quality trail close to town and only crosses three major roads? I’ll miss you, Wellington!</p>]]>
</content:encoded>
</item>
<item>
<title>Holdsworth-Jumbo Circuit</title>
<description>Overnight tramp in Tararua Range with outrageously good views</description>
<link>https://albert.nz/holdsworth-jumbo/</link>
<guid isPermaLink="false">/holdsworth-jumbo/</guid>
<pubDate>Fri, 08 Jan 2021 00:52:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Holdsworth-Jumbo Circuit is one of the more approachable Tararua tramps. It can be split into three sections: a climb up to Mt Holdsworth Summit, traversing a ridge to Pukeahurangi / Jumbo, then descending Raingauge Spur Track to exit along the valley floor. We set out intending to spend one night at Powell Hut then a second night at Jumbo Hut.</p>
<p>This was my second overnight tramp and the first I’ve organised myself.</p>
<h1>Day 1</h1>
<p>Mostly a slog up a staircase to Powell Hut. The hut sits just above the bushline. We glimpsed it from Rocky Lookout before the clouds rolled in. We had heard that the hut had a grand view out over the Wairarapa so it was a bit disappointing to arrive at the hut with only a few metres of visibility.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08888.CNMRcutV.webp" alt="The second half of the first day walk looked something like this: thick low cloud and lots of spooky trees. Here I&#x27;m hiding in one." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08888.CXWExhRX.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08888.Bd__lHEr.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08888.8gZtMXcv.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08888.CNMRcutV.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The second half of the first day walk looked something like this: thick low cloud and lots of spooky trees. Here I'm hiding in one.</figcaption></figure>
<h1>Day 2</h1>
<p>Prayers be answered! I was woken up at 6am by footsteps on the balcony and vivid red sunlight streaming in through the windows. Venturing outside, I was greeted by sunrise between two layers of cloud. The streaks of shadow created by the hills and clouds below was a hauntingly gorgeous sight and one I won’t forget easily. It may well be the most beautiful sight I’ve ever seen.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08912-HDR.Cbz2Y6-t.webp" alt="The early sunrise was hard to photograph. Here&#x27;s the view a bit later in the morning." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08912-HDR.x6rBx35V.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08912-HDR.ClQO7aZB.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08912-HDR.CL6IT1Jb.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08912-HDR.Cbz2Y6-t.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The early sunrise was hard to photograph. Here's the view a bit later in the morning.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08924.C98kI7p1.webp" alt="Doing stretches on the balcony. With views like this I could have stretched all day." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08924.B7k2a7IR.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08924.CqACg2Yn.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08924.C1UBp_Ps.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08924.C98kI7p1.webp 2560w" style="aspect-ratio: 1.638;" data-aspect="1.638" sizes="auto" loading="lazy"><figcaption>Doing stretches on the balcony. With views like this I could have stretched all day.</figcaption></figure>
<p>We set off up the hill and then along the ridge towards Jumbo Hut. We had calm weather which allowed us to enjoy the views. We heard from some seasoned local trampers that such days don’t happen often up here.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08942.EAhM4dYR.webp" alt="The double-layer cloud sandwich remained all day, and from the tops we retained a pristine view" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08942.CHZ3wQp4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08942.CjaT2NQ-.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08942.-O7jhiwl.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08942.EAhM4dYR.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"><figcaption>The double-layer cloud sandwich remained all day, and from the tops we retained a pristine view</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08962.DPcptFhT.webp" alt="Subalpine ridge between Mt Holdsworth and Pukeahurangi / Jumbo" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08962.D7tcT0wS.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08962.BGZIrzEM.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08962.BYQAzqf7.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08962.DPcptFhT.webp 2560w" style="aspect-ratio: 1.600;" data-aspect="1.600" sizes="auto" loading="lazy"><figcaption>Subalpine ridge between Mt Holdsworth and Pukeahurangi / Jumbo</figcaption></figure>
<p>We arrived at Jumbo Hut a bit after midday for lunch. It’s a significantly more crowded hut than Powell with only a single room. We knew the hut was booked out so relaxed around the hut for a couple of hours, waiting see if many people would turn up. Sure enough, more hut dwellers arrived as well as some campers. With plenty of daylight hours remaining, we decided to cut the tramp short and exit that same afternoon.</p>
<p>Immediately below Jumbo Hut is Rainguage Spur Track. It’s a steep, brutal mess of root steps, descending over 600m in less than 2km. Running down here would suck; if you’re running this loop as a half-marathon endeavour to do it counterclockwise so you get to run down the Powell Hut stairs.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210109_023257745.DDB-fw-F.webp" alt="Descending Raingauge Spur" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210109_023257745.tlTHE0vU.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210109_023257745.BazOQj1E.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210109_023257745.C1EIvI0i.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210109_023257745.DDB-fw-F.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Descending Raingauge Spur</figcaption></figure>
<p>Beyond this, we passed the secluded Atiwhakatu Hut and left along the trail of the same name.</p>]]>
</content:encoded>
</item>
<item>
<title>Mt Matthews Track</title>
<description>The pinnacle of Wellington bushwalking</description>
<link>https://albert.nz/mt-matthews/</link>
<guid isPermaLink="false">/mt-matthews/</guid>
<pubDate>Fri, 01 Jan 2021 01:45:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>There are some astonishing bushwalks in the Wellington region, and Mt Matthews track is a prime example. For the highest peak in the Remutaka Range, the trail to the summit is well-maintained and accessible (albeit steep!). At around 26km return from the Catchpool Valley carpark, it’s best attempted in summer with good weather. You’ll also want to bring a map, clothes and emergency preparations to survive the unexpected.</p>
<p>I did the track on New Year’s Day to ring in 2021 with plenty of sweat and vertical gain. The weather was exceptional: sunny, calm and just the right temperature.</p>
<p>The track to the summit consists of three tracks: Orongorongo Track, Big Bend Track, and Mt Matthews Track.</p>
<h1>Orongorongo and Big Bend Tracks</h1>
<p>The first 8km is along these tracks. They’re great non-technical tracks so I decided to run them in both directions. This reduced the 9-10 hours return DOC time to around 6 hours. Follow Big Bend Track past Haurangi Hut to the junction with Whakanui Track. Then cross onto the riverbed and start looking for Matthews Stream to the true left of the river.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_202528658.MP.DlRKUDqd.webp" alt="Bed of the Orongorongo River. Use a map when looking for the turnoff up Matthews Stream; I initially overshot it a fair way." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_202528658.MP.CntAYP3P.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_202528658.MP.DFDqcPOK.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_202528658.MP.hqiZLR2Z.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_202528658.MP.DlRKUDqd.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Bed of the Orongorongo River. Use a map when looking for the turnoff up Matthews Stream; I initially overshot it a fair way.</figcaption></figure>
<h1>Mt Matthews Track</h1>
<p>This is the meaty bit. With well over 750m of vertical gain in 5km, I decided to walk this section. It’s a straightforward track with tons of roots for steps. It levels off near the top. I didn’t see another soul in either direction.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_213415011.BTxVl3dR.webp" alt="The clearing 100m beyond the South Saddle turnoff. The last steep section heads up this spur but there&#x27;s a fair bit more walking beyond to get to the summit." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_213415011.DCrI008g.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_213415011.CBJe68Lu.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_213415011.CwEgNMJv.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_213415011.BTxVl3dR.webp 2560w" style="aspect-ratio: 0.750;" data-aspect="0.750" sizes="auto" loading="lazy"><figcaption>The clearing 100m beyond the South Saddle turnoff. The last steep section heads up this spur but there's a fair bit more walking beyond to get to the summit.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_220113196.BdUiLcSx.webp" alt="The last 500-600m of the track is lined with these beautiful, mossy, twisted trees. It&#x27;s intensely green and my favourite section of the trail." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_220113196.B_YzeP7C.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_220113196.Cw7nYUGt.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_220113196.BLLIsmBm.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_220113196.BdUiLcSx.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The last 500-600m of the track is lined with these beautiful, mossy, twisted trees. It's intensely green and my favourite section of the trail.</figcaption></figure>
<p>If you’re short, the summit may be underwhelming; at only 941m altitude, the peak is forested and I needed to step on a log to see above the bush. Peeking above the trees, the view spans in three directions: Palliser Bay, Wellington, and Remutaka Range.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222224972.DscxbE7p.webp" alt="The clearest view is that across Palliser Bay and out to Aorangi Range on the horizon. It&#x27;s clear that this is the dividing line between Wellington and the Wairarapa!" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222224972.BLNLS8tr.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222224972.Bhm0ilJs.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222224972.CcyByffD.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222224972.DscxbE7p.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>The clearest view is that across Palliser Bay and out to Aorangi Range on the horizon. It's clear that this is the dividing line between Wellington and the Wairarapa!</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222705243.zRtSxFu6.webp" alt="If you get the angles right you can catch a glimpse of Wellington city. I had to zoom way in on my cellphone photo but that&#x27;s a large portion of the city in frame." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222705243.Ee_3lzq9.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222705243.Cswbg2OD.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222705243.D73_mF9Y.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_222705243.zRtSxFu6.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>If you get the angles right you can catch a glimpse of Wellington city. I had to zoom way in on my cellphone photo but that's a large portion of the city in frame.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_225952692.MP.D_-5c8Ye.webp" alt="Looking along Remutaka Range, with Tararua Range in the distance. I had a strange feeling while looking along the range from this angle - a feeling that I was somehow inside the Wellington skyline." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_225952692.MP.dnySep7M.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_225952692.MP.CXsSr1os.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_225952692.MP.VfNyr6VW.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201231_225952692.MP.D_-5c8Ye.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Looking along Remutaka Range, with Tararua Range in the distance. I had a strange feeling while looking along the range from this angle - a feeling that I was somehow inside the Wellington skyline.</figcaption></figure>
<h1>The return</h1>
<p>I exited in similar fashion: walking down to the river, then running Big Bend and Orongorongo Tracks back to the car. I was exhausted and probably should have taken more food.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_000203335.DEvvQVoZ.webp" alt="Typically delightful forest up Mt Matthews" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_000203335.UiA4IKHp.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_000203335.Dk7joRfE.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_000203335.BQjNTBeY.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_000203335.DEvvQVoZ.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Typically delightful forest up Mt Matthews</figcaption></figure>
<p>Climbing up Mt Matthews was a special experience—more special than I thought it would be. The mountain is visible from almost everywhere in Wellington and while descending the track, I pondered that fact: in a way, the mountain had been in the backdrop of my entire Wellington upbringing and clambering up its slopes had made me appreciate that fact. It somehow seems corny to write it, but here goes: I don’t think I have ever felt so connected to the land. This walk has left a lasting impression. If you’ve got a day to adventure in Wellington—and certainly if you live there—I hope you consider Mt Matthews Track and perhaps have a similar experience to mine.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_032744690.CP8rnXaw.webp" alt="Mt Matthews as viewed from my balcony. It&#x27;s part of the Wellington skyline and always has been." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_032744690.zhtjUtw_.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_032744690.eh4KR2bS.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_032744690.D7N14-xz.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20210101_032744690.CP8rnXaw.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Mt Matthews as viewed from my balcony. It's part of the Wellington skyline and always has been.</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Baring Head</title>
<description>A rugged attraction on Wellington's south coast</description>
<link>https://albert.nz/baring-head/</link>
<guid isPermaLink="false">/baring-head/</guid>
<pubDate>Sun, 27 Dec 2020 00:52:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Baring Head is one of my favourite places in Wellington. I have been out there a couple of times in gale force southerlies and can highly recommend the experience. I mean to come back here to explore more trails around the hills, and perhaps to attempt some coastal astrophotography.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201226_234608764.BQhisVKc.webp" alt="Ferocious wind near the lighthouse" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201226_234608764.Cc381XQe.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201226_234608764.DUUpijiG.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201226_234608764.CD0x9VUs.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201226_234608764.BQhisVKc.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Ferocious wind near the lighthouse</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201227_000137319.dVJNi_IN.webp" alt="Wainuiomata River. It was a bit more sheltered as we headed back along the river to the car." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201227_000137319.DJgoHWJ7.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201227_000137319.C2BOsb_3.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201227_000137319.BpHAeIZz.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/PXL_20201227_000137319.dVJNi_IN.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>Wainuiomata River. It was a bit more sheltered as we headed back along the river to the car.</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Adventures in headphones</title>
<description>Detailing the headphones I've tried over the last 10 years: Sennheiser, Grado, Audio-Technica, Superlux and HiFiMan.</description>
<link>https://albert.nz/adventures-in-headphones/</link>
<guid isPermaLink="false">/adventures-in-headphones/</guid>
<pubDate>Sat, 14 Nov 2020 22:25:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>I’ve tried quite a few headphones in my time. For no particular reason, I have been wanting to document my thoughts on the models I have tried. Is this a note to self? A diary? A series of mini-reviews? You decide. Whatever it is, I’ve included links to some tracks that help prove the point. All of these songs are meaningful to me and are not to be missed.</p>
<p>Headphones are listed in order of when I acquired them.</p>
<h1>Previous headphones</h1>
<h2>2010-2014: Sennheiser HD212 Pro</h2>
<p>My first over-ear headphones, purchased second-hand as an upgrade from some old Panasonic on-ears. While I can’t remember how much I bought them for, it must have been a hell of a deal as I wore these things until they starting disintegrating. I remember being impressed by by the isolation and comfort from the soft oval pads. They had a very bassy sound signature but I can’t recall the details. I do recall enjoying listening to music through these, however.</p>
<h2>2014-2015: <a href="https://www.amazon.com/gp/product/B00L1NTJAW/ref=as_li_tl?ie=UTF8&#x26;camp=1789&#x26;creative=9325&#x26;creativeASIN=B00L1NTJAW&#x26;linkCode=as2&#x26;tag=albertnis-20&#x26;linkId=27026b88960f95d8e09e0ef66554e582">GRADO SR125e</a></h2>
<p>Perhaps it was my Sennheisers falling apart, or online research during a largely idle summer, but I found myself wanting to try something different. Open headphones seemed popular and promised a more impressive sound, so I picked up some discounted Grados and an Audioquest DragonFly DAC. More or less instantly I was disappointed by these cans. They were sublime for acoustic tracks (<a href="https://www.youtube.com/watch?v=ntEUfm30eFQ"><em>The Summer You Never Meant</em></a> by The Exponents)… and nothing else. This was due to an incredibly airy upper-end which brought so much realism to strings and vocals. Lacking bass made them fall flat for electronic music. Build quality, cable and comfort were appalling also. I’ll never realise how people rave about these lower-end SR Grados. If you listen exclusively to soft rock at home and your ears are either tiny or numb I’d highly recommend these.</p>
<p>A clumsy entry into the world of nice headphones. I sold them a few weeks after purchase.</p>
<h2>2015-2019: <a href="https://www.amazon.com/gp/product/B00HVLUR86/ref=as_li_tl?ie=UTF8&#x26;tag=albertnis-20&#x26;camp=1789&#x26;creative=9325&#x26;linkCode=as2&#x26;creativeASIN=B00HVLUR86&#x26;linkId=0d0fc45ccc6b73b1527ba0f168c2215f">Audio-Technica ATH-M50xBL</a></h2>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07833.BGv3wsYc.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07833.DFLqLbwl.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07833.BmboW7zh.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07833.BGv3wsYc.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"></p>
<p>I had actually tried these before the Grados and avoided them - but the Grado experience made me reconsider what I thought I knew. The M50x sound signature had no real surprises but was a clear upgrade from my old Sennheisers in terms of clarity and balance. Moreover, I suddenly appreciated the practicality in the M50x. Removable cables, foldable design, carrying bag - perfect! Plus these things paired so well with the DragonFly DAC, had very little sound leakage, and great isolation. I remember jamming to iconic EDM tracks (<a href="https://www.youtube.com/watch?v=I9QGpHScGug"><em>Afterglow</em></a> by Wilkinson) through these things during study sessions throughout university. I bought the 2014 Limited Edition blue and tan model which looked sublime. Oddly, the tan-coloured pads seemed less comfortable than the black on the M50xs owned by my friends. A good number of those friends had purchased the M50x on my recommendation.</p>
<p>Tried and true, but I eventually became either sick or bored of their one-trick-pony sound signature which only seemed to sound great for electronic music.</p>
<h2>2016-2019: <a href="https://www.amazon.com/gp/product/B003JOETX8/ref=as_li_tl?ie=UTF8&#x26;tag=albertnis-20&#x26;camp=1789&#x26;creative=9325&#x26;linkCode=as2&#x26;creativeASIN=B003JOETX8&#x26;linkId=23bb93b047c19298e9f2bbe88574e5e7">Superlux HD668B</a></h2>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07071.COwF398B.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07071._2kYtTge.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07071.ADmdhCuh.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07071.COwF398B.webp 1536w" style="aspect-ratio: 1.294;" data-aspect="1.294" sizes="auto" loading="lazy"></p>
<p>I heard some ravings online about this semi-open model. 55NZD from Massdrop was so tempting I just bought them. My reasoning at the time was something like: <em>Maybe the “semi” in “semi-open” meant they’d be less bad than the fully-open Grados</em>. They were certainly less bad but the comfort was horrendous. The sticky vinyl pads were large but shallow and my ears rubbed on the driver. I bought some 15NZD velour pads from ebay and they instantly became some of the most comfortable headphones I’ve ever owned. The cable design is ingenious, too: The earcups have a stubby male connector and the provided cables are simply extension cords!</p>
<p>The HD668B, helped by the plush new earpads, had an awesomely spacious sound and were my go-to gaming headphones. This soundstage helped with music rendition also, but unfortunately the high frequencies were just too aggressive to derive much enjoyment from the experience, even on the Topping D30/A30 stack I purchased around this time. Even for a dedicated gaming headphone though, these are quite the deal! I sold them for the same price nearly four years later but threw in the bonus pads.</p>
<h2>2016-2020: <a href="https://drop.com/buy/massdrop-sennheiser-hd6xx">Sennheiser/Massdrop HD 6XX</a></h2>
<p>There was so much hype around the release of the HD 6XX. This is basically a full-fat HD650 at half the price, after all. A friend and I bought into the hype, each buying multiple pairs with the intention of keeping a pair and flipping the rest on the New Zealand market.</p>
<p>Frankly, the hype was well-deserved and remains to this day. I can’t say enough good things about the HD 6XX. I used it almost daily for five years and it didn’t skip a beat. The mid-heavy sound of these things is <em>lush</em>. Vocals take on this blissfully full and natural tone. High frequencies sparkle but don’t dominate. Even after getting used to the sound signature, I would reliably get goosebumps listening to certain tracks on the HD 6XX (<a href="https://www.youtube.com/watch?v=_ReWd-Pg81w"><em>Amandine Insensible</em></a> by Sevdaliza, <a href="https://www.youtube.com/watch?v=mszN_cAttaA"><em>Going Home</em></a> by Ásgeir). This is not to mention that the oval pads and light weight of this headphones provided the most comfortable listening experience I’ve ever had.</p>
<p>Bass was the main weakness of the HD 6XX in my eyes (ears?). It was there, but it was not sufficient to drive electronic music. I found it took on something of a one-note tone and never had much texture or impact to it.</p>
<p>Still, the HD 6XX provides a true high-end headphone experience. I’d recommend this or the HD58X for really taking a dive into awesome-tier headphones. Eventually I sold these to a friend. I can only hope this person derives some of the same joy from these bad boys. Hell, they’ll probably last at least another five years.</p>
<h2>2017-2020: Sony WH-1000XM2</h2>
<p>The problem with hi-fi headphones like the HD 6XX is that they’re really only good for home listening. There’s minimal convenience there. My theory was that a closed-back, wireless, noise-cancelling headphone would provide all the convenience I needed for when I wasn’t listening to my HD 6XX - plus it would add some more bass for electronic listening. The Sonys were pleasant. I found the sound quality nice enough: sometimes muddy but usually even more appealing than the ATH-M50x. The comfort was a mixed bag - they were hot and sweaty, especially on my walking commute. They also had a fair bit of heft and undersized earpads. These problems were mostly fixed in the XM3 and <a href="https://www.amazon.com/gp/product/B0863TXGM3/ref=as_li_tl?ie=UTF8&#x26;tag=albertnis-20&#x26;camp=1789&#x26;creative=9325&#x26;linkCode=as2&#x26;creativeASIN=B0863TXGM3&#x26;linkId=9da712a9f8df7b66d49a70391b3bf310">XM4</a>, I believe. Maybe I should have waited. Luckily the Sonys are always in-demand and a co-worker bought them off me.</p>
<h2>2019: Meze/Massdrop 99 Noir</h2>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07064.CPUmzrUl.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07064.ldkozRdF.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07064.CEj0bfXr.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07064.CPUmzrUl.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"></p>
<p>I had been looking for ATH-M50x replacements in pursuit of a better home-listening experience for electronic music. The identical <a href="https://www.amazon.com/gp/product/B018VZBH5M/ref=as_li_tl?ie=UTF8&#x26;tag=albertnis-20&#x26;camp=1789&#x26;creative=9325&#x26;linkCode=as2&#x26;creativeASIN=B018VZBH5M&#x26;linkId=24e3d65eaf5109652c02c656998fca1c">99 Classics</a> were selling for almost twice the price domestically. People online left very positive reviews praising a bassy sound signature that retained excellent detail and musicality.</p>
<p>Those people were wrong.</p>
<p>In a similar situation to the Grado SR125i, I found myself wondering how the hell these get such good press. The bass was overwhelming and bloated. The comfort was borderline. They were built attractively but seemed fragile. I bought replacement pads thinking I was going crazy - they didn’t help. Thank goodness the Mezes sold quickly second-hand, on TradeMe. I refused to sell them to anybody I actually knew.</p>
<h2>2020: <a href="https://www.amazon.com/gp/product/B07YBWT8PK/ref=as_li_tl?ie=UTF8&#x26;tag=albertnis-20&#x26;camp=1789&#x26;creative=9325&#x26;linkCode=as2&#x26;creativeASIN=B07YBWT8PK&#x26;linkId=842e2feb4e436bcfc15a3f37219a30a1">HiFiMan HE4XX</a></h2>
<p>I felt ready to continue on my open-backed journey. I had heard about planar magnetic headphones and how they could provide some of the better open-backed bass. What if the HE4XX was the open-backed electronic-music headphone I longed for to live alongside my HD 6XX? I bit the bullet and purchased from Drop.</p>
<p>The HE4XX was less comfortable than the HD 6XX. The pads were plush but smaller, and the whole device was heavy. Unlike the Sennheiser it was hard to forget about wearing the HE4XX. The sound was surprisingly different to the HD 6XX. It was as if some of the mid-focused prowess of the HD 6XX had been traded for more textured bass and much clearer high-frequencies. I really enjoyed the HE4XX but I returned the headphone due to a notable driver imbalance.</p>
<h1>Current headphones</h1>
<h2>2019-: <a href="https://www.amazon.com/gp/product/B0016MNAAI/ref=as_li_tl?ie=UTF8&#x26;tag=albertnis-20&#x26;camp=1789&#x26;creative=9325&#x26;linkCode=as2&#x26;creativeASIN=B0016MNAAI&#x26;linkId=860006d4af673aff6ee7d6012a91408b">Beyerdynamic DT770 Pro 80 Ohm</a></h2>
<p>After the Mezes I was still on the hunt for a bassy-enough closed-back headphone to complement the HD 6XX. The reviews of the DT770 are all over the place, largely due to the three impedance versions which have their subtle differences. I settled on the 80 Ohm ones as they ostensibly have the most bass and least treble. I bought them from a UK supplier for a good price - well under what I paid for the M50x.</p>
<p>I really didn’t know what to expect from these, but overall I’m pleased. Pleased enough that I sold my M50x for these.</p>
<p>Upon unboxing I was struck with the indestructible build quality and awesome comfort. The bass on these headphones is gorgeous and just what I was after. With the DT770, it’s all about sub-bass. It’s cavernous, as if there’s no note deep enough to miss (<a href="https://www.youtube.com/watch?v=Yj6V_a1-EUA"><em>I Found</em></a> by Amber Run). The best part is the rest of the bass isn’t too bloated. Otherwise, the sound signature is more controversial. Vocals are consistently recessed and highs can be quite sharp - though the good soundstage does add immersion. The DT770 is most at home for electronic instrumental tracks, but is a surprisingly good all-rounder once you get used to the sound. Hell, it’s not bad for gaming, either.</p>
<h2>2020-: <a href="https://www.amazon.com/gp/product/B077XDWT7X/ref=as_li_tl?ie=UTF8&#x26;tag=albertnis-20&#x26;camp=1789&#x26;creative=9325&#x26;linkCode=as2&#x26;creativeASIN=B077XDWT7X&#x26;linkId=5ddf8dc07e102a34eb279e4dfb68f597">HiFiMan Sundara</a></h2>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08185.BWwV5mQA.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08185.CaO9k9UT.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08185.BTDApijN.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08185.iA7U3W3i.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC08185.BWwV5mQA.webp 2560w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy"></p>
<p>The HE4XX experience got me thinking. I really liked the tradeoff compared to the HD 6XX.</p>
<ol>
<li>The top-notch detail afforded by the more present high frequencies was undeniable. I enjoy treble that’s crispy but not overwhelming and HE4XX had nailed that.</li>
<li>If my open-backed pair could provide enough bass for electronic listening, maybe I could get sell the DT770. I enjoy downsizing after upgrading.</li>
<li>The path to improve the HD 6XX involves purchasing expensive subjective audio gear like tube amps and the like, to “improve” the sound by adding imperfections to the audio chain. Would something like the HE4XX be the best choice on my more analytical chain?</li>
</ol>
<p>I decided to go up a level, purchasing the higher-end Sundara, the bigger brother of the HE4XX/HE-400i. Spending 580NZD on a headphone was a big purchase. To help justify it, I set the expectation that this would replace my HD 6XX, not live alongside it. A high bar, but I wasn’t disappointed.</p>
<p>The Sundara solved many of the issues with HE4XX. A better headband, a smaller and lighter design, and slightly comfier earpads were highlights. And the sound is astonishingly enjoyable. Somehow it manages to be neutral but fun. High frequencies are emphasised somewhat, leading to tons of beautiful detail. Guitar strums and vocal consonants are always clear (<a href="https://www.youtube.com/watch?v=lJJT00wqlOo"><em>Should Have Known Better</em></a> by Sufjan Stevens). Mids are less rich than the HD 6XX but plenty present: vocals seem to surround your head as opposed to caressing your eardrums (<a href="https://www.youtube.com/watch?v=EUC2CRc6Its"><em>Watch Me Read You</em></a> by Odette). The bass is noticeably nicer than the HD 6XX. It has texture and impact - nothing dominating (I’d still love a touch more) but enough to make electronic music pleasant to listen to (<a href="https://www.youtube.com/watch?v=9_UFTpfeyIY"><em>Disarm</em></a> by Alltta).</p>
<p>On poorly-produced tracks, the Sundaras won’t help: they’ll sound flat and boring. Conversely, well-produced tracks will be presented in all their glory (<a href="https://www.youtube.com/watch?v=aIHF7u9Wwiw"><em>Good News</em></a> by Mac Miller, <a href="https://www.youtube.com/watch?v=Yxy1eF_w7sU"><em>Year of the Cat</em></a> by Al Stewart).</p>
<p>The Sundaras dethroned my HD 6XX. They’re not better in all ways but for me are definitely better overall. I’ve barely touched my DT770 since using the Sundara; I think I know which headphone to sell next.</p>
<h2>2020-: <a href="https://www.amazon.com/gp/product/B07YTTXKV2/ref=as_li_tl?ie=UTF8&#x26;tag=albertnis-20&#x26;camp=1789&#x26;creative=9325&#x26;linkCode=as2&#x26;creativeASIN=B07YTTXKV2&#x26;linkId=eeec83f6e829abc892421a317f07e103">QCY T5</a></h2>
<p>True story: These 40NZD true wireless earbuds replaced my 420NZD Sony WH-1000XM2. I realised my Sonys mostly delivered the convenience I wanted but at the expense of large size and sweaty earpads. I wondered: what would happen if I maximised convenience even at the expense of sound quality? The QCY T5 sounds alright, with super nice controlled bass even if the highs can be a tinny at times and there’s a slight buzz. But the convenience is extraordinary. They fit in a pocket, I can choose to use only one earbud, and the noise isolation is decent. Absurdly good for the price.</p>
<h1>Next steps</h1>
<p>The Sundara is well in the zone of diminishing returns; I don’t see myself entering into the zone of super-high-end headphones any time soon. Rather, I’ll probably sell my DT770 and upgrade the QCY T5 to higher-end true wireless earbuds.</p>
<h1>Reflections</h1>
<p>It’s not so much headphones that I find enjoyable, it’s finding different ways to experience the music I listen to. I enjoy owning a headphone for a while then selling it as I upgrade. I don’t want to become a hoarder (refer <a href="https://www.youtube.com/watch?v=bcSa8MOz8ug">Zeos’ wall 2020</a>). I appreciate that I’ve been able to experience music through all of these headphones over the years!</p>]]>
</content:encoded>
</item>
<item>
<title>Migrating from ESPHome to WLED, the easy over-the-air way</title>
<description>Moving from ESPHome to WLED can be done without wires or worries</description>
<link>https://albert.nz/esphome-wled-migration/</link>
<guid isPermaLink="false">/esphome-wled-migration/</guid>
<pubDate>Sun, 18 Oct 2020 00:44:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Over a year ago, I <a href="/esphome-arm">migrated</a> my LED strips from Lightt to <a href="https://esphome.io/">ESPHome</a>. I have recently moved on to the popular LED strip project <a href="https://github.com/Aircoookie/WLED">WLED</a>. This means my trusty <a href="https://www.electrodragon.com/product/esp-led-strip-board/">Electrodragon ESP LED Strip Boards</a> are on their third incarnation and have nearly two years of total uptime.</p>
<h1>Switching to WLED over-the-air (OTA)</h1>
<p>Migrating to WLED is super easy using the OTA features of ESPHome combined with the pre-compiled binaries available for WLED.</p>
<ol>
<li>
<p>Enable the <code>web_server</code> for the ESPHome device (if it isn’t already). Do this by adding the following to the device config and uploading:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">web_server:</span>
  <span class="hljs-attr">port:</span> <span class="hljs-number">80</span> <span class="hljs-comment"># Or your preferred port</span>
</code></pre>
</li>
<li>
<p>Download the relevant binary from the <a href="https://github.com/Aircoookie/WLED/releases">WLED releases page</a>. All ESP32 and many ESP8266 devices are supported - you’ll probably want the <code>_ESP32.bin</code> or <code>_ESP8266.bin</code> files respectively. My Electrodragon ESP LED boards use an ESP-12F module which is supported.</p>
</li>
<li>
<p>Navigate to the webserver of the ESPHome device using either IP address or mDNS name. Under the “OTA Update” section a “Choose file” button should be present.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/2020-10-13_09-38.Gddy2Czc.webp" alt="ESPHome web server" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/2020-10-13_09-38.SN0v5nOT.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/2020-10-13_09-38.Gddy2Czc.webp 768w" style="aspect-ratio: 1.702;" data-aspect="1.702" sizes="auto" loading="lazy"><figcaption>ESPHome web server</figcaption></figure>
</li>
<li>
<p>Click “Choose file”, select the WLED binary, then click the “Update” button.</p>
</li>
<li>
<p>Now you’re ready for WLED setup, starting at step 3 in the <a href="https://github.com/Aircoookie/WLED/wiki">official quick start guide</a>.</p>
</li>
</ol>
<h1>Reflections on the migration</h1>
<p>I was looking for an alternative to ESPHome due to some major bugs which were introduced with later versions. Most notably:</p>
<ul>
<li>Transition time was ignored so all transitions were the default duration.</li>
<li>One of my strips would sometimes glitch out and flash random, strobing colours. This happened once or twice a week, especially when effects were running.</li>
</ul>
<p>Some really compelling features attracted me to WLED.</p>
<ul>
<li>Home Assistant integration</li>
<li>Hyperion/real-time support (that’s one for a separate post)</li>
<li>Customisable white-channel behaviour</li>
<li>Gamma correction for colour but not for brightness</li>
<li>Effects are more intuitive (they respect the colour and power state of the strip)</li>
</ul>
<p>I’ve been pleasantly surprised by WLED so far. The only real loss from ESPHome is the file-based configuration. What impresses me the most is how scalable the software is: It has everything you need for basic control via the app or web interface - even timers and macros are baked in. But it also has great support for integration in larger systems with Home Assistant, MQTT, and Hyperion control. Because of this, WLED is great for anyone from LED strip first-timers to experienced home automators.</p>
<p>At this point, my recommendation for almost any LED setup is simple: Buy a SK6812 LED strip and the correct power supply. Control it with a Electrodragon ESP board running WLED.</p>]]>
</content:encoded>
</item>
<item>
<title>Upgrading branded IoT devices with tuya-convert and ESPHome</title>
<description>Or how my Kogan smart plug gained superpowers over-the-air</description>
<link>https://albert.nz/tuya-convert-esphome/</link>
<guid isPermaLink="false">/tuya-convert-esphome/</guid>
<pubDate>Sat, 11 Jul 2020 07:52:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07823.DLbi4zkh.webp" alt="Kogan smart plug" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07823.ER030pEw.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07823.BjLjmZAK.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07823.Bz-9qd3J.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07823.DLbi4zkh.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Kogan smart plug</figcaption></figure>
<p>A couple of weeks ago I was given a Kogan smart plug. This controls a wall outlet over WiFi and measures power draw. It’s a feature set similar to the <a href="https://www.itead.cc/sonoff-pow-r2.html">Sonoff Pow R2</a> which I’m currently using to control my PC. It has a couple of notable benefits over the Pow R2:</p>
<ul>
<li>It is sold officially in New Zealand</li>
<li>It requires no mains wiring</li>
</ul>
<p>The Kogan smart plug is manufactured by Tuya, a company making IoT devices on behalf of many brand names around the world. Thanks to a security vulnerability in Tuya devices, they’re known for being easy to reflash over-the-air with a tool called <a href="https://github.com/ct-Open-Source/tuya-convert">tuya-convert</a>. If you’ve read many of my previous posts, you may be aware that <a href="/esphome-button-xiaomi-zigbee/">I like ESPHome</a> and that <a href="/smart-home-architecture/">I don’t like brands telling me what to do</a>.</p>
<p>With that in mind, I set out to install ESPHome on my Kogan smart plug. Here’s how I did it using tuya-convert.</p>
<blockquote>
<p>This could totally brick your device. Follow these steps at your own risk and be aware that this is not a substitute for the official guides linked below!</p>
</blockquote>
<blockquote>
<p>Unsure if your device is made by Tuya? Check out the <a href="https://github.com/ct-Open-Source/tuya-convert/wiki">tuya-convert wiki</a> to ensure your device is supported.</p>
</blockquote>
<h1>Flashing the device</h1>
<p>Getting ESPHome flashed was surprisingly easy and just a matter of following instructions on the relevant guides.</p>
<h2>1. Build an ESPHome binary</h2>
<p>Before flashing, we need a flashable binary file for the device. For ESPHome, this means creating an entity and compiling a binary. To do this I mostly just followed the <a href="https://esphome-configs.io/guides/tuya-convert/#esphome">official guide</a> so read that for more details. But the process for me boiled down to this:</p>
<ol>
<li>Go to ESPHome dashboard</li>
<li>Create a really basic config with WiFi and OTA enabled. (This is so that there’s enough information to build a firmware file and enable updating. The exact device configuration will be fleshed out later.)</li>
<li>Compile</li>
<li>Download binary</li>
</ol>
<h2>2. Flash it</h2>
<p>I used tuya-convert in Docker on a Linux laptop for this process. For this you’ll want to follow the <a href="https://github.com/ct-Open-Source/tuya-convert#using-docker">tuya-convert Docker guide</a>. Here’s a summary:</p>
<ol>
<li>
<p>Clone the <a href="https://github.com/ct-Open-Source/tuya-convert">tuya-convert</a> repo</p>
</li>
<li>
<p>Copy your ESPHome binary to the <code>files</code> directory. (You’ll need to do this before the Docker build as for some reason the binaries are copied to the image rather than mounted in the container by default.)</p>
</li>
<li>
<p>Build the image</p>
<pre><code class="hljs language-bash">docker build -t tuya .
</code></pre>
</li>
<li>
<p>Copy the docker-compose template</p>
<pre><code class="hljs language-bash"><span class="hljs-built_in">cp</span> docker/docker-compose-sample.yml docker-compose.yml
</code></pre>
</li>
<li>
<p>Open the Docker Compose file and ensure the <code>WLAN</code> environment variable is the same as the one on your PC. (<code>ip link</code> will show all interfaces; I had to change mine from <code>wlan0</code> to <code>wlp3s0</code>).</p>
</li>
<li>
<p>At this stage I needed to manually disconnect my laptop from my home WiFi network</p>
</li>
<li>
<p>Follow the instructions to run tuya-convert</p>
<pre><code class="hljs language-bash">docker-compose up -d
docker-compose <span class="hljs-built_in">exec</span> tuya start
</code></pre>
</li>
<li>
<p>The remaining instructions will be displayed in the command line</p>
</li>
</ol>
<h1>Getting it working with the new firmware</h1>
<p>At this stage the Kogan plug has been flashed with a minimal ESPHome install. It should show up as “online” in the ESPHome dashboard. The good news is that it’s free from the manufacturer’s firmware! The bad news is that the minimal firmware has no real functionality so the device won’t actually do anything.</p>
<h2>Make a configuration for the device</h2>
<p>You’ll need to build up the ESPHome configuration with components to get it working like it did before. That requires you to know which pins on the ESP8266 do what inside the device. There are a few ways to obtain this information.</p>
<h3>Look on the ESPHome Device Configuration Repository</h3>
<p>This is the easiest option. The repository is available at <a href="https://esphome-configs.io/">esphome-configs.io</a>. If your device has a known config, it should be present in the relevant section. I managed to find the Kogan smart plug <a href="https://esphome-configs.io/devices/kogan-smarterhome-smart-plug-with-energy-meter/">here</a>. Be sure to carefully copy the relevant parts of the config from the website to your ESPHome configuration. Then upload it over-the-air to the device.</p>
<h3>Look on the tuya-convert wiki</h3>
<p>If there are no known ESPHome configs for your device, you’ll need to make your own from scratch. This isn’t too hard if you know the pin numbers for the device. The <a href="https://github.com/ct-Open-Source/tuya-convert/wiki">tuya-convert wiki</a> contains a list of all supported devices. Many have GPIO pins specified. You won’t be able to copy and paste a config to ESPHome, but the site will at least show how pins are configured for that device. That makes it much easier to build a configuration yourself.</p>
<h3>Look on the Tasmota Devices Template Repository</h3>
<p>At <a href="https://templates.blakadder.com/index.html">templates.blakadder.com</a> there is a repository of devices for the popular Tasmota firmware. Look up the device and it will show which pins are used for what.</p>
<h3>Work it out by trial and error</h3>
<p>If you don’t want to open the device, I’m sure it wouldn’t hurt to try some pins. Look up similar devices using the sources above and try the pins for that device by writing a config which uses your best-guess of pin numbers. That could provide a good starting point. As always, Googling and poking around forums is probably helpful also.</p>
<h3>Look inside the device</h3>
<p>Luckily I didn’t get this far. If you still don’t know the pins for your device, the surest way to determine them is probably to disassemble the device. It should be possible to work out which pins are which by tracing outputs and inputs to the ESP-8266 board inside.</p>
<h2>Get it integrated into the home automation system</h2>
<p>ESPHome is flexible and supports tons of interaction methods. I’m using the Home Assistant integration but there are many other options. Getting it all hooked up is beyond the scope of this post so check out the <a href="https://esphome.io/index.html">ESPHome docs</a> for more information.</p>
<h1>Reflections</h1>
<p>The whole process was easier than I expected and works marvellously on my Kogan switch. So far it seems like a good way to get my desired firmware running on locally-purchasable hardware without any signs of tampering. Now I want to buy the cheapest Tuya lightbulbs I can find and repeat the process with those.</p>]]>
</content:encoded>
</item>
<item>
<title>Using three-stage Dockerfile for accessing build output</title>
<description>How to make builds consistent and flexible with a single Dockerfile</description>
<link>https://albert.nz/three-stage-dockerfile/</link>
<guid isPermaLink="false">/three-stage-dockerfile/</guid>
<pubDate>Thu, 09 Jul 2020 07:17:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p><a href="https://docs.docker.com/develop/develop-images/multistage-build/">Multi-stage Dockerfiles</a> are a tried and true method for minimising the bloat of published docker images. The workflow is pretty typical:</p>
<ol>
<li>Build an artifact using a full-fat build image</li>
<li>Move it to a minimal runtime image where it can be executed</li>
</ol>
<p>Here’s a simple example. I have a TypeScript file called <a href="https://github.com/albertnis/demo-three-stage-dockerfile/blob/master/helloworld.ts"><code>helloworld.ts</code></a>. It greets people. I want to compile this file to JavaScript and run it with node. Here’s a typical two-stage Dockerfile that will achieve that:</p>
<pre><code class="hljs language-dockerfile">FROM node:14-buster as build
WORKDIR /project
COPY package.json .
COPY package-lock.json .
RUN npm i

COPY helloworld.ts .
# Build to dist/helloworld.js
RUN ["npm", "run-script", "build"]

FROM node:14-buster-slim as runtime
COPY --from=build /project/dist/helloworld.js .
ENTRYPOINT ["node", "helloworld.js"]
</code></pre>
<p>I can use this Dockerfile to build an image and run it as a container quite easily:</p>
<pre><code class="hljs language-bash">docker build -t helloworld .
docker run --<span class="hljs-built_in">rm</span> helloworld Albert
<span class="hljs-comment"># Hello Albert!</span>
</code></pre>
<p>Neato! So what’s the problem?</p>
<h1>The problem</h1>
<p>I don’t always want to run the thing I’m building! Often (but not always) I just want to obtain the build output. This can be useful for uploading the artifact - such as for a GitHub release - and is achieved by adding an extra step to the process:</p>
<ol>
<li>(As before) Build an artifact using a full-fat build image</li>
<li><strong>Move it to a scratch image where it can be exported</strong></li>
<li>(As before) Move it to a minimal runtime image where it can be executed</li>
</ol>
<p>But how to add this second step to the Dockerfile?</p>
<pre><code class="hljs language-dockerfile">FROM node:14-buster as build
WORKDIR /project
COPY package.json .
COPY package-lock.json .
RUN npm i

COPY helloworld.ts .
# Build to dist/helloworld.js
RUN ["npm", "run-script", "build"]

# highlight-start
FROM scratch as output
COPY --from=build /project/dist/helloworld.js .
# highlight-end

FROM node:14-buster-slim as runtime
COPY --from=build /project/dist/helloworld.js .
ENTRYPOINT ["node", "helloworld.js"]
</code></pre>
<p>As before, I can build and run the script easily enough:</p>
<pre><code class="hljs language-bash">docker build -t helloworld .
docker run --<span class="hljs-built_in">rm</span> helloworld Albert
<span class="hljs-comment"># Hello Albert!</span>
</code></pre>
<p>But now I’ve got a new trick up my sleeve for extricating the build output from the container:</p>
<pre><code class="hljs language-bash">DOCKER_BUILDKIT=1 docker build -t helloworld --target output -o dist .
<span class="hljs-built_in">ls</span> dist
<span class="hljs-comment"># helloworld.js</span>
</code></pre>
<p>The key here is using a build target in combination with the <code>-o</code> flag. This flag is enabled by activating BuildKit using the environment variable. With <code>-o</code>, Docker will copy all the files from an image’s filesystem into a specified folder once the build is complete. Using a <code>scratch</code> image for the <code>output</code> stage means that we’re left with only the single artifact from the <code>build</code> stage.</p>
<p>The best part of this process is that I know that the artifact I’m building is exactly the same as what would end up in the final running Docker image. It minimises the number of miscellaneous build scripts or Dockerfiles I would otherwise need and keeps all the build steps contained in a single source of truth.</p>
<p>This approach is compatible with Docker-based CI/CD solutions such as GitHub actions; Docker-out-of-Docker can be used to run these build commands <a href="https://github.com/albertnis/demo-three-stage-dockerfile/blob/master/.github/actions/docker-build/Dockerfile">within a <code>docker</code> Docker image</a>. It’s all a bit ridiculous, but works well.</p>
<h1>Where have I used this?</h1>
<p>I took advantage of a three-stage Dockerfile in <a href="https://github.com/albertnis/mqcontrol">mqcontrol</a> where it’s being used for <a href="https://github.com/albertnis/mqcontrol/releases">cross-compilation binary output</a>. That project also has a special requirement in that there are multiple runtime base images depending on what commands mqcontrol will run. That makes it a perfect candidate for a separate build output stage.</p>
<p>Additionally, I’ve made a minimal repository for the examples above. Find it at <a href="https://github.com/albertnis/demo-three-stage-dockerfile">albertnis/demo-three-stage-dockerfile</a>. Check out the <a href="https://github.com/albertnis/demo-three-stage-dockerfile/actions">actions</a> which automate <a href="https://github.com/albertnis/demo-three-stage-dockerfile/releases">releases</a> and <a href="https://github.com/albertnis/demo-three-stage-dockerfile/packages">packages</a> all using the one core Dockerfile for building.</p>]]>
</content:encoded>
</item>
<item>
<title>Remote controlling a computer with home automation</title>
<description>Developing a simple Go application to solve a problem</description>
<link>https://albert.nz/remote-computer-control-mqtt/</link>
<guid isPermaLink="false">/remote-computer-control-mqtt/</guid>
<pubDate>Thu, 11 Jun 2020 08:45:02 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>As part of my home automation system, I like to be able to shutdown my computer remotely. This way I can safely shutdown the computer before powering it off at the wall.</p>
<p>The challenge is: how do I make the computer respond to the message and instigate a shutdown?</p>
<p>This is not a new challenge, and there are personal-project utilities all over the place which achieve the goal. Previously I was doing this with software called MqttClient, a .NET GUI which ran on Windows. Now that I’m splitting my time between multiple operating systems, I wanted a small, cross-platform utility which could run quietly in the background and subscribe to shutdown requests.</p>
<p>Introducing: <strong>mqcontrol</strong></p>
<p>mqcontrol is yet another MQTT remote control utility. It’s small, cross-platform, CLI-only and availble as a <a href="https://hub.docker.com/r/albertnis/mqcontrol">Docker image</a>. The source is available on <a href="https://github.com/albertnis/mqcontrol">GitHub</a>.</p>
<h1>How I’m using mqcontrol</h1>
<h2>Remote hibernate as part of “safe off” script</h2>
<p>I’m using mqcontrol as part of a computer shutdown script. When I activate the script, Home Assistant does the following:</p>
<ol>
<li>Send the command to hibernate the computer</li>
<li>Wait for the computer power draw to drop below 20W</li>
<li>Switch off the computer at the wall after a delay</li>
</ol>
<p>Here’s a basic version of what that script looks like in Home Assistant YAML configuration:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">computer_off_safe:</span>
  <span class="hljs-attr">alias:</span> <span class="hljs-string">Turn</span> <span class="hljs-string">computer</span> <span class="hljs-string">off</span> <span class="hljs-string">safely</span>
  <span class="hljs-attr">sequence:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">service:</span> <span class="hljs-string">mqtt.publish</span>
      <span class="hljs-attr">data:</span>
        <span class="hljs-attr">topic:</span> <span class="hljs-string">"flat/abedroom/pc/hibernate"</span>
        <span class="hljs-attr">payload:</span> <span class="hljs-string">""</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">wait_template:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{ states('sensor.computer_power')|int &#x3C; 20 }}</span>"</span>
      <span class="hljs-attr">timeout:</span> <span class="hljs-string">"00:02:00"</span>
      <span class="hljs-attr">continue_on_timeout:</span> <span class="hljs-string">"false"</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">delay:</span>
        <span class="hljs-attr">seconds:</span> <span class="hljs-number">2</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">service:</span> <span class="hljs-string">switch.turn_off</span>
    <span class="hljs-attr">data:</span>
      <span class="hljs-attr">entity_id:</span> <span class="hljs-string">switch.computer_plug</span>
</code></pre>
<p>mqcontrol runs on the computer and listens for the hibernate command.</p>
<p>On Linux I use systemd to run the following command at boot:</p>
<pre><code class="hljs language-bash">mqcontrol -c <span class="hljs-string">"systemctl hibernate"</span> -t flat/abedroom/pc/hibernate
</code></pre>
<p>On Windows I use task scheduler to run an analogous command on login:</p>
<pre><code class="hljs language-bash">mqcontrol.exe -c <span class="hljs-string">"shutdown /h /t 0"</span> -t flat/abedroom/pc/hibernate
</code></pre>
<p>This way it doesn’t matter which OS I’m booted into; the command will always cause a hibernate.</p>
<h2></h2>
<h1>Learnings</h1>
<p>For a small one-weekend project, I managed to learn a ton while developing mqcontrol. I’ll be writing more about these for sure!</p>
<ul>
<li>Basics of Golang and its tooling.</li>
<li>Docker <a href="https://docs.docker.com/develop/develop-images/build_enhancements/">BuildKit</a> and three-stage Dockerfile to use build stage for both artifact generation and runtime image creation.</li>
<li>Docker <a href="https://github.com/docker/buildx">Buildx</a> to build and push cross-platform Docker images - but using Go cross-compilation on the build platform to eliminate the need for expensive emulation.</li>
<li><a href="https://github.com/features/actions">GitHub Actions</a> and Docker-out-of-Docker for releases based on Git tags and automated CI/CD.</li>
</ul>]]>
</content:encoded>
</item>
<item>
<title>Smart home architecture - DIY vs off-the-shelf home automation setup</title>
<description>Why smart homes can be a mess, and why there's a better way</description>
<link>https://albert.nz/smart-home-architecture/</link>
<guid isPermaLink="false">/smart-home-architecture/</guid>
<pubDate>Sat, 11 Apr 2020 00:54:12 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Smart home devices are all the rage these days. It’s simple to grasp the basic concept of what such devices can do - they enable remote control and automation by connecting to the network, where they can be controlled by a server. There are many ways of building a system to achieve this outcome. I often have trouble explaining to friends what a smart home really <em>is</em>, and what it means to build a “DIY” smart home system.</p>
<p>In this post I want to look at two ends of the effort spectrum when it comes to building a smart home system: “off-the-shelf” and “do-it-yourself”. I want to show why the low-effort option has some drawbacks, and why building a more customised smart home system yourself can be rewarding if you put in the effort.</p>
<h1>Architecture</h1>
<p>Here are what the systems look like. As an example, the smart home has the following devices in both cases:</p>
<ul>
<li>A Xiaomi Aqara hub plus two bulbs, temperature sensor and a button</li>
<li>A Philips Hue gateway with two bulbs, and LED strip and a dimmer switch</li>
<li>Two Wemo smart plugs and a Wemo light switch</li>
</ul>
<h2>The “off-the-shelf” smart home</h2>
<p>You go to an electronics store and buy the components. After setting things up according the manuals you’ll end up with the following:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/smart_home_architecture_ots.DP9XBaEB.svg" alt="Architecture diagram for off-the-shelf smart home" loading="lazy"><figcaption>Architecture diagram for off-the-shelf smart home</figcaption></figure>
<p>This is the typical “default” smart home setup. What you’ll notice here is that <strong>access to the devices is separated by brand</strong> (represented by blue rectangles). For device setup and control you’ll have to use the specific app or website for the device in question.</p>
<p>The only place where all the devices can be controlled together is in Google Assistant or Alexa. This means that to automate devices across brands, you’re forced to use the automation functionality inside Google Assistant or Alexa. For example, you might want the Wemo light switch to control the Hue LED strip. This automation functionality is dictated by Google or Amazon; it may or may not be sufficient for your needs.</p>
<p>For devices which do not operate on WiFi, you’ll need separate, brand-specific gateways. This can be seen for the Xiaomi and Philips systems. Both ecosystems use Zigbee for communication, but will require different hubs to communicate with the respective servers over the internet.</p>
<h2>The “do-it-yourself” smart home</h2>
<p>With a home server and some learning, the system can be built more like the following:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/smart_home_architecture_diy.xID6Wmb3.svg" alt="Architecture diagram for DIY smart home" loading="lazy"><figcaption>Architecture diagram for DIY smart home</figcaption></figure>
<p>Crucially, unlike the off-the-shelf system, <strong>access is controlled by domain</strong> (represented by purple rectangles). This means that the app you open is not determined by <em>who made the device</em>; it’s determined by <em>what you want to do</em>. These domain-specific services each run as a web server, shown by the blue boxes. In my case, these are Docker containers all running on a single server and defined in a single docker-compose file.</p>
<ul>
<li>
<p>Want to setup, monitor or change the state of devices? Open <a href="https://www.home-assistant.io/">Home Assistant</a>. States are all managed there - with its myriad integrations, it works with almost all major smart home brands.</p>
</li>
<li>
<p>Want to integrate custom WiFi devices which don’t work out of the box? Use MQTT with <a href="https://mosquitto.org/">mosquitto</a>. Zigbee devices like the Aqara and Hue devices earlier can share a single USB gateway which talks to mosquitto via a service called <a href="https://www.zigbee2mqtt.io/">zigbee2mqtt</a>.</p>
</li>
<li>
<p>Want to configure powerful automations within Home Assistant and beyond? <a href="https://nodered.org/">Node-RED</a> is great for that.</p>
</li>
<li>
<p>Want to save time-series data, graph, and query it? <a href="https://www.home-assistant.io/integrations/prometheus/">Prometheus</a> and <a href="https://grafana.com/">Grafana</a> are an awesome duo enabling just that!</p>
</li>
<li>
<p>Want to use voice to control devices? Google Assistant or Alexa are perfect.</p>
</li>
</ul>
<h1>Comparing the two systems</h1>
<p>Advantages of the <strong>off-the-shelf system</strong>:</p>
<ul>
<li>Easy setup</li>
<li>No need for own server</li>
</ul>
<p>Advantages of the <strong>DIY system</strong>:</p>
<ul>
<li>Vendor lock-in is eliminated</li>
<li>Apps and websites for interacting with the system are separated based on use case (“domain”), not brand</li>
<li>Individual domains can be built out as desired, not limited by vendor support for various domains</li>
<li>Support for open source or custom software running on IoT devices</li>
<li>Data is stored in fewer locations</li>
<li>Data sent to the internet can be eliminated or controlled</li>
<li>Can usually be set up to retain access through vendor apps and services if desired</li>
</ul>
<h1>Why I think the DIY system is worth it</h1>
<p>The default smart home setup is exactly what manufacturers want you to have, and it’s a mess. Vendor lock-in is my main concern with the smart home industry. If you have some Hue lights and want to buy a switch, you’ll avoid a cheap Aqara switch because that would require a second hub - even though both systems use Zigbee and are physically interoperable. You’re locked in to the Hue ecosystem and will most likely opt for a Philips device.</p>
<p>In my opinion, the bottom line is this: <strong>as a user, you shouldn’t have to care who made your smart home device</strong>. You should be able to pick the best device for your needs and budget and integrate it into the rest of your system. Software like Home Assistant has made this dream realisable with a low barrier to entry.</p>
<h1>What you need to get started</h1>
<p>All you need is a home server and some smart home devices! For a server, I’d recommend starting small. An SBC such as Raspberry Pi or Odroid-XU4 is a great choice and lets you get started for under $100. I’m currently running a second-hand Intel NUC, which is more powerful - also recommended.</p>
<p>In terms of software, my suggestion is Home Assistant. For an SBC you can <a href="https://www.home-assistant.io/hassio/">download and install Home Assistant as a SD card image</a>. If you want a more flexible and modular solution, I would highly recommend using <a href="https://www.home-assistant.io/docs/installation/docker/">Docker and docker-compose</a>.</p>
<p>Search for the Home Assistant integrations for your devices and get them set up. That’s the basic work done! The next steps are up to you - pick a domain you’re interested in, learn about it, and build it yourself.</p>]]>
</content:encoded>
</item>
<item>
<title>Lakes Kohangapiripiri and Kohangatera</title>
<description>Exploring the secret lakes of Wellington's Pencarrow coastline</description>
<link>https://albert.nz/kohangapiripiri/</link>
<guid isPermaLink="false">/kohangapiripiri/</guid>
<pubDate>Sun, 08 Mar 2020 00:52:00 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Mention the Pencarrow lakes to most Wellingtonians and they’ll have no idea what you’re talking about—just like so many of the coolest outdoors attractions in the region. Beyond the well-known Pencarrow Head, there are tracks all around these coastal lakes. With so many cool picnic and sightseeing spots, allow more time than I did and make a day of it. If it’s sunny, bring plenty of sunscreen also.</p>
<blockquote>
<p>The boardwalk/bridge across upper Lake Kohangapiripiri closed for maintenance shortly after I did this walk. Please double check that it has reopened before attempting.</p>
</blockquote>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07084.42xXdi00.webp" alt="The track climbs above Lake Kohangatera" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07084.JLsQ--Pq.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07084.BWdsRp8W.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07084.BduTi-42.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07084.42xXdi00.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>The track climbs above Lake Kohangatera</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07095.CMwmnbEx.webp" alt="Back to the coast, Baring Head is visible in the distance" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07095.VErAh4ne.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07095.BMbTXkEQ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07095.sUJdL1nL.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07095.CMwmnbEx.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Back to the coast, Baring Head is visible in the distance</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07101.D699fAQ6.webp" alt="Reeds of Lake Kohangapiripiri" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07101.amAFWCNl.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07101.DUdPn_36.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07101.BAEBySYn.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07101.D699fAQ6.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Reeds of Lake Kohangapiripiri</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07104.B8eRaGlo.webp" alt="Looking down to the main Pencarrow 4WD track. The lower lighthouse is just around that corner, with the upper one visible on the hill." srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07104.Bxrm5Lxz.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07104.SWX56Y_2.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07104.DXZP_siC.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07104.B8eRaGlo.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Looking down to the main Pencarrow 4WD track. The lower lighthouse is just around that corner, with the upper one visible on the hill.</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07108.DOlIkavY.webp" alt="The upper Pencarrow lighthouse" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07108.BPnQfoP2.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07108.BpwNQevf.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07108.CRtksh8_.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC07108.DOlIkavY.webp 2560w" style="aspect-ratio: 0.667;" data-aspect="0.667" sizes="auto" loading="lazy"><figcaption>The upper Pencarrow lighthouse</figcaption></figure>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_20200308_124128.8naScMtY.webp" alt="While approaching Pencarrow Head, I stopped to watch the A350 land from a distance" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_20200308_124128.k6O-oDdB.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_20200308_124128.ZQ1x50YJ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_20200308_124128.FH8n0tkD.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/IMG_20200308_124128.8naScMtY.webp 2560w" style="aspect-ratio: 1.333;" data-aspect="1.333" sizes="auto" loading="lazy"><figcaption>While approaching Pencarrow Head, I stopped to watch the A350 land from a distance</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Making a custom multi-click push button using ESPHome</title>
<description>Or how I abandoned custom firmware because somebody else was doing it better</description>
<link>https://albert.nz/esphome-button-xiaomi-zigbee/</link>
<guid isPermaLink="false">/esphome-button-xiaomi-zigbee/</guid>
<pubDate>Mon, 17 Feb 2020 07:21:12 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Bit by bit, I’m moving all my WiFi IoT devices over to use <a href="https://esphome.io">ESPHome</a>. I migrated my <a href="/esphome-arm/">LED strips</a> a few months ago and have been impressed with the reliability and convenience of ESPHome since. This time, I’m migrating the last device which is still running custom firmware.</p>
<p>The device in question is a button which lives beside my bed. I use it for turning off my lights at night and for toggling my PC at the wall. It’s WiFi-connected and uses the same <a href="/voice-activated-lighting-hardware/">brilliant ElectroDragon board</a> as my LED strips. I made some firmware for it a while ago which was based on <a href="https://github.com/albertnis/lightt">lightt</a>, my firmware for LED strip control. It kind of sucked. Crucially, it lacked any debouncing - so single clicks on the cheap button would register as double clicks about 30% of the time.</p>
<p>I started implementing debouncing in the codebase, but quickly realised I should just move to ESPHome. The problem? ESPHome doesn’t have an all-in-one multi-click button component! Luckily the platform is flexible enough for me to implement one myself.</p>
<h1>Desired behaviour</h1>
<p>Here I was inspired by zigbee2mqtt and its <a href="https://www.zigbee2mqtt.io/devices/WXKG11LM.html">implementation</a> of multi-click behaviour for a Xiaomi Zigbee button which I migrated recently. Upon a series of clicks, the Home Assistant device emits an event with a payload corresponding to the number of clicks (e.g. <code>“single”</code>, <code>“double”</code>) immediately followed by an empty state <code>""</code>. I like this representation of an instantaneous state, so decided to follow this behaviour for consistency’s sake.</p>
<h1>Implementation</h1>
<p>The implementation of the button in ESPHome consists of two key components: a text sensor and a binary sensor.</p>
<h2>Text sensor</h2>
<p>This is responsible for emitting the correct state text, and is the part visible to the “frontend” - Home Assistant, in my case. It’s basically a default sensor but with one twist: when the state is set to a non-empty string, the sensor will set itself back to a default empty state as per the desired behaviour.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">text_sensor:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">template</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">'Bedside button'</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">bedside_button</span>
    <span class="hljs-attr">icon:</span> <span class="hljs-string">'mdi:toggle-switch'</span>
    <span class="hljs-attr">on_value:</span> <span class="hljs-comment"># When a state is set</span>
      <span class="hljs-attr">then:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">if:</span>
            <span class="hljs-attr">condition:</span>
              <span class="hljs-attr">text_sensor.state:</span>
                <span class="hljs-attr">id:</span> <span class="hljs-string">bedside_button</span>
                <span class="hljs-attr">state:</span> <span class="hljs-string">''</span>
            <span class="hljs-attr">else:</span> <span class="hljs-comment"># If non-empty</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">delay:</span> <span class="hljs-string">20ms</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">text_sensor.template.publish:</span>
                  <span class="hljs-attr">id:</span> <span class="hljs-string">bedside_button</span>
                  <span class="hljs-attr">state:</span> <span class="hljs-type">!lambda</span> <span class="hljs-string">'return "";'</span> <span class="hljs-comment"># Reset to empty</span>
</code></pre>
<h2>Binary sensor</h2>
<p>The binary sensor is the brains of the operation. It reads the actual state of the pin across which the switch is connected. The switch contains some filters: <code>invert</code> makes the logic more intuitive; <code>delayed_on</code> and <code>delayed_off</code> provide basic debouncing. The real logic lives in the <code>on_multi_click</code> section. In each case, the text sensor is invoked to set an externally visible state. Crucially, this sensor is <code>internal</code> because its only purpose is to manipulate the text sensor.</p>
<p>Here’s a config which has a single click and a hold:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">binary_sensor:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">gpio</span>
    <span class="hljs-attr">pin:</span>
      <span class="hljs-attr">number:</span> <span class="hljs-string">GPIO5</span>
      <span class="hljs-attr">mode:</span> <span class="hljs-string">INPUT_PULLUP</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">raw_switch_state</span>
    <span class="hljs-attr">internal:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">filters:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">invert:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">delayed_on:</span> <span class="hljs-string">30ms</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">delayed_off:</span> <span class="hljs-string">30ms</span>
    <span class="hljs-attr">on_multi_click:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">timing:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">ON</span> <span class="hljs-string">for</span> <span class="hljs-string">40ms</span> <span class="hljs-string">to</span> <span class="hljs-string">400ms</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">OFF</span> <span class="hljs-string">for</span> <span class="hljs-string">at</span> <span class="hljs-string">least</span> <span class="hljs-string">50ms</span>
        <span class="hljs-attr">then:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">text_sensor.template.publish:</span>
              <span class="hljs-attr">id:</span> <span class="hljs-string">bedside_button</span>
              <span class="hljs-attr">state:</span> <span class="hljs-type">!lambda</span> <span class="hljs-string">'return "single";'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">timing:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">ON</span> <span class="hljs-string">for</span> <span class="hljs-string">at</span> <span class="hljs-string">least</span> <span class="hljs-string">2s</span>
        <span class="hljs-attr">then:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">text_sensor.template.publish:</span>
              <span class="hljs-attr">id:</span> <span class="hljs-string">bedside_button</span>
              <span class="hljs-attr">state:</span> <span class="hljs-type">!lambda</span> <span class="hljs-string">'return "hold";'</span>
</code></pre>
<p>I’ve put a more complete config up on <a href="https://gist.github.com/albertnis/f33066b0b3623a9839a3429c8f19f4d4">Gist</a>. It contains definitions for double- and triple-click, as well as components for the switch’s built-in LED.</p>
<h1>Testing</h1>
<p>Once getting the config uploaded and added in Home Assistant (see my <a href="/esphome-arm/">LED strip migration</a> for more details) I can see the externally-visible components of my switch in Home Assistant via the Devices screen. Great!</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/esphome-button.CL88hRNt.webp" alt="Screenshot of ESPHome button as seen in Home Assistant" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/esphome-button.CL88hRNt.webp 480w" style="aspect-ratio: 1.495;" data-aspect="1.495" sizes="auto" loading="lazy"><figcaption>Screenshot of ESPHome button as seen in Home Assistant</figcaption></figure>
<p>To test the states, I’ll load up Node-RED with a classic debugging flow:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/nodered-button-debug.pqd3iMOV.webp" alt="Screenshot of ESPHome button state debugging in Node-RED" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/nodered-button-debug.pqd3iMOV.webp 480w" style="aspect-ratio: 5.275;" data-aspect="5.275" sizes="auto" loading="lazy"><figcaption>Screenshot of ESPHome button state debugging in Node-RED</figcaption></figure>
<p>When I press the button once, the following state changes are logged:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/nodered-button-debug-log.DYsE324x.webp" alt="Screenshot of ESPHome button state logged in Node-RED" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/nodered-button-debug-log.DYsE324x.webp 311w" style="aspect-ratio: 1.341;" data-aspect="1.341" sizes="auto" loading="lazy"><figcaption>Screenshot of ESPHome button state logged in Node-RED</figcaption></figure>
<p>That’s perfect! Look at the <code>payload</code> attribute. The state is set to “single” before being cleared almost instantly.</p>
<h1>Automation</h1>
<p>Now it’s very easy to add the desired automations in Node-RED. I just create state event nodes, restricting to the bedside button entity and the state string desired:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/esphome-button-automations.C7o300Ky.webp" alt="Screenshot of Node-RED showing various automations" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/esphome-button-automations.C7o300Ky.webp 480w" style="aspect-ratio: 2.945;" data-aspect="2.945" sizes="auto" loading="lazy"><figcaption>Screenshot of Node-RED showing various automations</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>A better alarm clock, powered by LED strips and sleep science</title>
<description>Finding inspiration in the book Why We Sleep by Matthew Walker, I fill my bedroom with blue light on a schedule</description>
<link>https://albert.nz/sleep-science-alarm-lighting/</link>
<guid isPermaLink="false">/sleep-science-alarm-lighting/</guid>
<pubDate>Mon, 17 Feb 2020 01:21:12 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>I’m currently reading <a href="https://www.amazon.com/Why-We-Sleep-Unlocking-Dreams/dp/1501144316">Why We Sleep</a> by Matthew Walker. There’s an interesting section where he discusses the potential of technology and sleep science to improve our lives in the future. I was surprised that many of the suggestions seem achievable today for anyone with the right home automation devices. Here’s an excerpt:</p>
<blockquote>
<p>Come the morning, we can… saturate our indoor environments with powerful blue light that shuts off any lingering melatonin. This will help us wake up faster, more alert, and with a brighter mood, morning after morning.</p>
</blockquote>
<p>Sounds desirable, right? I’ve been using my bedroom LED strips as an wake-up alarm light for years now. After reading the paragraph above, I was inspired to make a couple of improvements to the system.</p>
<h1>Blue light alarm</h1>
<p>This is the easy bit! Previously my wake up light had a few phases. The idea was to simulate a sunrise - the lights would shift from off to orange to white. I simplified it such that now they just turn on to melatonin-busting blue. Here’s an example of my new wake-up script in Home Assistant. It’s not rocket science and simply wraps a light.turn_on service call.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">script:</span>
  <span class="hljs-attr">wake_up:</span>
    <span class="hljs-attr">alias:</span> <span class="hljs-string">Wake</span> <span class="hljs-string">up</span>
    <span class="hljs-attr">sequence:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">alias:</span> <span class="hljs-string">Bedroom</span> <span class="hljs-string">lights</span> <span class="hljs-string">to</span> <span class="hljs-string">blue</span>
        <span class="hljs-attr">service:</span> <span class="hljs-string">light.turn_on</span>
        <span class="hljs-attr">data:</span>
          <span class="hljs-attr">entity_id:</span> <span class="hljs-string">light.bedroom_lights</span> <span class="hljs-comment"># Group light which targets all lights in the room</span>
          <span class="hljs-attr">brightness:</span> <span class="hljs-number">255</span>
          <span class="hljs-attr">rgb_color:</span> [<span class="hljs-number">0</span>, <span class="hljs-number">20</span>, <span class="hljs-number">255</span>] <span class="hljs-comment"># Blue, more or less</span>
          <span class="hljs-attr">white_value:</span> <span class="hljs-number">80</span> <span class="hljs-comment"># Add some white channel to make it more natural</span>
          <span class="hljs-attr">transition:</span> <span class="hljs-number">300</span> <span class="hljs-comment"># Fade on over 5 minutes</span>
</code></pre>
<h1>Calendar-based scheduling</h1>
<p>I used to hardcode the time for triggering my light alarm. A simple automation would trigger the script at 7:15am on weekdays and 7:45am on weekends. That worked find 95% of the time, but didn’t properly account for mornings where I needed to wake up earlier or wasn’t at home.</p>
<h2>Setting up the integration</h2>
<p>I decided to leverage the Google Calendar integration in Home Assistant. The aim is to have the wake-up light activate when the first event of the day starts. The full setup is easy enough to follow (see the <a href="https://www.home-assistant.io/integrations/calendar.google/">Home Assistant docs</a>) and boils down to a couple of lines in the configuration file:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">google:</span>
  <span class="hljs-attr">client_id:</span> <span class="hljs-type">!secret</span> <span class="hljs-string">google_client_id</span>
  <span class="hljs-attr">client_secret:</span> <span class="hljs-type">!secret</span> <span class="hljs-string">google_client_secret</span>
</code></pre>
<h2>Event strategy</h2>
<p>By default, the Google Calendar entity is a sensor that is either “on” (event in progress) or “off” (event not in progress). The docs have some interesting options for tracking events such as sensor offsets and filter keywords, but I kept mine basic because I want my calendar to remain human-readable and simple.</p>
<p>Next, I created a repeating daily event for a morning routine. This establishes a “default” wake-up time. The best part is that now I can change up my scheduling when I have something early - or delete the alarm altogether if I don’t want the lights to turn on. Here’s what my calendar mornings look like:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/cal.D0JZvsPB.webp" alt="Google Calendar showing alarm events" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/cal.CUH165fn.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/cal.D0JZvsPB.webp 768w" style="aspect-ratio: 2.637;" data-aspect="2.637" sizes="auto" loading="lazy"><figcaption>Google Calendar showing alarm events</figcaption></figure>
<h2>Triggering the alarm</h2>
<p>So far, the wake-up script is set up and so is the Google Calendar integration. Time to stitch them together with some automation goodness! As always, my go-to- for automation is Node-RED. I’m using the <a href="https://flows.nodered.org/node/node-red-contrib-time-range-switch">time range switch</a> and <a href="https://flows.nodered.org/node/node-red-contrib-home-assistant-websocket">Home Assistant websocket</a> integrations.</p>
<p>Here is a simple subflow which does the trick. The wake up light will be triggered by the first calendar event which begins between 4am and 10am. Awesome!</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/node-red-alarm.C7W13PTX.webp" alt="Google Calendar showing alarm events" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/node-red-alarm.nSpLf1m9.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/node-red-alarm.C7W13PTX.webp 768w" style="aspect-ratio: 6.575;" data-aspect="6.575" sizes="auto" loading="lazy"><figcaption>Google Calendar showing alarm events</figcaption></figure>
<h1>Bonus round: Using Google Assistant to change the alarm time</h1>
<p>Now that the system is hooked into the Google ecosystem, it’s really easy to add a calendar event using Google Assistant which will trigger the lights early using. It’s a one-liner:</p>
<blockquote>
<p>Hey Google, add a calendar event: “Wake up early for my flight” tomorrow at 5:30am.</p>
</blockquote>
<p>That’s almost as easy as setting a traditional alarm with Google Assistant! As for moving or deleting existing events, I haven’t worked that one out yet.</p>]]>
</content:encoded>
</item>
<item>
<title>Automating amplifiers with an IR blaster and Google Home</title>
<description>Dumb speakers made smart with Home Assistant and Node-RED</description>
<link>https://albert.nz/automated-amplifier/</link>
<guid isPermaLink="false">/automated-amplifier/</guid>
<pubDate>Sun, 08 Dec 2019 03:31:12 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Smart speakers are great in some regards. They have voice assistants built-in and are always on and listening. But they lack the flexibility and modularity of the classic amplifier and passive speaker setup. I have just one pair of speakers for everything - Bluetooth, Chromecast and computer audio are my use-cases. But the only way to control my <a href="http://www.smsl-audio.com/productshow.asp?id=95">AD18</a> amplifier is manually, using an IR remote.</p>
<p><strong>How can such a traditional speaker setup be automated to deliver some more smarts?</strong></p>
<p>With an IR blaster and some setup, that’s how! Here’s what I’m trying to achieve:</p>
<ul>
<li>Amp is on when needed and off when not needed</li>
<li>Amp is always on the correct input</li>
</ul>
<p>If I can achieve these things then I’ll never have to reach for the remote again!</p>
<h1>Hooking the system into Home Assistant</h1>
<h2>Power state</h2>
<p>It’s important to be able to turn the amplifier on and off. Enable power control and state with a Broadlink switch within Home Assistant. <a href="/reverse-engineering-ir-ad18/">Find the IR packet</a> for your amplifier’s on and off commands. Then register it with something like the following yaml:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">switches:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">broadlink</span>
    <span class="hljs-attr">host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.1</span><span class="hljs-number">.100</span>
    <span class="hljs-attr">friendly_name:</span> <span class="hljs-string">Bedroom</span> <span class="hljs-string">Amplifier</span>
    <span class="hljs-attr">mac:</span> <span class="hljs-string">'4A:E8:E6:9D:51:A6'</span>
    <span class="hljs-attr">switches:</span>
      <span class="hljs-attr">amp:</span>
        <span class="hljs-attr">friendly_name:</span> <span class="hljs-string">Amplifier</span>
        <span class="hljs-attr">command_on:</span> <span class="hljs-string">JgBIAAABKpEVEBU1FRAVEBU1FRAVEBUQFRAVERQ1FRAVNRU1FRAVEBU1FRAVEBUQFRAVEBUQFRAVEBU1FTUVNRQ2FDUVNRU1FQANBQ==</span>
        <span class="hljs-attr">command_off:</span> <span class="hljs-string">JgBIAAABKpEVEBU1FRAVEBU1FRAVEBUQFRAVERQ1FRAVNRU1FRAVEBU1FRAVEBUQFRAVEBUQFRAVEBU1FTUVNRQ2FDUVNRU1FQANBQ</span>
</code></pre>
<p>This should appear in Home Assistant as below:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-amplifier.BoUL_b6t.webp" alt="Screenshot of amplifier power" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-amplifier.BoUL_b6t.webp 454w" style="aspect-ratio: 1.610;" data-aspect="1.610" sizes="auto" loading="lazy"><figcaption>Screenshot of amplifier power</figcaption></figure>
<p>Pressing on and off should send the correct commands and toggle the amplifier!</p>
<h2>Input state</h2>
<p>Here’s where things get a bit harder. How can the input be changed? In my case, I want to switch between computer, Chromecast and Bluetooth inputs. Because there’s no state feedback from the amplifier, we need to keep state in Home Assistant. My amplifier has a single button to cycle through the states. Here’s how I tackled this problem.</p>
<p>I have two <code>input_select</code> entities in Home Assistant. They’re basically dropdown menus with a list of options. One is the full list of five inputs of the amplifier and will reflect the current input of the amplifier. The second is the target list - a subset of the options from the full list containing only the three desired input options.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">input_select:</span>
  <span class="hljs-attr">amp_input_current:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Input</span> <span class="hljs-string">source</span> <span class="hljs-string">(actual)</span>
    <span class="hljs-attr">options:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">Bluetooth</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">AUX</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">USB</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">OPTI1</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">Chromecast</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">COAX</span>
    <span class="hljs-attr">initial:</span> <span class="hljs-string">USB</span>
    <span class="hljs-attr">icon:</span> <span class="hljs-string">mdi:video-input-component</span>
  <span class="hljs-attr">amp_input_target:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Input</span> <span class="hljs-string">source</span>
    <span class="hljs-attr">options:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">Bluetooth</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">Chromecast</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">USB</span>
    <span class="hljs-attr">initial:</span> <span class="hljs-string">USB</span>
    <span class="hljs-attr">icon:</span> <span class="hljs-string">mdi:video-input-component</span>
</code></pre>
<p>Now add a script to select the next input. The script will turn on the amp, broadcast a “next input” command then update the current input state accordingly.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">amp_next_input:</span>
  <span class="hljs-attr">alias:</span> <span class="hljs-string">Next</span> <span class="hljs-string">amp</span> <span class="hljs-string">input</span>
  <span class="hljs-attr">sequence:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">condition:</span> <span class="hljs-string">state</span>
      <span class="hljs-attr">entity_id:</span> <span class="hljs-string">switch.amp</span>
      <span class="hljs-attr">state:</span> <span class="hljs-string">'on'</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">service:</span> <span class="hljs-string">broadlink.send</span>
      <span class="hljs-attr">data:</span>
        <span class="hljs-attr">host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.1</span><span class="hljs-number">.100</span>
        <span class="hljs-attr">packet:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">JgBYAAABKZIVEBU1FRAVEBU1FRAVEBUQFRAVEBU1FRAVNRU1FBEVEBU1FDUVNRURFBEUERQRFBEUERQRFBEVNRQ1FTUVNRU1FQAFjwABKkgUAAxZAAEpSBUADQU=</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">service:</span> <span class="hljs-string">input_select.select_next</span>
      <span class="hljs-attr">data:</span>
        <span class="hljs-attr">entity_id:</span> <span class="hljs-string">input_select.amp_input_current</span>
</code></pre>
<p>While we’re at it, add a simple script to send the command to the amplifier <em>without</em> updating the state. This is useful if the amplifier becomes out of sync with Home Assistant.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">amp_next_input_naive:</span>
  <span class="hljs-attr">alias:</span> <span class="hljs-string">Correct</span> <span class="hljs-string">input</span>
  <span class="hljs-attr">sequence:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">service:</span> <span class="hljs-string">broadlink.send</span>
      <span class="hljs-attr">data:</span>
        <span class="hljs-attr">host:</span> <span class="hljs-number">192.168</span><span class="hljs-number">.1</span><span class="hljs-number">.100</span>
        <span class="hljs-attr">packet:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">JgBYAAABKZIVEBU1FRAVEBU1FRAVEBUQFRAVEBU1FRAVNRU1FBEVEBU1FDUVNRURFBEUERQRFBEUERQRFBEVNRQ1FTUVNRU1FQAFjwABKkgUAAxZAAEpSBUADQU=</span>
</code></pre>
<p>Add the power switch, target input and correction script to the UI and it’ll look a bit like this:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-pane.CsU8_Uj1.webp" alt="Screenshot of amplifier pane" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-pane.CsU8_Uj1.webp 451w" style="aspect-ratio: 1.436;" data-aspect="1.436" sizes="auto" loading="lazy"><figcaption>Screenshot of amplifier pane</figcaption></figure>
<p>But nothing happens when the input is changed. Now it’s time to automate the link between the input selector and the next input script.</p>
<h1>Automating input selection with Node-RED</h1>
<p>When the target input source is changed, I want to keep calling the “next input” script until the actual state matches the target. Here is the flow I’m using in Node-RED:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node.BFXPWJmm.webp" alt="Screenshot of amplifier pane" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node.DiUyxMJT.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node.BFXPWJmm.webp 768w" style="aspect-ratio: 4.103;" data-aspect="4.103" sizes="auto" loading="lazy"><figcaption>Screenshot of amplifier pane</figcaption></figure>
<p>Upon a target change, the target is stored in the flow for future comparison. We check to see if the amplifier is on - no use trying to change the input if it’s off! Then we get the current input from Home Assistant. If it doesn’t match the target, we switch to the next input, wait some time for the amplifier to catch up, then repeat the process. Go ahead and try changing the input from the Home Assistant UI, then watch the amp play catch-up.</p>
<h1>Automations</h1>
<p>Here are some automations which have worked well for me over the last few months. I’ve been using the amp in conjunction with my PC, Chromecast Audio and Google Home.</p>
<h2>Go to target on amplifier power-up</h2>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node-amp-on.CcPjSJj9.webp" alt="Screenshot of amplifier pane" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node-amp-on.CQ3MQVuu.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node-amp-on.CcPjSJj9.webp 768w" style="aspect-ratio: 7.619;" data-aspect="7.619" sizes="auto" loading="lazy"><figcaption>Screenshot of amplifier pane</figcaption></figure>
<p>When the amp turns on, wait for the amp’s power-up routine, then get the target state and trigger the input selection automation.</p>
<h2>Turn on with the computer</h2>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node-pc.BaoApZwi.webp" alt="Screenshot of amplifier pane" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node-pc.CnI3ix1o.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node-pc.BaoApZwi.webp 768w" style="aspect-ratio: 4.324;" data-aspect="4.324" sizes="auto" loading="lazy"><figcaption>Screenshot of amplifier pane</figcaption></figure>
<p>When the PC is turned on at the wall plug, wait 25 seconds for the boot. If the amplifier isn’t on, turn it on and set it to USB. This ensures that if the speakers are already in use then there will be no interruption or input switching. When the PC turns off, power off the amplifier if it’s switched to USB input.</p>
<h2>Turn on when casting starts</h2>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node-chromecast.A_Vv1p7V.webp" alt="Screenshot of amplifier pane" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node-chromecast.CxwlPpxT.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/screenshot-node-chromecast.A_Vv1p7V.webp 768w" style="aspect-ratio: 2.857;" data-aspect="2.857" sizes="auto" loading="lazy"><figcaption>Screenshot of amplifier pane</figcaption></figure>
<p>If the Chromecast starts playing, turn on the amp and set it to Chromecast input. When the Chromecast becomes stopped or idle (ie not playing or paused), wait a few seconds in case it’s just a change of track or playlist. Then turn off the amplifier.</p>
<p>Instant smart speakers right there! Ask Google Assistant to play a song through the Chromecast and the speakers will magically turn on and switch to the correct input.</p>
<h1>Reflection</h1>
<p>In this project, I learnt that - with a bit of automation - even old and/or “dumb” equipment can be made to be more convenient. I use these automations every day - it’s nice to be able to ask Google to play music on the speakers or boot the computer without having to think about what the amplifier was last being used for. I haven’t touched the remote for a long time. Best of all, I barely need to open Home Assistant to correct the input manually. That’s what’s automation’s all about: removing repetitive actions and genuinely making things faster and easier.</p>]]>
</content:encoded>
</item>
<item>
<title>Using browsers as IoT screens with React and MQTT.js</title>
<description>I built mq-display to show smart home status, notifications and more!</description>
<link>https://albert.nz/iot-display-mqtt/</link>
<guid isPermaLink="false">/iot-display-mqtt/</guid>
<pubDate>Sat, 02 Nov 2019 21:33:20 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>When it comes to smart home displays, I’ve seen two main options:</p>
<ol>
<li>
<p>Use a <a href="https://www.aliexpress.com/item/32896971385.html">small 1-inch OLED screen</a> with a microcontroller.</p>
</li>
<li>
<p>Buy a tablet, <a href="https://imgur.com/a/9xMZLdS">nail it to a wall</a> and load up the Home Assistant frontend.</p>
</li>
</ol>
<p>Personally, I love the simplicity of the first option. I just want a screen to read out the weather in the morning and show me what’s playing on the Chromecast. Nothing fancy. I also like the OLED aspect - this way, I can leave the screen “on” but black by default and will appear to be off. But why settle for a cheap 1-inch display when I have an old Nexus 6 lying around? To solve my problem, I built mq-display.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06666.D_eTI5kK.webp" alt="Display on table" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06666.vXTZuqyk.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06666.-AIo_iH9.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06666.D_eTI5kK.webp 1536w" style="aspect-ratio: 1.672;" data-aspect="1.672" sizes="auto" loading="lazy"><figcaption>Display on table</figcaption></figure>
<p>Mq-display is a React frontend which allows any web browser to behave as an IoT connected display. The site subscribes to a specified MQTT broker and listens for messages. Incoming messages can have a title, brightness, display duration and markdown content. If you’re interested in setting it up for yourself, check out the <a href="https://github.com/albertnis/mq-display">GitHub repo</a>.</p>
<h1>Integrations</h1>
<p>I’m hosting mq-display and mosquitto on a home server and accessing the frontend from an old Nexus 6 with the screen turned to maximum brightness and auto-locking disabled. Because of the OLED panel, the phone emits no light when it’s showing a blank black page. As a browser, I’m using <a href="https://play.google.com/store/apps/details?id=com.thanksmister.iot.wallpanel">WallPanel</a>. This is a full-screen Android browser recommended by some members of the Home Assistant community.</p>
<h2>Now playing</h2>
<p>It’s great being able to ask Google Home to play music on my Chromecast Audio. I use the feature daily. Incredibly, the system has no great way of telling you what song is playing (in the streaming world, maybe <a href="https://bluelabyrinths.com/2019/08/26/whats-wrong-with-streaming-the-death-of-music-as-art/">people don’t care</a>). Usually when I want to find out the song I open up home assistant on my phone to see. That ends with mq-display.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06678.CfVKv6BB.webp" alt="Display held in front of speakers" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06678.CQUxdoyO.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06678.C3b6wOHa.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06678.CfVKv6BB.webp 1536w" style="aspect-ratio: 1.569;" data-aspect="1.569" sizes="auto" loading="lazy"><figcaption>Display held in front of speakers</figcaption></figure>
<p>Whenever my Chromecast Audio changes state, I check to see if the device has starting playing or has changed track. If that’s the case, I want the display to show track information. So I format it with a template and broadcast it the to the <code>virtual/screen/nowplaying</code> topic. If the Chromecast stops playing, I broadcast an empty message on the same topic, which causes mq-display to remove that screen. The Node-RED automation looks something like this:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/now-playing-screenshot.hSzffUwE.webp" alt="Node-RED screenshot showing Now Playing automation" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/now-playing-screenshot.YGdBqWu2.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/now-playing-screenshot.hSzffUwE.webp 768w" style="aspect-ratio: 4.706;" data-aspect="4.706" sizes="auto" loading="lazy"><figcaption>Node-RED screenshot showing Now Playing automation</figcaption></figure>
<p>And just like that, gone are the days of scrambling to find what song that is.</p>
<h2>Weather report</h2>
<p>Every morning, I use my phone’s browser to check what the weather’s going to be like for that day. That’s important for answering several Important Morning Questions:</p>
<ol>
<li>What should I wear?</li>
<li>Can I fly drones today?</li>
<li>How wild is ultimate frisbee going to be?</li>
</ol>
<p>Suffice it to say my weather report checking is very predictable. Therefore I can automate it.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06681.thIRyhWp.webp" alt="Display on table showing weather" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06681.ChtM73jK.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06681.CUtp-w92.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06681.thIRyhWp.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Display on table showing weather</figcaption></figure>
<p>Every morning when I get out of bed (that automation is one for another post), I set Node-RED to get the latest weather information from Dark Sky and my outdoor temperature sensor, all via Home Assistant. It aggregates a weather summary, forecasts, wind and rain information, and puts it all in a nice little summary to show on my display. The Node-RED automation is a bit more complicated for this one, but it’s repetitive. Basically I grab various bits of data from Home Assistant then append them to the end of a message. I set the message to disappear after a few minutes.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/weather-report-screenshot.DimSwL4M.webp" alt="Node-RED screenshot showing Weather Report automation" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/weather-report-screenshot.2UwQpR1E.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/weather-report-screenshot.DimSwL4M.webp 768w" style="aspect-ratio: 2.437;" data-aspect="2.437" sizes="auto" loading="lazy"><figcaption>Node-RED screenshot showing Weather Report automation</figcaption></figure>
<h1>Reflections</h1>
<p>I enjoyed mq-display as a project because it fills a niche which I can really benefit from. Hopefully other people can also see the use-case for such a project too. For me, home automation is all about being able to make repetitive behaviours effortless. I’m already finding the system useful for reducing the number of times I pick up my phone to get weather or track information; I’m sure I will find even more opportunities for automation in the future.</p>
<h2>Next steps</h2>
<p>As a frontend-only application, I see no reason why I can’t host mq-display in one place on the internet to be used by all. I even set up a build on <a href="https://mq-display.netlify.com">Netlify</a>. The problem: Netlify (and, well, most of the modern internet) serves traffic over HTTPS and therefore unencrypted websockets will not work properly in most browsers. Fixing this would be great, though I’m not sure if many home users have set up encrypted websockets on their brokers.</p>
<p>Some more fields could be quite nice for the payloads accepted by mq-display. I’m already imagining image support (cover art or camera feeds could be awesome) and maybe features for icons and key-value pairs.</p>]]>
</content:encoded>
</item>
<item>
<title>Rendering React Server-Side with AWS Lambda</title>
<description>The dawn of serverless-side rendering?</description>
<link>https://albert.nz/serverless-side-rendering/</link>
<guid isPermaLink="false">/serverless-side-rendering/</guid>
<pubDate>Sat, 20 Jul 2019 05:15:20 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p><strong>2024 update</strong>: Obfuscator has become a project I enjoy rewriting every few years to learn about different technologies. Since this post, I deployed a <a href="/htmx-cloudflare-ai">version hosted on CloudFlare Pages using htmx</a> which has replaced this React SSR version. Read on to learn more about my 2019 adventures in server-side rendering on serverless infrastructure, which has since become a very popular way of serving frontends with the rise of Next.js and other meta-frameworks. Note that some of the links below may be broken as this version is no longer hosted.</p>
<p>I’ve been working on a React project called <a href="https://obfuscator.albertnis.com">Obfuscator</a>. It’s a rewrite of an older <a href="/obfuscator">project</a> where you can enter a phrase to translate through multiple languages in sequence. Essentially it’s a fun way to make a computer translator play Chinese whispers with itself.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/top-screen.DVnS6Ieg.webp" alt="Obfuscator top screen" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/top-screen.BOCHagXv.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/top-screen.fTsooECJ.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/top-screen.DVnS6Ieg.webp 1536w" style="aspect-ratio: 1.727;" data-aspect="1.727" sizes="auto" loading="lazy"><figcaption>Obfuscator top screen</figcaption></figure>
<p>With the product well-defined and my React knowledge workable at this stage, there was something else I focused on with this project: infrastructure. I wanted the entire thing to be serverless, cheap and scalable-to-zero. Single-page applications (SPAs) with serverless APIs and data stores are nothing new. But I realised nobody was running React one their Lambdas. Surely that’s a missed opportunity! So what did I do?</p>
<p>I ran React on a Lambda.</p>
<blockquote>
<p>Code for Obfuscator can be found on <a href="https://github.com/albertnis/obfuscator-serverless">GitHub</a></p>
</blockquote>
<h2>API Gateway</h2>
<p>The infrastructure of Obfuscator looks like this:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/architecture.Csj1MhLj.webp" alt="Obfuscator architecture diagram" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/architecture.Bpq9jakC.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/architecture.CszoRQZp.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/architecture.Csj1MhLj.webp 1536w" style="aspect-ratio: 1.778;" data-aspect="1.778" sizes="auto" loading="lazy"><figcaption>Obfuscator architecture diagram</figcaption></figure>
<p>It basically boils down to an API Gateway REST API with three resources:</p>
<ul>
<li><strong>S3 Proxy</strong>: An S3 bucket contains static resources, namely the Javascript and CSS bundles. Normally with SPAs an HTML file would live in here also. But not in this case!</li>
<li><strong>/translate</strong>: Points to a Lambda which runs my translate API. This is a simple lambda which chains calls to Amazon Translate, all bundled in validation logic.</li>
<li><strong>/</strong>: The root resource is what serves the HTML when visiting <code>obfuscator.albertnis.com/</code>. Normally you’d want this to point to a static HTML file à la <code>index.html</code>. But in this case it points to a Lambda which serves up HTML based on the results on a React render.</li>
</ul>
<h2>The Application Code</h2>
<p>The React root component here is called <code>&#x3C;App /></code>. The fun part is how we include this component. Backend and frontend have separate ways to “reach” the App component - beyond that it’s shared code all the way down. That’s the magic of server-side rendering (SSR).</p>
<h3>Backend</h3>
<p>The code is pretty typical of an SSR app: it renders the app as a string and puts that string in a larger HTML template. In this case the logic gets wrapped into the Lambda entrypoint function:</p>
<pre><code class="hljs language-js"><span class="hljs-comment">// server.js</span>
<span class="hljs-keyword">import</span> view <span class="hljs-keyword">from</span> <span class="hljs-string">'../../backend/view'</span>
<span class="hljs-keyword">import</span> ssr <span class="hljs-keyword">from</span> <span class="hljs-string">'../../backend/ServerClient'</span>

<span class="hljs-comment">// Lambda entrypoint</span>
<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> <span class="hljs-title function_">server</span> = (<span class="hljs-params">event, context, callback</span>) => {
	<span class="hljs-comment">// Get string representing app HTML</span>
	<span class="hljs-keyword">const</span> { content, state } = <span class="hljs-title function_">ssr</span>()

	<span class="hljs-comment">// Inject app HTML into full HTML template</span>
	<span class="hljs-keyword">const</span> page = <span class="hljs-title function_">view</span>(<span class="hljs-string">'Obfuscator'</span>, content, state)

	<span class="hljs-title function_">callback</span>(<span class="hljs-literal">null</span>, {
		<span class="hljs-attr">statusCode</span>: <span class="hljs-number">200</span>,
		<span class="hljs-attr">headers</span>: {
			<span class="hljs-string">'Content-Type'</span>: <span class="hljs-string">'text/html'</span>
		},
		<span class="hljs-attr">body</span>: page
	})
}
</code></pre>
<p>The server client serves as the base React element wrapping <code>&#x3C;App /></code>.</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// ServerClient.tsx</span>
<span class="hljs-comment">// [...]</span>
<span class="hljs-keyword">const</span> ssr = (): { <span class="hljs-attr">content</span>: <span class="hljs-built_in">string</span>; <span class="hljs-attr">state</span>: <span class="hljs-title class_">AppState</span> } => {
	<span class="hljs-keyword">let</span> content = <span class="hljs-title function_">renderToString</span>(
		<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Provider</span> <span class="hljs-attr">store</span>=<span class="hljs-string">{store}</span>></span>
			<span class="hljs-tag">&#x3C;<span class="hljs-name">App</span> /></span>
		<span class="hljs-tag">&#x3C;/<span class="hljs-name">Provider</span>></span></span>
	)
	<span class="hljs-keyword">let</span> state = store.<span class="hljs-title function_">getState</span>()
	<span class="hljs-keyword">return</span> { content, state }
}
</code></pre>
<p>Here’s the result of the Lambda run from the the console. The rendered app is clearly present:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/lambda-console.CUk8j10Y.webp" alt="Lambda console execution" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/lambda-console.-HcUKXtP.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/lambda-console.CUk8j10Y.webp 768w" style="aspect-ratio: 2.365;" data-aspect="2.365" sizes="auto" loading="lazy"><figcaption>Lambda console execution</figcaption></figure>
<h3>Frontend</h3>
<p>The client has a simpler structure with a simple client wrapper being used:</p>
<pre><code class="hljs language-tsx"><span class="hljs-comment">// Client.tsx</span>
<span class="hljs-comment">// [...]</span>
<span class="hljs-title function_">hydrate</span>(
	<span class="xml"><span class="hljs-tag">&#x3C;<span class="hljs-name">Provider</span> <span class="hljs-attr">store</span>=<span class="hljs-string">{store}</span>></span>
		<span class="hljs-tag">&#x3C;<span class="hljs-name">App</span> /></span>
	<span class="hljs-tag">&#x3C;/<span class="hljs-name">Provider</span>></span></span>,
	<span class="hljs-variable language_">document</span>.<span class="hljs-title function_">getElementById</span>(<span class="hljs-string">'app'</span>)
)
</code></pre>
<p>Note that <code>hydrate</code> is used rather than <code>render</code>. This indicates to React that it should expect to see an existing render from the server rather than populating a previously empty element.</p>
<h2>Deploying the Application</h2>
<p>Code is bundled via webpack with multiple entrypoints:</p>
<ul>
<li><strong>Client.tsx</strong>: The frontend client ends up as the main JavaScript bundle which is sent to S3 during deployment and served statically.</li>
<li><strong>server.js</strong>: The backend bundle used for rendering on Lambda. This includes the HTML template which is populated by the render.</li>
<li><strong>translate.ts</strong>: The translate API entrypoint used on the translate Lambda.</li>
</ul>
<h2>But Why?</h2>
<p>The main reason I see to use SSR is that behaviour improves in the time it takes to download the bundle to the browser. Instead of a blank page, users see a perfectly rendered - just unresponsive - application.</p>
<h3>How Feasible is it on Lambda?</h3>
<p>It was really quite doable to get SSR up and running on Lambda. But that doesn’t mean it’s a good idea. Lambda rendering a React application is going to be slower than statically serving a pre-rendered HTML file from S3 or especially a CDN. In the case of Obfuscator, pre-rendering is clearly a superior option as the page doesn’t change between loads or for different users.</p>
<p>This app is quite a simple SPA in that it’s a single route. Architecture may get more complicated for an app with routing. I’m guessing a Lambda proxy would be useful here but I’m not sure if that would play nice with the existing S3 proxy.</p>
<p>Then there’s the issue of cold starts. While barely noticeable, it is worth considering that performing SSR on Lambda will mean slower page loads after no requests have been made for a period of time. Caching at the API Gateway level could be a winner here.</p>
<h2>Reflections</h2>
<p>It’s been nice to revisit Obfuscator and give it some TLC. It’s even nicer that it now stands in a production environment with all infrastructure defined in code. Plus the serverless architecture means I don’t pay anything if it’s not used. There are definitely improvements to be made to the frontend and more features to add (perhaps saving results to DynamoDB and making them sharable).</p>
<p>Highlights of this coding journey included TypeScript and webpack. TypeScript never ceases to amaze me by making JavaScript not suck. I made the webpack configs from scratch for this one and for the first time I’m starting to really understand how to configure it.</p>
<p>As for using Lambdas for rendering - SSR for Obfuscator is totally unnecessary. One day I might look into ways of doing pre-rendering instead. But if your application is truly a good candidate for SSR just know that it is possible with Lambda.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/bottom-screen.QulC51IX.webp" alt="Obfuscator bottom screen" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/bottom-screen.QEBO1O_c.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/bottom-screen.A6p-OzRz.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/bottom-screen.QulC51IX.webp 1536w" style="aspect-ratio: 1.875;" data-aspect="1.875" sizes="auto" loading="lazy"><figcaption>Obfuscator bottom screen</figcaption></figure>]]>
</content:encoded>
</item>
<item>
<title>Using ESPHome on a Raspberry Pi with Docker</title>
<description>Moving old devices to a powerful new platform</description>
<link>https://albert.nz/esphome-arm/</link>
<guid isPermaLink="false">/esphome-arm/</guid>
<pubDate>Sat, 13 Jul 2019 23:09:20 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>A few weeks ago I changed the firmware on my LED strip controllers, migrating them from my custom software Lightt to <a href="https://esphome.io/">ESPHome</a>. I really enjoyed Lightt as a project. It taught me a ton about MQTT, LED strips and how home automation software interfaces with lights in general. It was also surprisingly reliable and had months of uptime on my strips. I identified <a href="https://github.com/albertnis/lightt/issues">several issues</a> in Lightt which needed attention, such as OTA flashing and LED effects. Rather than implementing these features myself I decided to cut my losses and move to ESPHome which already supports these things.</p>
<p>So what is ESPHome? It’s a home automation device manager for ESP8266 and ESP32 devices. It takes a yaml configuration file and uses it to compile a flashable firmware binary for an ESP device. ESPHome also includes a dashboard for managing devices and reconfiguring them - all over-the-air and within the dashboard! There are two components to ESPHome: the software that generates binaries, and the binaries themselves which run on devices. I’m going to call the software “ESPHome” and the binaries “ESPHome firmware” to avoid confusion.</p>
<p>There are really four steps for setting up an ESPHome system:</p>
<ul>
<li>Install ESPHome on a PC</li>
<li>Configure the device through the ESPHome dashboard</li>
<li>Upload a binary over serial (first-time use)</li>
<li>Upload a binary over-the-air</li>
</ul>
<h2>Install ESPHome on a PC</h2>
<p>First, get ESPHome running on a PC. Here’s a docker-compose v1 entry I’m using on an Odroid XU4 (ARMv7 like a Raspberry Pi). This will also work on an x86 PC if you remove the <code>-armv7</code>. Remember that ESPHome is just a configuration and build tool for the devices. It’s not required to be running all the time, just when you want to configure devices. The devices will maintain connectivity without ESPHome running on a server.</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">esphome:</span>
  <span class="hljs-attr">container_name:</span> <span class="hljs-string">esphome</span>
  <span class="hljs-attr">net:</span> <span class="hljs-string">'host'</span>
  <span class="hljs-attr">restart:</span> <span class="hljs-string">always</span>
  <span class="hljs-attr">user:</span> <span class="hljs-number">0</span><span class="hljs-string">:0</span>
  <span class="hljs-attr">environment:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">HOME</span> <span class="hljs-string">=</span> <span class="hljs-string">"/"</span>
  <span class="hljs-attr">image:</span> <span class="hljs-string">esphome/esphome-armv7:1.13.2</span>
  <span class="hljs-attr">ports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:6052:6052</span>
    <span class="hljs-bullet">-</span> <span class="hljs-number">0.0</span><span class="hljs-number">.0</span><span class="hljs-number">.0</span><span class="hljs-string">:6123:6123</span>
  <span class="hljs-attr">volumes:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">/mnt/dietpi_userdata/esphome-docker:/config:rw</span>
  <span class="hljs-attr">command:</span> <span class="hljs-string">config/</span> <span class="hljs-string">dashboard</span>
</code></pre>
<p>Run the docker-compose file with <code>docker-compose -f docker-compose.yml up -d</code> or the equivalent <code>docker run</code> command.</p>
<p>Now go to the server at port 6052 and you should be greeted with the following dashboard. At this stage there will be no devices present.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash.DeovDv4n.webp" alt="ESPHome dashboard" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash.BocAAAGg.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash.DeovDv4n.webp 768w" style="aspect-ratio: 1.212;" data-aspect="1.212" sizes="auto" loading="lazy"><figcaption>ESPHome dashboard</figcaption></figure>
<h2>Configure the Device Through the ESPHome Dashboard</h2>
<p>Press the pink “Add” button and follow the prompts before hitting “Submit”. You should see your new device listed on the dashboard. Click “Edit” next the new device and a yaml editor will appear. In the yaml configuration file you can specify all the features relevant to the device. This could include sensors, switches, lights, or any combination of things listed on the <a href="https://esphome.io/">official website</a>. In this case, we’re just using lights. My configuration file looks like this (secrets redacted):</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">esphome:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">desktop_light</span>
  <span class="hljs-attr">platform:</span> <span class="hljs-string">ESP8266</span>
  <span class="hljs-attr">board:</span> <span class="hljs-string">esp01_1m</span>

<span class="hljs-attr">wifi:</span>
  <span class="hljs-attr">ssid:</span> <span class="hljs-string">'ssid_here'</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">'wifi_password_here'</span>

<span class="hljs-comment"># Enable logging</span>
<span class="hljs-attr">logger:</span>

<span class="hljs-comment"># Enable Home Assistant API</span>
<span class="hljs-attr">api:</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">'api_access_password_here'</span>

<span class="hljs-attr">ota:</span>
  <span class="hljs-attr">password:</span> <span class="hljs-string">'ota_access_password_here'</span>

<span class="hljs-attr">light:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">neopixelbus</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">'Desktop light'</span>
    <span class="hljs-attr">id:</span> <span class="hljs-string">desktop_light</span>
    <span class="hljs-attr">restore_mode:</span> <span class="hljs-string">RESTORE_DEFAULT_OFF</span>
    <span class="hljs-attr">type:</span> <span class="hljs-string">GRBW</span>
    <span class="hljs-attr">pin:</span> <span class="hljs-string">GPIO2</span>
    <span class="hljs-attr">num_leds:</span> <span class="hljs-number">144</span>
    <span class="hljs-attr">variant:</span> <span class="hljs-string">SK6812</span>
    <span class="hljs-attr">method:</span> <span class="hljs-string">ESP8266_UART1</span>
    <span class="hljs-attr">default_transition_length:</span> <span class="hljs-string">300ms</span>
    <span class="hljs-attr">effects:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">addressable_rainbow:</span>
          <span class="hljs-attr">width:</span> <span class="hljs-number">200</span>
          <span class="hljs-attr">speed:</span> <span class="hljs-number">1</span>
</code></pre>
<p>Take some time to craft your configuration yaml, then click “Save” to save the changes before closing the editor to return to the dashboard.</p>
<h2>Upload a Binary Over Serial</h2>
<p>OTA firmware flashing relies on ESPHome firmware being installed in the first place! This means that for first-time setup on each ESP device, ESPHome firmware will need to be flashed to the ESP device manually over serial. Luckily ESPHome makes this quite easy. Click the menu dropdown next to the newly-created device.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash-menu-options.DkuMkMyp.webp" alt="Dashboard showing menu options" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash-menu-options.99oAlw-0.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash-menu-options.DkuMkMyp.webp 768w" style="aspect-ratio: 2.824;" data-aspect="2.824" sizes="auto" loading="lazy"><figcaption>Dashboard showing menu options</figcaption></figure>
<p>Click the “Compile” button. ESPHome will do its magic, generating C++ code based on the yaml configuration before compiling it for the ESP device. It uses <a href="https://platformio.org/">PlatformIO</a> behind the scenes.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash-compile-success.tHa1pQtZ.webp" alt="Dashboard showing successful compilation" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash-compile-success.D0PpX_M4.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash-compile-success.tHa1pQtZ.webp 768w" style="aspect-ratio: 1.212;" data-aspect="1.212" sizes="auto" loading="lazy"><figcaption>Dashboard showing successful compilation</figcaption></figure>
<p>Now click “Download binary” to save the ESPHome firmware. Plug in the ESP device to your computer over serial (using an FTDI adapter if needed). To flash the chip, I recommend using <a href="https://github.com/espressif/esptool">esptool.py</a> with the <a href="https://github.com/espressif/esptool#write-binary-data-to-flash-write_flash"><code>write_flash</code></a> command.</p>
<h2>Upload a Binary Over-the-Air</h2>
<p>The hard work is over! Now that the ESP devices have the ESPHome firmware flashed, they should appear as “Online” in the dashboard. Future configuration changes can now be pushed directly to the devices OTA. Simply find the “Upload” button either next to the dashboard entry for the device or below the yaml editor. Pressing this button will compile the binary as before, then poll the device until it comes back online with the new configuration.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash-upload-success.DLz3n_uJ.webp" alt="Dashboard showing successful upload" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash-upload-success.C0CMWq8_.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dash-upload-success.DLz3n_uJ.webp 768w" style="aspect-ratio: 1.218;" data-aspect="1.218" sizes="auto" loading="lazy"><figcaption>Dashboard showing successful upload</figcaption></figure>
<p>It’s all working nicely. Remember that each ESP device exposes its own API, so ESPHome itself is only really needed for configuration changes and compilation.</p>
<h1>Adding the Device in Home Assistant</h1>
<p>Home Assistant has a solid ESPHome integration. Simply navigate to the Configuration -> Integrations screen and discovered devices can be added easily. If you’re doing a migration, I’d recommend renaming old devices <em>before</em> adding the ESPHome ones to avoid IDs being suffixed by Home Assistant. If set, enter the API password used earlier. Once added, you can view the entities associated with the device.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ha-esphome.B-0abUNw.webp" alt="ESPHome device configuration in Home Assistant" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ha-esphome.NR16plI8.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ha-esphome.B-0abUNw.webp 768w" style="aspect-ratio: 1.928;" data-aspect="1.928" sizes="auto" loading="lazy"><figcaption>ESPHome device configuration in Home Assistant</figcaption></figure>
<h1>Reflections</h1>
<p>The migration process was quite easy once I worked out how to get the Docker image up and running (<code>user: 0:0</code> and <code>- HOME = ”/”</code> were key). In fact, the <a href="https://esphome.io/guides/getting_started_command_line.html">official guide</a> says setup is only possible on an RPi using Hass.io or <code>pip</code>, which is bogus. These days, if I can’t Dockerise a self-contained service then I’m quite hesitant to install it on a server.</p>
<p>I’m enjoying the simplicity of modifying configurations and also the gamma-correction features that are baked into to ESPHome’s light component. One point of disappointment is how the firmware manages the white channel on my RGBW LED strip. I would prefer the white channel not be directly accessible and instead be used to add CRI to the existing RGB channels (in other words, use white LEDs for brightness and add colour cast using RGB only as needed). That’s the way Xiaomi Yeelights work, and I modeled Lightt on that behaviour.</p>
<p>ESPHome has some quite powerful integrations and components which I’ll definitely consider in future projects as an alternative to (poorly) coding C++ myself. It’s also a good alternative to <a href="https://github.com/arendst/Sonoff-Tasmota">Tasmota</a> which I’m currently running on a Sonoff Pow R2 switch. Perhaps a migration for that switch is the next project!</p>]]>
</content:encoded>
</item>
<item>
<title>How to Automate a Heat Pump Unit</title>
<description>Bringing smarts to dumb air conditioning units</description>
<link>https://albert.nz/automating-ac/</link>
<guid isPermaLink="false">/automating-ac/</guid>
<pubDate>Sat, 15 Jun 2019 02:10:20 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>In the <a href="/reverse-engineering-ac">previous article</a> I went into the details of reverse-engineering a heat pump remote. We got to the stage were it was possible to send an arbitrary state to the heat pump by constructing IR packets from scratch. I even wrote some code to do the packet construction (<a href="https://github.com/albertnis/fujitsu-ar-ry13-ir-codes">view it on GitHub</a>). But how do we make this useful? As a Home Assistant user, I wanted to be able to control the heat pump via Home Assistant. This would open up a world of automation potential as well as Google Assistant and Alexa control.</p>
<h1>Automate</h1>
<p>From the get-go, I had an idea of how I was going to implement this process. A virtual heat pump entity will be present and state-managed in Home Assistant. Changes to the state of this fake heat pump will get picked up by a Node-RED automation which will construct a packet and sent it to the heat pump via the Home Assistant Broadlink blaster component.</p>
<h2>Home Assistant</h2>
<p>I needed a simple virtual heat pump entity in Home Assistant. The <a href="https://www.home-assistant.io/components/climate.mqtt/">climate.mqtt</a> entity is perfect for this. It operates optimistically, which means it assumes any commands are applied successfully. As a result the virtual state is updated instantly upon sending a command. Optimistic state management is generally less desirable than proper state feedback, but it’s the best we can do in this case - that’s how the original remote operates, anyway.</p>
<p>A suitable climate entity looks like this:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">climate:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">mqtt</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">fujitsu</span>
    <span class="hljs-attr">qos:</span> <span class="hljs-number">2</span>
    <span class="hljs-attr">availability_topic:</span> <span class="hljs-string">virtual/nodered/availability</span>
    <span class="hljs-attr">min_temp:</span> <span class="hljs-number">16</span>
    <span class="hljs-attr">max_temp:</span> <span class="hljs-number">30</span>
    <span class="hljs-attr">temp_step:</span> <span class="hljs-number">1</span>
    <span class="hljs-attr">modes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'off'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'auto'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'cool'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'dry'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'fan_only'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'heat'</span>
    <span class="hljs-attr">fan_modes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'Auto'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'Quiet'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'Low'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'Med'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'High'</span>
    <span class="hljs-attr">swing_modes:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'Off'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'Vertical'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'Horizontal'</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">'Both'</span>
    <span class="hljs-attr">power_command_topic:</span> <span class="hljs-string">'flat/lounge/heatpump/power/set'</span>
    <span class="hljs-attr">mode_command_topic:</span> <span class="hljs-string">'flat/lounge/heatpump/mode/set'</span>
    <span class="hljs-attr">temperature_command_topic:</span> <span class="hljs-string">'flat/lounge/heatpump/temperature/set'</span>
    <span class="hljs-attr">fan_mode_command_topic:</span> <span class="hljs-string">'flat/lounge/heatpump/fan/set'</span>
    <span class="hljs-attr">swing_mode_command_topic:</span> <span class="hljs-string">'flat/lounge/heatpump/swing/set'</span>
    <span class="hljs-attr">current_temperature_topic:</span> <span class="hljs-string">'flat/lounge/temperature/current'</span>
</code></pre>
<p>We actually don’t need the MQTT command functionality that this climate entity provides as we’ll just be directly monitoring the state from Node-RED. But I defined topics anyway as it won’t do any harm. The heat pump looks like this on the Home Assistant dashboard:</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ha-heatpump-detail.3dLI1mcT.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ha-heatpump-detail.3dLI1mcT.webp 433w" style="aspect-ratio: 0.557;" data-aspect="0.557" sizes="auto" loading="lazy">
<p>Perfect! You might not have a current temperature readout at this stage, but I’ll address that with a Node-RED automation later in the article. We can play around with the virtual heat pump at this stage. Obviously, the heat pump will not respond as we haven’t developed an automation yet. The state on the virtual heatpump will update, however.</p>
<h2>Docker</h2>
<p>Before we move on to automating the heat pump, we need to find somewhere for the packet-generating code to live. As a sadistic web developer, I couldn’t pass up the opportunity to wrap the code in a simple web API and put it in a Docker container.</p>
<blockquote>
<p>You can totally do this without Docker. The best option would be to organise the code into script (“function”) nodes in Node-RED. JavaScript code is available at the repo. The rest of this article will assume you have the code running in some kind of web API, however.</p>
</blockquote>
<p>We just need to build an image and run the container with something like the following:</p>
<pre><code class="hljs language-bash">git <span class="hljs-built_in">clone</span> https://github.com/albertnis/fujitsu-ar-ry13-ir-codes
<span class="hljs-built_in">cd</span> fujitsu-ar-ry13-ir-codes
docker build -t ir-sandbox .
docker run -p 8080:8080 -d ir-sandbox --name=<span class="hljs-string">"ir-sandbox"</span> --restart=<span class="hljs-string">"always"</span>
</code></pre>
<p>I’ll be adding this to my home automation docker-compose file someday, but for now the manual build and run does the trick! At this stage IR codes can be constructed via the API. For example:</p>
<pre><code class="hljs language-bash">$ curl <span class="hljs-string">"http://localhost:8080/broadlink?tempC=18&#x26;mode=heat&#x26;fanSpeed=Quiet&#x26;swing=Off&#x26;powerOn=0"</span>
JgAGAWg0DQ0NDQ0mDQ0NJg0NDQ0NDQ0mDSYNDQ0NDQ0NJg0mDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NJg0NDQ0NDQ0NDQ0NDQ0NDSYNDQ0NDQ0NDQ0mDSYNJg0mDSYNJg0mDSYNDQ0NDSYNDQ0NDQ0NDQ0NDQ0NDQ0NDSYNJg0NDQ0NDQ0NDQ0NDQ0NDSYNDQ0NDQ0NDQ0mDQ0NDQ0NDQ0NDQ0NDQ0NJg0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDSYNDQ0NDQ0NDQ0NDSYNDQ0NDQ0NJg3/DQUAAA==
</code></pre>
<h2>Node-RED</h2>
<p>Time for the fun part! I made an automation flow which will run every time the virtual heat pump’s state changes. It looks like this:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/node-red-heatpump.BogsvKB8.webp" alt="Node-RED automation flow" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/node-red-heatpump.D_FCigFG.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/node-red-heatpump.BogsvKB8.webp 768w" style="aspect-ratio: 3.810;" data-aspect="3.810" sizes="auto" loading="lazy"><figcaption>Node-RED automation flow</figcaption></figure>
<p>I’ll run through the steps briefly:</p>
<ol>
<li>
<p><strong>Heatpump state changed</strong> runs whenever something about the heat pump is altered. This includes mere changes to the ambient temperature.</p>
</li>
<li>
<p><strong>Current temp change</strong> halts the flow if only the current temperature is changed. We don’t need to send IR commands if the ambient temperature changes in the room. It causes the heat pump to beep all the time which is annoying. We’re only interested in actual command changes.</p>
</li>
<li>
<p><strong>Is it off now?</strong> If the heat pump is newly off, a Home Assistant script is run which just broadcasts the stateless off code.</p>
</li>
<li>
<p><strong>Newly on?</strong> Sets the relevant parameter depending on whether the heat pump is just turning on or has already been powered up. This parameter matters for construction of the payload for some reason.</p>
</li>
<li>
<p><strong>Map state to remote query</strong> just reorganises new state information into a neater structure within the flow message under the <code>query</code> key. This step isn’t strictly needed.</p>
</li>
<li>
<p><strong>Construct query string</strong> is a template node which changes the <code>query</code> object into a string to be passed to our API.</p>
<pre><code class="hljs language-txt">?tempC={{query.tempC}}&#x26;mode={{query.mode}}&#x26;fanSpeed={{query.fanSpeed}}&#x26;swing={{query.swing}}&#x26;powerOn={{query.powerOn}}
</code></pre>
</li>
<li>
<p><strong>Get remote code</strong> calls the API.</p>
</li>
<li>
<p><strong>Set packet</strong> moves the response string into the <code>msg.payload</code> key of the flow message.</p>
</li>
<li>
<p><strong>Send IR code</strong> is where we close the loop. It invokes the <code>switch.broadlink_send_packet</code> service in Home Assistant to send our code to a Broadlink IR blaster entity. The data template looks like this:</p>
<pre><code class="hljs language-txt">{
  "packet": "{{payload}}"
}
</code></pre>
</li>
</ol>
<p>There’s just one more automation we need to set up, to get current temperature showing up in the heatpump entity. I don’t have access to the heat pump’s thermostat so I circumvented this by putting a <a href="https://www.aliexpress.com/item/2018-Xiaomi-Aqara-Smart-Air-Pressure-Temperature-Humidity-Environment-Sensor-Smart-control-via-Mihome-APP-Zigbee/32867769187.html?">Aqara temperature sensor</a> in the same room. The following automation flow maps any temperature changes of that sensor to the MQTT topic which was set as the <code>current_temperature_topic</code> of our virtual heatpump:</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/node-red-temperature.CyoBpzGR.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/node-red-temperature.CyoBpzGR.webp 480w" style="aspect-ratio: 8.727;" data-aspect="8.727" sizes="auto" loading="lazy">
<h1>Wrap-up</h1>
<p>Time to see this beast in action!</p>
<iframe src="https://www.youtube.com/embed/rYGHHMw8gq8" frameborder="0" class="video" allowfullscreen></iframe>
<p>Without a doubt, working on this heat pump has been one of the most satisfying home automation projects I have worked on for a while. From learning about how IR communication really works, to cracking the code of the remote, to wrapping the whole thing in an API - it’s been a great learning experience.</p>]]>
</content:encoded>
</item>
<item>
<title>How to Reverse Engineer a Heat Pump Unit</title>
<description>Cracking the code and paving the way to automation</description>
<link>https://albert.nz/reverse-engineering-ac/</link>
<guid isPermaLink="false">/reverse-engineering-ac/</guid>
<pubDate>Fri, 14 Jun 2019 23:09:20 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06428.lUaS0qWG.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06428.5AixjfyW.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06428.CR2722FG.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/DSC06428.lUaS0qWG.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"></p>
<p>Recently, I learnt about some of the <a href="/reverse-engineering-ir-ad18">basics of IR communication</a>. These fundamentals are interesting, but I wanted to apply them to a real-world situation: automating my heat pump. The aim was to be able to set the heat pump to any setting using Home Automation software and an IR blaster, without the heat pump’s own remote control. Here’s how I did it - hopefully the steps are helpful for anybody else looking to get into heat pump automation.</p>
<blockquote>
<p>There are various encodings of IR payloads below. It gets a bit technical and I’ll be switching between pronto and Broadlink notation without much explanation. If you’re new to this I highly recommend checking out the <a href="/reverse-engineering-ir-ad18">article on the basics</a> before proceeding.</p>
</blockquote>
<h1>Research</h1>
<p>Before diving blindly into the project, I did some digging. First of all, I found the model of my remote: in this case, a Fujitsu AR-RY13. Like most heat pump or A/C remotes, the state of the heat pump is managed on the remote. That means that when you, say, change the temperature on the remote, an IR code will be emitted which includes the remote’s entire state, including things like swing mode, fan speed, and operation mode. There are some codes - like the power off code - which are <em>stateless</em> and don’t change based on heat pump state.</p>
<p>In researching similar remotes online, I came across a <a href="http://files.remotecentral.com/library/21-1/fujitsu/air_conditioner/index.html">useful document</a> put together by David Abrams on Remote Central. It contains details of the IR protocol used in Fujitsu’s AR-RY16 remote. The model name sounded similar to my AR-RY13 so I used this document as a starting point, assuming the two protocols would be identical or similar.</p>
<h1>Reverse-engineer protocol</h1>
<p>There are two things that need to be reverse engineered here:</p>
<ul>
<li><strong>Timing of the remote codes</strong>: How bits of data are converted into IR pulses.</li>
<li><strong>Construction of the data packets</strong>: How heat pump states are converted in bits of data.</li>
</ul>
<p>I tackled timing of the remote codes first because it will let us try out some basic stateless codes without worrying about constructing state packets.</p>
<h2>Timing of remote codes</h2>
<p>The off code is an example of a stateless code which can be sent to the heat pump. Using the Broadlink RM Mini 3’s learning capability, I captured the off packet of my remote control. It looks like this:</p>
<pre><code class="hljs language-txt">JgB2AG4zDwsQCxAmDwwPJhALEAsPDA8mECYQCw8MDwsQJhAmDwsQCxALEAsPDA8MDwsQCxALEAsPDA8LEAsQJg8MDwsQCxALEAsPDA8LECYQCxALDwwPCxAmEAsPDA8LEAsQCxALDyYQCxAmECYPJhAmDyYQJhAADQUAAA==
</code></pre>
<p>That’s the base64 representation which is used by Broadlink devices. Decoding it reveals the following byte array:</p>
<pre><code class="hljs language-txt">[26, 00, 76, 00, 6e, 33, 0f, 0b, 10, 0b, 10, 26, 0f, 0c, 0f, 26, 10, 0b, 10, 0b, 0f, 0c, 0f, 26, 10, 26, 10, 0b, 0f, 0c, 0f, 0b, 10, 26, 10, 26, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 0f, 0c, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 0f, 0b, 10, 0b, 10, 26, 0f, 0c, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 0f, 0b, 10, 26, 10, 0b, 10, 0b, 0f, 0c, 0f, 0b, 10, 26, 10, 0b, 0f, 0c, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 26, 10, 0b, 10, 26, 10, 26, 0f, 26, 10, 26, 0f, 26, 10, 26, 10, 00, 0d, 05, 00, 00]
</code></pre>
<p>In Broadlink protocol, most of these numbers represent timings in multiples of 2^-15s (the first few and last few are a <a href="https://github.com/mjg59/python-broadlink/blob/master/protocol.md#sending-data">bit different</a>). I know that this code, when sent to the heat pump, will turn it off. The trick now is to try to derive this same code from the information in David Abrams’ document. Then I will know that I have worked out the timing information for the heat pump and can send any bits to the heat pump. The document says this is the code:</p>
<pre><code class="hljs language-txt">00101000110001100000000000001000000010000100000010111111
</code></pre>
<p>That’s a series of bits which need to be converted to a code. The document says that “one” bits become <code>0010 0010</code> and “zero” bits become <code>0010 002e</code>. Those are <em>pronto codes</em> which means each hex value is a number of cycles. I added the leader and trailer specified in the document then converted this to a Broadlink code by using the frequency 39kHz, close to the 38kHz specified by Abrams. I chose this because the resulting code had the “26” values which I saw in the real packet. This is a good indicator that the frequency is close. Here’s what this generated value looks like, with the real bytes (from before) alongside:</p>
<pre><code class="hljs language-txt">Real:      [26, 00, 76, 00, 6e, 33, 0f, 0b, 10, 0b, 10, 26, 0f, 0c, 0f, 26, 10, 0b, 10, 0b, 0f, 0c, 0f, 26, 10, 26, 10, 0b, 0f, 0c, 0f, 0b, 10, 26, 10, 26, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 0f, 0c, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 0f, 0b, 10, 0b, 10, 26, 0f, 0c, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 0f, 0b, 10, 26, 10, 0b, 10, 0b, 0f, 0c, 0f, 0b, 10, 26, 10, 0b, 0f, 0c, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 26, 10, 0b, 10, 26, 10, 26, 0f, 26, 10, 26, 0f, 26, 10, 26, 10, 00, 0d, 05, 00, 00]
Generated: [26, 00, 76, 00, 68, 34, 0d, 26, 0d, 26, 0d, 0d, 0d, 26, 0d, 0d, 0d, 26, 0d, 26, 0d, 26, 0d, 0d, 0d, 0d, 0d, 26, 0d, 26, 0d, 26, 0d, 0d, 0d, 0d, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 0d, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 0d, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 0d, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 0d, 0d, 26, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, ff, 0d, 05, 00, 00]
</code></pre>
<p>The good news is that the length is perfect! We’re clearly on to something here. In our generated code the “one” bits have become burst pairs of <code>[0d, 0d]</code> and the “zero” bits are <code>[0d, 26]</code>. But notice how they don’t line up very well: our first <code>[0d, 26]</code> lines up with a <code>[0f, 0b]</code> in the real code and our first <code>[0d, 0d]</code> lines up with a <code>[10, 26]</code>. It appears our bit mappings are reversed! Looks like this remote is a bit different to the AR-RY16 that Abrams documented.</p>
<p>I performed the same generation as before, but this time “one” bits become <code>0010 002e</code> and “zero” bits become <code>0010 0010</code> - opposite to before. I left the trailer and leader the same.</p>
<pre><code class="hljs language-txt">Real:             [26, 00, 76, 00, 6e, 33, 0f, 0b, 10, 0b, 10, 26, 0f, 0c, 0f, 26, 10, 0b, 10, 0b, 0f, 0c, 0f, 26, 10, 26, 10, 0b, 0f, 0c, 0f, 0b, 10, 26, 10, 26, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 0f, 0c, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 0f, 0b, 10, 0b, 10, 26, 0f, 0c, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 0f, 0b, 10, 26, 10, 0b, 10, 0b, 0f, 0c, 0f, 0b, 10, 26, 10, 0b, 0f, 0c, 0f, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 26, 10, 0b, 10, 26, 10, 26, 0f, 26, 10, 26, 0f, 26, 10, 26, 10, 00, 0d, 05, 00, 00]
Generated + flip: [26, 00, 76, 00, 68, 34, 0d, 0d, 0d, 0d, 0d, 26, 0d, 0d, 0d, 26, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 26, 0d, 26, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 26, 0d, 26, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 26, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 26, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 26, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 0d, 26, 0d, 0d, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, 26, 0d, ff, 0d, 05, 00, 00]
</code></pre>
<p>Looks much better! Note how the first <code>[0d, 0d]</code> lines up with a <code>[0f, 0b]</code>. The real code has a bit of jitter so this is a match as far as I’m concerned. One thing I’m not too sure about is the <code>ff</code> which appears in the generated code near the end. Luckily, it doesn’t seem to affect things. This code, when sent to the blaster, is recognised by the heat pump as an off code.</p>
<blockquote>
<p>I leant on Abrams’ work to validate this step of the reverse-engineering process. If you don’t have such a document, this is still totally doable! You’ll just have to start with the intial captured packet and derive bits directly from that packet. Play around with frequency ranges and bit mappings to get a feel for what will or won’t work on your heat pump.</p>
</blockquote>
<h2>Construction of the data packets</h2>
<p>Having worked out timing information, we can move on to look at data. Abrams’ document says the heat pump broadcasts its entire state in 16 bytes of information. Let’s capture a code and take a look. This is a code for 30 degrees Celsius, heat mode, no fan, no swing, with the heat pump already on:</p>
<pre><code class="hljs language-txt">JgAGAXAyEAsQCxAlEAsQJhAKEQoQCxAmECUQCxALEAsQJRElEAsPDBAKEAsRChALEAsQChEKEQoQCxALEAsPJhALEAsPDBAKEQoQCxALECYQChEKEAsQCxAlESUQJhAlECYQJhAlECYQCxAKESUQCxALEAoQCxALDwwQCxAKESUQJhALEAoQCxALEAsQCw8MECUQJhAlEQoQCxAmEAoRChALDwwQCxAKEAwPJhALDwwQChEKEAsQCw8MEAoRChALEAsQCxAKEQoQCxALEAsQCxAKEQoQCxALEAsQChEKEAsQCxALEAoRChEKEAsQCxALECUQCw8MDwwQChALECYQCxAKESUQJhAADQUAAA==
</code></pre>
<p>Decoded into timing bytes, we get this:</p>
<pre><code class="hljs language-txt">[26, 00, 06, 01, 70, 32, 10, 0b, 10, 0b, 10, 25, 10, 0b, 10, 26, 10, 0a, 11, 0a, 10, 0b, 10, 26, 10, 25, 10, 0b, 10, 0b, 10, 0b, 10, 25, 11, 25, 10, 0b, 0f, 0c, 10, 0a, 10, 0b, 11, 0a, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 0f, 26, 10, 0b, 10, 0b, 0f, 0c, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 26, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 25, 11, 25, 10, 26, 10, 25, 10, 26, 10, 26, 10, 25, 10, 26, 10, 0b, 10, 0a, 11, 25, 10, 0b, 10, 0b, 10, 0a, 10, 0b, 10, 0b, 0f, 0c, 10, 0b, 10, 0a, 11, 25, 10, 26, 10, 0b, 10, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 10, 25, 10, 26, 10, 25, 11, 0a, 10, 0b, 10, 26, 10, 0a, 11, 0a, 10, 0b, 0f, 0c, 10, 0b, 10, 0a, 10, 0c, 0f, 26, 10, 0b, 0f, 0c, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 0f, 0c, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 25, 10, 0b, 0f, 0c, 0f, 0c, 10, 0a, 10, 0b, 10, 26, 10, 0b, 10, 0a, 11, 25, 10, 26, 10, 00, 0d, 05, 00, 00]
</code></pre>
<p>We can remove the Broadlink preamble and postamble, as well as the leader and trailer pair to give us this:</p>
<pre><code class="hljs language-txt">[10, 0b, 10, 0b, 10, 25, 10, 0b, 10, 26, 10, 0a, 11, 0a, 10, 0b, 10, 26, 10, 25, 10, 0b, 10, 0b, 10, 0b, 10, 25, 11, 25, 10, 0b, 0f, 0c, 10, 0a, 10, 0b, 11, 0a, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 0f, 26, 10, 0b, 10, 0b, 0f, 0c, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 26, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 25, 11, 25, 10, 26, 10, 25, 10, 26, 10, 26, 10, 25, 10, 26, 10, 0b, 10, 0a, 11, 25, 10, 0b, 10, 0b, 10, 0a, 10, 0b, 10, 0b, 0f, 0c, 10, 0b, 10, 0a, 11, 25, 10, 26, 10, 0b, 10, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0b, 0f, 0c, 10, 25, 10, 26, 10, 25, 11, 0a, 10, 0b, 10, 26, 10, 0a, 11, 0a, 10, 0b, 0f, 0c, 10, 0b, 10, 0a, 10, 0c, 0f, 26, 10, 0b, 0f, 0c, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 0f, 0c, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 0a, 11, 0a, 11, 0a, 10, 0b, 10, 0b, 10, 0b, 10, 25, 10, 0b, 0f, 0c, 0f, 0c, 10, 0a, 10, 0b, 10, 26, 10, 0b, 10, 0a, 11, 25, 10, 26]
</code></pre>
<p>That’s 256 timing entries in the array, or 128 burst pairs. Each pair is a bit - meaning we have 16 bytes right here, as predicted by Abrams’ document. Maybe this won’t be so hard after all. Using the timing knowledge gleaned earlier, we can go through and replace each pair with a bit. We can’t just do a find and replace. This is due to the jitter I mentioned earlier: “One” bits aren’t always <code>[0d, 26]</code> - sometimes they are <code>[10, 25]</code> or <code>[0f, 26]</code>. We can look at proportions instead. Here’s the kind of substitution rule which works: Each pair where the second item is at least 1.5 times the first item encodes a “one” bit. Otherwise we take the pair as encoding a “zero” bit. Applying this rule for each pair of timing values, the code becomes:</p>
<pre><code class="hljs language-txt">00101000110001100000000000001000000010000111111110010000000011000000011100100000001000000000000000000000000000000000010000010011
</code></pre>
<p>Or, in hex:</p>
<pre><code class="hljs language-txt">[28, c6, 00, 08, 08, 7f, 90, 0c, 07, 20, 20, 00, 00, 00, 04, 13]
</code></pre>
<p>Boom! We have ourselves a state payload. Are all these hex codes getting confusing? Remember that we are now in the data domain. These bytes represent encoded state <em>data</em>, not <em>timing</em>. We just made an algorithm to convert a captured IR packet to a data payload. Let’s apply the algorithm to an IR code for 18 degrees celsius instead of 30 with all other settings equal. Can you spot the difference?</p>
<pre><code class="hljs language-txt">30deg: [28, c6, 00, 08, 08, 7f, 90, 0c, 07, 20, 20, 00, 00, 00, 04, 13]
18deg: [28, c6, 00, 08, 08, 7f, 90, 0c, 04, 20, 20, 00, 00, 00, 04, 11]
                                         ^                           ^
</code></pre>
<p>Now we know that the second nibble of the ninth byte determines temperature, with <code>7</code> meaning 30°C and <code>4</code> meaning 18°C.</p>
<p>We are now in a position to reverse engineer the remainder of the state payload. I did this by changing one thing on the remote at a time and running a script to extract the state payload. By seeing what changes in the payload, I was able to map out what each byte means in the payload. I put the results on <a href="https://github.com/albertnis/fujitsu-ar-ry13-ir-codes">Github</a>. Interestingly, the bytes are generally different to Abrams’ document but have the same position.</p>
<h3>Checksum</h3>
<p>The final byte of the payload is the checksum. If it’s not correct then the heat pump will ignore the entire packet, presumably because it assumes interference. Abrams gives a simple way to calculate the checksum but it didn’t work for the AR-RY13. I found a more complex algorithm <a href="https://stackoverflow.com/a/48533869">online</a> and played around with different ranges of bytes to checksum until the generated code matched real-world examples. The final algorithm is something like this:</p>
<ol>
<li>Reverse each of bytes 9 - 15. e.g. 11100001 -> 10000111 (0xe1 -> 0x87) for a single byte</li>
<li>Sum those reversed bytes</li>
<li>Take result of (208 - sum) % 256</li>
<li>Reverse bytes of result</li>
</ol>
<blockquote>
<p>Determining checksums may take a wee bit of trial and error. You’ll most likely need to generate some packets to test, as below.</p>
</blockquote>
<h1>Generating packets</h1>
<p>We have fully reverse engineered the AR-RY13 protocol at this stage. We know how the remote encodes bits as timings, plus how to get a 16 byte code from the heat pump state. At this stage we can write code to generate IR codes given a heat pump state, in the following steps:</p>
<ol>
<li>Receive a desired heat pump state from a user.</li>
<li>Generate the corresponding 16-bit state payload, including checksum. Or just use a stateless “off code” if the heat pump is turning off.</li>
<li>Convert to timing information using bit mapping and frequency. This includes appending trailer and prepending leader.</li>
<li>Convert to desired format (e.g. Broadlink).</li>
<li>Send to IR blaster.</li>
<li>Profit!</li>
</ol>
<h1>What now?</h1>
<p>In the <a href="/automating-ac">next article</a> I’ll go into how we can wrap this workflow in an automation pipeline using Node-RED for intuitive control through Home Assistant.</p>]]>
</content:encoded>
</item>
<item>
<title>Understanding Infrared Remote Control</title>
<description>Going beyond the remote in pursuit of automation</description>
<link>https://albert.nz/reverse-engineering-ir-ad18/</link>
<guid isPermaLink="false">/reverse-engineering-ir-ad18/</guid>
<pubDate>Sat, 11 May 2019 08:23:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/rm3.BQmxPIsO.webp" alt="Broadlink RM3 Mini3" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/rm3.Bq8Mxg3W.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/rm3.DWxYTbcu.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/rm3.B1VvFmzl.webp 1536w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/rm3.BQmxPIsO.webp 2560w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"><figcaption>Broadlink RM3 Mini3</figcaption></figure>
<p>The humble Wi-Fi IR blaster is a powerful tool in home automation. Suddenly any device with a traditional remote control can be commanded over the air. With my <a href="https://www.aliexpress.com/item/Broadlink-RM-Mini-3-mini3-WIFI-IR-Remote-Control-For-Smart-Home-Automation-by-APP-For/32907686132.html">Broadlink RM Mini 3</a> and its <a href="https://www.home-assistant.io/components/broadlink/">Home Assistant Component</a>, all I need to do is tell Home Assistant to send a packet to the blaster and it will do the job! I want to answer a couple of questions here:</p>
<ul>
<li>What is a packet, anyway?</li>
<li>What packet do I send?</li>
</ul>
<p>These are questions I only recently answered for myself by <a href="https://github.com/albertnis/fujitsu-ar-ry13-ir-codes">reverse-engineering a heatpump remote</a>. In this post I’ll use simpler examples to explain the basics.</p>
<h2>What is a packet, anyway?</h2>
<p>To understand packets, we need to understand how IR remotes work. When you press a button on a remote, a message is broadcast as precisely-timed pulses of infrared light which are emitted from the LED and picked up by the receiver. There can be hundreds of pulses in a single IR message - easy to take for granted when you just want to turn your TV on!</p>
<p>A packet is simply data sent to a WiFi blaster which contains all this timing information so that it can broadcast the IR message just as a remote would. It’s basically an encoded form of the IR message. What the packet looks like can depend on the IR blaster being used.</p>
<h3>Time domain: raw pulses</h3>
<p>Let’s look more closely at how IR pulses work.</p>
<p>The pulses adhere to a <strong>carrier frequency</strong> where the LED will turn on or off for a certain number of carrier frequency cycles in succession. The frequency is typically around 40kHz, but just like most things in the world of IR, the carrier frequency varies between manufacturers and devices. There’s really no standardisation here!</p>
<p>Here is an imaginary sequence of six remote pulses at a carrier frequency of 20kHz. We can think of the pulses as a series of 12 alternating on/off durations (each pulse consists of on time, then off time). Read the table left-to-right to get an idea of different ways of looking at the time-domain information. Notice how the pulse durations change between pulses.</p>

































































<table><thead><tr><th>Pulse</th><th>1</th><th></th><th>2</th><th></th><th>3</th><th></th><th>4</th><th></th><th>5</th><th></th><th>6</th><th></th></tr></thead><tbody><tr><td>IR state</td><td>On</td><td>Off</td><td>On</td><td>Off</td><td>On</td><td>Off</td><td>On</td><td>Off</td><td>On</td><td>Off</td><td>On</td><td>Off</td></tr><tr><td>Cycles (n)</td><td>70</td><td>130</td><td>16</td><td>16</td><td>30</td><td>16</td><td>16</td><td>16</td><td>16</td><td>16</td><td>70</td><td>110</td></tr><tr><td>Time (ms)</td><td>3.5</td><td>6.5</td><td>0.8</td><td>0.8</td><td>1.5</td><td>0.8</td><td>0.8</td><td>0.8</td><td>0.8</td><td>0.8</td><td>3.5</td><td>5.5</td></tr></tbody></table>
<p>This is the kind of timing information that can be encoded into a packet for sending to an IR blaster. Note that in the real world, messages are considerably longer than this.</p>
<h3>Data domain: beyond the pulses</h3>
<p>The pulses aren’t just random, of course. They encode a data payload which can be picked up by the receiving device. For simple remotes this data is typically some kind of key code, which can be parsed at the receiving end. On more complex remotes such as heatpump remotes, they can consist of state data and even have checksums!</p>
<p>Let consider the example from the before. Another way of writing the cycles is with [n-on, n-off] notation, like this:</p>
<pre><code class="hljs language-txt">[70, 130], [16, 16], [30, 16], [16, 16], [16, 16], [70, 110]
</code></pre>
<p>These pairs are called <strong>burst pairs</strong>. Grouping pulses into burst pairs makes outliers clearer. The long pulses and the beginning and end of the message stand out here. They are probably the <strong>leader</strong> and <strong>trailer</strong>, respectively. Such pulses are used to serve as a kind of “marker” for the receiver. But they typically don’t encode data. As we’re trying to extract data, let’s remove them, leaving the following:</p>
<pre><code class="hljs language-txt">[16, 16], [30, 16], [16, 16], [16, 16]
</code></pre>
<p>When we get to a stage of having two types of burst pair, we’re in a good spot. This means we can extract binary from the pairs. Typically the pairs with a proportionally longer “on” duration mean a <code>1</code> and the other pairs mean <code>0</code>. In our case some pairs have a 30-cycle “on” duration and some have a 16-cycle “on” duration. Let’s replace the 30-cycle pairs with <code>1</code> and the others with <code>0</code>:</p>
<pre><code class="hljs language-txt">0100
</code></pre>
<p>So our remote data is <code>0b0100</code>. Or, in other words, the number <code>4</code>. If that seems like quite a lot of pulse information for one nibble of data, that’s because it is! But we need to retain all this timing information in order to faithfully rebroadcast the signal from the IR blaster and have it received properly.</p>
<h3>Encoding pulses as packets</h3>
<p>Say we want to send this timing data to an IR blaster as packet. What do we send? Pulses are truly just a series of durations, but we can’t simply use the table or array notations I’ve used for demonstration. We need a more useful, standardised format. Two encodings I’m familiar with are Pronto and Broadlink.</p>
<h4>Pronto codes</h4>
<p>Pronto are used by Pronto-branded IR blasters and are also a generally accepted IR code format. Do a web search for “pronto codes” and you’ll find a ton for your device. Pronto codes are hexadecimal and consist of a “preamble” which declares carrier frequency and message length, followed by the burst pairs defined as cycle counts. Here’s what our previous example looks like as a Pronto code packet, with some extra annotation added beneath.</p>
<pre><code class="hljs language-txt">0000 00cf 0006 0000 0046 0082 0010 0010 001e 0010 0010 0010 0010 0010 0046 006e
     freq  len      leader    bit 1     bit 2     bit 3     bit 4     trailer
preamble --->       burst pairs --->
</code></pre>
<p>There’s much more information about this popular format at <a href="http://www.remotecentral.com/features/irdisp1.htm">Remote Central</a>.</p>
<h4>Broadlink codes</h4>
<p>Broadlink devices like my IR blasters use their own code format. Broadlink codes are basically burst-pair information converted from cycles to multiples of a certain time period. This is surrounded by a Broadlink preamble and postamble, then converted to base64 encoding before sending to the device (which I’m doing via Home Assistant). Here’s our (rather ugly) Broadlink code packet.</p>
<pre><code class="hljs language-txt">JgAOAHLVGhoxGhoaGhpytA0FAAAAAAAAAAAAAA==
</code></pre>
<p>Check out my <a href="https://github.com/albertnis/fujitsu-ar-ry13-ir-codes/blob/master/src-js/pronto2broadlink.js">pronto2broadlink</a> script for conversion from Pronto to Broadlink format. A good write-up of the Broadlink format is <a href="https://github.com/mjg59/python-broadlink/blob/master/protocol.md#sending-data">here</a>.</p>
<h2>What packet do I send?</h2>
<p>The previous example is all well and good, but it assumes we know what timing data we need to send to the blaster. Unless we already have a Pronto code or similar, it’s unlikely we know what to send. There are three main ways I have successfully obtained codes for devices.</p>
<h3>Learning mode</h3>
<p>Most, if not all, IR blasters come with a learn mode. In this mode, the blaster operates “in reverse” and receives signals from remotes. For example, you could enter learn mode, press the power button on your TV remote, and then receive the resulting packet back from the blaster. Sending this packet to the blaster in the future will broadcast that message to toggle the TV power. The best part? Since learning happens on your blaster, you know that it will give you the correct packet format for use in the future - so there’s no need for conversion or inspection of the packet.</p>
<h3>Find it online</h3>
<p>Searching for IR codes online can be surprisingly fruitful. Some manufacturers publish <a href="https://www.google.com/search?client=ubuntu&#x26;channel=fs&#x26;q=pioneer+ir+codes+xls&#x26;ie=utf-8&#x26;oe=utf-8">entire spreadsheets</a> of Pronto codes. These documents often contain “secret” codes not found on the remote. <a href="https://www.engadget.com/2009/02/05/hd-101-discrete-ir-codes/">Discrete IR codes</a> are a great example; these codes are perfect for home automation as they remove some state-management burden. IR code databases are also present. As with Broadlink blasters, you may need to convert the Pronto codes to vendor-specific packet formats.</p>
<h3>Reverse-engineering</h3>
<p>For complex remotes, reverse-engineering can work. This is the wild west of IR codes. The approach basically consists of analysing a remote, extracting transmitted data for various keypresses and then attempting to work out how data payloads are structured and what they mean. The data can then be changed and reconstructed into artificially-created packets. I recently did this successfully with a <a href="https://github.com/albertnis/fujitsu-ar-ry13-ir-codes">heatpump</a> (one for a future post). Here’s an example of a less successful effort, with my SMSL AD18 amplifier. In this case, each key transmitted a data payload of one byte:</p>

































































<table><thead><tr><th>Function</th><th>Data (hex)</th><th>Data (dec)</th><th>Data/15 (dec)</th></tr></thead><tbody><tr><td>Toggle power</td><td>87</td><td>135</td><td>9</td></tr><tr><td>Mute</td><td>96</td><td>150</td><td>10</td></tr><tr><td>Center</td><td>2d</td><td>45</td><td>3</td></tr><tr><td>Up</td><td>4b</td><td>75</td><td>5</td></tr><tr><td>Down</td><td>69</td><td>105</td><td>7</td></tr><tr><td>Left</td><td>c3</td><td>195</td><td>13</td></tr><tr><td>Right</td><td>a5</td><td>165</td><td>11</td></tr><tr><td>Next input</td><td>e1</td><td>225</td><td>15</td></tr><tr><td>Fn</td><td>1e</td><td>30</td><td>2</td></tr></tbody></table>
<p>I noticed that all payload values were multiples of 15, and that 9 multiples of 15 were missing! Did that mean 9 “secret” commands were present which could include discrete power commands? In this case, no. I tried broadcasting the other multiple of 15 and nothing happened. You win some, you lose some.</p>
<h2>Summary</h2>
<p>The humble IR remote is a powerful tool and, thanks to IR blasters, is one that can be integrated into a home automation system effectively. It’s been fascinating to learn about how the IR messaging works and the encodings people use to store and share codes. I have found that reverse-engineering remote controls can be a surprisingly engaging challenge - a challenge which is extra rewarding when it works. At $25 for one of these little blasters, it’s hard not to recommend getting one!</p>]]>
</content:encoded>
</item>
<item>
<title>Using MQTT for Availability and Retained State</title>
<description>Building more resilient networks with the LWT and retained state features of MQTT</description>
<link>https://albert.nz/mqtt-availability-retained-state/</link>
<guid isPermaLink="false">/mqtt-availability-retained-state/</guid>
<pubDate>Thu, 03 Jan 2019 23:23:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>One of the crucial aspects of MQTT is the broker. Among other things, the broker manages connected clients, ensuring messages are received from and sent to the correct devices. The broker doesn’t simply forward messages, however. It can also respond to devices being connected and disconnected - an immensely useful feature. I recently updated <a href="https://github.com/albertnis/lightt">lightt</a> to leverage two MQTT features:</p>
<ul>
<li><strong>Last Will and Testament (LWT)</strong> for availability updates</li>
<li><strong>Retained messages</strong> for persistent state</li>
</ul>
<p>Here’s an explanation of what I did to get these features working for me, with some examples from Home Assistant and mosquitto. If you want to try out the examples, install <a href="https://mosquitto.org/download/">mosquitto</a>.</p>
<h2>Last Will and Testament (LWT)</h2>
<p>Binary devices have two basic states, <strong>on</strong> or <strong>off</strong>:</p>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/capture_on.DGuUNbJ9.webp" alt="Home Assistant light on" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/capture_on.DGuUNbJ9.webp 285w" style="aspect-ratio: 6.786;" data-aspect="6.786" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/capture_off.Bp8HBJLH.webp" alt="Home Assistant light off" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/capture_off.Bp8HBJLH.webp 282w" style="aspect-ratio: 6.409;" data-aspect="6.409" sizes="auto" loading="lazy"></p>
<p>There is a third state, even for binary devices, which is very useful: <strong>unavailable</strong>:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/capture_unavailable.DFfL62dq.webp" alt="Home Assistant light unavailable" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/capture_unavailable.DFfL62dq.webp 283w" style="aspect-ratio: 6.581;" data-aspect="6.581" sizes="auto" loading="lazy"><figcaption>Home Assistant light unavailable</figcaption></figure>
<p>An unavailable state allows home automation software to prevent commands being sent to a disconnected device. It is also useful for monitoring the health of devices, or as a failsafe. For instance, if an alarm system is powered down, other devices can be alerted to this fact. Google Assistant will also mention if devices are unavailable.</p>
<p>But how can a device tell other devices when it is disconnected? This is where the power of the broker and <a href="https://www.hivemq.com/blog/mqtt-essentials-part-9-last-will-and-testament/">LWT</a> comes in. When a device connects to a broker, it can specify an LWT topic and payload. The broker receives this information and will publish it when that device is disconnected. Essentially, the device says <em>“I’d like to connect. When I eventually disconnect, publish this message”</em>. The broker takes on the responsibility and publishes the LWT payload on the device’s behalf when it becomes disconnected.</p>
<p>Let’s give this a go in Arduino C++ using <a href="https://github.com/knolleary/pubsubclient">PubSubClient</a>:</p>
<pre><code class="hljs language-cpp"><span class="hljs-comment">// Connect to the broker and tell it to publish "offline" to the availability topic when disconnected in the future</span>
<span class="hljs-type">char</span> availabilityTopic[] = <span class="hljs-string">"light/desktop/availability"</span>;
<span class="hljs-type">bool</span> mqttConnected = client.<span class="hljs-built_in">connect</span>(
    mqttClientname,
    mqttUser,
    mqttPassword,
    availabilityTopic,
    <span class="hljs-number">0</span>,
    <span class="hljs-number">1</span>,
    <span class="hljs-string">"offline"</span>);

<span class="hljs-keyword">if</span> (mqttConnected) {
    client.<span class="hljs-built_in">subscribe</span>(commandTopic, <span class="hljs-number">1</span>);

    <span class="hljs-comment">// Publish "online" to the availability topic now to signify connection</span>
    client.<span class="hljs-built_in">publish</span>(availabilityTopic, <span class="hljs-string">"online"</span>, <span class="hljs-literal">true</span>);

    Serial.<span class="hljs-built_in">println</span>(<span class="hljs-string">"connected"</span>);
  }
</code></pre>
<p>You can play around with this by using mosquitto_sub to investigate messages. Run mosquitto_sub then turn the device on and off. You should see something like this:</p>
<pre><code class="hljs language-bash">$ mosquitto_sub -t <span class="hljs-string">"light/desktop/availability"</span> -v -h localhost
light/desktop/availability online
light/desktop/availability offline
</code></pre>
<p>The “offline” message is received like magic <em>after</em> the device loses power! The broker has delivered the last will and testament of the client.</p>
<p>Home Assistant can be set up to use this topic. By default, it expects “online” and “offline” as payloads on the availability topic. Here is an example <a href="https://www.home-assistant.io/components/light.mqtt/#template-schema">mqtt_template</a> light entry in <code>configuration.yaml</code>:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">light:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">mqtt_template</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">"Desktop light"</span>
    <span class="hljs-attr">state_topic:</span> <span class="hljs-string">"light/desktop/state"</span>
    <span class="hljs-attr">command_topic:</span> <span class="hljs-string">"light/desktop/command"</span>
    <span class="hljs-attr">availability_topic:</span> <span class="hljs-string">"light/desktop/availability"</span>
    <span class="hljs-string">...</span>
</code></pre>
<h2>Retained Messages</h2>
<p>When a device powers up, what state should it be in? Let’s take a light for example. An easy option would be to have the light turn on to the same colour each time. Or maybe have the light remain off (“black”) to make restarts inconspicuous. The best option is to have the light retain its previous state. There are two main ways to do that:</p>
<ol>
<li>The light remembers its own last state, perhaps by writing every change to persistent storage and reading this storage on boot.</li>
<li>The light gets its previous command from the broker and executes that command to restore state.</li>
</ol>
<p>Both approaches have their merits, but I like the second option because it ensures all parts of our setup are working from the ground truth provided by the broker.</p>
<p>MQTT has a feature just for this - <a href="https://www.hivemq.com/blog/mqtt-essentials-part-8-retained-messages/">retained messages</a>. Any message can be sent as a retained message. This message will be sent to future subscribers to the topic <em>as soon as they subscribe</em>. Only the latest retained message for that topic will be sent. Let’s see it in action using mosquitto_sub and mosquitto_pub.</p>
<pre><code class="hljs language-bash">$ mosquitto_sub -t <span class="hljs-string">"light/test"</span> -v -h localhost

</code></pre>
<p>Nothing to see here! No messages are being published to this channel so the output is empty. Kill this command and we can do some publishing:</p>
<pre><code class="hljs language-bash">$ mosquitto_pub -t <span class="hljs-string">"light/test"</span> -h localhost -m <span class="hljs-string">"retained!"</span> -r
$ mosquitto_pub -t <span class="hljs-string">"light/test"</span> -h localhost -m <span class="hljs-string">"not retained"</span>
</code></pre>
<p>We sent two messages to the test topic, with a key difference: the <code>-r</code> flag in the first command means this message is retained and will be sent to new subscribers. We can check this by subscribing again:</p>
<pre><code class="hljs language-bash">$ mosquitto_sub -t <span class="hljs-string">"light/test"</span> -v -h localhost
light/test retained!
</code></pre>
<p>This is excellent and can work both ways in home automation:</p>
<ul>
<li>If the device publishes state updates as retained, the home automation software will receive state on connection to the broker.</li>
<li>If the home automation software publishes commands as retained, the device will receive commands on connection, hopefully restoring state.</li>
</ul>
<p>On the device in Arduino C++, this is simple and looks like this:</p>
<pre><code class="hljs language-cpp"><span class="hljs-comment">// The "true" means retain in PubSubClient</span>
client.<span class="hljs-built_in">publish</span>(stateTopic, output, <span class="hljs-literal">true</span>);
</code></pre>
<p>In Home Assistant, it is also simple:</p>
<pre><code class="hljs language-yaml"><span class="hljs-attr">light:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">platform:</span> <span class="hljs-string">mqtt_template</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">"Desktop light"</span>
    <span class="hljs-attr">command_topic:</span> <span class="hljs-string">"light/desktop/command"</span>
    <span class="hljs-attr">retain:</span> <span class="hljs-literal">true</span>
    <span class="hljs-string">...</span>
</code></pre>
<h2>Summary</h2>
<p>Leveraging the LWT and retain features of MQTT has made me realise just how useful MQTT can be. These features facilitate convenient use cases in home automation software. Previously I thought MQTT’s broker seemed like a redundant middleman. Now it is clear just how powerful this architecture is for transparently managing subscribers and their payloads.</p>]]>
</content:encoded>
</item>
<item>
<title>Improving LED lighting with ESP8226 and custom firmware</title>
<description>Getting to grips with IoT networking</description>
<link>https://albert.nz/voice-activated-lighting-hardware/</link>
<guid isPermaLink="false">/voice-activated-lighting-hardware/</guid>
<pubDate>Tue, 19 Jun 2018 20:23:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<blockquote>
<p>This post is a bit out of date. I loved exploring LED control by writing custom software like Lightt, but nowadays I use much better software written by others. I recommend you check out my posts on <a href="/esphome-wled-migration">WLED</a> (great for individually-addressable colour strips like those described in this article) and <a href="/esp32-led-strip">ESPHome</a>.</p>
</blockquote>
<p>Over the last three years, LED strip lighting has become a staple of my desk setup. I like it for a few reasons:</p>
<ul>
<li>Strips behind a desk add bias lighting, making it more comfortable to look at a screen at night.</li>
<li>Strips stuck along the bottom of a monitor provide a work and keyboard light.</li>
<li>It adds colour to a room, without painting or doing anything permanent.</li>
</ul>
<p>But there have been some drawbacks this whole time:</p>
<ul>
<li>I haven’t settled on a seriously useful control mechanism. I’ve tried an IR remote, but that’s most appropriate for a single zone. I settled on rheostats, which are simple but require physical access.</li>
<li>Running from a breadbroad means there is always a tangle of wires in the corner of my desk.</li>
<li>Power supply woes. I’d have an Arduino run from my computer’s USB, then power the strips with separate higher-power 12V and 5V power supplies. The 5V power supply was a computer PSU, which was loud and impractical.</li>
</ul>
<p>I had been thinking of implementing a WiFi control system for a while, but never really saw how it would fix those last two issues. Then I found this <a href="http://www.electrodragon.com/product/esp-led-strip-board/">downright amazing board from ElectroDragon</a>, which gave me a lot of hope. I bought three and prepared to level up my Arduino skills for the ESP8266.</p>
<h2>The Board</h2>
<p>The ElectroDragon ESP LED Strip Board essentially consists of an ESP8266 microcontroller with input terminals for power and output terminals for and LED strip. The coolest part is that it’s designed to be powered with a single supply. Just plug in a power supply to suit your strip, select the correct pin jumper settings and all voltage regulation will be taken care of. The board is a small package and even comes with an injection-moulded enclosure and leads for power input and output. Here’s how I set it up for a ‘dumb’ 12V RGB strip and a ‘smart’ (SK6812) 5V RGBW strip.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/12V-annotated.Dmh3hp7q.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/12V-annotated.iKHQ818c.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/12V-annotated.Dmh3hp7q.webp 768w" style="aspect-ratio: 2.254;" data-aspect="2.254" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/5V-annotated.CPOqtNzD.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/5V-annotated.o6xMwAOi.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/5V-annotated.CPOqtNzD.webp 768w" style="aspect-ratio: 2.254;" data-aspect="2.254" sizes="auto" loading="lazy">
<h2>Programming an ESP8266</h2>
<p>Actually getting code on the ESP8266 is harder than a regular Arduino. It’s surprising how much digging I had to do to find some answers. Here’s the low-down on what I learnt about flashing this chip:</p>
<ul>
<li>You’ll need a USB to TTL adapter to program this board and most other ESP8266, as there is not onboard USB port.</li>
<li>Despite the ESP8266 being a 3.3V board, use the 5V rail to power the ESP LED strip board when the USB adapter is connected. I don’t know why this is the case, but it works a treat.</li>
<li>The Arduino IDE is convenient for programming this board. You’ll need to install the <a href="https://github.com/esp8266/Arduino">ESP8266 Arduino core</a>. Once that’s done, I recommend switching to a more powerful editor like VSCode, which can program the board via the Arduino plugin. Check out Lightt’s <a href="https://github.com/albertnis/lightt">GitHub page</a> for my board settings which work well for uploading to the ESP LED strip board.</li>
<li>This one’s important: Unlike most Arduinos, the ESP8266 has to be manually put into programming mode before an upload can happen. To do this, the GPIO0 pin must be connected to ground at powerup. On the ESP LED strip board, do this by holding down the ‘IO0’ button while you plug the USB adapter into the computer.</li>
</ul>
<p>Here’s what my programming setup looks like with a CP2102 USB to TTL adapter:</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/CP2102-annotated.BH42R2gQ.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/CP2102-annotated.BDBTurPE.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/CP2102-annotated.BH42R2gQ.webp 768w" style="aspect-ratio: 2.254;" data-aspect="2.254" sizes="auto" loading="lazy">
<h2>All About Lightt</h2>
<p>Feel free to clone and flash <a href="https://github.com/albertnis/lightt">Lightt</a> (fork and PR too, if you’re keen!). I learnt a ton while making Lightt and it has been a really rewarding undertaking. Here are some key learnings.</p>
<h3>MQTT</h3>
<p>MQTT is a network protocol which is perfect for IoT. Basically a ‘broker’ runs the MQTT server on your network. I’m using a Raspberry Pi 2 with Mosquitto to achieve this. MQTT works on a publish/subscribe basis. Messages can be published by devices connected to the broker, and each message has content (‘payload’) and a subject (‘topic’). Devices can also subscribe by listening for messages which have a certain topic.</p>
<p>In the case of Lightt, each board has two topics: command and state. The board subscribes to the command topic in order to receive commands from the home automation software. It publishes messages on its state topic to confirm a command has been executed and to regularly let the home automation server know what its current state is.</p>
<h3>Message Formats</h3>
<p>The board needs to know a command from the server looks like, and also how to format state messages. String processing is a bit of a pain on microcontrollers, so my aim here was to let the home automation server do the processing and keep the messages themselves very structured.</p>
<h3>Robustness Never Hurt Anyone</h3>
<p>I ran into issues with runtime errors and odd network disconnections which would cause resetting. It turns out the ESP8266 can reset if its loop times become too slow. I fixed the issue by removing delays in the code and instead implementing more robust checkers for MQTT and WiFi connections in the main loop.</p>
<h3>Crossfades Are Still Cool</h3>
<p>I’m really happy with the crossfade functionality in Lightt. There’s a default 150ms crossfade duration, but this can actually be overridden if a transition is defined in the MQTT command. This allows for some awesome behaviour like 30-minute crossfades for sunrise alarms. More on that in another blog post…</p>
<h3>Dimming Curves Are a Thing</h3>
<p>Human perception of brightness isn’t linear at all - it’s more logarithmic, like most senses. I implemented a dimming curve for the first time and included a Python helper script. It would awesome to have more than 255 steps to play with, but this is a limitation of the 8-bit colour signal expected by smart strips.</p>
<h2>More Experiments to Come</h2>
<p>I love being able to program a WiFi-controlled microcontroller. It allows for so many connected, low-power, and out-of-sight projects - and that’s exactly what has made the ESP8266 so popular for IoT projects. The ESP LED strip board in particular has made it possible to run homebrew Arduino-like software without the nest of wires and improvised controls. It looks like the ESP8266 will be driving my desk lighting for the forseeable future, in a project which finally feels finished.</p>]]>
</content:encoded>
</item>
<item>
<title>Adventures with the MERN Stack</title>
<description>Teaching myself full-stack JavaScript with a WYSIWYG CMS of sorts</description>
<link>https://albert.nz/mern/</link>
<guid isPermaLink="false">/mern/</guid>
<pubDate>Tue, 04 Jul 2017 10:23:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-1.D5ybjvRN.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-1.DHn34aIS.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-1.D5ybjvRN.webp 768w" style="aspect-ratio: 1.733;" data-aspect="1.733" sizes="auto" loading="lazy">
<p><em>Beautifully generic.</em></p>
<p>Never fear: Hex Solutions isn’t an actual company. Rather, I imagined the overly vague company in a recent project which I used to teach myself all about full stack JavaScript web development. While the project was more or less completely contrived, it does address a problem summarised below:</p>
<p><em>What if an individual or small company wants a basic, maintainable, and elegant landing page without the fuss of an entire platform such as Wordpress?</em></p>
<p>From this precept, Level was born. The idea of Level is to make content editing by the user trivially simple even for those with little technological prowess. The user can login securely without any page reloads or navigation and start editing the page straight away.</p>
<h2>The User Workflow</h2>
<p>First of all, the user scrolls to the bottom of the page to find the login link:</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-2.CK3555Fr.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-2.DXsSCJ_6.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-2.CK3555Fr.webp 768w" style="aspect-ratio: 1.720;" data-aspect="1.720" sizes="auto" loading="lazy">
<p>Clicking this link shows the login form, where credentials can be entered:</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-3.CrVVCjhy.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-3.DEqjpmXE.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-3.CrVVCjhy.webp 768w" style="aspect-ratio: 1.720;" data-aspect="1.720" sizes="auto" loading="lazy">
<p>If the credentials are correct, the page enters editing mode. This is all done with no page reloads or redirects.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-4.DrSUZODn.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-4.DF-h5NYq.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-4.DrSUZODn.webp 768w" style="aspect-ratio: 1.720;" data-aspect="1.720" sizes="auto" loading="lazy">
<p>Dynamic fields are now editable throughout the page. As the user edits a field, the save status is shown in the toolbar. Changes are sent to the server, debounced by several seconds from user input to prevent request spamming.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-5.CS_p-OYm.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-5.CLaYJz28.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-5.CS_p-OYm.webp 768w" style="aspect-ratio: 1.720;" data-aspect="1.720" sizes="auto" loading="lazy">
<p>The user is notified when the server has received and stored changes:</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-6.CG_BWroh.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-6.Dt0VBCuR.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-6.CG_BWroh.webp 768w" style="aspect-ratio: 1.720;" data-aspect="1.720" sizes="auto" loading="lazy">
<p>The process allows for simple editing. The user can see what field they are editing, can toggle spellcheck, and can logout at any time.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-7.82DeWAjt.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-7.CKf7T1C3.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mern-7.82DeWAjt.webp 768w" style="aspect-ratio: 1.720;" data-aspect="1.720" sizes="auto" loading="lazy">
<h2>The Technical Details</h2>
<p>The app is full-stack and entirely written in JavaScript using the MERN stack. Here’s a quick summary of how the stack works in Level:</p>
<ul>
<li><strong>MongoDB:</strong> Uses two collections: one for users and one for fields.</li>
<li><strong>Express:</strong> Handles the app routing. Level is more or less single-page, so there’s nothing too fancy here, but there are routes set up for actions such as logins, logouts and field edits.</li>
<li><strong>React:</strong> The app is built on React.js. Interface files are used for both client-side and server-side rendering.</li>
<li><strong>Node.js:</strong> The runtime used for Level.</li>
</ul>
<p>There are other key libraries which I used:</p>
<ul>
<li><strong>Redux:</strong> Used for state management and action dispatching. In other words, a lot of heavy lifting!</li>
<li><strong>redux-observable:</strong> Brings RxJS to redux. In Level it takes care of the AJAX workload; namely, sending login attempts and edits. It also has neat functionality for debouncing those edits before sending.</li>
<li><strong>Passport:</strong> Server-side authentication management. Used with a few session libraries, makes user management quite convenient. Obviously, changes will only be pushed to the database if an authenticated session is detected.</li>
</ul>
<h2>Reflections</h2>
<p>What an adventure! Five years ago, I avoided JavaScript where possible. So much of what I saw JavaScript used for could be easily accomplished by other means (often CSS animations). Diving into this project opened my eyes to some interesting things.</p>
<ul>
<li><strong>ES2016 makes JavaScript simpler.</strong> Level was developed using babel through Webpack to compile JavaScript files. While this can seem like an unnecessary evil, it means ES2016 can be used. This flavour of JavaScript has some neat features, one of which is no semicolons. A friend commented that the ES2016 code looked more ‘Pythonic’ due to the lack of semicolons and presence of import/export statements.</li>
<li><strong>Server side rendering is awesome.</strong> I mean, it’s nothing new, really. PHP, Python, and just about any backend language do the exact same thing when used to generate websites. What’s cool about the full-stack JavaScript approach is that the server code can be pointed at the <em>exact same code</em> used for frontend rendering. Obviously, Webpack bundling means that server and client are using different JavaScript files, but server-side rendering enables React to pick up directly where the server left off and start handling the page right after initial load. This was a huge discovery for me: in the past JavaScript seemed to me like something which had to painstakingly hook into various pre-rendered components to attach listeners and the like. This way, your app is ‘delivered’ to the client in a ready-to-go form.</li>
<li><strong>MongoDB is surprisingly simple.</strong> It’s just saving JSON files. And because the whole app is in JavaScript, JSON is ridiculously convenient. Mongoose makes interactions with the database in code straightforward. At least for smaller projects, it seems more convenient than SQL.</li>
<li><strong>Redux took a while to learn</strong>. There are reducers, actions, and connections to manage, not to mention the state itself. I found Redux to be one of those ‘practice makes perfect’ things. I’m nowhere near perfect but I can get actions whipping through the app pretty quickly now, and linking them to components seems like less of a chore than it did initially.</li>
<li><strong>I recommend source maps</strong>. I’ll admit it: I barely realised what a source map was, let alone how it could help me. That was, until halfway through the project. Webpack can easily generate source maps and it makes debugging many times easier than probing code with ‘console.log’.</li>
</ul>
<p>While Level is most certainly a solution looking for a problem, the app has taught me a great deal about full-stack development. Relatively simple projects like this are great for getting to grips with new ways of doing things, especially in a world with so many JavaScript frameworks floating around. I don’t have access to a Node.js host for Level, but if I ever needed a simple single page for something, I might just dust off Level someday.</p>]]>
</content:encoded>
</item>
<item>
<title>Castle Hill</title>
<description>Photos from the scenic mountainous landscape near Arthur's Pass, 90 minutes from Christchurch</description>
<link>https://albert.nz/castle-hill/</link>
<guid isPermaLink="false">/castle-hill/</guid>
<pubDate>Mon, 26 Jun 2017 01:23:28 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>The flat took a day trip to this strange stony place, and plenty of adventures were had.</p>
<p>It was easy to explore between the large boulders which make up the landscape.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-1.BSe5yvpz.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-1.B4fejzp3.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-1.Bj8qFaTa.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-1.BSe5yvpz.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<p>The others explored along the top of a rock cluster.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-2.3glm2ZC4.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-2.BEzgNugh.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-2.QDbb0PFa.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-2.3glm2ZC4.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-4.B53lbAdn.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-4.DvR54daJ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-4.Cxns1wo0.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-4.B53lbAdn.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<p>I decided to head up Castle Hill to seek out some views.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-3.B_rsvmls.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-3.CjuQbVfl.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-3.B3w3UHDO.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-3.B_rsvmls.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-5.CsoI4-jP.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-5.Dsw8if5G.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-5.B1MkCh4j.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-5.CsoI4-jP.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<p>From the top of the hill, there were fantastic views of the grassland below, as well as the mountains beyond.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-6.pNNQGd_N.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-6.CMliADIY.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-6.BFa7MLCz.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-6.pNNQGd_N.webp 1536w" style="aspect-ratio: 2.000;" data-aspect="2.000" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-7.-6OzW1RN.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-7.CrOge9FC.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-7.Bch8XXzT.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/castle-7.-6OzW1RN.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">]]>
</content:encoded>
</item>
<item>
<title>Piano Vision</title>
<description>Evaluating the use of Kinect depth cameras to capture piano keypresses from above</description>
<link>https://albert.nz/piano-vision/</link>
<guid isPermaLink="false">/piano-vision/</guid>
<pubDate>Sun, 14 May 2017 04:23:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>In an effort to focus more of my attention on coding, I managed to pick up two computer science papers at the start of the year to add to my mechanical engineering degree. One of these was computer vision.</p>
<p>My only previous experience with computer vision merged my interest in programming with my interest in music. This previous project, named “VMIDI”, used an RGB camera to track markers attached to the action of a partially-dismantled piano. The code utilised Python and OpenCV to track keypresses and their velocities. While a good learning opportunity, VMIDI was slow, intrusive and never made it very far.</p>
<p>When I discovered students could specify our own projects in the computer vision course, making the sequel to VMIDI seemed like a natural evolution of my previous line of thought. So I decided to give VMIDI2 a go, this time armed with one year more coding experience and departmental backing.</p>
<p>There was a third difference between VMIDI and VMIDI2: the C++ language. Using C++ for VMIDI2 ended up being a bit of a revelation. While Python computer vision can be fast if native OpenCV calls are maximised, C++ makes speed seem effortless. The source code for VMIDI2 is available on <a href="https://github.com/albertnis/vmidi2">GitHub</a>.</p>
<p>The rest of this article is a compressed version of the <a data-sveltekit-reload="" href="/Nisbet_Green_Capture_of_Dynamic_Piano_Performance_with_Depth_Vision.pdf">research paper</a> I wrote on this topic.</p>
<h2>Method</h2>
<p>The Kinect camera is positioned above the keyboard, as shown by this IR frame:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/raw-unpressed.4RlMPcG8.webp" alt="IR frame showing camera positioning" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/raw-unpressed.4RlMPcG8.webp 480w" style="aspect-ratio: 1.206;" data-aspect="1.206" sizes="auto" loading="lazy"><figcaption>IR frame showing camera positioning</figcaption></figure>
<p>The keyboard is extracted from the depth frame (not pictured) using perspective warp on manually-selected points. After applying a bilateral filter to remove some noise, the following frame is obtained:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/norm-unpressed.DG_mLzNU.webp" alt="Normalised depth frame after warp" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/norm-unpressed.DG_mLzNU.webp 480w" style="aspect-ratio: 4.211;" data-aspect="4.211" sizes="auto" loading="lazy"><figcaption>Normalised depth frame after warp</figcaption></figure>
<p>Keys are manually overlaid over this image. These rectangles will be used as key masks for key depth tracking in a future step.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/norm-unpressed-overlay.BVTfU68w.webp" alt="Normalised depth frame after warp with key overlay" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/norm-unpressed-overlay.BVTfU68w.webp 480w" style="aspect-ratio: 4.211;" data-aspect="4.211" sizes="auto" loading="lazy"><figcaption>Normalised depth frame after warp with key overlay</figcaption></figure>
<p>This unpressed state is used to create a difference image for successive frames. Each pixel in this difference image has a value of millimetres below its initial, unpressed state. When unpressed, it looks like this:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/col-norm-unpressed.DsxvrlR6.webp" alt="Unpressed normalised difference image" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/col-norm-unpressed.DsxvrlR6.webp 480w" style="aspect-ratio: 4.211;" data-aspect="4.211" sizes="auto" loading="lazy"><figcaption>Unpressed normalised difference image</figcaption></figure>
<p>It’s basically black, because the keyboard is essentially still in its initial state. Each key’s rectangle is used as a mask within which the mean pixel value is calculated (shown in red for the A3 key). Each mean value is assigned to its key. Additionally assigned is an n=5 rolling average of this value (shown in blue).</p>
<p>When pressed, the depth reflects the change:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/col-norm-pressed.BukAwGyW.webp" alt="Pressed normalised difference image" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/col-norm-pressed.BukAwGyW.webp 480w" style="aspect-ratio: 4.211;" data-aspect="4.211" sizes="auto" loading="lazy"><figcaption>Pressed normalised difference image</figcaption></figure>
<p>We’ll get to the velocity value (shown in green) later, but first there is an important consideration to make when tracking keypresses. Often hands cover unpressed keys while playing. It is important to exclude from the difference image any pixels belonging to hands. To achieve this, I made a simple hand detection procedure. Pixels a certain distance above the keyboard are thresholded:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/hand1.B3OPV_eB.webp" alt="Hand threshold mask" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/hand1.B3OPV_eB.webp 480w" style="aspect-ratio: 4.528;" data-aspect="4.528" sizes="auto" loading="lazy"><figcaption>Hand threshold mask</figcaption></figure>
<p>These pixels include hands and a smattering of noise. To remove the noise, an image opening step was applied. A further dilation made the hand area more conservative, in an attempt to approximately cover parts of fingers near the key level.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/hand2.DZ3RghSQ.webp" alt="Opened and dilated hand threshold mask" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/hand2.DZ3RghSQ.webp 480w" style="aspect-ratio: 4.528;" data-aspect="4.528" sizes="auto" loading="lazy"><figcaption>Opened and dilated hand threshold mask</figcaption></figure>
<p>Finally, this mask was subtracted from the key mask before finding average depths. Here’s an example of such a combined mask. It can be seen the subtraction of the hand frame has taken a ‘nibble’ out of the otherwise rectangular key mask.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/hand3.Bx5gaaJX.webp" alt="Key mask with hand region subtracted" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/hand3.Bx5gaaJX.webp 480w" style="aspect-ratio: 4.528;" data-aspect="4.528" sizes="auto" loading="lazy"><figcaption>Key mask with hand region subtracted</figcaption></figure>
<p>Each key depth mean obtained from these masks is tracked and used to generate a rolling average. When this average exceeds the press depth required for the key to sound (measured at about 4.5mm on my piano), the reverse difference method is used to work out how quickly the key was going when it reached sounding point. The following graph shows how this occurs. A gradient is calculated using the points straddling the point of sound line.</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/keypressflat.iA7DFtBe.svg" alt="Keypress detection overview" loading="lazy"><figcaption>Keypress detection overview</figcaption></figure>
<h2>Results</h2>
<p>Various keys were pressed multiple times with varying volumes. Using a microphone recording and Adobe Audition, the peak amplitude of each keypress was extracted from an audio recording. These amplitude values were used as a baseline to which the calculated press velocities were compared. Plotting calculated keypress velocity against measured audio levels yielded the following graph:</p>
<figure><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/aggregateflat.XzkN7r4K.svg" alt="Results overview" loading="lazy"><figcaption>Results overview</figcaption></figure>
<p>Which is great! Lines were matched with R<sup>2</sup> values ranging from 0.81 to 0.92.</p>
<h2>Future Research and Improvements</h2>
<ul>
<li><strong>Some full-on performance analysis.</strong> VMIDI2’s results were based on repeated keypresses. This makes sense for testing a proof of concept, however it made comparison with previous research difficult. The prior research which I could turn up generally neglected velocity sensitivity but had quite thorough result gathering.</li>
<li><strong>Integration with an AMT workflow.</strong> A notable piece of prior research by <a href="http://www.cs.uleth.ca/~cheng/papers/nime2015.pdf">Akbari</a> presented a system for automatic music transcription (AMT). Essentially an RGB camera is pointed at a keyboard, and when a performance occurs, sheet music is generated. This has three phases: keyboard registration, keypress detection and transcription. VMIDI2 as it stands is ‘weak’ in some ways, as it requires manual keyboard registration and does not contain transcription features. It would be great to try to use it as the keypress detection stage in a larger process.</li>
<li><strong>Investigation into keypress detection robustness.</strong> A wide range of keys should be tested and compared to audio amplitudes. Do, say, lower keys make less sound for the same press velocity? Does each keyboard benefit from an individual calibration ‘profile’ of some sort?</li>
<li><strong>Investigation into camera positioning.</strong> Distance is a big one here. One application of VMIDI2 may well be hanging a Kinect sensor over a publicly accessible keyboard. Keeping the sensor well out of reach would be ideal, and it would be worth testing the effect of distance on accuracy. Angle may likewise be useful to test.</li>
</ul>]]>
</content:encoded>
</item>
<item>
<title>DarkMOD</title>
<description>A dynamic MusicBee skin using PHP</description>
<link>https://albert.nz/darkmod/</link>
<guid isPermaLink="false">/darkmod/</guid>
<pubDate>Mon, 06 Feb 2017 08:23:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p><a href="http://getmusicbee.com/">MusicBee</a> is without a doubt the best desktop music player I have used. I’ve been a convert now for about two years, lapping up features such as custom application launching, custom hotkeys, automatic renumbering, folder organisation and transcoding. The list goes on, and if you’re on Windows I highly suggest giving it a go.</p>
<p>Early in 2016 I attempted to combat one of my few gripes with MusicBee: the mediocre built-in themes. While the latest 3.0 version of MusicBee has helped, the only decent theme in previous versions was the rather fantastic <a href="http://getmusicbee.com/forum/index.php?topic=10884.0">DarkRED Flat</a> by forum user Endeavour1934. Using these theme files as a starting point, I developed a slightly more colourful variation. The idea was to have a single accent colour which could be easily changed to any desired colour</p>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/darkmod_screenshot.jlMrnuOI.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/darkmod_screenshot.q_-M2lo_.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/darkmod_screenshot.jlMrnuOI.webp 768w" style="aspect-ratio: 1.450;" data-aspect="1.450" sizes="auto" loading="lazy"></p>
<p>During the process, I identified a couple of things about the skinning process which I thought could be improved:</p>
<ol>
<li>The titlebar colour and border colour are actually a tiled PNG image, defined in base64 encoding within the theme file.</li>
<li>Themes cannot expose any settings to the user through the MusicBee UI.</li>
</ol>
<p>My skin contains one accent colour, but I realised that if the user wanted to change this colour they would have to not only change the colour variable within the theme XML file, but also generate matching PNGs for the border and title bar shades before exporting to base64 and pasting into the file. Not ideal!</p>
<p>From this conundrum, <a href="https://darkmod.albertnis.com">DarkMOD</a> was born. DarkMOD is a web app, which does the following:</p>
<ol>
<li>Receives a colour via GET request, sent from a webpage frontend with a pretty preview window which I spent far too long Photoshopping.</li>
<li>Calculates required shades and tints for use in the theme.</li>
<li>Generates PNG images in base64 to represent borders, title bar and checkboxes.</li>
<li>Runs through a template of the skin, replacing placeholders with the correct colours and resources.</li>
<li>Returns the completed skin file as XML.</li>
</ol>
<p>Don’t have or want MusicBee? Don’t have or want Windows? <a href="https://darkmod.albertnis.com">Head over to the project</a> anyway; the preview window should give you a good idea of what DarkMOD is about!</p>
<p>The official forum thread for DarkMOD can be found on <a href="http://getmusicbee.com/forum/index.php?topic=17373.0">the MusicBee forum</a>. The source code for DarkMOD is available on <a href="https://github.com/albertnis/darkmod-js">GitHub</a>.</p>]]>
</content:encoded>
</item>
<item>
<title>Obfuscator</title>
<description>Highlighting the potentially hilarious flaws of modern computer translators</description>
<link>https://albert.nz/obfuscator/</link>
<guid isPermaLink="false">/obfuscator/</guid>
<pubDate>Sat, 05 Nov 2016 08:23:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p><strong>2024 update</strong>: Obfuscator has become a project I enjoy rewriting every few years to learn about different technologies. Since this post, I deployed a <a href="/serverless-side-rendering">version using server-rendered React on AWS Lambda</a>, which has since been replaced by a <a href="/htmx-cloudflare-ai">version hosted on CloudFlare Pages using htmx</a>. Read on to learn more about the original PHP version of Obfuscator—the principles remain the same to this day.</p>
<p>I have always found online translators so interesting. With the continuous shrinking of the world led by the continuous growth of the internet, it is hardly surprising that we have been algorithmically converting between languages for years now, with online services like Google Translate and Bing Translate establishing themselves as vital when travelling, learning and communicating. These algorithms are good. Darn good, in fact — with <a href="http://petapixel.com/2015/01/14/googles-translate-app-can-now-use-camera-translate-world-real-time/">computer vision</a> adding to the mix for an even more functional experience.</p>
<p>If my years of learning French only taught me two things about language, they would have to be the following:</p>
<ol>
<li>Language is complicated.</li>
<li>When it comes to translation, darn good is not equal to perfect.
Indeed, while online translators are nearly always good enough, they are not perfect by any stretch of the imagination.</li>
</ol>
<p>I set out to explore and exaggerate the shortcomings of online translators by creating Obfuscator (I’m no longer hosting this version, but the latest version which uses a similar concept can be found <a href="https://obfuscator.albertnis.com/">here</a>). For those who only speak one or two languages, it can be hard to gauge the inaccuracy of translation software from a single pass. Using the Bing translate API, Obfuscator takes a phrase and translates it through three successive languages before returning it to the origin language where it can be revealed to the viewer in all its glory. Depending on the languages and phrase, the translation software may completely nail it. Other times, it may not:</p>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/example.DFd_XYb9.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/example.VhOG1Jbo.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/example.BN5ELhmU.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/example.DFd_XYb9.webp 1536w" style="aspect-ratio: 3.200;" data-aspect="3.200" sizes="auto" loading="lazy"></p>
<p>The source code for the original version of Obfuscator is available on <a href="https://github.com/albertnis/obfuscator/">GitHub</a>. Or read more about the <a href="/serverless-side-rendering">new version</a>!</p>]]>
</content:encoded>
</item>
<item>
<title>Mahia</title>
<description>A voyage to the scenic Mahia peninsula on the east coast of New Zealand</description>
<link>https://albert.nz/mahia/</link>
<guid isPermaLink="false">/mahia/</guid>
<pubDate>Sat, 30 Jul 2016 01:23:28 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>A short detour during a recent road trip to Wairoa uncovered the picturesque town of Mahia on the east coast of New Zealand.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-1.5jYhgseq.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-1.DaKyBoo-.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-1.DCwNsSFk.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-1.5jYhgseq.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-2.CAOqJnio.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-2.CxwmNzPf.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-2.Dm-KIUN7.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-2.CAOqJnio.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-4.C-dG-h6Y.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-4.DFh_SCVA.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-4.C0302Tc9.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-4.C-dG-h6Y.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-3.CvWbPliX.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-3.Dz0wMhiF.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-3.KnEAzKi1.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-3.CvWbPliX.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-5.BiLDGxJX.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-5.CF9QVFDs.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-5.Pu0df2yE.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-5.BiLDGxJX.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<p>On the way home, we had time for a brief stop at Lake Tutira, in the hills north of Napier.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-6.D2zXPOmJ.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-6.CG00ResF.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-6.BiMv0nJo.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-6.D2zXPOmJ.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-7.AyK0I1lh.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-7.CEDIg5wK.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-7.B37kcYOj.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/mahia-7.AyK0I1lh.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">]]>
</content:encoded>
</item>
<item>
<title>Afterglow</title>
<description>An experiment in spectra: using an MSGEQ7 to make RGB LED lights pulse to the music</description>
<link>https://albert.nz/afterglow/</link>
<guid isPermaLink="false">/afterglow/</guid>
<pubDate>Sun, 12 Jun 2016 08:23:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>In 2015, I discovered two glorious things in the world of electronics: Arduinos and RGB LED strip lights. Bounded only by imagination and enabled only by eBay, I set out to combine these two items in the grooviest way possible.</p>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/led.DKk_W5Qn.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/led.B0zFB5_c.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/led.C5eSUfX7.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/led.DKk_W5Qn.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"></p>
<p>As it turns out, with three MOSFETs, an IR receiver and a bit of code, it was surprisingly easy to replicate the functionality of the bundled LED strip controller with an Arduino as the brains. With a bit more code, I was able to implement buttery-smooth colour-crossfading and coloured strobing with different speeds.</p>
<p>This was all very interesting, however I knew I could do more. What if I could get the strip to dance to music? A quick search showed a popular method of achieving this was the fast Fourier transform. Fourier transforms are pretty cool, however after a bit more research I found that a higher resolution and faster response could be attained with a hardware-based approach using an <a href="https://www.sparkfun.com/products/10468">MSGEQ7</a> spectral analyser. So I bought a couple on eBay.</p>
<p>The MSGEQ7 is a neat little IC. Essentially, it combines a multiplexer and seven bandpass filters. When wired up properly to an audio source, the MSG receives regular signals from a microcontroller to send back amplitude data from each of the seven bands. This means only three pin connections are required between the chip and the controller - perfect for my Arduino Nano with many pins already used!</p>
<p>The code used for the MSGEQ7 is somewhat particular due to timing requirements, however the code I put together boils down to a nested loop which fetches spectrum data as quickly as possible, corrects for line noise, and maps three of the seven band amplitudes to the colour channels on the LED strip. I messed around with many different mapping configurations, but settled on red as band 1/7 (low bass, 63Hz), blue as band 2/7 (high bass, 160Hz) and green as band 6/7 (treble, 6.25kHz). I ended up avoiding mapping mid-tones because it didn’t look quite as impactful as emphasising bass and treble frequencies. I suppose that often these midtones are dominated by vocals, keyboard and the like, which are typically less rhythmic than bass or treble.</p>
<p>After settling on a colour scheme, I had a great deal of fun paging through my music library identifying the best- and worst-looking songs for the setup. One of the best was <em>Red Dress</em> by Submotion Orchestra. Here’s the result:</p>
<iframe src="https://www.youtube.com/embed/vmSqzH9hZJs" frameborder="0" class="video" allowfullscreen></iframe>
<p>This project has been a great one so far, but there were frustrating times. The main annoyance was the capacitor used on the audio stage before input to the MSGEQ7. At relatively high volumes, the capacitor blew, resulting in a sad, flickering green output to the LED strip with which I became all too familiar. While I simply replaced blown capacitors, the problems could probably be fixed with higher quality caps, or additional circuit protection.</p>
<p>So what’s next for the LED strip? I am keen to continue experimenting with the MSGEQ7 project, as I think there are some refinements to make. Some sort of denoising or time averaging could reduce the amount of flickering. There may also be other algorithms worth exploring to increase the smoothness and “realism” of the light response with introducing too much lag. It may also be interesting to compare the MSGEQ7’s output with FFT methods to see what the difference really is.</p>
<p>After that, I’m looking forward to continuing to experiment with other hardware which can interface with the Arduino. These future experiments sure look bright.</p>]]>
</content:encoded>
</item>
<item>
<title>A Cursory Revelation</title>
<description>Learning about the benefits of gaming mice, on a budget</description>
<link>https://albert.nz/a-cursory-revelation/</link>
<guid isPermaLink="false">/a-cursory-revelation/</guid>
<pubDate>Fri, 08 Apr 2016 22:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>A decent gaming mouse for $35? Even second-hand, that seemed just about too good to be true. Nevertheless, I bought the mouse as part of my recent <a href="/the-apex">peripherals upgrade</a>. So far the experience has been fantastic, to say the least.</p>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/g500.Dh9ZgSHI.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/g500.CgKKFkGH.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/g500.oMC6vvKa.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/g500.Dh9ZgSHI.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"></p>
<p>It didn’t start off too fantastically though. Just a week after I received my second-hand Logitech G500 I started noticing frustrating double-click issues, where the mouse would erratically click and release when the left button was held down. These second-hand qualms reached the point where I switched back to my trusty <a href="http://www.newegg.com/Product/Product.aspx?Item=N82E16826105099">Microsoft WMO1.1A</a> while I researched causes and potential fixes. First up, I followed through a <a href="https://www.youtube.com/watch?v=dWJk4EWfyOA">popular video</a> on YouTube, which involved shielding the top of the switch to prevent static buildup. This only worked partially for me however and the mouse was still barely usable. Eventually I bit the bullet and opened up the mouse switch to clean the contact point. The tutorial I used is <a href="http://zalbee.intricus.net/2014/02/how-i-fixed-my-logitech-g500-mouse/">here</a>. I opted not to remove the spring from the switch and simply scraped the contact point with a flat head screwdriver. While I couldn’t see any buildup before scraping, the trick worked marvellously; once reassembled, the mouse was as good as new!</p>
<p>I’ve had my G500 working for a couple of months now and it’s safe to say that I will have trouble ever going back to three-button mice. I don’t love the G500 because it’s a gaming mouse — I haven’t even been doing much gaming — I love it because it’s a productive mouse, and a comfortable one at that. The software Logitech provides for the G500 manages to get out of the way, quitely sorting out mappings and program detection in the background. And those mappings can be quite powerful. For instance, I’ve replaced the horizontal scrolling action with switching between dekstops. The first thumb button switches to the previous window in a desktop. Just these two buttons have saved me plenty of time while multitasking. By replacing the DPI down button with Windows Task View, I can get an even more detailed overview of my windows when the thumb button isn’t enough. This is perfect when many documents are open.</p>
<p>But it doesn’t stop there. The software can detect which program is focused and load up sets of program- or game-specific mappings. For me this is massively helpful in Adobe Lightroom, where I can now flag and develop photos one-handed without having to move my cursor up to the toolbar.</p>
<p>This all sounds very much like a marketing pitch for Logitech, but in reality I am simply amazed by how flexible multi-button mice are. Initially I was fearful that the additional buttons would be flashy and useless, but once I took the time to decide on useful mappings, I realised the potential these mice have to improve the entire window management experience as well as certain applications. Are there other brands who have even better mice and software? Quite possibly. All I know is that I’ve found a new personal standard for a mouse. I just wish I had discovered it sooner.</p>]]>
</content:encoded>
</item>
<item>
<title>The Apex?</title>
<description>A deeply flawed keyboard that succeeds in being a gimmicks but fails at being a keyboard</description>
<link>https://albert.nz/the-apex/</link>
<guid isPermaLink="false">/the-apex/</guid>
<pubDate>Thu, 25 Feb 2016 21:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>A new house means a new setup. With my audio system pretty much as good as I need it to be, I took this opportunity to turn my attention to my computer peripherals. First up: the keyboard.</p>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/apex.7l_LpMos.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/apex.BaUr7JNC.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/apex.D2O26t8G.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/apex.7l_LpMos.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"></p>
<p>I’ve been using a <a href="http://www.logitech.com/en-nz/product/wireless-combo-mk220">Logitech K220</a> for over two years now. While I like having a portable keyboard which barely takes up any desk space, the K220 is truly unnatural to type on and, at least in my experience, invariably elicits negative comments from those who use it for the first time. It was this that triggered the start of my research into mechanical keyboards. Having tried a friend’s Das keyboard with Cherry MX Blue switches, I had experienced first-hand what mechanical typing felt (and sounded) like, and I liked it. My enthusiasm was short-lived. It turns out that even second-hand mechanical keyboards sell for over $100, not to mention the cost of high-end boards with individual backlighting and USB hubs. Part of me just wasn’t ready to spend so much on a keyboard. Which is why I compromised by buying a second-hand <a href="https://steelseries.com/gaming-keyboards/apex-350">Steelseries Apex</a> keyboard.</p>
<p>The Apex is a membrane keyboard, and it turns out that, in comparison to a mechanical keyboard, the Apex was a pretty big compromise indeed. Don’t get me wrong: with five backlight colour zones, macro keys and a USB hub, the Apex has some decent features in a good-looking package, but after using the keyboard for a few hours I realised that I had lost sight of my original keyboard goal of increased comfort, instead giving in to the allure of features. The allure of needless gimmicks. The truth is that the Apex isn’t particularly comfortable to type on, and is particularly cumbersome in size. And who really needs 22 macro keys? Not me.</p>
<p>Nevertheless, in a situation not unlike the time I <a href="/going-open">went open</a>, using the Apex has ended up being a useful learning experience in that I have a better understanding of what I want and what I don’t want in a product. I’d like a mechanical keyboard. I’d like a backlight, but I don’t need a colour-changing one. I’d like a relatively compact keyboard. I’d like a comfortable keyboard. I don’t want gimmicks.</p>
<p>Knowing this, I jumped onto Massdrop and investigated their fantastic mechanical keybaord section. Sure enough, the <a href="https://www.massdrop.com/buy/magicforce-68-key-mini-mechanical-keyboard?mode=guest_open">Magicforce 68-Key Mini Mechanical Keyboard</a> seemed to fit the bill, with full-size keys, a backlight and a solid design. I got one with Gateron brown switches for their general-purpose tactility and look forward to receiving it in April. I’m really hoping that it will quench my thirst for a better typing experience — without satiating my hunger for gimmicks.</p>]]>
</content:encoded>
</item>
<item>
<title>Back to the Music</title>
<description>An offline music addict in a world of streaming, I dig into the pros of cons of being a iPod user in 2015</description>
<link>https://albert.nz/back-to-the-music/</link>
<guid isPermaLink="false">/back-to-the-music/</guid>
<pubDate>Sun, 30 Aug 2015 03:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>The world of music today is a brave new one indeed. Every time I blink it seems that a new streaming service is born. People are using these services to fulfil all their music needs. And I think that’s sad.</p>
<p>The only thing is, I can’t quite put my finger on why I think it’s sad. I’ve tried streaming services before, and they have many great features — shared public playlists, a vast array of music, a solid user base — but I’ve never been able to stick with a streaming service, always preferring to manage my own personal collection of music files. It’s a far cry from the attic of a meticulous record collector. There’s no familiar scent of papery dust. There’s no musty, almost tangible nostalgia. But there’s a sense of collection. A collection I can catalogue and tag far too pedantically. A collection I control. A collection I can play whenever and wherever I want.</p>
<p>That last point has always been a bit of a struggle for me however. My phone has limited storage and any music I want to play offline has to be downloaded from Google Play. It’s more convenient than Spotify, but not by much. Which is why the inheritance of a humble 5th generation iPod Nano has been a surprisingly pleasant experience for me. While adding music to the device from MusicBee wasn’t exactly seamless (thanks Apple), it is possible via a workaround, and it didn’t take long to set it up with 14GB of my favourite tunes. It also didn’t take long to remember just how great iPods were and still are.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ipod.D2md2UN0.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ipod.BSxcPCQE.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ipod.BQ_h2aok.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ipod.D2md2UN0.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<p>First of all, this thing is pretty much indestructible. It’s built light and it’s built solid. While it was already a bit bruised when I received it, the Nano is quite robust to say the least, yet thin at the same time. Battery life is great, with hours and hours of playback potential, and that’s to be expected; there’s no battery-sapping WiFi and Bluetooth here, just music. Plus the click wheel remains an asset, one which myself and many others will miss in future Apple products.</p>
<p>The Nano isn’t perfect, however. The menus seem clumsy at times with poor sorting. Compilations are treated weirdly. I don’t know why there’s a camera. I could really do with a few more gigabytes. But features aside, the iPod’s been great so far. In a world where it would be so easy to simply listen to the same Spotify summer playlist on repeat, this device is letting me rediscover some of the most enjoyable nooks of my own collection.</p>]]>
</content:encoded>
</item>
<item>
<title>Birds</title>
<description>Some bird photography from over the years</description>
<link>https://albert.nz/birds/</link>
<guid isPermaLink="false">/birds/</guid>
<pubDate>Thu, 16 Apr 2015 03:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Birds have always intrigued me, and getting good snaps of them is often a great challenge. Here are some from my childhood home in Wellington.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-1.zK47gy18.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-1.CJ0KXPKF.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-1.zK47gy18.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-2.CtcbKQ3z.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-2.C_EusGGs.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-2.CtcbKQ3z.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-3.Ccz1658V.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-3.DAK2vlfL.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-3.Ccz1658V.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-4.-PZ0YS56.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-4.BwbKhEf5.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-4.-PZ0YS56.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-5.CKpOTHmS.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-5.DPYyj8Rj.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-5.CKpOTHmS.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-6.CQfFR8i1.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-6.B4cuQRJN.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/birds-6.CQfFR8i1.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">]]>
</content:encoded>
</item>
<item>
<title>Makara Hills</title>
<description>Rural walk with seaside views just to the west of Wellington</description>
<link>https://albert.nz/makara-hills/</link>
<guid isPermaLink="false">/makara-hills/</guid>
<pubDate>Thu, 16 Apr 2015 03:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>The Makara hills are home to walkways with awesome views.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-1.BQaCn6oz.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-1.BbP7JtCW.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-1.BQaCn6oz.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-2.hvAXoIc8.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-2.AWtwXavD.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-2.hvAXoIc8.webp 768w" style="aspect-ratio: 1.505;" data-aspect="1.505" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-3.o8dkKd9k.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-3.D2zgwgHM.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-3.o8dkKd9k.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-4.BvxEHveW.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-4.D6cgEmr5.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-4.BvxEHveW.webp 768w" style="aspect-ratio: 1.505;" data-aspect="1.505" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-5.DHTSNrsp.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-5.CE0IwWFG.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-5.DHTSNrsp.webp 768w" style="aspect-ratio: 5.393;" data-aspect="5.393" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-6.BvRp1HmO.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-6.xtaJfJJM.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-6.BvRp1HmO.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-7.DR9aq297.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-7.C3IIiHuh.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/makara-7.DR9aq297.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">]]>
</content:encoded>
</item>
<item>
<title>Moto L</title>
<description>Impatience made me delve into custom Android ROMs for my Motorola Moto G</description>
<link>https://albert.nz/moto-l/</link>
<guid isPermaLink="false">/moto-l/</guid>
<pubDate>Sun, 12 Apr 2015 03:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Lollipop has been a long time coming.</p>
<p>In fact, it still hasn’t officially arrived to a large proportion of Moto G owners, myself included. Motorola’s rollout of Android 5.0.2 Lollipop has been <a href="https://forums.motorola.com/posts/edc7501cc4">frustratingly slow</a>, having taken nearly three months and counting, releasing to individual carriers in individual countries. I’m sure that I’m missing something but the whole ordeal is a shambles given that the software on the Moto G is so very nearly vanilla. What’s more, 4.4.4 KitKat has been plagued with memory leak issues. The Facebook app started taking 30 seconds to open. Every time I returned to the home screen the launcher would have to restart after having run out of memory and music would stop playing after a few seconds if I opened another app. Clearing the cache didn’t help.</p>
<p>My phone is the ‘Global GSM’ version of the US Moto G XT1032. After waiting months with no update, I became fed up with my nearly-crippled phone and turned to <a href="http://www.reddit.com/r/motog">Reddit</a> and <a href="http://forum.xda-developers.com/moto-g">XDA Forums</a> where I found many users in the same situation. I read daunting guides about manually flashing system images and custom ROMs. Yesterday I bit the bullet and somehow succeeded in manually installing Android Lollipop 5.1 on my phone (technical details below).</p>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/lollipop.Br53-hth.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/lollipop.C_-ad1vE.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/lollipop.DBNEcSS6.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/lollipop.Br53-hth.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"></p>
<p>My first impressions of 5.1 compared to 4.4.4 are fantastic. Not only are those slick material design elements abundantly present, but memory management has come a long way. Apps open promptly, and the launcher only crashes if I do some serious multitasking. I can listen to music while browsing Facebook and using Facebook Messenger with no issues whatsoever. Plus there’s a whole host of new settings to explore, and a mysterious camera flash bug has been fixed. It’s early days for Lollipop on my phone though; I’m just hoping that performance won’t deteriorate over time. But so far, so good. In fact, 5.1 is so smooth and bug-free that this whole experience has made me even more skeptical of Motorla’s customer support and rollout strategy.</p>
<p>There were many steps to installing Android 5.1 Lollipop, and I’ll summarise them here:</p>
<ol>
<li><strong>Unlock the bootloader.</strong> This step has <a href="https://motorola-global-portal.custhelp.com/app/standalone/bootloader/unlock-your-device-a">officical instructions</a> provided by Motorla itself. This voids the phone’s warranty and erases all data on the device. Unlocking the bootloader allows the installation of images and ROMs not authorised by Motorola. I had some trouble rebooting afterwards, but it worked after retrying a couple of times.</li>
<li><strong>Flash Android 4.4.4 GPe.</strong> <a href="http://forum.xda-developers.com/moto-g/general/gpe-ota-lrx21z-5-0-1-xt1033-xt1032-t2969847"><em>pfufle</em>’s excellent tutorial</a> at XDA Forums is what I followed for the rest of the process. After downloading and setting everything up on my computer, I followed step 2.2 to install the Google Play Edition of 4.4.4 KitKat on my phone. This essentially changes the phone to the Google Play Edition of the Moto G which, like a Nexus Device, receives updates early and directly from Google.</li>
<li><strong>Flash the 5.0.2 OTA update.</strong> Still following pfufle’s tutorial, 5.0.2 now needs to be flashed (manually, for some reason). This is done through booting into recovery and using ADB. After doing this step I ended up being stuck at the boot animation. After trying a <a href="http://forum.xda-developers.com/showpost.php?p=57444986&#x26;postcount=1">suggested fix</a> in vain, I realised I had actually missed some lines of code in step 2.2. The key things to learn here are (a) use the batch files provided, (b) if anything gets stuck while booting, holding down the power button for a few seconds will force the phone to turn off, and (c) the process can always be restarted from step 2.1.</li>
<li><strong>Install Android 5.1.</strong> This is the easy part. After successfully booting into 5.0.2, connect to the internet and wait a few minutes if needed. Just like an unmodified phone, a notification to install 5.1 will appear. Simply download and install it.</li>
<li><strong>Flash modem firmware (if needed).</strong> The tutorial mentions flashing modem firmware. For whatever reason, my network performed just fine, 3G and all, without additional modification. Perhaps this means the hardware in the Global GSM Moto G is identical to that in the GPe device.</li>
<li><strong>Consider rooting (optional).</strong> It doesn’t look hard, but I decided to pass on this step. Rooting the phone does have its perks, however I’m happy with 5.1 as it stands.</li>
</ol>]]>
</content:encoded>
</item>
<item>
<title>The Dragonfly Effect</title>
<description>I take my first dip into better computer audio</description>
<link>https://albert.nz/the-dragonfly-effect/</link>
<guid isPermaLink="false">/the-dragonfly-effect/</guid>
<pubDate>Fri, 06 Feb 2015 03:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Around the time I bought my <a href="/going-open">Grado SR125i</a>, I found the Audioquest Dragonfly DAC at <a href="http://onlinehifi.co.nz/">Online Hi-Fi</a> for a fantastic price. So why did I buy a DAC? Well, I had been thinking about purchasing an amplifier to improve the sound quality of my music, when I realised an amplifier isn’t very useful if the source isn’t up to par. Enter the Dragonfly DAC: a well-reviewed and affordable digital-to-analogue converter which could bypass my laptop’s sound card to provide at least theoretically improved sound quality suitable for my soon-to-be-upgraded headphones.</p>
<p><img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dragonfly.DCe-Zoxp.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dragonfly.DKMk19Gr.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dragonfly.CcWb5jXR.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/dragonfly.DCe-Zoxp.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy"></p>
<p>When I unboxed the Dragonfly, I was surprised by how well-built this little machine is. It feels like a heavily over-engineered flash drive: heavy for its size and beautifully surrounded by a soft-touch finish. A cap and pleather case is included. Just when I thought it couldn’t look any cooler, I plugged it into my computer. The Dragonfly emblem lights up with one of four colours, depending on the sample rate at which it is being used. On Windows, sample rate is set globally, but applications can override it if needed. MusicBee can use WASAPI to essentially commandeer the Dragonfly to change its sample rate depending on the track playing.</p>
<p>So far, so good. However, the most important attribute of a DAC is undoubtedly sound quality. Flicking through my test track playlist with my ATH-M50XBL on, the Dragonfly performs nicely compared to my PC’s built-in soundcard. I heard the most notable improvement in the high frequencies. Crackles, pops and cymbals become far clearer, more spacious and more realistic. This effect comes off as eerie in several tracks by Baths, with the sibilance in Lovely Bloodflow and Miasma Sky suddenly seeming to breathe down my neck. The Dragonfly also shines when it comes to bass. Luckily bass isn’t boosted here, but becomes tighter. This is not always apparent in electronic music but is clear in more acoustic music: The drum kicks in Aaron Jerome’s very well-mastered version of Dancing Girl became less boomy, enabling me to hear the actual kick of the bass pedal more clearly. In terms of mids, I haven’t been hearing much difference. Vocals, guitar and piano do seem more spacious and maybe a touch more forwards, however a different pair of headphones would probably show more improvement.</p>
<p>So what’s the verdict? Has the humble Dragonfly revolutionised my music experience? The answer is both yes and no. For better or for worse, the Dragonfly and the ATH-M50XBL enable me to identify well-mastered albums. Tracks on these albums sound clean, detailed and generally sublime. But not everything is well-mastered (Amy Winehouse and Imagine Dragons come to mind). It is on such albums that the Dragonfly effect is wasted.</p>]]>
</content:encoded>
</item>
<item>
<title>Going Closed</title>
<description>A new headphone adventure ends early as I realise open-back headphones aren't perfect</description>
<link>https://albert.nz/going-closed/</link>
<guid isPermaLink="false">/going-closed/</guid>
<pubDate>Mon, 26 Jan 2015 03:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>That didn’t take long. Sure enough, after only three weeks with the SR125i, I have sold my open-backed cans and am back on the closed-back bandwagon. That’s not to say I regret the purchase of the mighty Grados. Here are a few things I learnt from the experience:</p>
<ol>
<li><strong>Rock can sound amazing.</strong> The airiness and natural sound the SR125i delivered was unlike anything else I’ve ever heard for rock and jazz music. There’s something about how guitar, piano and strings sound on these headphones that brings life to such genres. The best example I heard of this was probably <em>The Summer You Never Meant</em> by The Exponents. From the first strum the song absolutely brimmed with energy while sounding completely effortless.</li>
<li><strong>A natural sound isn’t everything.</strong> That lovely acousitc sound is awe-inspiring for a lot of music, rock and jazz especially, and was one of the main reasons I settled on the SR125i in the first place. But I quickly realised that for someone who mostly listens to electronic music, a natural sound actually isn’t desirable a lot of the time, and this mostly has to do with the nature of open-backed bass. The bass was there, sure, but unlike closed cans, this bass serves only to supplement the strum of a bass guitar or to add oomph to the kick of a drum. It is simply not capable of provide a driving force to bass-heavy music - the very driving force electro and hip-hop music requires.</li>
<li><strong>Versatility never hurt anyone.</strong> I’m pretty easy when it comes to headphone comfort, and I was never really bothered by the simple, poorly-padded design of the SR125i. What did irk me, though, was the presence of a 6.35mm audio jack and the absence of a removable cable. I mean, really, who would prefer to have a 6.35mm jack over a 3.5mm one? I guess it didn’t help that my two dollar adapter slowly began to give up the ghost shortly after I obtained the SR125i.</li>
<li><strong>Audio-Technica knows a thing or two.</strong> I honestly don’t know where to start: style, comfort, sound, versatility; the ATH-M50X really does have it all. I managed to pop back to <a href="http://wellington.listeningpost.co.nz/">The Listening Post</a> while the blue version was still available, and what a pair they are. They’re like the good-looking, more spacious, more detailed, older cousin of my old HD-212 Pro. On this note, maybe the SR125i was the old, wise (but often frustrating and irrationally inflexible) grandparent.</li>
</ol>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ath.Cq4J8Uc8.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ath.DDeQmJLa.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ath.BEIcjrMG.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/ath.Cq4J8Uc8.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<p>The SR125i is dead, long live the ATH-M50X!</p>]]>
</content:encoded>
</item>
<item>
<title>Going Open</title>
<description>Moving beyond the closed-back world of the ATH-M50x and starting a new headphone adventure</description>
<link>https://albert.nz/going-open/</link>
<guid isPermaLink="false">/going-open/</guid>
<pubDate>Wed, 24 Dec 2014 03:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>Recently I decided to upgrade my computer audio experience to match my improving and expanding music library. So I bought a DAC and some new headphones. The former is the <a href="/the-dragonfly-effect">Audioquest Dragonfly v1</a>, a well-reviewed DAC which I got for the ridiculous(ly low) price of $99 in a clearance sale at <a href="http://onlinehifi.co.nz/">Online Hi-Fi</a>. It took a lot longer to come to a decision on new headphones. After doing some research, I decided going open-back would be a good upgrade from my closed-back Sennheiser HD-212 Pros. I went to the absolute gem of a store which is <a href="http://wellington.listeningpost.co.nz/">The Listening Post</a> where they have a whole lot of mid-range headphones available for audition with an amp. The staff directed me straight to the Grado SR125i, on sale for $199. I was expecting the bass to be a let down with open-backed heaphones such as these, but the Grados had enough deep bass for a bit of electronic music. The Sennheiser HD-558 was quickly dismissed for lack of bass, despite its quite frankly astounding comfort. I had a quick listen to the store’s Audio-Technica ATH-M50x (a headphone I had listened to many times before) and the bass seemed boomy and confined after the open-backed offerings. I settled on the Grado SR125i.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/grado.DrQUakmd.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/grado.8J56_9hr.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/grado.DqXlzPOW.webp 768w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/grado.DrQUakmd.webp 1536w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<p>The Dragonfly-SR125i combo has been a revelation for me in many ways. Rock music has come alive with a new level of detail. In more simple tracks like R.E.M.’s <em>Nightswimming</em>, the action of the piano is clear and vocals are reasonably forward. Complex tracks such as <em>Dirty Work</em> by Steely Dan sound layered. I’ll admit it though, while the SR125i’s bass does have decent extension (Clean Bandit’s <em>Nightingale</em> is a great subbass test) and presence, it lacks the punch of my trusty HD-212 Pros. I’ll definitely be keeping my previous headphones for their extremely fun (if far-from-neutral) sound signature, which is well suited to electronic music, as well as for their isolation.</p>]]>
</content:encoded>
</item>
<item>
<title>Waikawa Bay</title>
<description>A quiet spot in the Marlborough Sounds</description>
<link>https://albert.nz/waikawa-bay/</link>
<guid isPermaLink="false">/waikawa-bay/</guid>
<pubDate>Thu, 30 Jan 2014 03:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>In January 2014, I spent a week in a remote part of the Marlborough Sounds, opposite the southern tip of D’Urville Island.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-1.cjsesl32.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-1.Dn58O5ZA.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-1.cjsesl32.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-2.CFoFN2oD.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-2.C8zpOKDL.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-2.CFoFN2oD.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-3.Cz72Eyd9.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-3.D7ArUZ35.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-3.Cz72Eyd9.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-4.DuRDvGXM.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-4.CWsjKh0q.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-4.DuRDvGXM.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-5.8OfTQZxQ.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-5.Dx0SyTXe.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-5.8OfTQZxQ.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-6.bIJKDDv6.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-6.BAWmW_El.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/waikawa-6.bIJKDDv6.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">]]>
</content:encoded>
</item>
<item>
<title>San Francisco</title>
<description>An enjoyable stay in an unbelievable city</description>
<link>https://albert.nz/san-francisco/</link>
<guid isPermaLink="false">/san-francisco/</guid>
<pubDate>Wed, 31 Jul 2013 03:47:25 GMT+0</pubDate>
<content:encoded>
<![CDATA[<p>In July 2013 I was lucky enough to travel to San Francisco as a family holiday, armed with my trustworthy 40D and four lenses. The city proved to be even more picturesque than I had expected.</p>
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-1.TXngS5FN.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-1.BebHyMMA.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-1.TXngS5FN.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-2.CWjTQnlz.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-2.Bb7Tii1C.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-2.CWjTQnlz.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-3.DEE5tlXx.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-3.jwAvuh2l.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-3.DEE5tlXx.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-4.CNjf29Rn.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-4.ME3FJAvQ.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-4.CNjf29Rn.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-5.BS7XVirR.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-5.ChHoJs09.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-5.BS7XVirR.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-6.ioSzbtnr.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-6.6sSwr75k.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-6.ioSzbtnr.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">
<img src="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-7.zvgqH8sL.webp" alt="" srcset="https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-7.Cd--6xGp.webp 480w,https://9c137227.albert-nz.pages.dev/_app/immutable/assets/sanfran-7.zvgqH8sL.webp 768w" style="aspect-ratio: 1.500;" data-aspect="1.500" sizes="auto" loading="lazy">]]>
</content:encoded>
</item>
</channel>
</rss>