mikecoats.com Zola 2025-03-30T13:37:00+00:00 https://mikecoats.com/atom.xml Stop the WANPTEK/NANKADF power supply's beeps 2025-03-30T13:37:00+00:00 2025-03-30T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/nankadf-power-supply-quiet/ <p>The Nankadf (aka Wanptek) WPS3010H power supply is an excellent piece of test equipment to have on the bench. It's cheap, reliable and it's got a wide range for both voltage and current output.</p> <p><img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/in-situ.webp" alt="My Nankdaf WPS3010H power supply charging some Li-ion cells." loading="lazy" decoding="async" /></p> <h2 id="">Problem</h2> <p>It does have one fatal flaw; out of the box it beeps on every interaction.</p> <p>Turn it on? *beep* Reduce the current limit by 0.1 A? *beep* Nudge the voltage up by 0.01 V? *beep*</p> <h2 id="-1">Solution 1 - Turn off the beeps in the menu</h2> <p>According to <a rel="noopener" target="_blank" href="https://web.archive.org/web/20220501203314/https://m.media-amazon.com/images/I/B1pusLYs5dS.pdf">the manual for this range</a> of PSUs, we can dive in to the system settings and stop the beeps.</p> <blockquote> <p><strong>System settings</strong></p> <p>Press and hold the Output button for 5 seconds to enter the system setting function. According to the needs of use, the machine can be set by default.</p> <p>The system default setting items include:</p> <p>[...]</p> <ol start="4"> <li>Buzzer switch.</li> </ol> <p>[...]</p> <p>After entering the system settings, turn the voltage encoding switch to change the default parameters of the current project; press the voltage encoding switch to switch to the next item, if you have switched to the last item (item 6), click the voltage encoding switch again, the machine will Save the parameters and exit the setting state.</p> </blockquote> <p>Changing the setting for item 4 from <code>1</code> to <code>0</code>, should turn off the beeps.</p> <p><img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/manual-beeps.webp" alt="An excerpt from the manual indicating a buzzer mute, when options 4 is set to 0." loading="lazy" decoding="async" /></p> <p>This works for all of the beeps except for one. The power supply still beeps on start up. Unfortunately, this beep is the longest, and seemingly loudest, noise it makes.</p> <h2 id="-2">Solution 2 - Remove the beeper from the PSU</h2> <p>Do you really own anything if you don&#39;t modify it?</p> <p>Luckily for us buzzers and beepers are usually large, obvious, and simple to desolder once you find them. Let&#39;s tear the power supply down and remove that buzzer!</p> <p>Start by pulling off the two knobs from the voltage and current selectors, then undo the screws holding the metal cover to the body of the supply. You don&#39;t need to pay too much attention to which screw came from where, just be aware of the different threads for screwing in to metal vs. plastic.</p> <p><img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/front.webp" alt="The front of the power supply, without knobs on the voltage and current controls." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/side.webp" alt="The side of the power supply. 4 screws are visible" loading="lazy" decoding="async" /></p> <p>Once inside, be careful around the circuit boards, particularly the capacitors, since they could still hold enough energy to give you a jolt. Remove two screws from the bottom of the front panel and one from the cross brace and then front panel is loose enough to move around.</p> <p><img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/cross-brace.webp" alt="A close up of a screw connecting the front panel to a cross brace." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/control-board-connected.webp" alt="The front panel mechanically detached but electrically connected to the rest of the power supply." loading="lazy" decoding="async" /></p> <p>Disconnect the two ribbon cables from the back of the PCB and the five screws circled in white. The control panel PCB can then be removed from the front casing.</p> <p><img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/control-board-disconnected.webp" alt="The control panel PCB, both mechanically and electrically disconnected." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/pcb-with-beeper.webp" alt="A wide shot of the PCB, buzzer visible." loading="lazy" decoding="async" /></p> <p>A quick look around the board reveals <code>BUZ1</code> over by the rotary encoders used for setting the voltage and current. Luckily for us it&#39;s a big through-hole part, so desoldering takes just a couple of seconds with a decent iron and a solder sucker.</p> <p><img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/close-up-with-beeper.webp" alt="A close-up shot of the PCB, buzzer visible." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/nankadf-power-supply-quiet/close-up-no-beeper.webp" alt="A close-up shot of the PCB, the buzzer has been removed." loading="lazy" decoding="async" /></p> <p>Re-assembly is just the teardown in reverse! Power it up, test it still works, then bask in the silent bliss of a land without beepers or buzzers.</p> Low Tech Prototyping 2025-03-22T13:37:00+00:00 2025-03-22T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/low-tech-prototyping/ <p>In <a href="/van-spoof-muntzing/">my earlier</a> VanSpoof <a href="/van-spoof-muntzing/">prototyping posts</a> I was working with circuit boards designed for testing and debugging, but not really suitable for installing on to the bike. They're too big and the wrong shape to fit in any of the any wee nooks or crannies around the bike, so I need to come up with something smaller and find somewhere to install it.</p> <p>A chap called Max reached out and suggested that we could possibly design a board that could fit inside the frame tubes, but I'm not sure if the current STM32 chip is small enough to fit through the existing hole used by the e-shifter cable. There are other, smaller, chips available but I don't know if they've got the right peripherals on the correct pins for our purposes.</p> <p>Instead, I looked at the device replaced with my VanSpoof, the e-shifter itself!</p> <p><img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/gears.webp" alt="An open VanMoof e-shifter." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/motor.webp" alt="The same e-shifter with the gearset removed." loading="lazy" decoding="async" /></p> <p>Taking the shell apart reveals a series of gears driven by a motor. Taking out the gearset shows the PCB within the e-shifter. It&#39;s a pretty convoluted shape, with cutouts for the gears&#39; bearings and it has been heatstaked in place, so a 1:1 replacement is off the cards. Removing the motor leaves a mostly rectangular area which could give us enough space to fit all our components.</p> <p><img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/empty.webp" alt="The motor area of the e-shifter with the motor removed." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/prototype.webp" alt="The same space filled with a small piece of MDF." loading="lazy" decoding="async" /></p> <p>Due to the awkward position of the space and the surrounding case, taking direct measurements with my callipers proved tricky. Its jaws weren&#39;t quite long enough to prevent the body from fouling on the case. This is when I returned to one of my favourite low tech prototyping methods. 1:1 scale models from paper, card, or board! Using some thin MDF sheets I recovered from old picture frame backings, I cut and trimmed a piece until it was a snug fit in the space.</p> <p>I could then pop that piece back out of the space and measure it instead.</p> <p><img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/full-width.webp" alt="A set of digital callipers measuring the full width of the piece." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/notch-width.webp" alt="A set of digital callipers measuring the reduced width of the piece." loading="lazy" decoding="async" /></p> <p>Measurements of 31.45 mm reducing to 27.45 mm were taken for the width of the cavity.</p> <p><img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/full-depth.webp" alt="A set of digital callipers measuring the full depth of the piece." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/notch-depth.webp" alt="A set of digital callipers measuring the reduced depth of the piece." loading="lazy" decoding="async" /></p> <p>The depth of the space was found to be 21.55 mm reducing to 18.20 mm.</p> <p>Transferring these measurements to KiCAD let me throw together a quick design to see if all of the components are likely to fit. I still need to work out if everything is routable within these new constraints, but my early tests are hopeful.</p> <p><img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/template.webp" alt="The piece of MDF placed on the e-shifter." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/2025-03-22-low-tech-prototyping/fit-test.webp" alt="A screenshot of KiCAD showing the VanSpoof circuit roughly laid out in the same shape." loading="lazy" decoding="async" /></p> <p>I believe the incoming cable, with Orange, Black, Brown and White wires, is terminated with a <a rel="noopener" target="_blank" href="https://www.jst.co.uk/productSeries.php?pid=93&amp;cat=117">JST-SH connector</a>. This hopefully means that I can use a JST-SR header, <a rel="noopener" target="_blank" href="https://web.archive.org/web/20240620152546/https://www.jst.co.uk/downloads/series/eSH_(21-03-24).pdf">probably a BM04B-SRSS-TB</a>, and make the VanSpoof a drop-in replacement part for X3 and S3 owners.</p> GNU Terry Pratchett 2025-03-12T13:37:00+00:00 2025-03-12T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/x-clacks-overhead/ <p>This post is a break from the technical show-and-tell theme I've adopted of late and is much more personal. Please feel free to skip if that's likely to bug you.</p> <p><img src="https://assets.mikecoats.xyz/x-clacks-overhead/books.webp" alt="A stack of books by Terry Pratchett." loading="lazy" decoding="async" /></p> <p>Today marks ten years since the passing of Terry Pratchett.</p> <p>I grew up with his work; the first Discworld novel was published when I was just 3 months old. I spent my adolescence reading his books, playing the video games, and wishing that Sky TV and the BBC did something more with their adaptations than just throwing large sums of money at the casting directors.</p> <p>I now find myself parenting a two-year-old with an insatiable appetite for books. We obviously get through our fair share of classics like <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/The_Gruffalo">The Gruffalo</a> and <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/We&#x27;re_Going_on_a_Bear_Hunt">We&#39;re Going on a Bear Hunt</a>, but none of them make me smile like being asked for &quot;daddy&#39;s book&quot; at bedtime. The 20 minutes spent reading from our latest Anhk-Morporkian adventure as the little one drifts off are the best 20 minutes of my day.</p> <p>In honour of the impact he&#39;s had on my life, I have joined the ranks of <a rel="noopener" target="_blank" href="https://www.revk.uk/2015/03/no-one-is-actually-dead-until-ripples.html">similarly nerdy sysadmins</a> and enabled the <code>x-clacks-overhead</code> header on all of my websites and services. GNU Terry Pratchett.</p> <p>For those <a rel="noopener" target="_blank" href="https://xkcd.com/1053/">not in the know</a> about clacks and GNU, <a rel="noopener" target="_blank" href="https://old.reddit.com/u/daggerdragon/">daggerdragon</a> has written this explanation on <a rel="noopener" target="_blank" href="http://www.gnuterrypratchett.com/">gnuterrypratchett.com</a>.</p> <blockquote> <p>In Terry Pratchett&#39;s Discworld series, the clacks are a series of semaphore towers loosely based on the concept of the telegraph. Invented by an artificer named Robert Dearheart, the towers could send messages &quot;at the speed of light&quot; using standardized codes. Three of these codes are of particular import:</p> <ul> <li>G: send the message on</li> <li>N: do not log the message</li> <li>U: turn the message around at the end of the line and send it back again</li> </ul> <p>When Dearheart&#39;s son John died due to an accident while working on a clacks tower, Dearheart inserted John&#39;s name into the overhead of the clacks with a &quot;GNU&quot; in front of it as a way to memorialize his son forever (or for at least as long as the clacks are standing.)</p> </blockquote> <p>If you want to spot other sites out there using the clacks to honour Terry&#39;s legacy, there are extensions for both <a rel="noopener" target="_blank" href="https://addons.mozilla.org/en-US/firefox/addon/gnu_terry_pratchett/">Firefox</a> and <a rel="noopener" target="_blank" href="https://chromewebstore.google.com/detail/clacks-overhead-gnu-terry/lnndfmobdoobjfcalkmfojmanbeoegab">Chromium</a> based browsers that pick up the header and flash the message in their icon.</p> <p><img src="https://assets.mikecoats.xyz/x-clacks-overhead/firefox-clacks.webp" alt="A screenshot of Firefox&#39;s X-Clacks-Overhead extension detecting the header sent by my mastodon server." loading="lazy" decoding="async" /></p> VanSpoof – A-Muntzing We Will Go 2025-03-05T13:37:00+00:00 2025-03-05T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/van-spoof-muntzing/ <p>With my first few VanSpoof prototypes assembled and the firmware coming together nicely, I realised it was time to start working on the second and hopefully final version of the board.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-muntzing/van-spoof.webp" alt="The back side of a VanSpoof prototype PCB." loading="lazy" decoding="async" /></p> <p>I had noticed that the L78L05, responsible for providing 5 V for the microcontroller&#39;s LDO and the USART pull-up resistor, was sometimes getting a little hot.</p> <p>The initial version of the VanSpoof circuit was my first time designing with SMD parts, so I included a bunch of extra LEDs to prove that each stage of the power supply and the microcontroller were working. According to my power supply, at 24 V my circuit is drawing 27 mA and the L78L05 handles about 19 of those 24 V. That&#39;s about 0.6 W being dropped by the regulator.</p> <p>The resistor in this slow-motion video is burning 0.5 W, almost equivalent to the L78L05&#39;s load, and provides more than enough heat to flash boil IPA on contact.</p> <p><video controls disablepictureinpicture playsinline src="https://assets.mikecoats.xyz/van-spoof-muntzing/sizzle.webm" alt="A drop of IPA evaporates, sizzling, on a resistor."></video></p> <p>I was intrigued by how much of that current draw was being consumed by my 3 status LEDs and how much was actually being used by the microcontroller to talk to the USART pins. Instead of knuckling down and calculating the values, I turned to the tried and tested method of <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Muntzing">Muntzing</a>. Unlike the eponymous Mr. Muntz, I didn&#39;t physically cut parts out of an existing circuit; I used one of the <a rel="noopener" target="_blank" href="https://docs.oshpark.com/services/two-layer/">spare boards I&#39;d ordered from OSH Park</a>, including only the bare minimum of components needed to get the board to boot.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-muntzing/muntz-ed.webp" alt="The front of a VanSpoof prototype PCB with all LEDs and their resistors missing" loading="lazy" decoding="async" /></p> <p>Leaving off those three LEDs and their accompanying resistors dropped the current draw to a maximum of 6 mA. The L78L05 now only has to burn off 0.1 W. It is even running cool enough I don&#39;t think I&#39;ll need to redesign the power supply circuit for the final version.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-muntzing/muntz-ed-power.webp" alt="A power supply showing 100 mW of power draw." loading="lazy" decoding="async" /></p> <p>In my final design, I also won&#39;t be including a physical push button to reset the board, so I removed the whole &quot;External reset circuit&quot; detailed on page 66 of <a rel="noopener" target="_blank" href="https://web.archive.org/web/20250116100153/https://www.st.com/resource/en/datasheet/stm32g030f6.pdf">the STM32G030x6/x8 datasheet</a>. The datasheet claims &quot;the reset network protects the device against parasitic resets&quot;. I&#39;ve no formal training in electronics, so I have no idea what that really means or how often you could expect the device to undergo a &quot;parasitic reset&quot;. It turns out that my office has the perfect conditions to place an STM32 in to parasitic reset, so every time I booted the board without a debugger connected, it would reset. Adding the NRST capacitor back allowed the board to function properly once more.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-muntzing/nrst.webp" alt="A close up of the PCB showing the NRST capacitor." loading="lazy" decoding="async" /></p> <p>Another problem I identified with the first prototype was the pretty slow rise-time on the USART TX signal. Every transition from 0 to 5 V happened with a definite &quot;charging capacitor&quot; shaped curve.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-muntzing/full-shark.webp" alt="A zoomed out oscilloscope trace. Each pulse has a curved top." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/van-spoof-muntzing/zoom-shark.webp" alt="A zoomed in oscilloscope trace. Each pulse has a curved top." loading="lazy" decoding="async" /></p> <p>In my original design I used a 62 kΩ resistor as my 5 V pull up, which I picked out of an abundance of caution. The IO pins on these 3.3 V STM32 chips are only 5 V &quot;tolerant&quot;, so I didn&#39;t want to push too much current through the pin.</p> <p>According to the datasheet, the chip&#39;s internal pull-up resistor has a typical value of 40 kΩ. Using 3.3 V as the supply means that STMicroelectronics would typically push and pull just 82.5 μA through the pin. Being naive, I tried to maintain that low current with my higher 5 V USART signal by staying as close to 60.6 kΩ as I could.</p> <p>A closer read of the datasheet reveals the IO pins can actually sink and source 15 mA. To quickly assess the response of the board to a lower pull-up value, I stacked three of my 62 kΩ resistors in parallel for an equivalent resistance of approximately 20 kΩ.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-muntzing/pull-up.webp" alt="Three resistors stacked in parallel to try to square off the RX trace." loading="lazy" decoding="async" /></p> <p>The USART signal&#39;s rise-time with this configuration is much better. The new 240 μA current is still two orders of magnitude away from the chip&#39;s limits, too.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-muntzing/full-straight.webp" alt="A zoomed out oscilloscope trace. Each pulse has a squared off top." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/van-spoof-muntzing/zoom-straight.webp" alt="A zoomed in oscilloscope trace. Each pulse has a squared off top." loading="lazy" decoding="async" /></p> <p>With these changes tested, I&#39;m much happier designing a new version of the board and sending it off for manufacture.</p> Simplify VCD 2025-02-28T13:37:00+00:00 2025-02-28T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/simplify-vcd/ <p>Simplify VCD is a command-line tool designed to make working with Value Change Dump (VCD) files more efficient and faster.</p> <p><img src="https://assets.mikecoats.xyz/simplify-vcd/cover.png" alt="Simplify VCD on PyPI." loading="lazy" decoding="async" /></p> <p>VCD files can quickly become large and unwieldy, making analysis and processing slow and cumbersome. This tool enables users to clip sections of VCD files, trim irrelevant data, and reduce the resolution to a more manageable timescale, significantly improving processing speed and usability.</p> <p><img src="https://assets.mikecoats.xyz/simplify-vcd/timed-sigrok.png" alt="A VCD processed into microsecond resolution, being timed and interpreted by sigrok-cli. A multi-second long capture was processed in just over a second." loading="lazy" decoding="async" /></p> <p>It started out as a simple script to convert the <code>analyzer</code> captures from my <a rel="noopener" target="_blank" href="https://glasgow-embedded.org/latest/intro.html">Glasgow Digital Interface Explorer</a> down to the micro-second scale to speed up analysing them with <a rel="noopener" target="_blank" href="https://sigrok.org/wiki/Main_Page">Sigrok &amp; Pulseview</a>, but it grew arms and legs until it became what you see here.</p> <p><img src="https://assets.mikecoats.xyz/simplify-vcd/pulseview-modbus.png" alt="A Modbus conversation visualised in Pulseview." loading="lazy" decoding="async" /></p> <p>It&#39;s distributed <a rel="noopener" target="_blank" href="https://pypi.org/project/simplify-vcd/">via PyPI</a>, so installing it is just a <code>pip install simplify-vcd</code> away! The code is available from <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/simplify-vcd">my Codeberg repository</a>. It&#39;s free software so you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p> <p>I can&#39;t vouch it will play nicely with the VCDs produced by your favorite tool, but it&#39;s managed fine with the captures I&#39;ve made with my Glasgow.</p> <p><img src="https://assets.mikecoats.xyz/simplify-vcd/glasgow.jpg" alt="A Glasgow Digital Interface Explorer" loading="lazy" decoding="async" /></p> <p>Pull Requests to support other, more complicated, VCD files are welcome!</p> VanSpoof - Prototype 2 - Echo Firmware 2025-02-17T13:37:00+00:00 2025-02-17T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/van-spoof-prototype-2-firmware-1/ <p>In <a href="/van-spoof-prototype-1-part-1/">part 1</a> and <a href="/van-spoof-prototype-1-part-2/">part 2</a> of building my first VanSpoof prototype, I managed to flash the microcontroller on the PCB with a blinky demo. This time round, let's see about sending and receiving some serial data.</p> <p>In this prototype, we'll read every byte that comes in to our USART, then echo it straight back out. We can adjust our code's forever loop to wait until it's <code>read()</code> a byte from the USART, then <code>write!()</code> it out again.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#93a1a1;">// Forever more... </span><span> </span><span style="color:#859900;">loop </span><span>{ </span><span> </span><span style="color:#93a1a1;">// Read a byte from the USART, waiting until we&#39;ve got one. </span><span> </span><span style="color:#268bd2;">let</span><span> byte = </span><span style="color:#859900;">block!</span><span>(usart.</span><span style="color:#859900;">read</span><span>()).</span><span style="color:#859900;">expect</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Failed to read from USART2.</span><span style="color:#839496;">&quot;</span><span>); </span><span> </span><span style="color:#93a1a1;">// Write it straight back out again. </span><span> </span><span style="color:#859900;">write!</span><span>(usart, </span><span style="color:#839496;">&quot;</span><span style="color:#cb4b16;">{}</span><span style="color:#839496;">&quot;</span><span>, byte).</span><span style="color:#859900;">expect</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Failed to write to USART2.</span><span style="color:#839496;">&quot;</span><span>); </span><span> } </span></code></pre> <p>If we hook up a <a rel="noopener" target="_blank" href="https://glasgow-embedded.org/latest/intro.html">Glasgow Digital Interface Explorer</a> to our PCB's RX &amp; TX pins, we can send our laptop's key-presses to the VanSpoof and see what we get back. Typing the traditional "Hello, world!" message gives us the following output.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-2-firmware-1/ascii-codes.png" alt="A screenshot of Glasgow output. Instead of text, the ASCII codes are shown." loading="lazy" decoding="async" /></p> <p>That&#39;s not exactly what I was expecting to see. Those are the ASCII character codes for the letters in the string. To check there&#39;s nothing wrong with our firmware or how we&#39;ve set up the Glasgow, we can run a small test program locally on my laptop.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#268bd2;">let</span><span> hello_world_bytes:</span><span style="color:#859900;">Vec</span><span>&lt;</span><span style="color:#268bd2;">u8</span><span>&gt; = </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Hello, world!</span><span style="color:#839496;">&quot;</span><span>.</span><span style="color:#859900;">as_bytes</span><span>().</span><span style="color:#859900;">to_vec</span><span>(); </span><span> </span><span style="color:#859900;">for</span><span> i </span><span style="color:#859900;">in</span><span> hello_world_bytes.</span><span style="color:#859900;">iter</span><span>() { </span><span> </span><span style="color:#859900;">print!</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#cb4b16;">{}</span><span style="color:#839496;">&quot;</span><span>,i); </span><span> } </span></code></pre> <p>That snippet of code shows the same behaviour as we observed with the VanSpoof and Glasgow combination.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-2-firmware-1/codes-again.png" alt="A screenshot of the snippet&#39;s output. ASCII codes, not characters are visible." loading="lazy" decoding="async" /></p> <p>A stream of bytes, sent via <code>write!()</code> or <code>print!()</code>, appear as the string-ified representation of their ASCII codes. I think this is because I&#39;ve only minimally changed our blinky code, and we&#39;re still getting our <code>write!</code> macro from <a rel="noopener" target="_blank" href="https://doc.rust-lang.org/std/fmt/trait.Write.html">the <code>core::fmt::Write</code> trait</a>. Its documentation describes it as,</p> <blockquote> <p>A trait for writing or formatting into Unicode-accepting buffers or streams.</p> </blockquote> <p>A <code>u8</code> isn&#39;t actually a &quot;byte of ASCII&quot;, but an 8-bit number, so it makes sense the formatter would shape it to fit and string-ify the value. <a rel="noopener" target="_blank" href="https://doc.rust-lang.org/std/io/trait.Write.html">The <code>core::io::Write</code> trait</a> is probably more appropriate for us once we start moving to byte-streams such as Modbus frames. It&#39;s described as,</p> <blockquote> <p>A trait for objects which are byte-oriented sinks.</p> </blockquote> <p>Just now, casting the byte to a char before writing it out will do, but we will have to remember to come back to this later.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#93a1a1;">// Forever more... </span><span> </span><span style="color:#859900;">loop </span><span>{ </span><span> </span><span style="color:#93a1a1;">// Read a byte from the USART, waiting until we&#39;ve got one. </span><span> </span><span style="color:#268bd2;">let</span><span> byte = </span><span style="color:#859900;">block!</span><span>(usart.</span><span style="color:#859900;">read</span><span>()).</span><span style="color:#859900;">expect</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Failed to read from USART2.</span><span style="color:#839496;">&quot;</span><span>); </span><span> </span><span style="color:#93a1a1;">// Write it straight back out again. </span><span> </span><span style="color:#859900;">write!</span><span>(usart, </span><span style="color:#839496;">&quot;</span><span style="color:#cb4b16;">{}</span><span style="color:#839496;">&quot;</span><span>, byte </span><span style="color:#859900;">as </span><span style="color:#268bd2;">char</span><span>).</span><span style="color:#859900;">expect</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Failed to write to USART2.</span><span style="color:#839496;">&quot;</span><span>); </span><span> } </span></code></pre> <p>That small change gives us the correct output when we couple our PCB back up to the Glasgow.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-2-firmware-1/correct-output.png" alt="A screenshot of Glasgow output. Text is correctly shown." loading="lazy" decoding="async" /></p> <p>If we pop a <a rel="noopener" target="_blank" href="https://mikecoats.com/spy-break-inject/">Spy! Break! Inject!</a> between the Glasgow and the VanSpoof, we can hook up our oscilloscope probes and see the byte and its echo being transmitted.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-2-firmware-1/echo-trace.jpg" alt="Oscilloscope output. A character can be seen being received then sent." loading="lazy" decoding="async" /></p> <p>We can even use this to see the difference between &#39;A&#39; (<code>0b01000001</code>) and &#39;a&#39; (<code>0b01100001</code>).</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-2-firmware-1/upper-a.jpg" alt="Oscilloscope output. An &#39;A&#39; character can be seen being received then sent." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/van-spoof-prototype-2-firmware-1/lower-a.jpg" alt="Oscilloscope output. An &#39;a&#39; character can be seen being received then sent." loading="lazy" decoding="async" /></p> <p>The surpising thing is that the bytes are in the opposite direction to what I&#39;d expect to see - the extra bit to shift to lower-case is towards the end of the bitstreams rather than the start. It turns out this because we normally visualise binary numbers <abbr title="Most Significant Bit">MSB</abbr> first, but <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Serial_port#Data_bits">USARTs send and receive</a> <abbr title="Least Significant Bit">LSB</abbr> first.</p> <blockquote> <p>Most serial communications designs send the data bits within each byte least significant bit first.</p> </blockquote> <p>Now I&#39;ve proven we can send and receive data over our USART pins, it&#39;s time to knuckle down and code up a Modbus frame assembly routine. We can then try to understand and respond to the bike&#39;s Modbus messages.</p> VanSpoof - Prototype 1 - Firmware 2025-02-14T13:37:00+00:00 2025-02-14T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/van-spoof-prototype-1-part-2/ <p><a href="/van-spoof-prototype-1-part-1/">Last time</a>, we built a prototype PCB. This time, let’s take it to blinky!</p> <p>All the code in this post, and the extra project files needed to build and flash it, can be found under the <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/van-spoof/src/tag/prototype-1/firmware">prototype-1 tag on my Codeberg repo</a>.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-2/blinky.jpg" alt="The blinky test LED on a VanSpoof prototype PCB." loading="lazy" decoding="async" /></p> <p>The first thing we need to do is set up our environment. Since this is a firmware project, rather than software, we need to be a bit stricter with how we write our code. We want to prevent ourselves from doing unsafe things, and we need to tell the compiler that we’re running on a microprocessor without a traditional main function or a full standard library. We also want to allow an (incorrectly flagged) unused import as this is how we import our panic handler during debugging.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#![</span><span style="color:#268bd2;">deny</span><span>(warnings)] </span><span>#![</span><span style="color:#268bd2;">deny</span><span>(unsafe_code)] </span><span>#![</span><span style="color:#268bd2;">no_main</span><span>] </span><span>#![</span><span style="color:#268bd2;">no_std</span><span>] </span><span>#![</span><span style="color:#268bd2;">allow</span><span>(unused_imports)] </span></code></pre> <p>We import a number of crates here, the most important being our STM32’s <abbr title="Hardware Abstraction Layer">HAL</abbr>. We’re using a low level HAL, specific to our chip, to prevent us from trying to use features or peripherals that aren’t present on the physical device but might be on others in the range.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#93a1a1;">// Gives us access to our hardware features; timers, gpios, usarts, etc. </span><span style="color:#859900;">use </span><span>stm32g0xx_hal::{ </span><span> stm32, </span><span> prelude::*, </span><span> serial::{BasicConfig, StopBits}, </span><span> time::{Bps, MicroSecond} </span><span>}; </span><span> </span><span style="color:#93a1a1;">// Punt Unicode strings out over the usart. </span><span style="color:#859900;">use </span><span>core::fmt::Write; </span><span> </span><span style="color:#93a1a1;">// We need to block on our timer `wait`s, so that we actually &quot;wait&quot; for them. </span><span style="color:#859900;">use </span><span>nb::block; </span><span> </span><span style="color:#93a1a1;">// Use RTT for debug messaging, including panics. </span><span style="color:#859900;">use</span><span> panic_rtt_target </span><span style="color:#859900;">as _</span><span>; </span><span style="color:#859900;">use </span><span>rtt_target::{rprintln, rtt_init_print}; </span><span> </span><span style="color:#93a1a1;">// `main` is our entry point. </span><span style="color:#859900;">use </span><span>cortex_m_rt::entry; </span></code></pre> <p>Next, we need to get our code up and running and start accessing our chip’s peripherals.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#[</span><span style="color:#268bd2;">entry</span><span>] </span><span style="color:#268bd2;">fn </span><span style="color:#b58900;">main</span><span>() -&gt; </span><span style="color:#859900;">! </span><span>{ </span><span> </span><span style="color:#93a1a1;">// Initialize our debugger, all our potential panics go through it. </span><span> </span><span style="color:#859900;">rtt_init_print!</span><span>(); </span><span> </span><span> </span><span style="color:#93a1a1;">// Grab and set up the chip&#39;s peripherals. </span><span> </span><span style="color:#268bd2;">let</span><span> stm_dp = stm32::Peripherals::take().</span><span style="color:#859900;">expect</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Cannot take STM32 Peripherals.</span><span style="color:#839496;">&quot;</span><span>); </span><span> </span><span style="color:#268bd2;">let </span><span style="color:#586e75;">mut</span><span> rcc = stm_dp.</span><span style="color:#cb4b16;">RCC</span><span>.</span><span style="color:#859900;">constrain</span><span>(); </span></code></pre> <p>We can then go on to configure the <abbr title="General Purpose Input/Output">GPIO</abbr> pins to reflect our PCB design. In particular, we need to set our TX pin to Open Drain Output mode, using our external resistor, R3, to pull up the line to 5v.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-2/pull-up.jpg" alt="The pull-up resistor on the TX line on a VanSpoof prototype PCB." loading="lazy" decoding="async" /></p> <p>The RX pin needs to be configured to match a similar arrangement on the bike, so we set it up as a Pull-up Input. Our 3.3v STM32 is 5v tolerant, we can get away with using the internal pull-up resistor without any level shifting or further configuration.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#93a1a1;">// Grab GPIO A since that&#39;s where our USART and LED are. </span><span> </span><span style="color:#268bd2;">let</span><span> gpioa = stm_dp.</span><span style="color:#cb4b16;">GPIOA</span><span>.</span><span style="color:#859900;">split</span><span>(</span><span style="color:#859900;">&amp;</span><span style="color:#586e75;">mut</span><span> rcc); </span><span> </span><span> </span><span style="color:#93a1a1;">// Set up the pins to match the circuit board. </span><span> </span><span style="color:#268bd2;">let </span><span style="color:#586e75;">mut</span><span> user_led = gpioa.pa11.</span><span style="color:#859900;">into_push_pull_output</span><span>(); </span><span> </span><span style="color:#268bd2;">let</span><span> tx_pin = gpioa.pa2.</span><span style="color:#859900;">into_open_drain_output</span><span>(); </span><span> </span><span style="color:#268bd2;">let</span><span> rx_pin = gpioa.pa3.</span><span style="color:#859900;">into_pull_up_input</span><span>(); </span></code></pre> <p>With our pins configured correctly, we can move on and set up our peripherals. We identified during reverse-engineering that the signal was 9600 8-N-1, so we can set up our USART to match. A timer is started running, triggering itself every 1/4 of a second.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#93a1a1;">// Build our usart, 9600 8-N-1 on USART2 pins. </span><span> </span><span style="color:#268bd2;">let </span><span style="color:#586e75;">mut</span><span> usart = stm_dp </span><span> .</span><span style="color:#cb4b16;">USART2 </span><span> .</span><span style="color:#859900;">usart</span><span>( </span><span> (tx_pin, rx_pin), </span><span> BasicConfig::default() </span><span> .</span><span style="color:#859900;">baudrate</span><span>(Bps(</span><span style="color:#6c71c4;">9_600</span><span>)) </span><span> .</span><span style="color:#859900;">wordlength_8</span><span>() </span><span> .</span><span style="color:#859900;">parity_none</span><span>() </span><span> .</span><span style="color:#859900;">stopbits</span><span>(StopBits::</span><span style="color:#cb4b16;">STOP1</span><span>), </span><span> </span><span style="color:#859900;">&amp;</span><span style="color:#586e75;">mut</span><span> rcc, </span><span> ) </span><span> .</span><span style="color:#859900;">expect</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Failed to initialize USART2.</span><span style="color:#839496;">&quot;</span><span>); </span><span> </span><span> </span><span style="color:#93a1a1;">// Use TIMER17 as our Blinky, triggering every 1/4 second. </span><span> </span><span style="color:#268bd2;">let </span><span style="color:#586e75;">mut</span><span> timer = stm_dp.</span><span style="color:#cb4b16;">TIM17</span><span>.</span><span style="color:#859900;">timer</span><span>(</span><span style="color:#859900;">&amp;</span><span style="color:#586e75;">mut</span><span> rcc); </span><span> timer.</span><span style="color:#859900;">start</span><span>(MicroSecond::micros(</span><span style="color:#6c71c4;">250_000</span><span>)); </span></code></pre> <p>Now we’ve brought up all of our peripherals, we can let the debugger know we’re ready to go.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#93a1a1;">// Show the debugger-operator we&#39;re alive. </span><span> </span><span style="color:#859900;">rprintln!</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Hello, world!</span><span style="color:#839496;">&quot;</span><span>); </span></code></pre> <p>In an infinite, loop 4 times a second, we turn on the LED, write a message to the USART, then turn the LED off again.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span> </span><span style="color:#93a1a1;">// Forever more... </span><span> </span><span style="color:#859900;">loop </span><span>{ </span><span> </span><span style="color:#93a1a1;">// Flash the LED while we send our message out of the USART. </span><span> user_led.</span><span style="color:#859900;">set_high</span><span>().</span><span style="color:#859900;">ok</span><span>(); </span><span> </span><span style="color:#859900;">writeln!</span><span>(usart, </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Blink!</span><span style="color:#dc322f;">\n</span><span style="color:#839496;">&quot;</span><span>).</span><span style="color:#859900;">expect</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Failed to write to USART2.</span><span style="color:#839496;">&quot;</span><span>); </span><span> user_led.</span><span style="color:#859900;">set_low</span><span>().</span><span style="color:#859900;">ok</span><span>(); </span><span> </span><span> </span><span style="color:#93a1a1;">// Wait for the timer before we go again. </span><span> </span><span style="color:#859900;">block!</span><span>(timer.</span><span style="color:#859900;">wait</span><span>()).</span><span style="color:#859900;">ok</span><span>(); </span><span> } </span></code></pre> <p>It turns out that it takes so little time to send &quot;Blink!&quot; at 9600 baud that the light flash is barely visible to the naked eye. The following video has been captured at a high frame-rate and played back at 1/8 speed.</p> <p><video controls autoplay disablepictureinpicture loop playsinline src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-2/blinky.webm" alt="A demonstration of the VanSpoof PCB blinking its LED"></video></p> <p>Hooking up a signal analyser like a <a rel="noopener" target="_blank" href="https://glasgow-embedded.org/latest/intro.html">Glasgow Digital Interface Explorer</a> lets us see the &quot;Blink!&quot; messages scrolling past.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-2/blinky-uart.png" alt="A screenshot of Glasgow output. The word &#39;Blink!&#39; appears repeatedly." loading="lazy" decoding="async" /></p> VanSpoof - Prototype 1 - Hardware 2025-02-08T13:37:00+00:00 2025-02-08T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/van-spoof-prototype-1-part-1/ <p>Let's look at building a prototype PCB for my VanSpoof project.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/proto.jpg" alt="A photograph of the front of a fully populated prototype VanSpoof PCB." loading="lazy" decoding="async" /></p> <p>In my <a href="/vanmoof-eshifter-reverse-engineering-part-1/">first post</a> on reverse engineering the VanMoof e-shifter, I confirmed there was a 24v power supply from the bike to the e-shifter. In the <a href="/vanmoof-eshifter-reverse-engineering-part-2/">latest reversing post</a>, I determined that the communication is Modbus RTU with its USART lines pulled up to 5v. That won&#39;t let me write a fully-featured firmware, but it&#39;s more than enough information to let me design a prototype PCB.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/render-front.jpg" alt="A render of the front of a prototype VanSpoof PCB." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/render-back.jpg" alt="A render of the back of a prototype VanSpoof PCB." loading="lazy" decoding="async" /></p> <p>I&#39;ve a few goals I&#39;d like to hit while building this prototype. I really want to get better at soldering SMD components, so I&#39;m going to try to use as few through-hole parts as I can get away with. I&#39;m also interested in designing a board that can be ordered directly from the online PCBA houses, so other folks in need can get their hands on them.</p> <p>If you&#39;ve been following this blog for a while, you&#39;d probably be able to guess that I&#39;m looking to use an STM32 as the brains of the spoofer. The catchily named <a rel="noopener" target="_blank" href="https://web.archive.org/web/20250116100153/https://www.st.com/resource/en/datasheet/stm32g030f6.pdf">STM32G030F6P6</a> is a perfect match for my needs. The 20 pin TSSOP footprint is just about hand solderable whilst still providing easy access to each of the peripherals I&#39;ll need.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/stm32.jpg" alt="A STM32 placed on a PCB." loading="lazy" decoding="async" /></p> <p>To get everything up and running, the first things to sort out are the power supply subsystems. The 24v supply from the bike needs to be brought down to 5v for the serial pull-ups. A simple linear voltage regulator is good enough for the first round of prototyping. This board is more likely to be running on my workbench than on the bike, so I can simply dial back the input voltage to keep heat dissipation down.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/5v-power.jpg" alt="The 5 v power supply with indicator LED." loading="lazy" decoding="async" /></p> <p>The bike is stored in an outdoor shed all year round, so having parts that are rated to perform at subzero temperatures would be essential to protect the board against those cold winter nights.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/sub-zero.jpg" alt="An eve outdoor temperature sensor." loading="lazy" decoding="async" /></p> <p>A SOT-89 package device is perfect since it&#39;s small enough to keep the board size to a minimum while still being hand solder-able. STMicroelectronics have a chip, the <a rel="noopener" target="_blank" href="https://web.archive.org/web/20250126091858/https://www.st.com/resource/en/datasheet/l78l.pdf">L78L05ABUTR</a>, which meets these criteria.</p> <p>To provide 3.3v from the incoming 5v, a low dropout regulator will be fine. If I can find something that&#39;s pin compatible with the 5v regulator, that should keep my board layout coherent. ST&#39;s <a rel="noopener" target="_blank" href="https://web.archive.org/web/20250130104437/https://www.st.com/resource/en/datasheet/ld2981.pdf">LD2981ABU33TR</a> fits the bill nicely.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/3v3-power.jpg" alt="The 3.3 v power supply with indicator LED." loading="lazy" decoding="async" /></p> <p>Since the boards will be hand soldered during prototyping, adding status LEDs to the regulator outputs will let me check everything&#39;s working as I&#39;d expect.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/swd-header.jpg" alt="The SWD header on a STM32 Discovery board." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/programming-header.jpg" alt="The programming header on a VansSpoof PCB." loading="lazy" decoding="async" /></p> <p>My STM32 Discovery board has an on-board ST-Link which I&#39;ll use for programming, so I&#39;ll add a matching set of pin headers to my prototype. I&#39;ll also include a push-button to reset the microcontroller, just in case I manage to get the firmware in such a state during development that it can&#39;t recover itself.</p> <p>To confirm I&#39;ve soldered everything together correctly and managed to successfully program the chip, I&#39;ll hang an LED off the GPIO for a &quot;blinky&quot; status.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/blinky.jpg" alt="The blinky LED." loading="lazy" decoding="async" /></p> <p>The final physical consideration is for strain relief on the four wires coming in from the bike. The WIGO catalogue specifies the wires attached to the shifter&#39;s connector as 30AWG so they&#39;ll need careful handling when they&#39;re hanging off the side of a bike at 25 kph. A few loops to take up the slack in the wire should keep the solder joint safe.</p> <p><img src="https://assets.mikecoats.xyz/van-spoof-prototype-1-part-1/strain-relief.jpg" alt="A wire looped through strain relief holes." loading="lazy" decoding="async" /></p> <p>The full schematic and board layout can be found under <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/van-spoof/src/tag/prototype-1/pcb">the prototype-1 tag on my Codeberg repo</a> and is released under the terms of <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/van-spoof/src/tag/prototype-1/CERN-OHL-S-2.0.txt">the CERN Open Hardware Licence Version 2 - Strongly Reciprocal</a>.</p> Next Trains 2025-01-25T13:37:00+00:00 2025-01-25T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/next-trains/ <p><a rel="noopener" target="_blank" href="https://mikecoats.social/@mike/113845844716375802">Last week</a>, I built <a rel="noopener" target="_blank" href="https://next-trains.mikecoats.xyz/">a small web app to show the next few trains</a> that are scheduled to arrive at or depart from my local railway station.</p> <p><img src="https://assets.mikecoats.xyz/next-trains/example.png" alt="A screenshot of the Next Trains app showing the next few arrivals and departures from my local station." loading="lazy" decoding="async" /></p> <p>My two-year-old daughter loves trains, and since the station&#39;s <a rel="noopener" target="_blank" href="https://www.openstreetmap.org/#map=17/57.479511/-4.222580">in the city centre, next to all the shops</a>, any time we can combine running errands with some playtime is well worth it. Having an at-a-glance view of all the trains soon to be at the station lets me plan when we should leave the house, or finish our shopping, so we can see a few trains come and go.</p> <p>The app is up and running and available at <a rel="noopener" target="_blank" href="https://next-trains.mikecoats.xyz/">next-trains.mikecoats.xyz</a>. The application is free software so you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The code is available from <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/next-trains">my Codeberg repository</a>.</p> <p>When I built my last silly web project, <a href="/poison-the-wellms/">Poison the WeLLMs</a>, I got half-way to publishing it as a PyPI package, moving the dependencies and scripts over to a <code>pyproject.toml</code> file, but I never actually finished the job. Prompted by <a rel="noopener" target="_blank" href="https://fosstodon.org/@stfn">Stefan on Mastodon</a> and their blog post, <a rel="noopener" target="_blank" href="https://stfn.pl/blog/52-published-my-first-python-package-to-pypi/">&quot;I published my first package to PyPi&quot;</a>, I bit the bullet, got stuck in and published the app as <a rel="noopener" target="_blank" href="https://pypi.org/project/next-trains/"><em>my</em> first PyPI package</a>.</p> <p>I fully expect no-one but me will ever install it, but packaging the app makes it much easier for me to install onto my server.</p> Reverse Engineering a VanMoof e-shifter – Part 2 – Decoding the Signals 2025-01-15T13:37:00+00:00 2025-01-15T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/vanmoof-eshifter-reverse-engineering-part-2/ <p>At the end of the <a href="/vanmoof-eshifter-reverse-engineering-part-1/">last post</a>, I left everyone hanging, having tapped into and sniffed some data being transferred on the wires between the bike and the e-shifter. The questions to answer were:</p> <blockquote> <ul> <li>Is the bike the requester, or is it the responder?</li> <li>What speed and format was that data?</li> <li>What messages are being transmitted?</li> </ul> </blockquote> <p>The reliability of this e-shifter in the X3 and S3 has been argued as the cause of the failure and eventual bankruptcy of VanMoof. If I can decode these messages, I have the opportunity to build a replacement module that could spoof the original e-shifter.</p> <p>This component fails and has failed for many owners, and historically this failure occurred during the bike's warranty period. It's been suggested that VanMoof provided so many replacements that they used up their entire stock and were unable to make or sell any more bikes. We've had the e-shifter fail and be replaced twice on our bike since owning it. I really need to come up with a solution before it fails again.</p> <p>Now, on to the reverse engineering!</p> <h2 id="is-the-bike-the-requester-or-is-it-the-responder">Is the bike the requester, or is it the responder?</h2> <p>On the oscilloscope, we could see that a request appears to be sent on one wire and a response is received on the other.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/scope-traces.jpg" alt="Two traces on an oscilloscope. One looks to be a question, the other looks to be the correlated response." loading="lazy" decoding="async" /></p> <p>By removing jumpers from the data lines on the Spy! Break! Inject! we can determine which side asks the question and which side responds.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/broken-tx-diagram.png" alt="A diagram showing a jumper removed from a Spy! Break! Inject! board." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/broken-tx-scope.jpg" alt="Two traces on an oscilloscope. There appears to be a question, but there is no accompanying response." loading="lazy" decoding="async" /></p> <p>Here we see that the bike&#39;s responsible for asking the questions and, with no signals being received by the e-shifter, no responses are generated and transmitted.</p> <h2 id="">What speed and format was that data?</h2> <p>Using the right-angle pin headers on the Spy! Break! Inject!, we can hook up a logic analyser like a <a rel="noopener" target="_blank" href="https://glasgow-embedded.org/latest/intro.html">Glasgow Digital Interface Explorer</a>.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/capture-signal.jpg" alt="A Spy! Break! Inject! board connected to a Glasgow." loading="lazy" decoding="async" /></p> <p>With the Glasgow in place, we can capture the data being sent between the bike and the e-shifter. That capture can be opened in GTKWave for visualisation and measurement-taking.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/question-and-answer-stream.png" alt="Two traces in GTKWave. The first trace has a series of packets, each followed closely on the second trace by another packet." loading="lazy" decoding="async" /></p> <p>Zooming in on the stream of data allows us to identify individual packets.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/question-and-answer-packet.png" alt="Two traces, zoomed in to the bit level. A question packet of bits appears first, followed by a packet of bits in response." loading="lazy" decoding="async" /></p> <p>Zooming in further, we can see individual bits in the data stream. The on-transition of this bit is a little noisy, but we can still use it as the basis of our timing calculations.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/noisy-bit.png" alt="A single bit, with a noisy start." loading="lazy" decoding="async" /></p> <p>The total time from the first spike of the bit turning on until it turned off again was 104,187 nanoseconds. Inverting that period to a baud rate gives us 9598.13, or as damn near as 9600 bps as we&#39;d expect to find. Zooming back out again, we can see the question packet lasts 8,380,458 ns and the response packet is 7,279,542 ns long. Taking a bit as ~104,000 nanoseconds, that means a question packet is ~80 bits long and the answer packet is ~70 bits.</p> <p>Assuming both of our question and answer packets use the same framing, the greatest common factor of 70 and 80 is 10, meaning our question is likely made up of 8 bytes, each 10 bits long, and our answer is 7 of these 10-bit bytes. 10-bit bytes seem silly until you think about <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter#Data_framing">serial UART framing</a>. Commonly, UART frames will begin with a start bit, then all the data bits, then an optional parity bit and between 1 and 2 stop bits. One of the <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Serial_port#Conventional_notation">most common UART framing is 8-N-1</a>, or 8 bits of data, no parity bits, 1 stop bit. Taken with the mandatory start bit, you get to 10-bits transmitted per byte, confirming 8 bytes for our question and 7 bytes for our answer.</p> <h2 id="-1">What messages are being transmitted?</h2> <p>Guessing that our data is in 9600-8-N-1 format, we can use PulseView to decode the captured signals in to the bytes transmitted.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/pulseview-uart.png" alt="A screenshot of PulseView showing the signals decoded in to UART bytes." loading="lazy" decoding="async" /></p> <p>The request packet is decoded to <code>0x20, 0x03, 0x00, 0x01, 0x00, 0x01, 0xD3, 0x7B</code> and the response is decoded as <code>0x20, 0x03, 0x02, 0x02, 0x00, 0x05, 0x23</code>. Any time I see bytes repeated at the start of several packets, it sets off my spidey-senses that we&#39;re looking at common headers or addressing information. Similarly, a series of &quot;low&quot; or &quot;small&quot; bytes such as <code>0x00</code> and <code>0x01</code> followed by some unusually high values like <code>0xD3</code> or <code>0x23</code> screams out &quot;checksum&quot; to me.</p> <p>Searching the web for &quot;16-bit checksum&quot; and &quot;16-bit CRC&quot; led me to the <a rel="noopener" target="_blank" href="https://www.crccalc.com/">CRC Calc</a> website. Dumping the first 6 bytes of the request packet in to their form and selecting CRC-16, gave a list of possible checksums. Searching within the results table for <code>D3</code> and <code>7B</code> highlighted <a rel="noopener" target="_blank" href="https://www.crccalc.com/?crc=200300010001&amp;method=CRC-16%2FMODBUS&amp;datatype=hex&amp;outtype=hex">the CRC-16/MODBUS row</a>, although it presented the bytes as <code>0x7BD3</code> as opposed to <code>0xD37B</code> which we expected based on what we saw within PulseView.</p> <p>Now we think we&#39;re probably dealing with Modbus, we can do some research and try to understand the rest of the &quot;conversation&quot;. Wikipedia has <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Modbus#Modbus_RTU">an article on Modbus</a> including a section on Modbus RTU, which appears to match the data we&#39;re seeing. According to that page, the checksum is transmitted least-significant byte first which means the CRC Calc site was correct, <code>0x7BD3</code> is the checksum, but it is transmitted as <code>D3</code> followed by <code>7B</code>.</p> <p>PulseView can be leaned on again to interpret the Modbus packets and reveal their contents.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/pulseview-modbus.png" alt="A screenshot of PulseView showing the signals decoded in to Modbus packets." loading="lazy" decoding="async" /></p> <p>This conversation appears to show a controller asking a peripheral device with ID 32 (<code>0x20</code>) a question, and a peripheral device with ID 32 sending an answer. Digging in to the question packet, we can see more details.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/question-target.png" alt="The first half of a Modbus question." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/question-details.png" alt="The other half of a Modbus question." loading="lazy" decoding="async" /></p> <p>The question appears to be to &quot;read a register&quot;, starting at address <code>0x0001</code>, reading <code>0x0001</code> units of data. We can also look more closely at the answer.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/answer-target.png" alt="The first half of a Modbus answer." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-2/answer-details.png" alt="The other half of a Modbus answer." loading="lazy" decoding="async" /></p> <p>Here, we can see a response indicating that our peripheral device has read a register, returning 2 bytes of data, <code>0x0200</code> or 512.</p> <p>What is in register <code>0x0001</code> and what does a value of <code>0x0200</code> mean? Who knows?! Do we even need to understand the communications, or will rote repetition of prerecorded packets be enough to fool the bike?</p> <p>In my next blog post, I&#39;ll detail a small project that can log an entire bike ride&#39;s worth of communication. Hopefully we can attempt to analyse the messages, or we can at least learn to spoof the correct responses to the bike&#39;s requests.</p> Reverse Engineering a VanMoof e-shifter – Part 1 – Connectors and Connections 2024-12-15T13:37:00+00:00 2024-12-15T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/vanmoof-eshifter-reverse-engineering-part-1/ <p>Anyone who's followed this blog for a while will know that we own a VanMoof e-bike, and we have a love/hate relationship with it due to its reliability. The most common and dreaded complaint for VanMoof owners is "Err 44". Error code 44 on a VanMoof X3 or S3 means that the bike cannot communicate with the e-shifter and gear shifting will not be available.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/vanmoof-eshifter.jpg" alt="A VanMoof e-shifter." loading="lazy" decoding="async" /></p> <p>VanMoof really tried to be the &quot;Apple&quot; of e-bikes, and their e-shifter is part of that. Instead of producing a single-speed bike with no gears or a traditional manually shifted bike they built a bike with an automatic electronic gear shift. At certain speeds the bike will automatically shift gears, giving its rider more torque to get going from a stand-still or more speed once the bike was moving along.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/gear-changer.jpg" alt="A close-up of the changing mechanism of a VanMoof e-shifter." loading="lazy" decoding="async" /></p> <p>If I can reverse engineer the communications between the bike and the e-shifter, I should be able to build a replacement module that spoofs the original. The gears in the bike&#39;s Sturmey Archer hub can be manually, and semi-permanently, set if the broken e-shifter is removed from the bike. The e-assistance, up to 15.5mph from the electric motor, and the boost button, giving 100% motor power from a stand-still, means the bike is still perfectly usable if it&#39;s manually shifted in to 3rd gear. Whether broken or removed, the bike will complain that it cannot communicate with the e-shifter and present &quot;Err 44&quot; whenever the bike comes to a stop.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/four-poles.jpg" alt="An end-on view of the connector on a VanMoof e-shifter." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/higo.jpg" alt="A side view of the connector on a VanMoof e-shifter." loading="lazy" decoding="async" /></p> <p>The e-shifter has a 4 pole, trapezoidal connector, in blue plastic, with the word Higo written on the side. Searching for Higo e-bike connectors returns results for the <a rel="noopener" target="_blank" href="https://www.higocon.com/">Chinese company HIGO</a> who specialise in waterproof connectors and harness system. It seems they&#39;ve partnered with <a rel="noopener" target="_blank" href="https://www.ac-solutions.be/">A&amp;C Solutions</a> to act as their European distributor, running the <a rel="noopener" target="_blank" href="https://www.higoconnector.com/en">higoconnector.com</a> website. A <a rel="noopener" target="_blank" href="https://web.archive.org/web/20240831162424/https://www.higoconnector.com/en/download/q9w0z">PDF catalogue on the European site</a> includes our connector on page 24 and the order code we need to buy some.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/higo-catalogue.jpg" alt="A page from Higo&#39;s catalogue." loading="lazy" decoding="async" /></p> <p>Calculating the codes <code>Z409FM</code> and <code>Z409FG</code> from the brochure, we can now search for those connectors and luckily find a German shop selling both the <a rel="noopener" target="_blank" href="https://shop.e-bike-technologies.de/en/connectors/signal/mini/higo-mini-f/higo-b4-f-detail">socket</a> and the <a rel="noopener" target="_blank" href="https://shop.e-bike-technologies.de/en/connectors/signal/mini/higo-mini-f/higo-s4-f-detail">plug</a> we need. Reinhard from e-bike-technologies was excellent, arranging the international shipment on the same day, and my delivery arrived within 4 days.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/new-higo-pair.jpg" alt="An end-on view of two new Higo plug and socket connectors." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/new-higo-pins.jpg" alt="A close-up, end-on view of a new Higo socket connectors, showing pin numbers." loading="lazy" decoding="async" /></p> <p>With the new connectors in hand, it&#39;s possible to connect them to a <a href="/spy-break-inject/">Spy! Break! Inject!</a> and use a multimeter to test for continuity. We can therefore determine how the pin numbering of the connectors matches up to the 4 coloured wires within the cable.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/new-higo-sbi.jpg" alt="A Spy! Break! Inject! board in line with a VanMoof e-shifter." loading="lazy" decoding="async" /></p> <table><thead><tr><th>Pin Number</th><th>Wire Colour</th></tr></thead><tbody> <tr><td>1</td><td>Black</td></tr> <tr><td>2</td><td>Yellow</td></tr> <tr><td>3</td><td>Blue</td></tr> <tr><td>4</td><td>Red</td></tr> </tbody></table> <p>Now we&#39;ve wired a Higo plug and socket to a Spy! Break! Inject!, we can insert our assembly in-line with the bike and e-shifter. Powering up the bike and probing between the test points on the Spy! Break! Inject! lets us explore the voltages available between the wires.</p> <table><thead><tr><th>Wire</th><th>Voltage</th></tr></thead><tbody> <tr><td>Black</td><td>0v</td></tr> <tr><td>Yellow</td><td>24v</td></tr> <tr><td>Blue</td><td>5v</td></tr> <tr><td>Red</td><td>5v</td></tr> </tbody></table> <p>At first glance, finding 24v on the yellow wire is surprising. On reflection, the e-shifter <em>is</em> &quot;mechatronic&quot;, so having the extra power available makes sense. A higher voltage would make it easier for the motor to physically shift gears. Having two 5v lines is interesting. These could be VCC and some bidirectional data for the microcontroller, or it could be the TX and RX lines for communication.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/scope-probes.jpg" alt="A Spy! Break! Inject! board in line with a VanMoof e-shifter, with oscilloscope probes connected." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-eshifter-reverse-engineering-part-1/scope-traces.jpg" alt="Oscilloscope traces of e-shifter communications." loading="lazy" decoding="async" /></p> <p>Clipping an oscilloscope to the two 5v lines reveals data on both, apparently in a request and response pattern.</p> <p>Is the bike the requester, or it is it the responder? What speed and format is that data? What messages are they transmitting? All these questions and more, answered <a href="/vanmoof-eshifter-reverse-engineering-part-2/">next time</a>!</p> Introducing Spy! Break! Inject! 2024-12-11T13:37:00+00:00 2024-12-11T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/spy-break-inject/ <p>Spy! Break! Inject! is a 4-wire <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack#cite_note-9">Monster-in-the-Middle</a> circuit. Inserting this board inline with any 4-wire bus lets you spy on, break into and inject signals. It's a purpose-built breadboard for reverse engineers.</p> <p><img src="https://assets.mikecoats.xyz/spy-break-inject/photo-front.jpg" alt="A photograph of the front of a Spy! Break! Inject!" loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/spy-break-inject/photo-back.jpg" alt="A photograph of the back of a Spy! Break! Inject!" loading="lazy" decoding="async" /></p> <p>The Würth terminal blocks have specifically been chosen to support wires down to 30 AWG for working with the smaller conductors commonly found in multicore communication cables. Right-angle pins and straight sockets allow connecting DuPont style jumper wires to other components such as microcontrollers or signal analysers. A row of test points along the bottom of the board support the spring-loaded hooks typically found on oscilloscope probes. All these connections are routed through a bank of jumper headers, allowing the paths to be broken and signals routed off-board.</p> <p><img src="https://assets.mikecoats.xyz/spy-break-inject/render-front.jpg" alt="A render of the front of a Spy! Break! Inject!" loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/spy-break-inject/render-back.jpg" alt="A render of the back of a Spy! Break! Inject!" loading="lazy" decoding="async" /></p> <p>Multi-meter probes can be connected across the rails to measure voltages. Current draw for a component can be measured by removing the jumper from the VCC rail and clipping an ammeter in line.</p> <p><img src="https://assets.mikecoats.xyz/spy-break-inject/current-measure.png" alt="A diagram showing a Spy! Break! Inject! being used to measure current." loading="lazy" decoding="async" /></p> <p>Removing jumpers and connecting wires to the pin headers allows signals to be routed away to a microcontroller for logging, relaying, proxying and modification.</p> <p><img src="https://assets.mikecoats.xyz/spy-break-inject/microcontroller-proxy.png" alt="A diagram showing a Spy! Break! Inject! being used to modify a signal on the fly." loading="lazy" decoding="async" /></p> <p>Spy! Break! Inject! is released under the terms of the <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/spy-break-inject/src/branch/main/LICENSE">CERN Open Hardware Licence Version 2 - Strongly Reciprocal</a>. The project files, including KiCAD schematics and PCB designs are available from <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/spy-break-inject">my Codeberg repository</a>. It has been <a rel="noopener" target="_blank" href="https://certification.oshwa.org/uk000068.html">certified as open source hardware</a> by the Open Source Hardware Association.</p> <p><img src="https://assets.mikecoats.xyz/spy-break-inject/certification.png" alt="OSWHA Certification Logo for Spy! Break! Inject!, OSHW UK000068" loading="lazy" decoding="async" /></p> Make Cisco Catalyst 2960-X switches run almost silently 2024-12-05T13:37:00+00:00 2024-12-05T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/cisco-2960x-quiet/ <p>Cisco Catalyst 2960-X switches are now over a decade old and have been discontinued for over two years. As a result, companies are getting twitchy and have started to replace these switches, even though they still work and are still supported until 2027.</p> <p>On eBay, you can find 48-port PoE switches with stack connectors and either four SFP or two SFP+ uplink ports for under £100. This seems like a great deal until you unbox them and power them up to get them configured.</p> <h2 id="problem">Problem</h2> <p>The 2960-X has a single 12v blower fan to remove heat from the case and it <em>screams</em>. It's unbelievably loud both during startup and once it gets hot. Even during normal operation it's still unreasonably loud for most homes.</p> <p><img src="https://assets.mikecoats.xyz/cisco-2960x-quiet/blower-fan.jpg" alt="Blower fan inside a 2960-X switch." loading="lazy" decoding="async" /></p> <p>Like all those second-hand 1U servers on eBay that seem too good value to be true, these switches aren&#39;t really suitable for use anywhere outside the server room or data centre either. You really need to do something about the noise before you can use them in the home. Folks over on Reddit have done <a rel="noopener" target="_blank" href="https://old.reddit.com/r/homelab/comments/172zj5s/cisco_2960x24psl_fan_replacement/">some work on this</a> and identified a couple of options.</p> <h2 id="">Solution 1 - Disconnect internal blower fan</h2> <p>There are several small screws around the case that when removed allow access to the switch&#39;s innards. The blower fan is very obvious, as is its power connector. Pop it off, then reassemble the switch.</p> <p><img src="https://assets.mikecoats.xyz/cisco-2960x-quiet/fan-header.jpg" alt="Circuit board inside a 2960-X switch, showing the fan header." loading="lazy" decoding="async" /></p> <p>Powering it back on, you&#39;ll find it doesn&#39;t really care if its fan has been disconnected.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">cisco-switch#show</span><span> env fan </span><span style="color:#b58900;">FAN</span><span> is OK </span></code></pre> <p>It also reckons its internal temperature is fine, even with no airflow through the case.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">cisco-switch#show</span><span> env temperature </span><span style="color:#b58900;">SYSTEM</span><span> TEMPERATURE is OK </span></code></pre> <p>However, these statuses hide the real truth - the temperatures inside the switch are rising! They&#39;re getting quite high and they&#39;re getting there quickly. With just three of the 48 ports providing PoE to the WAPs in my house, the temperatures are almost up to the Red level and well beyond the comfortable ranges in <a rel="noopener" target="_blank" href="https://www.cisco.com/c/en/us/products/collateral/switches/catalyst-2960-x-series-switches/datasheet_c78-728232.html#Specifications">Cisco&#39;s documentation</a>.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">cisco-switch#show</span><span> power inline </span><span style="color:#859900;">| </span><span style="color:#b58900;">include</span><span> on </span><span style="color:#b58900;">Gi2/0/8</span><span> auto on 15.4 Ieee PD 4 30.0 </span><span style="color:#b58900;">Gi2/0/45</span><span> auto on 15.4 Ieee PD 0 30.0 </span><span style="color:#b58900;">Gi2/0/47</span><span> auto on 15.4 Ieee PD 0 30.0 </span><span style="color:#b58900;">cisco-switch#show</span><span> env all </span><span style="color:#b58900;">FAN</span><span> is OK </span><span style="color:#b58900;">SYSTEM</span><span> TEMPERATURE is OK </span><span style="color:#b58900;">System</span><span> Temperature Value: 64 Degree Celsius </span><span style="color:#b58900;">System</span><span> Temperature State: YELLOW </span><span style="color:#b58900;">Yellow</span><span> Threshold : 58 Degree Celsius </span><span style="color:#b58900;">Red</span><span> Threshold : 68 Degree Celsius!</span><span style="color:#859900;">[</span><span>...</span><span style="color:#859900;">] </span></code></pre> <p>To be fair to the switch, it&#39;s been installed in the top position in my mini rack, in the cupboard under my stairs. The rack runs double duty as the stand for my laser printer, so it&#39;s just about the hottest place in my whole house that I could have installed it, exacerbated by the complete lack of natural airflow.</p> <h2 id="-1">Solution 2 - Replace internal blower fan</h2> <p>Noctua make a 40mm fan, the catch-ily named <a rel="noopener" target="_blank" href="https://noctua.at/en/nf-a4x20-pwm">NF-A4x20-PWM</a>, that fits neatly in a 1U chassis and can even have its speed controlled by the Cisco&#39;s PWM circuit. The folks over on Reddit designed a custom bracket, but with my lack of a 3D printer and a love of the &quot;hack&quot;, I decided that hot-gluing a couple of fans in place would do the trick.</p> <p><img src="https://assets.mikecoats.xyz/cisco-2960x-quiet/removed.jpg" alt="The empty space where the blower fan was previously installed." loading="lazy" decoding="async" /></p> <p>You&#39;ll need to do some clever jiggery-pokery with the cables and the connectors if you want variable speed control for the fans as the pins don&#39;t line up in the right spacing or order. In my own testing though, I couldn&#39;t see a big enough difference in noise by letting them spin down a bit. As a result I went with the easy option, snip the connector!</p> <p><img src="https://assets.mikecoats.xyz/cisco-2960x-quiet/snipped.jpg" alt="The power wires from the blower fan, with the fan snipped off." loading="lazy" decoding="async" /></p> <p>Noctua fans come with an Omni Join connector that allows you to reuse the Cisco fan connector, turning it in to a Noctua fan socket.</p> <p><img src="https://assets.mikecoats.xyz/cisco-2960x-quiet/omni-join.jpg" alt="The Noctua Omni-Join connected to the Cisco fan header." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/cisco-2960x-quiet/installed.jpg" alt="The short cable from the Noctua Omni-Join connected to the installed Cisco fan header." loading="lazy" decoding="async" /></p> <p>Also included in the Noctua box is a Y-Cable that lets you run two fans from a single header.</p> <p><img src="https://assets.mikecoats.xyz/cisco-2960x-quiet/y-cable.jpg" alt="Noctua Y-Cable connected to a Noctua Omni Join connected to the Cisco mainboard." loading="lazy" decoding="async" /></p> <p>With that in place we can now install our pair of 40mm fans. I&#39;ll save you the gory details of my hot-glue job, but the gist of it is that the lip where the blower fan exhausted it&#39;s warm air is almost a friction fit for our two fans, so just plug them in, hold them in place and secure them with a few blobs of hot glue.</p> <p>The switch still doesn&#39;t care that we&#39;ve been monkeying around with its fans.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">cisco-switch#show</span><span> env fan </span><span style="color:#b58900;">FAN</span><span> is OK </span><span> </span><span style="color:#b58900;">cisco-switch#show</span><span> env temp </span><span style="color:#b58900;">SYSTEM</span><span> TEMPERATURE is OK </span></code></pre> <p>However, checking for all the readings, we see a big difference.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">cisco-switch#show</span><span> env all </span><span style="color:#b58900;">FAN</span><span> is OK </span><span style="color:#b58900;">SYSTEM</span><span> TEMPERATURE is OK </span><span style="color:#b58900;">System</span><span> Temperature Value: 51 Degree Celsius </span><span style="color:#b58900;">System</span><span> Temperature State: GREEN </span><span style="color:#b58900;">Yellow</span><span> Threshold : 58 Degree Celsius </span><span style="color:#b58900;">Red</span><span> Threshold : 68 Degree Celsius </span></code></pre> <p>A 13 Degree Celsius reduction in temperature from adding two small, almost silent, fans. The switch is now quiet enough that it fades away behind the normal background noise of the house like the hum of our central heating pump or the occasional laptop fan whirring to life.</p> <h2 id="-2">Comments</h2> <blockquote> <p class="byline">Wouter on 2024-12-06 wrote:</p> <p>I installed A dc&#x2F;dc converter from one dollar between the power of the fan. Works also fine.</p> </blockquote> Leaving WordPress for Zola 2024-12-03T13:37:00+00:00 2024-12-03T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/leaving-wordpress/ <p>I've used WordPress to run this website for the last 4 years. I first hosted it with <a rel="noopener" target="_blank" href="https://laughingsquid.us/managed-wordpress-hosting/">Laughing Squid</a>, who resell Pressable's service and then for the last year or so I've hosted with <a rel="noopener" target="_blank" href="https://www.mythic-beasts.com/hosting">Mythic Beasts</a>, who run their own servers. I've enjoyed using WordPress as a piece of software; it does the job well. Without it, I'd never have blogged so often or on the range of topics I have done to date.</p> <p>Recent actions by Automattic and Matt Mullenweg have showed they are no longer the benevolent stewards they once pitched themselves as and, unfortunately, I feel I have to move off their platform. Realistically, this blog only ever used 1% of the capacity or capability of WordPress, and very little of the content could be considered "dynamic", so a static site generator is more than adequate for my needs.</p> <p><img src="https://assets.mikecoats.xyz/leaving-wordpress/header.png" alt="A screenshot of this blog&#39;s config file." loading="lazy" decoding="async" /></p> <p>Like every dev out there, I&#39;ve prototyped and half built several SSGs over the years, but that has always led to more time being spent on the software and less time actually blogging. Instead, this time I&#39;ve just selected a piece of software that matches up with my &quot;head view&quot; of the structure of the site and have chosen <a rel="noopener" target="_blank" href="https://www.getzola.org/">Zola</a>. I can&#39;t vouch for it&#39;s long term usability, but I&#39;ve managed to convert all of my WordPress posts to markdown files, wrapped them up in <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/mikecoats.com">a git repo on Codeberg</a>, and slapped a few templates together and managed to get a working site out of the other end. I&#39;m still hosting with Mythic Beasts, but instead of their hosted WordPress service, I&#39;m renting <a rel="noopener" target="_blank" href="https://www.mythic-beasts.com/servers/virtual">a virtual server</a> and serving the static files with <a rel="noopener" target="_blank" href="https://caddyserver.com/">Caddy</a>.</p> <h2 id="">Caveats</h2> <p>There are two problems with the migration. The first is that there was a misconfiguration in my old WordPress site that despite naming posts with fixed slugs like <code>poison-the-wellms</code>, WordPress still knew those posts by its own internal naming scheme like <code>?p=1023</code>. This was invisible everywhere except for the unique ID within the RSS and Atom feeds, so unfortunately anyone who was subscribed to my blog before the migration has just seen all of my old posts pop up again as if they were brand new. My apologies.</p> <p>The second problem is one of accepting comments. WordPress runs on PHP, so providing interactivity such as comment submission is practically automatic. Static sites, though, can&#39;t do that. I very rarely receive comments, but I didn&#39;t want to remove them entirely from the site. Somehow, I must score quite highly on the SEO front when it comes to failing VanMoof batteries. Both of my <a href="/vanmoof-back-to-life-part-1/">VanMoof reverse engineering</a> and <a href="/vanmoof-back-to-life-part-2-battery/">repairing posts</a> have received a fair few comments and I like to believe I&#39;ve helped a few people get their rather expensive bikes back on the road.</p> <p>I spent an afternoon investigating ways of adding comments, either via JavaScript plugins or through some sort of social network embedding, but in the end I realised I could just accept emails and then update the pages manually. My workflow on WordPress was to manually approve comments once I received an email notification, all I&#39;ve done is add a copy &amp; paste step in the middle. To this end, I&#39;ve added <code>mailto</code> links at the bottom of every page, that go to a stand-alone email address and mailbox for collecting comments.</p> <p>I&#39;ve still some styles to tweak and pages to wrangle, but I feel it&#39;s better to just get the new site up and running and fix the remaining problems later. Perfect is the enemy of good, and all that!</p> Debian Packaging from First Principles – Part 2 – Dependencies 2024-09-24T13:37:00+00:00 2024-09-24T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/debian-packaging-first-principles-part-2-dependencies/ <p>Following on from <a href="/debian-packaging-first-principles-part-1-simple/">my first blog post</a> in this series, today I’m looking at dependencies within <code>.deb</code> files. As with part 1, all of the source code files for these packages are available from <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles">my project’s repository</a>.</p> <p><img src="https://assets.mikecoats.xyz/debian-packaging-first-principles-part-2-dependencies/heading.png" alt="A screenshot showing the successful installation of two mutually dependent packages." loading="lazy" decoding="async" /></p> <h2 id="">Impossible Dependencies</h2> <p>First of all, we’ll build an un-installable package with impossible to resolve dependencies. The <a rel="noopener" target="_blank" href="https://packages.debian.org/bookworm/libreoffice"><code>libreoffice</code> Debian package</a> is composed of a number individual packages such as <code>libreoffice-writer</code>, <code>libreoffice-calc</code>, and <code>libreoffice-impress</code>. Imagine if Microsoft fully embraced their open-source ambitions; we could one day see <code>microsoftoffice-word</code>, <code>microsoftoffice-excel</code> and <code>microsoftoffice-powerpoint</code>. Until then, the following package will remain impossible to install.</p> <pre data-lang="yaml" style="background-color:#fdf6e3;color:#657b83;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#268bd2;">Package</span><span>: </span><span style="color:#2aa198;">dpfp-impossibledependencies </span><span style="color:#268bd2;">Version</span><span>: </span><span style="color:#2aa198;">0.1.0-1 </span><span style="color:#268bd2;">Architecture</span><span>: </span><span style="color:#2aa198;">all </span><span style="color:#268bd2;">Description</span><span>: </span><span style="color:#2aa198;">An effectively empty deb file with impossible to resolve dependencies, manually assembled to understand Debian packaging. </span><span style="color:#268bd2;">Maintainer</span><span>: </span><span style="color:#2aa198;">Mike Coats &lt;i.am@mikecoats.com&gt; </span><span style="color:#268bd2;">Depends</span><span>: </span><span style="color:#2aa198;">microsoftoffice-word, microsoftoffice-excel, microsoftoffice-powerpoint, microsoftoffice-outlook </span></code></pre> <p>Since we’re not going to be able to install any files, anyway, the impossible package can be empty by including a blank tar file. This can be accomplished by passing the <code>--files-from /dev/null</code> argument to <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles/src/commit/d3c9e7b48f34e3b7e2e46c0af957235c7c783be7/impossible-dependencies/Makefile#L11">the tar command within the Makefile</a>.</p> <p>Attempting to install that file will return an error for each package that <code>dpkg</code> is unable to find.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo dpkg</span><span style="color:#268bd2;"> -i</span><span> ./dpfp-impossibledependencies_0.1.0-1_all.deb </span><span style="color:#b58900;">Selecting</span><span> previously unselected package dpfp-impossibledependencies. </span><span>(</span><span style="color:#b58900;">Reading</span><span> database ... 160683 files and directories currently installed.) </span><span style="color:#b58900;">Preparing</span><span> to unpack .../dpfp-impossibledependencies_0.1.0-1_all.deb ... </span><span style="color:#b58900;">Unpacking</span><span> dpfp-impossibledependencies (0.1.0-1) </span><span style="color:#b58900;">... </span><span style="color:#b58900;">dpkg:</span><span> dependency problems prevent configuration of dpfp-impossibledependencies: </span><span> </span><span style="color:#b58900;">dpfp-impossibledependencies</span><span> depends on microsoftoffice-word</span><span style="color:#859900;">; </span><span style="color:#b58900;">however: </span><span> </span><span style="color:#b58900;">Package</span><span> microsoftoffice-word is not installed. </span><span> </span><span style="color:#b58900;">dpfp-impossibledependencies</span><span> depends on microsoftoffice-excel</span><span style="color:#859900;">; </span><span style="color:#b58900;">however: </span><span> </span><span style="color:#b58900;">Package</span><span> microsoftoffice-excel is not installed. </span><span> </span><span style="color:#b58900;">dpfp-impossibledependencies</span><span> depends on microsoftoffice-powerpoint</span><span style="color:#859900;">; </span><span style="color:#b58900;">however: </span><span> </span><span style="color:#b58900;">Package</span><span> microsoftoffice-powerpoint is not installed. </span><span> </span><span style="color:#b58900;">dpfp-impossibledependencies</span><span> depends on microsoftoffice-outlook</span><span style="color:#859900;">; </span><span style="color:#b58900;">however: </span><span> </span><span style="color:#b58900;">Package</span><span> microsoftoffice-outlook is not installed. </span><span> </span><span style="color:#b58900;">dpkg:</span><span> error processing package dpfp-impossibledependencies (--install)</span><span style="color:#859900;">: </span><span> </span><span style="color:#b58900;">dependency</span><span> problems - leaving unconfigured </span><span style="color:#b58900;">Errors</span><span> were encountered while processing: </span><span> </span><span style="color:#b58900;">dpfp-impossibledependencies </span></code></pre> <h2 id="-1">Plain Dependency</h2> <p>A more rational example is <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles/src/commit/cfe74ac60e8c34e19147f087320c7b1fd7646e4a/plain-dependency/">one that depends on the single, already installed, package</a> we created last time. To do so, our <code>depends</code> line becomes much simpler this time, just the name of the simple, no-dependency, package.</p> <pre data-lang="yaml" style="background-color:#fdf6e3;color:#657b83;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#268bd2;">Package</span><span>: </span><span style="color:#2aa198;">dpfp-plaindependency </span><span style="color:#268bd2;">Version</span><span>: </span><span style="color:#2aa198;">0.1.0-1 </span><span style="color:#268bd2;">Architecture</span><span>: </span><span style="color:#2aa198;">all </span><span style="color:#268bd2;">Description</span><span>: </span><span style="color:#2aa198;">A bare-minimum deb file with a simple dependency, manually assembled to understand Debian packaging. </span><span style="color:#268bd2;">Maintainer</span><span>: </span><span style="color:#2aa198;">Mike Coats &lt;i.am@mikecoats.com&gt; </span><span style="color:#268bd2;">Depends</span><span>: </span><span style="color:#2aa198;">dpfp-simple </span></code></pre> <p>To prove that this package really does depend on <code>dpfp-simple</code> we’ll <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles/src/commit/cfe74ac60e8c34e19147f087320c7b1fd7646e4a/plain-dependency/data/opt/debian-packaging-first-principles/plain-dependency">add a symbolic link in our new package</a> that refers to the file installed in the first package.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> ls</span><span style="color:#268bd2;"> -l</span><span> data/opt/debian-packaging-first-principles/plain-dependency </span><span style="color:#b58900;">total</span><span> 8 </span><span style="color:#b58900;">-rw-r--r--</span><span> 1 mike mike 77 Jul 12 12:50 dpfp-plain-dependency-success.txt </span><span style="color:#b58900;">lrwxrwxrwx</span><span> 1 mike mike 69 Jul 12 12:49 dpfp-simple-success.txt -&gt; /opt/debian-packaging-first-principles/simple/dpfp-simple-success.txt </span></code></pre> <p>Unlike the impossible example, installing this package goes smoothly and inspecting the installed files will show the text file and symbolic link side by side.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo dpkg</span><span style="color:#268bd2;"> -i</span><span> ./dpfp-plaindependency_0.1.0-1_all.deb </span><span style="color:#b58900;">Selecting</span><span> previously unselected package dpfp-plaindependency. </span><span>(</span><span style="color:#b58900;">Reading</span><span> database ... 160683 files and directories currently installed.) </span><span style="color:#b58900;">Preparing</span><span> to unpack .../dpfp-plaindependency_0.1.0-1_all.deb ... </span><span style="color:#b58900;">Unpacking</span><span> dpfp-plaindependency (0.1.0-1) </span><span style="color:#b58900;">... </span><span style="color:#b58900;">Setting</span><span> up dpfp-plaindependency (0.1.0-1) </span><span style="color:#b58900;">... </span></code></pre> <p>Listing the files and their contents will show that we’re pulling in the file from the first, pre-installed, package too.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> ls</span><span style="color:#268bd2;"> -l</span><span> /opt/debian-packaging-first-principles/plain-dependency/ </span><span style="color:#b58900;">total</span><span> 8 </span><span style="color:#b58900;">-rw-r--r--</span><span> 1 root root 77 Jul 12 12:50 dpfp-plain-dependency-success.txt </span><span style="color:#b58900;">lrwxrwxrwx</span><span> 1 root root 69 Jul 12 12:49 dpfp-simple-success.txt -&gt; /opt/debian-packaging-first-principles/simple/dpfp-simple-success.txt </span><span> </span><span style="color:#b58900;">$</span><span> cat /opt/debian-packaging-first-principles/plain-dependency/dpfp-</span><span style="color:#859900;">*</span><span>.txt </span><span style="color:#b58900;">Debian</span><span> Packaging from First Principles </span><span> </span><span style="color:#b58900;">Plain</span><span> Dependency </span><span> </span><span style="color:#b58900;">Success! </span><span style="color:#b58900;">Debian</span><span> Packaging from First Principles </span><span> </span><span style="color:#b58900;">Simple </span><span> </span><span style="color:#b58900;">Success! </span></code></pre> <h2 id="-2">Mutual Dependencies</h2> <p>Until now all of our dependencies have formed a simple tree, but now we’re going to build <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles/src/commit/55ce642ad795da4ea2d56efd33cce924554cf0e1/mutual-dependencies">a pair of packages that depend on each other</a>. One will not be able to be installed without the other.</p> <p><img src="https://assets.mikecoats.xyz/debian-packaging-first-principles-part-2-dependencies/plain.png" alt="A diagram representing a single package depending on a second." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/debian-packaging-first-principles-part-2-dependencies/mutual.png" alt="A diagram representing two packages that depend on each other." loading="lazy" decoding="async" /></p> <p>We will first create <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles/src/commit/55ce642ad795da4ea2d56efd33cce924554cf0e1/mutual-dependencies/a/control/control#L6">an “a” package that depends on our “b” package</a>.</p> <pre data-lang="yaml" style="background-color:#fdf6e3;color:#657b83;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#268bd2;">Package</span><span>: </span><span style="color:#2aa198;">dpfp-mutualdependency-a </span><span>[</span><span style="color:#6c71c4;">...</span><span>] </span><span style="color:#268bd2;">Depends</span><span>: </span><span style="color:#2aa198;">dpfp-mutualdependency-b </span></code></pre> <p>We will then create the <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles/src/commit/55ce642ad795da4ea2d56efd33cce924554cf0e1/mutual-dependencies/b/control/control#L6">counterpart “b” package that will depend on our first “a” package</a>.</p> <pre data-lang="yaml" style="background-color:#fdf6e3;color:#657b83;" class="language-yaml "><code class="language-yaml" data-lang="yaml"><span style="color:#268bd2;">Package</span><span>: </span><span style="color:#2aa198;">dpfp-mutualdependency-b </span><span>[</span><span style="color:#6c71c4;">...</span><span>] </span><span style="color:#268bd2;">Depends</span><span>: </span><span style="color:#2aa198;">dpfp-mutualdependency-a </span></code></pre> <p>Attempting to install only one of the mutually dependent packages will fail, referencing the other half of the pair.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo dpkg</span><span style="color:#268bd2;"> -i</span><span> dpfp-mutualdependency-a_0.1.0-1_all.deb </span><span style="color:#b58900;">[...] </span><span style="color:#b58900;">dpkg:</span><span> dependency problems prevent configuration of dpfp-mutualdependency-a: </span><span> </span><span style="color:#b58900;">dpfp-mutualdependency-a</span><span> depends on dpfp-mutualdependency-b</span><span style="color:#859900;">; </span><span style="color:#b58900;">however: </span><span> </span><span style="color:#b58900;">Package</span><span> dpfp-mutualdependency-b is not installed. </span><span style="color:#b58900;">[...] </span><span style="color:#b58900;">Errors</span><span> were encountered while processing: </span><span> </span><span style="color:#b58900;">dpfp-mutualdependency-a </span></code></pre> <p>Similarly, attempting to install only the second of the pair will return the same error.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo dpkg</span><span style="color:#268bd2;"> -i</span><span> dpfp-mutualdependency-b_0.1.0-1_all.deb </span><span style="color:#b58900;">[...]dpkg:</span><span> dependency problems prevent configuration of dpfp-mutualdependency-b: </span><span> </span><span style="color:#b58900;">dpfp-mutualdependency-b</span><span> depends on dpfp-mutualdependency-a</span><span style="color:#859900;">; </span><span style="color:#b58900;">however: </span><span> </span><span style="color:#b58900;">Package</span><span> dpfp-mutualdependency-a is not configured yet. </span><span style="color:#b58900;">[...] </span><span style="color:#b58900;">Errors</span><span> were encountered while processing: </span><span> </span><span style="color:#b58900;">dpfp-mutualdependency-b </span></code></pre> <p>Installing both packages of the mutual pair at the same time will complete successfully because dpkg can resolve the dependencies.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo dpkg</span><span style="color:#268bd2;"> -i</span><span> dpfp-mutualdependency-a_0.1.0-1_all.deb dpfp-mutualdependency-b_0.1.0-1_all.deb </span><span style="color:#b58900;">Selecting</span><span> previously unselected package dpfp-mutualdependency-a. </span><span>(</span><span style="color:#b58900;">Reading</span><span> database ... 160686 files and directories currently installed.) </span><span style="color:#b58900;">Preparing</span><span> to unpack dpfp-mutualdependency-a_0.1.0-1_all.deb ... </span><span style="color:#b58900;">Unpacking</span><span> dpfp-mutualdependency-a (0.1.0-1) </span><span style="color:#b58900;">over</span><span> (0.1.0-1) </span><span style="color:#b58900;">... </span><span style="color:#b58900;">Selecting</span><span> previously unselected package dpfp-mutualdependency-b. </span><span style="color:#b58900;">Preparing</span><span> to unpack dpfp-mutualdependency-b_0.1.0-1_all.deb ... </span><span style="color:#b58900;">Unpacking</span><span> dpfp-mutualdependency-b (0.1.0-1) </span><span style="color:#b58900;">over</span><span> (0.1.0-1) </span><span style="color:#b58900;">... </span><span style="color:#b58900;">Setting</span><span> up dpfp-mutualdependency-b (0.1.0-1) </span><span style="color:#b58900;">... </span><span style="color:#b58900;">Setting</span><span> up dpfp-mutualdependency-a (0.1.0-1) </span><span style="color:#b58900;">... </span></code></pre> <p>In the first post in this series I got to grip with how deb packages are put together and now in this second post I understand how they’re connected to each other. Next time, I’m going to look at repackaging an existing piece of software for an architecture it wasn’t originally published for, and then try to host it on my own “apt” repository.</p> Connecting to Serial Ports with Windows Terminal 2024-08-23T13:37:00+00:00 2024-08-23T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/serial-windows-terminal/ <p>Windows Terminal is a killer app for Windows 10 &amp; 11. With it you can launch Windows command interpreter, Powershell, bash, and WSL sessions. It is however missing the ability to connect to COM ports and serial connections.</p> <p>If you’ve been a sysadmin on or with Windows long enough you’ve probably got PuTTY installed which will let you connect to serial consoles. Launching a whole other tool with different font &amp; colour scheme settings, when Terminal should be able to do the job, seems silly to me. Luckily PuTTY bundles the <code>plink</code> tool which lets us connect to serial ports from the command line.</p> <p><img src="https://assets.mikecoats.xyz/serial-windows-terminal/plink-help.png" alt="A screenshot of plink’s help output." loading="lazy" decoding="async" /></p> <p>Speaking to the device at the other end of your serial port is as simple as passing the COM port as an argument, as long as your serial port’s default configuration matches the other device’s settings.</p> <p><img src="https://assets.mikecoats.xyz/serial-windows-terminal/plink-cisco.png" alt="A screenshot of plink being used to connect to a Cisco switch." loading="lazy" decoding="async" /></p> <p>If you need to set some parameters such as a different baud rate these can be passed as arguments too but before we go too far and start adding a full <code>plink</code> session to our Windows Terminal configuration there’s an important note <a rel="noopener" target="_blank" href="https://the.earth.li/~sgtatham/putty/0.81/htmldoc/Chapter7.html#plink">in the PuTTY documentation</a> about <code>plink</code>.</p> <blockquote> <p>Plink is probably not what you want if you want to run an interactive session in a console window.</p> </blockquote> <p>This is true, but it probably won’t be obvious why until it suddenly becomes frustrating. It does not pass any of the “interactive” keystrokes in a console session, such as the up arrow to allow correcting mistyped commands.</p> <p><img src="https://assets.mikecoats.xyz/serial-windows-terminal/plink-arrow.png" alt="A screenshot of plink being used to connect to a Cisco switch, illustrating the lack of arrow key support." loading="lazy" decoding="async" /></p> <p>Thankfully, <a rel="noopener" target="_blank" href="https://github.com/fasteddy516">Edward Wright</a> wrote a nice tool, <a rel="noopener" target="_blank" href="https://github.com/fasteddy516/SimplySerial">SimplySerial</a>, that picks up where <code>plink</code> leaves off. Once installed, it can be launched much like <code>plink</code>, by running the <code>ss</code> command, passing the COM port as an argument.</p> <p><img src="https://assets.mikecoats.xyz/serial-windows-terminal/ss-cisco.png" alt="A screenshot of SimplySerial being used to connect to a Cisco switch." loading="lazy" decoding="async" /></p> <p><img src="https://assets.mikecoats.xyz/serial-windows-terminal/ss-arrow.png" alt="A screenshot of SimplySerial being used to connect to a Cisco switch, illustrating arrow key support." loading="lazy" decoding="async" /></p> <p>The SimplySerial documentation includes <a rel="noopener" target="_blank" href="https://github.com/fasteddy516/SimplySerial?tab=readme-ov-file#using-simplyserial-with-windows-terminal">a hint on how to add</a> <code>ss</code> to Windows Terminal, but it’s default behaviour isn’t as clean as I’d like. I suggest adding the <code>-nostatus</code> flag to prevent the tab title from being renamed and the <code>-quiet</code> flag to suppress the large start-up banner.</p> <p><img src="https://assets.mikecoats.xyz/serial-windows-terminal/terminal-settings.png" alt="A screenshot of the Windows Terminal settings for a SimplySerial tab." loading="lazy" decoding="async" /></p> <p>With this configuration added to Windows Terminal, we can create a new tab with the COM5 profile.</p> <p><img src="https://assets.mikecoats.xyz/serial-windows-terminal/terminal-profile.png" alt="A screenshot of Windows Terminal showing the new SimplySerial profile." loading="lazy" decoding="async" /></p> <p>This launches a nice, clean, serial session ready for you to configure your network switch or talk to your microcontroller.</p> <p><img src="https://assets.mikecoats.xyz/serial-windows-terminal/terminal-ss-tab.png" alt="A screenshot of SimplySerial being used to connect to a Cisco switch." loading="lazy" decoding="async" /></p> Poison the WeLLMs 2024-08-12T13:37:00+00:00 2024-08-12T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/poison-the-wellms/ <p>It’s a little cheeky, but I’m really quite proud to announce the initial release of “Poison the WeLLMs”; a reverse-proxy that serves disassociated-press style re-imaginings of your upstream pages, poisoning any LLMs that scrape your content.</p> <p><img src="https://assets.mikecoats.xyz/poison-the-wellms/logo.jpeg" alt="A screenshot of the Poison the WeLLMs logo. The main focus is a water well with the letters LLM visible in the outline of the stone blocks. Next to the well is a bottle of poison, the text indicating it contains Amonita muscaria." loading="lazy" decoding="async" /></p> <h2 id="">It does what?</h2> <p>It is a reverse-proxy. It sits between your visitors and your web-server and has the ability to modify requests and responses on-the-fly.</p> <p>It produces disassociated-press style re-imaginings of your upstream pages. It reads the contents of your pages, splits the text into sentences, and then splits those sentences into word-pairs. It then randomly rebuilds the word-pairs into new sentences, arranging those sentences in to new paragraphs and returns the generated page to the visitor. These new, generated, pages have the look and feel of real text, but probably make little to no sense.</p> <p>It poisons LLMs. In a recent <a rel="noopener" target="_blank" href="https://www.nature.com/articles/s41586-024-07566-y">paper, published in Nature</a>, it was found that “indiscriminate use of model-generated content in training causes irreversible defects in the resulting models”. Deliberately feeding LLM scraper-bots pre-hallucinated text might reduce or remove the value gained from indiscriminately scanning websites and would potentially even degrade the performance of, or poison, the resulting models.</p> <h2 id="-1">What does that look like?</h2> <p>Here’s an example of one of my blog posts and then what would be served up to any LLM bot that scrapes a poisoned copy.</p> <p><img src="https://assets.mikecoats.xyz/poison-the-wellms/original-blog.jpeg" alt="A screenshot of the first three paragraphs of my Data Loss blog." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/poison-the-wellms/poisoned-blog.jpeg" alt="A screenshot of a textual hallucination based on the content in my Data Loss blog. It begins with the nonsense, “Its practice of radical honesty, that its use can be found in advance of the world mirror it. I just wait until i&#39;m completely unreadable.”" loading="lazy" decoding="async" /></p> <p>This is another recent post and the equivalent page presented to LLM bots.</p> <p><img src="https://assets.mikecoats.xyz/poison-the-wellms/original-aa.jpeg" alt="A screenshot of the first paragraph of my aa-sms blog." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/poison-the-wellms/poisoned-aa.jpeg" alt="A screenshot of a textual hallucination based on the content in my aa-sms blog. It begins, “The repository. Terms of the project&#39;s root. From crates.io. I hope to rust and it&#39;s git repository and can be launched by running." loading="lazy" decoding="async" /></p> <p>In both of these examples, all of the HTML tags are lost. Formatting, links and images are all stripped and just paragraphs of text are returned.</p> <h2 id="-2">How does it work?</h2> <p>We grab the text content of the upstream pages, parse out each sentence on the page, then build a list of word-pairs that make up each sentence. We use <code>None</code> to signify the start and end of a sentence. Taking the simple sentence,</p> <blockquote> <p>“this is the only sentence”,</p> </blockquote> <p>we can split it into the following word-pairs.</p> <p><img src="https://assets.mikecoats.xyz/poison-the-wellms/word-pairs.png" alt="A diagram showing the sentence split in to adjacent word-pairs." loading="lazy" decoding="async" /></p> <p>If we treat these word-pairs like <code>cons</code> cells or <code>tree</code> nodes, we can build a graph that indicates the reading flow through the sentence.</p> <p><img src="https://assets.mikecoats.xyz/poison-the-wellms/pairs-tree.png" alt="The same diagram with arrows connecting the word-pairs in to a directed graph." loading="lazy" decoding="async" /></p> <p>Simplifying this by removing the duplicated information, we get the following, much more readable, graph.</p> <p><img src="https://assets.mikecoats.xyz/poison-the-wellms/single-sentence.png" alt="A diagram illustrating the single sentence that can be generated from the input “this is the only sentence”." loading="lazy" decoding="async" /></p> <p>Following the flow through the word-pairs we can see that “this is the only sentence” is the only sentence an algorithm could generate from this graph.</p> <p>Creating another graph, this time based on a single sentence but with one word repeated in a couple of places,</p> <blockquote> <p>“this is the sentence that might have the loop“,</p> </blockquote> <p>we can see there are more options for the algorithm to follow.</p> <p><img src="https://assets.mikecoats.xyz/poison-the-wellms/looping-sentence.png" alt="A diagram illustrating the looping structure that can be generated from the input “this is the sentence that might have the loop”." loading="lazy" decoding="async" /></p> <p>In addition to the input text, “this is the sentence that might have the loop”, we can see that the algorithm could take a shortcut and skip the loop returning, “this is the loop”. It could also generate an infinite number of extended sentences by running round the loop several times before exiting, producing something like “this is the sentence that might have the sentence that might have the sentence that might have the sentence that might have the loop”.</p> <p>If we feed the algorithm two sentences that share a single word,</p> <blockquote> <p>“this is the only sentence” and</p> <p>“only word that matters”,</p> </blockquote> <p>we can see there are four paths through the graph.</p> <p><img src="https://assets.mikecoats.xyz/poison-the-wellms/two-sentences.png" alt="A diagram illustrating the four possible sentences that can be generated from the input “this is the only sentence. only word that matters”." loading="lazy" decoding="async" /></p> <p>The algorithm could therefore produce not only the two original input sentences, but two completely new ones, “only sentence” and “this is the only word that matters”.</p> <p>Taking things further, here’s a graph built from an entire paragraph of one of my <a href="/categories/new-music-monday/">#NewMusicMonday</a> posts.</p> <p><img src="https://assets.mikecoats.xyz/poison-the-wellms/graph.png" alt="A diagram illustrating the extensive graph that can be built from just a single paragraph of text." loading="lazy" decoding="async" /></p> <p>With my tongue firmly lodged in my cheek, I like to refer to these graphs as little language models, or maybe wee language models depending on which side of the border you are.</p> <h2 id="-3">See it in action</h2> <p>At the moment, I don’t have fully working copy running in front of my site, I’ve instead got it installed on my app server at https://poisoned.mikecoats.xyz. Since we only rewrite text not HTML, including <code>a</code> tags, you won’t be able to follow and of the links there, so instead here’s three pages that are being served through the reverse-proxy.</p> <ul> <li><a rel="noopener" target="_blank" href="https://poisoned.mikecoats.xyz/aa-sms/">https://poisoned.mikecoats.xyz/aa-sms/</a></li> <li><a rel="noopener" target="_blank" href="https://poisoned.mikecoats.xyz/a-blind-spot-in-my-backup-strategy/">https://poisoned.mikecoats.xyz/a-blind-spot-in-my-backup-strategy/</a></li> <li><a rel="noopener" target="_blank" href="https://poisoned.mikecoats.xyz/newmusicmonday-mwwb-slomatics/">https://poisoned.mikecoats.xyz/newmusicmonday-mwwb-slomatics/</a></li> </ul> <p>If you have a browser extension that lets you alter your user-agent, such as <a rel="noopener" target="_blank" href="https://addons.mozilla.org/en-US/firefox/addon/user-agent-string-switcher/">User-Agent Switcher and Manager</a> for Firefox, visiting those links with an agent string like <code>ClaudeBot</code> or <code>GPTBot</code> should give you the hallucinated versions. A visit with your normal user-agent will give you the regular content. If you’re on a platform, or use a browser, where user-agent spoofing is not possible the following links will take you directly to the hallucinations.</p> <ul> <li><a rel="noopener" target="_blank" href="https://bot-view.poisoned.mikecoats.xyz/aa-sms/">https://bot-view.poisoned.mikecoats.xyz/aa-sms/</a></li> <li><a rel="noopener" target="_blank" href="https://bot-view.poisoned.mikecoats.xyz/a-blind-spot-in-my-backup-strategy/">https://bot-view.poisoned.mikecoats.xyz/a-blind-spot-in-my-backup-strategy/</a></li> <li><a rel="noopener" target="_blank" href="https://bot-view.poisoned.mikecoats.xyz/newmusicmonday-mwwb-slomatics/">https://bot-view.poisoned.mikecoats.xyz/newmusicmonday-mwwb-slomatics/</a></li> </ul> <p>There are still some rough edges around the text and sentence parsers that would improve the quality of the output, and I’ve spotted some server errors crop up from time to time, so it’s not finished but I think it’s good enough to show off.</p> <p>The code is available from <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/poison-the-wellms">my git repository</a> and it includes some quite terrible installation instructions if you want to play with it yourself. Right now I’ve only got configuration files that integrate with Caddy, but I’m looking in to Nginx and Apache versions too.</p> <p>The application is free software so you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. The configuration files are released under the MIT License as they’re based on a JSON file from <a rel="noopener" target="_blank" href="https://coryd.dev">Cory Dransfeldt’s</a> <a rel="noopener" target="_blank" href="https://github.com/ai-robots-txt/ai.robots.txt">upstream ai.robots.txt repository</a>.</p> aa-sms – Send messages from Rust with Andrews & Arnold’s SMS API 2024-07-31T13:37:00+00:00 2024-07-31T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/aa-sms/ <p>I mentioned <a href="/newmusicmonday-mwwb-slomatics/">a few posts ago</a> that I’d been learning a new programming language, and this post is the result of that work. I’m excited to announce <a rel="noopener" target="_blank" href="https://crates.io/crates/aa-sms">the <code>aa-sms</code> library</a> for sending SMS text messages from Rust with <a rel="noopener" target="_blank" href="https://support.aa.net.uk/SMS_API">Andrews &amp; Arnold’s APIs</a>.</p> <p><img src="https://assets.mikecoats.xyz/aa-sms/code.jpeg" alt="Screenshot of some example code using the aa-sms library to send an SMS text message" loading="lazy" decoding="async" /></p> <p>Before you can send messages using this library, you’ll need an account with <a rel="noopener" target="_blank" href="https://www.aa.net.uk/voice-and-mobile/voip-information/">Andrews and Arnold and be subscribed to their VoIP service</a> with one or more phone numbers.</p> <p>The library is <a rel="noopener" target="_blank" href="https://crates.io/crates/aa-sms">available for installation from crates.io</a> and can be added to your own Rust project with a simple <code>cargo add aa-sms</code>.</p> <p>It’s built using the Builder pattern that seems common to Rust libraries and which I’ve previously known as a Fluent interface from my Java days. An <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/aa-sms/src/branch/main/examples/send-library.rs">example program</a> using the library is included in the repository and can be launched by running <code>cargo run --example send-library</code> from the project’s root. Another example, <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/aa-sms/src/branch/main/examples/send-reqwest.rs">this time using the reqwest library directly</a>, is included for comparison and can be launched similarly, <code>cargo run --example send-library</code>.</p> <p><img src="https://assets.mikecoats.xyz/aa-sms/alert.jpeg" alt="A screenshot of an iPhone receiving an SMS sent using the aa-sms library" loading="lazy" decoding="async" /></p> <p>This code is at a very early stage, and I’m still new to rust and it’s idioms. So, please use it but be aware it comes with a massive caveat emptor. If anyone has any recommendations or fancies doing a wee code review, all constructive criticism is well received. I’ve already got <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/aa-sms/issues">a number of improvements</a> I hope to make soon.</p> <p>The source code is available from <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/aa-sms">it’s git repository</a> and is released as free software under the <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/aa-sms/src/branch/main/LICENSE.md">terms of the GNU General Public License</a> as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</p> #NewMusicMonday – Kiasmos & Warrington-Runcorn New Town Development Plan 2024-07-29T13:37:00+00:00 2024-07-29T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/newmusicmonday-kiasmos-warrington-runcorn/ <p>This past week I spent a whole day scurrying about the server room at work, wiring up a full rack of switches, routers and site-to-site VPN devices. This coming week, I’ll be doing more of the same. A decent set of isolating, noise-cancelling, earbuds can do wonders to overcome the rushing whirr from rack after rack of servers and the throbbing rumble of air-conditioner units. Add some slick Electronica beats into the mix and you’re dropped right in to the zone.</p> <p>This weeks #NewMusicMonday recommendations are the latest releases from <a rel="noopener" target="_blank" href="https://kiasmos.bandcamp.com/">Kiasmos</a> and the excellently named <a rel="noopener" target="_blank" href="https://warrington-runcorn-cis.bandcamp.com/music">Warrington-Runcorn New Town Development Plan</a>.</p> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-kiasmos-warrington-runcorn&#x2F;kiasmos.jpg" alt="Cover art of II by Kiasmos." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;kiasmos.bandcamp.com&#x2F;album&#x2F;ii">II</a> by Kiasmos</p> </figcaption> </figure> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-kiasmos-warrington-runcorn&#x2F;warrington-runcorn.jpg" alt="Cover art of Your Community Hub by Warrington-Runcorn New Town Development Plan." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;warrington-runcorn-cis.bandcamp.com&#x2F;album&#x2F;your-community-hub">Your Community Hub</a> by Warrington-Runcorn New Town Development Plan</p> </figcaption> </figure> Debian Packaging from First Principles – Part 1 – Simple .deb 2024-07-25T13:37:00+00:00 2024-07-25T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/debian-packaging-first-principles-part-1-simple/ <p>I’ve used Debian as my distro of choice for well over a decade and whilst I’ve become familiar with “using” Debian I’ve never really understood how it worked under the hood. I understand that behind the scenes of <code>apt install</code> there’s a repository out on the internet somewhere, some packages are downloaded, and then their contents are installed, but how that happens is a bit of a black-box mystery to me.</p> <p><img src="https://assets.mikecoats.xyz/debian-packaging-first-principles-part-1-simple/demo.jpeg" alt="A screenshot of a Makefile to build a simple deb package." loading="lazy" decoding="async" /></p> <p>In this first post in the series I aim to uncover what exactly is a <code>deb</code> file, what it contains and how one works. In subsequent posts I hope to better understand package-to-package dependencies, and eventually <code>apt</code> repositories. If you want to follow along with my code from this post and across all of the posts in this series, the files can be found in <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles/src/branch/main/simple">this directory</a> from within <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles">this git repository</a>.</p> <h2 id="">deb Format</h2> <p>To begin with, I first consulted the <code>deb</code> format’s <code>man</code> page.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> man 5 deb </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">The</span><span> file is an ar archive with a magic value of !&lt;arch&gt;. </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">The</span><span> first member is named debian-binary and contains a </span><span style="color:#b58900;">series</span><span> of lines, separated by newlines. Currently only </span><span style="color:#b58900;">one</span><span> line is present, the format version number, 2.0 at </span><span style="color:#b58900;">the</span><span> time this manual page was written. </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">The</span><span> second required member is named control.tar. It is </span><span style="color:#b58900;">a</span><span> tar archive containing the package control information </span><span style="color:#b58900;">as</span><span> a series of plain files, of which the file control is </span><span style="color:#b58900;">mandatory </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">The</span><span> third, last required member is named data.tar. It </span><span style="color:#b58900;">contains</span><span> the filesystem as a tar archive </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">These</span><span> members must occur in this exact order. </span><span style="color:#93a1a1;"># [...] </span></code></pre> <h2 id="ar"><code>ar</code> Archive</h2> <p>The first requirement from that man page is that a <code>deb</code> file is “an <code>ar</code> archive with a magic value of !<arch>.” I’m not familiar with the <code>ar</code> tool, so I’ve no idea if it’s special or difficult to set “!<arch>” as a magic value. To test <code>ar</code>‘s default behaviour, we can create an empty file, add it to a new <code>ar</code> archive, then inspect the archive’s content with a hex viewer such as <code>xxd</code>.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> touch empty </span><span> </span><span style="color:#b58900;">$</span><span> ar r empty.ar empty </span><span style="color:#b58900;">ar:</span><span> creating empty.ar </span><span> </span><span style="color:#b58900;">$</span><span> xxd</span><span style="color:#268bd2;"> -c</span><span> 8</span><span style="color:#268bd2;"> -g</span><span> 1 empty.ar </span><span style="color:#b58900;">00000000:</span><span> 21 3c 61 72 63 68 3e 0a !&lt;arch&gt;. </span><span style="color:#b58900;">00000008:</span><span> 65 6d 70 74 79 2f 20 20 empty/ </span><span style="color:#b58900;">00000010:</span><span> 20 20 20 20 20 20 20 20 </span><span style="color:#b58900;">00000018:</span><span> 30 20 20 20 20 20 20 20 0 </span><span style="color:#b58900;">00000020:</span><span> 20 20 20 20 30 20 20 20 0 </span><span style="color:#b58900;">00000028:</span><span> 20 20 30 20 20 20 20 20 0 </span><span style="color:#b58900;">00000030:</span><span> 36 34 34 20 20 20 20 20 644 </span><span style="color:#b58900;">00000038:</span><span> 30 20 20 20 20 20 20 20 0 </span><span style="color:#b58900;">00000040:</span><span> 20 20 60 0a </span></code></pre> <p>From this output we can see that <code>ar</code> is using “!<arch>” by default, so we don’t need to take any special action here.</p> <h2 id="debian-binary"><code>debian-binary</code></h2> <p>Creating the first member of the archive, <code>debian-binary</code>, is as straightforward as creating a plain text file with a single line containing “2.0”.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> vim debian-binary </span><span style="color:#b58900;">2.0 </span></code></pre> <h2 id="control-tar"><code>control.tar</code></h2> <p>The archive’s second member, <code>control.tar</code>, needs to contain a <code>control</code> file as a bare minimum. Consulting the <code>deb-control</code> format’s <code>man</code> page reveals the format and contents of that file, including which fields are required.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> man 5 deb-control </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">This</span><span> file contains a number of fields. Each field begins </span><span style="color:#b58900;">with</span><span> a tag, such as Package or Version (case insensitive)</span><span style="color:#b58900;">, </span><span style="color:#b58900;">followed</span><span> by a colon, and the body of the field </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">FIELDS </span><span style="color:#93a1a1;"># [...] </span><span> </span><span style="color:#b58900;">Package:</span><span> package-name (required) </span><span style="color:#93a1a1;"># [...] </span><span> </span><span style="color:#b58900;">Version:</span><span> version-string (required) </span><span style="color:#93a1a1;"># [...] </span><span> </span><span style="color:#b58900;">Architecture:</span><span> arch</span><span style="color:#859900;">|</span><span style="color:#b58900;">all</span><span> (required) </span><span style="color:#93a1a1;"># [...] </span></code></pre> <p>It looks like we only need to provide three fields to get <code>dpkg</code> to accept our <code>deb</code> file, a name, the package’s version and the target architecture. Architecture’s the easiest here, we can just use “all” since we’re not actually bundling any platform-specific binaries. Version is straightforward too, we can just use a sensible “first” version such as 0.0.1 and accompany it with a “first” package number to build “0.0.1-1”. The name is to our own whims and since it’s the Simple example from my Debian Packaging from First Principles series, “dpfp-simple” is good enough.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> mkdir control </span><span> </span><span style="color:#b58900;">$</span><span> vim control/control </span><span style="color:#b58900;">Package:</span><span> dpfp-simple </span><span style="color:#b58900;">Version:</span><span> 0.0.1-1 </span><span style="color:#b58900;">Architecture:</span><span> all </span></code></pre> <p>With our <code>control</code> file in place, we can now bundle it in to the <code>control.tar</code> file which will be later added to the <code>deb</code>. You can probably get away without setting the user and group to root, but you might end up leaking your own username and group in the <code>tar</code> file without it.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> tar </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --create </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --file</span><span> control.tar </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --owner</span><span>=root </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --group</span><span>=root </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --directory</span><span>=control </span><span style="color:#dc322f;">\ </span><span> control </span><span> </span><span style="color:#b58900;">$</span><span> tar </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --list </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --verbose </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --file</span><span> control.tar </span><span style="color:#b58900;">-rw-r--r--</span><span> root/root 54 2024-07-10 12:44 control </span></code></pre> <p>We can see the <code>control.tar</code> file contains a single control file, matching the <code>man</code> pages’s specifications.</p> <h2 id="data-tar"><code>data.tar</code></h2> <p>The <code>data.tar</code> file needs to hold the package’s contents as they appear on the filesystem, so we should create a deep hierarchy of directories and place a simple text file within it.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> mkdir </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --parents </span><span style="color:#dc322f;">\ </span><span> data/opt/debian-packaging-first-principles/simple </span><span> </span><span style="color:#b58900;">$</span><span> vim data/opt/debian-packaging-first-principles/simple/dpfp-simple-success.txt </span><span style="color:#b58900;">Debian</span><span> Packaging from First Principles </span><span> </span><span style="color:#b58900;">Simple </span><span> </span><span style="color:#b58900;">Success! </span></code></pre> <p>With our package’s filesystem laid out, we can bundle the success file and it’s directory hierarchy in to the <code>data.tar</code> we need to add to the deb. We follow the same process as we did for the <code>control.tar</code> earlier.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> tar </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --create </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --file</span><span> data.tar </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --owner</span><span>=root </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --group</span><span>=root </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --directory</span><span>=data </span><span style="color:#dc322f;">\ </span><span> opt/ </span><span> </span><span style="color:#b58900;">$</span><span> tar </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --list </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --verbose </span><span style="color:#dc322f;">\ </span><span style="color:#268bd2;"> --file</span><span> data.tar </span><span style="color:#b58900;">drwxr-xr-x</span><span> root/root 0 2024-07-10 14:45 opt/ </span><span style="color:#b58900;">drwxr-xr-x</span><span> root/root 0 2024-07-10 14:45 opt/debian-packaging-first-principles/ </span><span style="color:#b58900;">drwxr-xr-x</span><span> root/root 0 2024-07-10 14:48 opt/debian-packaging-first-principles/simple/ </span><span style="color:#b58900;">-rw-r--r--</span><span> root/root 67 2024-07-10 14:47 opt/debian-packagi </span></code></pre> <p>This time, we can see <code>tar</code>‘s created entries for the whole directory structure as well as the text file which will be installed by <code>dpkg</code>.</p> <h2 id="our-package-deb"><code>our-package.deb</code></h2> <p>Now we’ve created the three member files described by the first <code>man</code> page, we can use the <code>ar</code> command to bundle them together to build our final <code>deb</code> package.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> ar </span><span style="color:#dc322f;">\ </span><span> r dpfp-simple_0.0.1-1_all.deb </span><span style="color:#dc322f;">\ </span><span> debian-binary </span><span style="color:#dc322f;">\ </span><span> control.tar </span><span style="color:#dc322f;">\ </span><span> data.tar </span><span style="color:#b58900;">ar:</span><span> creating dpfp-simple_0.0.1-1_all.deb </span><span> </span><span style="color:#b58900;">$</span><span> ar tv dpfp-simple_0.0.1-1_all.deb </span><span style="color:#b58900;">rw-r--r--</span><span> 0/0 4 Jan 1 01:00 1970 debian-binary </span><span style="color:#b58900;">rw-r--r--</span><span> 0/0 10240 Jan 1 01:00 1970 control.tar </span><span style="color:#b58900;">rw-r--r--</span><span> 0/0 10240 Jan 1 01:00 1970 data.tar </span></code></pre> <p>Here the <code>ar</code> command has added the three member files to our <code>deb</code> file and they appear to be in the correct order, such that Debian should accept them for installation.</p> <h2 id="-1">Installing and Removing</h2> <p>You can use either <code>apt</code> or <code>dpkg</code> to install a local <code>deb</code> file, but for simplicity’s sake, I’ll stick with <code>dpkg</code> for just now. Once the package has been installed, we should be able to find the included text file in the correct location within the filesystem.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo dpkg</span><span style="color:#268bd2;"> --install</span><span> dpfp-simple_0.0.1-1_all.deb </span><span style="color:#b58900;">dpkg:</span><span> warning: parsing file </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">/var/lib/dpkg/tmp.ci/control</span><span style="color:#839496;">&#39; </span><span style="color:#b58900;">near</span><span> line 4 package </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">dpfp-simple</span><span style="color:#839496;">&#39;</span><span>: </span><span> </span><span style="color:#b58900;">missing </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">Description</span><span style="color:#839496;">&#39;</span><span> field </span><span style="color:#b58900;">dpkg:</span><span> warning: parsing file </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">/var/lib/dpkg/tmp.ci/control</span><span style="color:#839496;">&#39; </span><span style="color:#b58900;">near</span><span> line 4 package </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">dpfp-simple</span><span style="color:#839496;">&#39;</span><span>: </span><span> </span><span style="color:#b58900;">missing </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">Maintainer</span><span style="color:#839496;">&#39;</span><span> field </span><span style="color:#b58900;">Selecting</span><span> previously unselected package dpfp-simple. </span><span>(</span><span style="color:#b58900;">Reading</span><span> database ... 136384 files and directories </span><span style="color:#b58900;">currently</span><span> installed.) </span><span style="color:#b58900;">Preparing</span><span> to unpack dpfp-simple_0.0.1-1_all.deb ... </span><span style="color:#b58900;">Unpacking</span><span> dpfp-simple (0.0.1-1) </span><span style="color:#b58900;">... </span><span style="color:#b58900;">Setting</span><span> up dpfp-simple (0.0.1-1) </span><span style="color:#b58900;">... </span><span> </span><span style="color:#b58900;">$</span><span> less /opt/debian-packaging-first-principles/simple/dpfp-simple-success.txt </span><span style="color:#b58900;">Debian</span><span> Packaging from First Principles </span><span> </span><span style="color:#b58900;">Simple </span><span> </span><span style="color:#b58900;">Success! </span></code></pre> <p>Interesting. It appears that whilst the <code>deb</code> man page only describes three fields as required, it will be quite loud about two other fields missing from the <code>control</code> file. Despite these warnings, the files are extracted correctly and can be viewed as we’d hoped. To remove our package again, we need to specify its name instead of the <code>deb</code> file.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo dpkg</span><span style="color:#268bd2;"> --remove</span><span> dpfp-simple </span><span style="color:#b58900;">dpkg:</span><span> warning: parsing file </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">/var/lib/dpkg/status</span><span style="color:#839496;">&#39;</span><span> near </span><span style="color:#b58900;">line</span><span> 1939 package </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">dpfp-simple</span><span style="color:#839496;">&#39;</span><span>: </span><span> </span><span style="color:#b58900;">missing </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">Description</span><span style="color:#839496;">&#39;</span><span> field </span><span style="color:#b58900;">dpkg:</span><span> warning: parsing file </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">/var/lib/dpkg/status</span><span style="color:#839496;">&#39;</span><span> near </span><span style="color:#b58900;">line</span><span> 1939 package </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">dpfp-simple</span><span style="color:#839496;">&#39;</span><span>: </span><span> </span><span style="color:#b58900;">missing </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">Maintainer</span><span style="color:#839496;">&#39;</span><span> field </span><span>(</span><span style="color:#b58900;">Reading</span><span> database ... 136387 files and directories </span><span style="color:#b58900;">currently</span><span> installed.) </span><span style="color:#b58900;">Removing</span><span> dpfp-simple (0.0.1-1) </span><span style="color:#b58900;">... </span><span> </span><span style="color:#b58900;">$</span><span> ls</span><span style="color:#268bd2;"> -al</span><span> /opt </span><span style="color:#b58900;">total</span><span> 12 </span><span style="color:#b58900;">drwxr-xr-x</span><span> 1 root root 4096 Jul 10 16:09 . </span><span style="color:#b58900;">drwxr-xr-x</span><span> 1 root root 4096 Jan 1 2021 .. </span></code></pre> <p>It appears that as part of whatever Debian does to store information about its installed packages, it copies the <code>control</code> file in to some central list and still complains about missing fields even as it’s removing the defective package.</p> <h2 id="-2">Bug Fixing</h2> <p>In order that we’re being well behaved citizens of the <code>deb</code> ecosystem, we should fix those warnings. We need to add the two missing fields to our <code>control</code> file, update our version number to indicate we’re on our second package version, and rebuild the <code>deb</code> following the earlier steps.</p> <pre data-lang="diff" style="background-color:#fdf6e3;color:#657b83;" class="language-diff "><code class="language-diff" data-lang="diff"><span>Package: dpfp-simple </span><span style="color:#dc322f;">- Version: 0.0.1-1 </span><span style="color:#859900;">+ Version: 0.0.1-2 </span><span>Architecture: all </span><span style="color:#859900;">+ Description: A bare-minimum deb file, manually assembled to understand Debian packaging. </span><span style="color:#859900;">+ Maintainer: Mike Coats &lt;i.am@mikecoats.com&gt; </span></code></pre> <p>With the changes from that <code>diff</code> in place, and a new <code>deb</code> file built, we can try installing and removing the package again to see if the warnings have been resolved.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo dpkg</span><span style="color:#268bd2;"> --install</span><span> ./dpfp-simple_0.0.1-2_all.deb </span><span style="color:#b58900;">Selecting</span><span> previously unselected package dpfp-simple. </span><span>(</span><span style="color:#b58900;">Reading</span><span> database ... 137130 files and directories currently installed.) </span><span style="color:#b58900;">Preparing</span><span> to unpack ./dpfp-simple_0.0.1-2_all.deb ... </span><span style="color:#b58900;">Unpacking</span><span> dpfp-simple (0.0.1-2) </span><span style="color:#b58900;">... </span><span style="color:#b58900;">Setting</span><span> up dpfp-simple (0.0.1-2) </span><span style="color:#b58900;">... </span><span> </span><span style="color:#b58900;">$</span><span> sudo dpkg</span><span style="color:#268bd2;"> --remove</span><span> dpfp-simple </span><span>(</span><span style="color:#b58900;">Reading</span><span> database ... 137133 files and directories currently installed.) </span><span style="color:#b58900;">Removing</span><span> dpfp-simple (0.0.1-2) </span><span style="color:#b58900;">... </span></code></pre> <p>Bingo! No more warnings!</p> <p>To simplify the repeated building of the deb and its member files, I wrapped up the process in <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/debian-packaging-first-principles/src/branch/main/simple/Makefile">a small, hand-rolled, Makefile</a>. My plan for the next few blog posts is to iterate on this project to understand more about <code>deb</code> files and <code>apt</code> tooling, first getting to grips with dependencies.</p> Recover a single drive from an abandoned RAID 1 mirrored pair 2024-07-10T13:37:00+00:00 2024-07-10T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/recover-single-drive-raid-pair/ <p>Like most folks who’ve been in tech for a couple of decades, I’ve accumulated my fair share of obsolete equipment. I normally either find new homes for old hardware or have it responsibly recycled but, for peace of mind, I always shuck the hard drives first and promise to myself to “deal with them later”. “Later” never seems to happen and during a recent home-office tidy I discovered I’d been hoarding 11 hard drives of various flavours and vintages. Most of these drives were simply pulled from laptops or external drive caddies so mounting them, backing up any extant data and securely erasing them is a simple affair within any reasonably modern Debian install. One of the drives was pulled from an old <a rel="noopener" target="_blank" href="https://www.synology.com/en-eu/support/download/DS210j?version=5.2#docs">Synology DS210j NAS</a> however.</p> <p><img src="https://assets.mikecoats.xyz/recover-single-drive-raid-pair/drives.jpeg" alt="A photograph of a stack of 2.5&quot; HDDs pulled from old laptops. The labels on each has been scribbled over and the word ERASED can be read on one of them." loading="lazy" decoding="async" /></p> <p>The drives in the DS210j had been set up as a <a rel="noopener" target="_blank" href="https://raid.wiki.kernel.org/index.php/What_is_RAID_and_why_should_you_want_it%3F#RAID1.2FMirror_Mode">mirrored pair</a> for resiliency. I’ve always understood that to mean that a full, identical, copy of the data was made to both disks, but I’ve never poked under the hood to see how that worked. Can one drive be mounted as if it was always just a single disk? How much of the RAID array’s state has been lost by pulling the drive and getting rid of the NAS? Is there even anything left to recover and erase?</p> <p>Here’s the steps I went through to enumerate and mount the data partition from a single drive that was once part of a RAID 1 mirrored pair.</p> <p>I use this <a rel="noopener" target="_blank" href="https://www.amazon.co.uk/dp/B01EK9LRLM">USB-SATA adapter from StarTech</a> to connect old drives to my computer since it comes with an external power supply, so it should support even the beefiest 3.5″ spinning metal HDDs without dropping out. Connecting the drive and adaptor to the PC reveals the following logs.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo journalctl</span><span style="color:#268bd2;"> --catalog --pager-end --follow </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">Jul</span><span> 02 12:56:06 dev-box kernel: usb 1-1.1: new high-speed USB device number 8 using ehci-platform </span><span style="color:#b58900;">Jul</span><span> 02 12:56:06 dev-box kernel: usb 1-1.1: New USB device found, idVendor=174c, idProduct=235c, bcdDevice= 1.00 </span><span style="color:#b58900;">Jul</span><span> 02 12:56:06 dev-box kernel: usb 1-1.1: New USB device strings: Mfr=2, Product=3, SerialNumber=1 </span><span style="color:#b58900;">Jul</span><span> 02 12:56:06 dev-box kernel: usb 1-1.1: Product: USB 3.0 Destop HD EP0 Product string </span><span style="color:#b58900;">Jul</span><span> 02 12:56:06 dev-box kernel: usb 1-1.1: Manufacturer: ASMT </span><span style="color:#b58900;">Jul</span><span> 02 12:56:06 dev-box kernel: usb 1-1.1: SerialNumber: 0000000000B8 </span><span style="color:#b58900;">Jul</span><span> 02 12:56:06 dev-box kernel: scsi host1: uas </span><span style="color:#b58900;">Jul</span><span> 02 12:56:06 dev-box kernel: scsi 1:0:0:0: Direct-Access ASMT USB 3.0 Destop H 0 PQ: 0 ANSI: 6 </span><span style="color:#b58900;">Jul</span><span> 02 12:56:06 dev-box kernel: sd 1:0:0:0: </span><span style="color:#859900;">[</span><span>sdb</span><span style="color:#859900;">]</span><span> Spinning up disk... </span><span style="color:#b58900;">Jul</span><span> 02 12:56:18 dev-box kernel: ............ready </span><span style="color:#b58900;">Jul</span><span> 02 12:56:18 dev-box kernel: sd 1:0:0:0: </span><span style="color:#859900;">[</span><span>sdb</span><span style="color:#859900;">]</span><span> 1953525168 512-byte logical blocks: (1.00 TB/932 GiB) </span><span style="color:#b58900;">Jul</span><span> 02 12:56:18 dev-box kernel: sd 1:0:0:0: </span><span style="color:#859900;">[</span><span>sdb</span><span style="color:#859900;">]</span><span> Write Protect is off </span><span style="color:#b58900;">Jul</span><span> 02 12:56:18 dev-box kernel: sd 1:0:0:0: </span><span style="color:#859900;">[</span><span>sdb</span><span style="color:#859900;">]</span><span> Mode Sense: 43 00 00 00 </span><span style="color:#b58900;">Jul</span><span> 02 12:56:18 dev-box kernel: sd 1:0:0:0: </span><span style="color:#859900;">[</span><span>sdb</span><span style="color:#859900;">]</span><span> Write cache: enabled, read cache: enabled, doesn</span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">t support DPO or FUA </span><span style="color:#2aa198;">Jul 02 12:56:18 dev-box kernel: sd 1:0:0:0: [sdb] Optimal transfer size 33553920 bytes </span><span style="color:#2aa198;">Jul 02 12:56:18 dev-box kernel: sdb: sdb1 sdb2 sdb3 </span><span style="color:#2aa198;">Jul 02 12:56:18 dev-box kernel: sd 1:0:0:0: [sdb] Attached SCSI disk </span><span style="color:#2aa198;"># [...] </span></code></pre> <p>We can see that Debian’s picked up the new USB device and set up our drive under <code>/dev/sdb</code> with three partitions <code>/dev/sdb[1-3]</code>. We can then use fdisk to inspect the partition table and try to see what each partition is for.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo fdisk /dev/sdb </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">Command</span><span> (m for help)</span><span style="color:#859900;">:</span><span> p </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">Device</span><span> Boot Start End Sectors Size Id Type </span><span style="color:#b58900;">/dev/sdb1</span><span> 63 4980149 4980087 2.4G fd Linux raid autodetect </span><span style="color:#b58900;">/dev/sdb2</span><span> 4980150 6024374 1044225 509.9M fd Linux raid autodetect </span><span style="color:#b58900;">/dev/sdb3</span><span> 6281415 1953520064 1947238650 928.5G fd Linux raid autodetect </span><span style="color:#93a1a1;"># [...] </span><span style="color:#b58900;">Command</span><span> (m for help)</span><span style="color:#859900;">:</span><span> q </span></code></pre> <p>It looks like <code>/dev/sdb3</code> is likely to be our data partition since it takes up &gt;90% of the total space on the drive. We can try to mount that partition to take a look at any remaining files, but Linux won’t mount a RAID member directly.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo mount /dev/sdb1 /mnt </span><span style="color:#b58900;">mount:</span><span> /mnt: unknown filesystem type </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">linux_raid_member</span><span style="color:#839496;">&#39;</span><span>. </span><span> </span><span style="color:#b58900;">dmesg</span><span>(1) </span><span style="color:#b58900;">may</span><span> have more information after failed mount system call. </span></code></pre> <p>Referring to <a rel="noopener" target="_blank" href="https://global.download.synology.com/download/Document/Hardware/DataSheet/DiskStation/10-year/DS210j/enu/Synology_DS210j_Data_Sheet_enu.pdf">an old datasheet for the Synology</a>, it looks like the only file system it supported was EXT3, so we should be able safe enough to assume that’s what’s actually on that partition.</p> <p><img src="https://assets.mikecoats.xyz/recover-single-drive-raid-pair/synology.png" alt="A snippet from Synology&#39;s datasheet showing EXT3 support with additional FAT &amp; NTFS formats noted for external disks only." loading="lazy" decoding="async" /></p> <p>We can go back in to fdisk to change the partition’s type from raid member to regular linux partition. That involves changing it’s ID from FD to 83.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo fdisk /dev/sdb </span><span style="color:#b58900;">Command</span><span> (m for help)</span><span style="color:#859900;">:</span><span> t </span><span style="color:#b58900;">Partition</span><span> number (1-3, default 3)</span><span style="color:#859900;">:</span><span> 3 </span><span style="color:#b58900;">Hex</span><span> code or alias (type L to list all)</span><span style="color:#859900;">:</span><span> L </span><span> </span><span style="color:#93a1a1;">#[...] </span><span style="color:#b58900;">Aliases: </span><span> </span><span style="color:#b58900;">linux</span><span> - 83 </span><span style="color:#93a1a1;">#[...] </span><span> </span><span style="color:#b58900;">raid</span><span> - FD </span><span style="color:#93a1a1;">#[...] </span><span style="color:#b58900;">Hex</span><span> code or alias (type L to list all)</span><span style="color:#859900;">:</span><span> 83 </span><span> </span><span style="color:#b58900;">Changed</span><span> type of partition </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">Linux raid autodetect</span><span style="color:#839496;">&#39;</span><span> to </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">Linux</span><span style="color:#839496;">&#39;</span><span>. </span><span> </span><span style="color:#b58900;">Command</span><span> (m for help)</span><span style="color:#859900;">:</span><span> w </span></code></pre> <p>Unfortunately, that doesn’t seem to work. There must be some other data encoded within the partition table or filesystem referring back to the old format ID so it won’t automatically mount the partition.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo mount /dev/sdb3 /mnt </span><span style="color:#b58900;">mount:</span><span> /mnt: unknown filesystem type </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">linux_raid_member</span><span style="color:#839496;">&#39;</span><span>. </span><span> </span><span style="color:#b58900;">dmesg</span><span>(1) </span><span style="color:#b58900;">may</span><span> have more information after failed mount system call. </span></code></pre> <p>At this point I’m still pretty sure that the partition is really just ext3 under the covers, so it should be possible to mount it by specifying the filesystem type explicitly in the mount command. We can use ext4 for this on a modern computer as there’s a good degree of <a rel="noopener" target="_blank" href="https://developer.ibm.com/tutorials/l-anatomy-ext4/#forward-and-backward-compatibility2">forward and backward compatibility</a> between the two filesystems.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo mount</span><span style="color:#268bd2;"> --types</span><span> ext4 /dev/sdb3 /mnt </span></code></pre> <p>Running that mount command returned no errors, so to double check we can ask the system for a list of it’s currently mounted partitions and see how it’s getting on.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> mount </span><span style="color:#b58900;">[...] </span><span style="color:#b58900;">/dev/sdb3</span><span> on /mnt type ext4 (rw,relatime) </span></code></pre> <p>That looks promising, we might be on to something. The next step is to see if there any files available under the mountpoint to prove we’ve really attached the partition properly.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> ls</span><span style="color:#268bd2;"> --all -l --human-readable</span><span> /mnt </span><span style="color:#b58900;">[...] </span><span style="color:#b58900;">-rw-------</span><span> 1 root root 6.0K May 3 2014 aquota.group </span><span style="color:#b58900;">-rw-------</span><span> 1 root root 8.0K May 1 2014 aquota.user </span><span style="color:#b58900;">drwx------</span><span> 2 root root 4.0K Jun 4 2014 @autoupdate </span><span style="color:#b58900;">drwxr-xr-x</span><span> 7 root root 4.0K Apr 14 2014 @download </span><span style="color:#b58900;">[...] </span></code></pre> <p>Bingo! A whole list of files with a reasonably appropriate date have been found on the drive. Now I can back up anything that I still need a copy of, erase the disk when I’m done, and reflect on the fact that this drive has been in my to-do pile for a decade. Oops.</p> #NewMusicMonday – Mammoth Weed Wizard Bastard & Slomatics 2024-07-08T13:37:00+00:00 2024-07-08T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/newmusicmonday-mwwb-slomatics/ <p>I’ve been on annual leave this week, so like any good nerd I’ve spent all of my free time learning a new programming language. I do struggle sometimes where I can’t seem to concentrate on writing code, or at least well-documented code, if there’s a plain, easy to understand English speaking voice playing in the same room. I find I’m just too easily distracted. I therefore love music that I can let wash over me, like instrumental tracks and foreign-language punk and metal.</p> <p>I saw someone on social media this week recommend the split-EP by <a rel="noopener" target="_blank" href="https://mammothweedwizardbastard.bandcamp.com">Mammoth Weed Wizard Bastard</a> &amp; <a rel="noopener" target="_blank" href="https://slomatics.bandcamp.com/">Slomatics</a>, TOTEMS. I’ve never listened to much Doom or Sludge Metal, but the tracks from both of these bands certainly seem to fit my “wash over me” criteria, particularly when listened to with a large set of over-ear headphones. As well as that split-EP, for this week’s <a href="/categories/new-music-monday/">#NewMusicMonday</a> I’ve included an album each from <a rel="noopener" target="_blank" href="https://slomatics.bandcamp.com/">Slomatics</a> and <a rel="noopener" target="_blank" href="https://mammothweedwizardbastard.bandcamp.com">Mammoth Weed Wizard Bastard</a>.</p> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-mwwb-slomatics&#x2F;split.jpeg" alt="Cover art of TOTEMS by Mammoth Weed Wizard Bastard &amp; Slomatics." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;slomatics.bandcamp.com&#x2F;album&#x2F;totems">TOTEMS</a> by Mammoth Weed Wizard Bastard &amp; Slomatics</p> </figcaption> </figure> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-mwwb-slomatics&#x2F;slomatics.jpeg" alt="Cover art of Future Echo Returns by Slomatics." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;slomatics.bandcamp.com&#x2F;album&#x2F;future-echo-returns">Future Echo Returns</a> by Slomatics</p> </figcaption> </figure> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-mwwb-slomatics&#x2F;mwwb.jpeg" alt="Cover art of Nachthexen by Mammoth Weed Wizard Bastard." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;mammothweedwizardbastard.bandcamp.com&#x2F;album&#x2F;nachthexen">Nachthexen</a> by Mammoth Weed Wizard Bastard</p> </figcaption> </figure> STM32 to Blinky with Rust – Part 3 – HAL and Blinky 2024-07-04T13:37:00+00:00 2024-07-04T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/stm32-blinky-rust-part-3-hal-blinky/ <p>In the <a href="/stm32-blinky-rust-part-1-hardware/">first part of this series</a>, we connected the dev board and installed the <code>probe-rs</code> tool. In <a href="/stm32-blinky-rust-part-2-debug/">part 2</a>, we created a basic project that compiled and built successfully, flashed it to the chip and watched it print “Hello, world!” over the debug interface back to the host computer. This time we’ll blink the LEDs on the STM32F407G-DISC1 Discovery board, starting with a single LED and finishing with all four in a pattern.</p> <p><img src="https://assets.mikecoats.xyz/stm32-blinky-rust-part-3-hal-blinky/header.jpeg" alt="A photograph of an STM32F4 dev board with the east and west LEDs illuminated." loading="lazy" decoding="async" /></p> <p>As before, you can follow along with the code snippets here, or see the completed single LED blinky project under <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/naive-blinky">the <code>naive-blinky</code> tag</a> in my repo. The first thing we need to do is add the STM32 HAL to our <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/naive-blinky/Cargo.toml">Cargo.toml</a> file. This provides all the interfaces we need to access the hardware features of our STM32F4. To specify the unique set of hardware that makes up the STM32F407, we opt in to the F4 runtime feature and the F407 feature.</p> <pre data-lang="toml" style="background-color:#fdf6e3;color:#657b83;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[</span><span style="color:#b58900;">dependencies</span><span>] </span><span style="color:#93a1a1;"># ... </span><span style="color:#268bd2;">hal </span><span>= { </span><span style="color:#268bd2;">package </span><span>= </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">stm32-hal2</span><span style="color:#839496;">&quot;</span><span>, </span><span style="color:#268bd2;">version </span><span>= </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">1.8.3</span><span style="color:#839496;">&quot;</span><span>, </span><span style="color:#268bd2;">features </span><span>= [</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">f407</span><span style="color:#839496;">&quot;</span><span>, </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">f4rt</span><span style="color:#839496;">&quot;</span><span>] } </span></code></pre> <p>In the <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/naive-blinky/src/main.rs">main.rs</a> file, we always need to import the <code>cortex_m</code> package in some fashion to ensure the linker can find a <code>critical-section</code> but, whereas we previously threw away the import, this time we want to pull out <code>Delay</code> so we can use it later to pause between each blink.</p> <pre data-lang="diff" style="background-color:#fdf6e3;color:#657b83;" class="language-diff "><code class="language-diff" data-lang="diff"><span style="color:#dc322f;">- use cortex_m as _; </span><span style="color:#859900;">+ use cortex_m::delay::Delay; </span></code></pre> <p>We import <code>Clocks</code> from our renamed <code>stm32-hal2stm32-blinky-rust-part-2-debug/</code> package which will be used later to configure the pause between blinks, and several parts of the <code>gpio</code> module to allow us to toggle the onboard LEDs.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#859900;">use </span><span>hal::{ </span><span> clocks::Clocks, </span><span> gpio::{Pin, PinMode, Port}, </span><span>}; </span></code></pre> <p>To work out which pins on the chip are connected to which LEDs on the board, we can refer to the <a rel="noopener" target="_blank" href="https://www.st.com/resource/en/user_manual/um1472-discovery-kit-with-stm32f407vg-mcu-stmicroelectronics.pdf">Discovery board’s user manual</a>.</p> <p><img src="https://assets.mikecoats.xyz/stm32-blinky-rust-part-3-hal-blinky/leds.jpeg" alt="A screenshot from the dev board’s user manual indicating the connections for the four LEDs." loading="lazy" decoding="async" /></p> <p>The orange LED, labelled LD3 on the board, is connected to PD13 on the chip, so we set it up as an output pin.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#268bd2;">let </span><span style="color:#586e75;">mut</span><span> north_led = Pin::new(Port::D, </span><span style="color:#6c71c4;">13</span><span>, PinMode::Output); </span></code></pre> <p>We then set up the MCU’s clock configuration, to be used later when we pause between blinks.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#268bd2;">let</span><span> clock_cfg = Clocks::default(); </span><span>clock_cfg.</span><span style="color:#859900;">setup</span><span>().</span><span style="color:#859900;">unwrap</span><span>(); </span><span style="color:#268bd2;">let </span><span style="color:#586e75;">mut</span><span> delay = Delay::new( </span><span> cortex_m::Peripherals::take().</span><span style="color:#859900;">unwrap</span><span>().</span><span style="color:#cb4b16;">SYST</span><span>, </span><span> clock_cfg.</span><span style="color:#859900;">systick</span><span>(), </span><span>); </span></code></pre> <p>Now we’ve prepared the LED’s pin for toggling on and off and a delay timer that can be used to pause between blinks, we can do just that. In a loop. Forever!</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#859900;">loop </span><span>{ </span><span> north_led.</span><span style="color:#859900;">toggle</span><span>(); </span><span> delay.</span><span style="color:#859900;">delay_ms</span><span>(</span><span style="color:#6c71c4;">1_000</span><span>); </span><span>} </span></code></pre> <p>At this point <code>cargo run</code> will be compile and flash the above code to the STM32, and one of the LEDs will flash slowly.</p> <p><video controls autoplay disablepictureinpicture loop playsinline src="https://assets.mikecoats.xyz/stm32-blinky-rust-part-3-hal-blinky/naive-blinky.webm" alt="The north, orange, LED blinks on and off slowly."></video></p> <p>That’s cool, but if you’re reading this blog post and have followed along so far, there’s a chance you’re a massive nerd, just like me. Given 4 LEDs, we could flash a pattern such as displaying the binary numbers from 0 to 15 but that would be too easy. Instead we can flash the Gray coded pattern of those numbers instead. The completed code for this stage can be found under <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/gray-blinky">the <code>gray-blinky</code> tag</a> in my repo.</p> <p>First we need to create an array with all 16 possible iterations of a 4-bit Gray code. We then need a similarly sized 4-bit bitmask to represent each LED.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#268bd2;">const </span><span style="color:#cb4b16;">GRAY</span><span>: [</span><span style="color:#268bd2;">u32</span><span>; </span><span style="color:#6c71c4;">16</span><span>] = [ </span><span> </span><span style="color:#6c71c4;">0b0000</span><span>, </span><span> </span><span style="color:#6c71c4;">0b0001</span><span>, </span><span style="color:#93a1a1;">// Least-significant bit 0 appears first </span><span> </span><span style="color:#6c71c4;">0b0011</span><span>, </span><span style="color:#93a1a1;">// LSb 1 appears second </span><span> </span><span style="color:#6c71c4;">0b0010</span><span>, </span><span> </span><span style="color:#6c71c4;">0b0110</span><span>, </span><span style="color:#93a1a1;">// LSb 2 appears third </span><span> </span><span style="color:#6c71c4;">0b0111</span><span>, </span><span> </span><span style="color:#6c71c4;">0b0101</span><span>, </span><span> </span><span style="color:#6c71c4;">0b0100</span><span>, </span><span> </span><span style="color:#6c71c4;">0b1100</span><span>, </span><span style="color:#93a1a1;">// LSb 3, or MSb, appears fourth </span><span> </span><span style="color:#6c71c4;">0b1101</span><span>, </span><span> </span><span style="color:#6c71c4;">0b1111</span><span>, </span><span> </span><span style="color:#6c71c4;">0b1110</span><span>, </span><span> </span><span style="color:#6c71c4;">0b1010</span><span>, </span><span> </span><span style="color:#6c71c4;">0b1011</span><span>, </span><span> </span><span style="color:#6c71c4;">0b1001</span><span>, </span><span> </span><span style="color:#6c71c4;">0b1000</span><span>, </span><span>]; </span><span> </span><span style="color:#268bd2;">const </span><span style="color:#cb4b16;">NORTH</span><span>: </span><span style="color:#268bd2;">u32 </span><span>= </span><span style="color:#6c71c4;">0b0001</span><span>; </span><span style="color:#268bd2;">const </span><span style="color:#cb4b16;">EAST</span><span>: </span><span style="color:#268bd2;">u32 </span><span>= </span><span style="color:#6c71c4;">0b0010</span><span>; </span><span style="color:#268bd2;">const </span><span style="color:#cb4b16;">SOUTH</span><span>: </span><span style="color:#268bd2;">u32 </span><span>= </span><span style="color:#6c71c4;">0b0100</span><span>; </span><span style="color:#268bd2;">const </span><span style="color:#cb4b16;">WEST</span><span>: </span><span style="color:#268bd2;">u32 </span><span>= </span><span style="color:#6c71c4;">0b1000</span><span>; </span></code></pre> <p>If we perform a <a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Bitwise_operation#AND">bitwise AND</a> between a position in the Gray array and one of those cardinal directions’ bitmasks, we’ll be left with a positive value indicating when that LED should be illuminated. We can then make the same test for the other three directions and we’ll know whether to light each of our LEDs on every cycle through the loop.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#268bd2;">let </span><span style="color:#586e75;">mut</span><span> counter: </span><span style="color:#268bd2;">usize </span><span>= </span><span style="color:#6c71c4;">0</span><span>; </span><span style="color:#859900;">loop </span><span>{ </span><span> </span><span style="color:#268bd2;">let</span><span> bits = </span><span style="color:#cb4b16;">GRAY</span><span>[counter]; </span><span> </span><span> </span><span style="color:#859900;">if</span><span> bits </span><span style="color:#859900;">&amp; </span><span style="color:#cb4b16;">NORTH </span><span>&gt; </span><span style="color:#6c71c4;">0 </span><span>{ </span><span> north_led.</span><span style="color:#859900;">set_high</span><span>(); </span><span> } </span><span style="color:#859900;">else </span><span>{ </span><span> north_led.</span><span style="color:#859900;">set_low</span><span>(); </span><span> } </span><span> </span><span style="color:#93a1a1;">// ... </span><span>} </span></code></pre> <p>At the end of every loop we need to increment our position in the Gray list, but we must not overflow the end of the array and instead jump back to the start.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">loop </span><span>{ </span><span> // ... </span><span> counter += 1; </span><span> if counter &gt; 15 { </span><span> counter = 0 </span><span> } </span><span>} </span></code></pre> <p>Compiling and flashing the board with these changes will make the LEDs on the board flash according to the current position in the Gray code array.</p> <p><video controls autoplay disablepictureinpicture loop playsinline src="https://assets.mikecoats.xyz/stm32-blinky-rust-part-3-hal-blinky/gray-blinky.webm" alt="The four LEDs blink on and off slowly, in the pattern of a 4-bit Gray code."></video></p> <p>There we go! STM32 to Blinky with Rust complete. We can now use this project as a worked example to base any other STM32 embedded Rust projects from and give them a hopefully head-ache free kickstart.</p> STM32 to Blinky with Rust – Part 2 – Development and Debug Toolchain 2024-06-30T13:37:00+00:00 2024-06-30T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/stm32-blinky-rust-part-2-debug/ <p>In the <a href="/stm32-blinky-rust-part-1-hardware/">first part of this series</a>, we connected the dev board and installed the <code>probe-rs</code> tool. This time we’ll create a basic project that prints “Hello, world!” over the debug interface. This should prove we have a valid compilation toolchain, can successfully flash our own firmware to a STM32F4 and then connect to it for debugging.</p> <p><img src="https://assets.mikecoats.xyz/stm32-blinky-rust-part-2-debug/header.jpeg" alt="A screenshot of a successful compilation and flashing of a rust based, “Hello, world!”, firmware." loading="lazy" decoding="async" /></p> <p>Since we’ll be building for the STM32F407, a Cortex-M4F chip, we need the correct rust target installed. This is done using the <code>rustup</code> command we installed in the previous post in the series.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> rustup target add thumbv7em-none-eabihf </span></code></pre> <p>There are a number of embedded Arm templates that can be used with the <code>cargo generate</code> tool, but the ones I tried were quite out of date and were not compatible with the RTT debug messages used by <code>probe-rs</code>. Instead, we’ll create our project from scratch, including only the bare minimum of files to get us up and running. If you feel ambitious, you can follow along with the snippets in this blog post, like a true child of the 80’s typing away at your Sinclair Spectrum. Alternatively, I’ve posted the completed code from this post to a repo on Codeberg, under the <code>debug</code> tag.</p> <p><a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/debug">https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/debug</a></p> <p>The repo can be cloned and the tag checked out by running these three commands.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> git clone git@codeberg.org:MikeCoats/stm32-to-blinky.git </span><span style="color:#b58900;">$</span><span> git fetch</span><span style="color:#268bd2;"> --all --tags </span><span style="color:#b58900;">$</span><span> git checkout tags/debug</span><span style="color:#268bd2;"> -b</span><span> debug-branch </span></code></pre> <p>To define the project you need a <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/debug/Cargo.toml">Cargo.toml</a> manifest file. The package details like name, author and version aren’t important, but the dependencies section and a binary target are needed to get a compiled and linked firmware built.</p> <pre data-lang="toml" style="background-color:#fdf6e3;color:#657b83;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[</span><span style="color:#b58900;">package</span><span>] </span><span style="color:#93a1a1;"># [...] </span><span> </span><span>[</span><span style="color:#b58900;">dependencies</span><span>] </span><span style="color:#268bd2;">cortex-m </span><span>= { </span><span style="color:#268bd2;">version </span><span>= </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">0.7.7</span><span style="color:#839496;">&quot;</span><span>, </span><span style="color:#268bd2;">features </span><span>= [</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">critical-section-single-core</span><span style="color:#839496;">&quot;</span><span>] } </span><span style="color:#268bd2;">cortex-m-rt </span><span>= </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">0.7.3</span><span style="color:#839496;">&quot; </span><span style="color:#268bd2;">panic-rtt-target </span><span>= </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">0.1.3</span><span style="color:#839496;">&quot; </span><span style="color:#268bd2;">rtt-target </span><span>= </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">0.5.0</span><span style="color:#839496;">&quot; </span><span> </span><span>[[</span><span style="color:#b58900;">bin</span><span>]] </span><span style="color:#268bd2;">name </span><span>= </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">stm32-to-blinky</span><span style="color:#839496;">&quot; </span><span style="color:#268bd2;">test </span><span>= </span><span style="color:#b58900;">false </span><span style="color:#268bd2;">bench </span><span>= </span><span style="color:#b58900;">false </span></code></pre> <p>At the time of writing, these packages were up to date and the correct features had been enabled. I’ve found some older documentation and blog posts that worked with the then-current packages, but newer versions have both added and deprecated features that prevent compilation and linking.</p> <p>Next up, we need a <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/debug/memory.x">memory map</a> for our chip. Our ST Discovery board has an STM32F407VGT6 so, <a rel="noopener" target="_blank" href="https://www.st.com/resource/en/datasheet/stm32f407vg.pdf">referring to its datasheet</a>, we know it has 1M of flash starting at 0x0800_0000 and 128K of RAM starting at 0x2000_0000.</p> <p><img src="https://assets.mikecoats.xyz/stm32-blinky-rust-part-2-debug/map.jpeg" alt="The memory map for a STM32F407VGT6. The SRAM blocks are clearly visible but the Flash section is hidden in a wall of text, so has been highlighted." loading="lazy" decoding="async" /></p> <pre style="background-color:#fdf6e3;color:#657b83;"><code><span>MEMORY </span><span>{ </span><span> FLASH : ORIGIN = 0x08000000, LENGTH = 1024K </span><span> RAM : ORIGIN = 0x20000000, LENGTH = 128K </span><span>} </span></code></pre> <p>We need a <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/debug/build.rs">build script</a> to accompany our memory map to ensure the map is used by the linker, placing the appropriate sections of our binary at the correct locations. Your build script could get more complicated over time, but this is enough to get us going for now. All we need to build “Hello, world!” is to include the linker script from cortex-m-<code>rt</code> in the compiler’s link arguments.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span style="color:#268bd2;">fn </span><span style="color:#b58900;">main</span><span>() { </span><span> </span><span style="color:#859900;">println!</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">cargo:rustc-link-arg=-Tlink.x</span><span style="color:#839496;">&quot;</span><span>); </span><span>} </span></code></pre> <p>Rust will compile and link for the host system’s architecture by default, such as <code>x86_64</code> or <code>aarch64</code>. We must, then, supply <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/debug/.cargo/config.toml">a config file</a> to tell it to build for our STM32F4’s Cortex-M4F <code>thumbv7em</code> architecture instead. We can also tell cargo to allow its run command to flash and debug our chip with <code>probe-rs</code> by setting up the target’s runner.</p> <pre data-lang="toml" style="background-color:#fdf6e3;color:#657b83;" class="language-toml "><code class="language-toml" data-lang="toml"><span>[</span><span style="color:#b58900;">build</span><span>] </span><span style="color:#268bd2;">target </span><span>= </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">thumbv7em-none-eabihf</span><span style="color:#839496;">&quot; </span><span> </span><span>[</span><span style="color:#b58900;">target</span><span>.</span><span style="color:#b58900;">thumbv7em-none-eabihf</span><span>] </span><span style="color:#268bd2;">runner </span><span>= </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">probe-rs run --chip STM32F407VGTx</span><span style="color:#839496;">&quot; </span></code></pre> <p>With all the project configuration out of the way, we can finally write <a rel="noopener" target="_blank" href="https://codeberg.org/MikeCoats/stm32-to-blinky/src/tag/debug/src/main.rs">our “Hello, world!” program</a>. There are some important lines in the code to point out here.</p> <p>One line 1, we’re telling rust to not use the standard library, which is important as without an OS running on the chip there won’t be a standard library available. Line 2 tells rust not to emit a “main” entrypoint, which is only useful when operating systems try to launch your program. Instead, on line 9, we indicate where our entrypoint is. This way we tell the linker which function needs to be placed at the matching location in the STM32’s memory.</p> <pre data-lang="rust" style="background-color:#fdf6e3;color:#657b83;" class="language-rust "><code class="language-rust" data-lang="rust"><span>#![</span><span style="color:#268bd2;">no_std</span><span>] </span><span>#![</span><span style="color:#268bd2;">no_main</span><span>] </span><span> </span><span style="color:#859900;">use</span><span> cortex_m </span><span style="color:#859900;">as _</span><span>; </span><span style="color:#859900;">use </span><span>cortex_m_rt::entry; </span><span style="color:#859900;">use </span><span>rtt_target::{rtt_init_print, rprintln}; </span><span style="color:#859900;">use</span><span> panic_rtt_target </span><span style="color:#859900;">as _</span><span>; </span><span> </span><span>#[</span><span style="color:#268bd2;">entry</span><span>] </span><span style="color:#268bd2;">fn </span><span style="color:#b58900;">main</span><span>() -&gt; </span><span style="color:#859900;">! </span><span>{ </span><span> </span><span style="color:#859900;">rtt_init_print!</span><span>(); </span><span> </span><span style="color:#859900;">rprintln!</span><span>(</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">Hello, world!</span><span style="color:#839496;">&quot;</span><span>); </span><span> </span><span> </span><span style="color:#859900;">loop </span><span>{} </span><span>} </span></code></pre> <p>The <code>rtt</code> libraries need a “critical section” to be linked or they will, in turn, fail to link. The <code>cortex_m</code> package is included on line 4 so that its critical section implementation is linked but, as we don’t directly use any of its contents, we can discard the result of the import.</p> <p>The final two lines of interest are 10 and 14. The method signature <code>-&gt; !</code> indicates that the function never returns and the infinite <code>loop {}</code> ensures that it will never do so.</p> <p>With all the pieces in place we can now build our binary. Running <code>cargo build</code> will download and compile all of our dependencies, along with our code, and output our program formatted for Cortex-M4F chips, linked to match our STM32F4’s memory map.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> cargo build </span><span style="color:#b58900;">[...] </span><span> </span><span style="color:#b58900;">Compiling</span><span> cortex-m v0.7.7 </span><span style="color:#b58900;">[...] </span><span> </span><span style="color:#b58900;">Compiling</span><span> cortex-m-rt v0.7.3 </span><span style="color:#b58900;">[...] </span><span> </span><span style="color:#b58900;">Compiling</span><span> rtt-target v0.5.0 </span><span style="color:#b58900;">[...] </span><span> </span><span style="color:#b58900;">Compiling</span><span> stm32-to-blinky v0.1.0 (/home/mike/projects/stm32-to-blinky) </span><span> </span><span style="color:#b58900;">Compiling</span><span> panic-rtt-target v0.1.3 </span><span style="color:#b58900;">[...] </span><span> </span><span style="color:#b58900;">Finished </span><span>`</span><span style="color:#b58900;">dev</span><span>` profile </span><span style="color:#859900;">[</span><span>unoptimized + debuginfo</span><span style="color:#859900;">]</span><span> target(s) </span><span style="color:#b58900;">in</span><span> 12.47s </span></code></pre> <p>If at this point you get a linker error instead of success, there’s a chance you’re missing some of the binary tools needed to manipulate compiled code for other target architectures. Adding the <code>llvm-tools</code> component gives us versions of the tools which support all of the architectures that rust itself supports.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> rustup component add llvm-tools </span></code></pre> <p>Now that we’ve successfully built the project, we can deploy it to our bare-metal chip. The previously configured <code>cargo run</code> command will flash the program to the STM32F407VGT6 and attach the debugger, ready to receive output from the chip. Finally, “Hello, world!” appears on our console.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> cargo run </span><span> </span><span style="color:#b58900;">Finished </span><span>`</span><span style="color:#b58900;">dev</span><span>` profile </span><span style="color:#859900;">[</span><span>unoptimized + debuginfo</span><span style="color:#859900;">]</span><span> target(s) </span><span style="color:#b58900;">in</span><span> 0.03s </span><span> </span><span style="color:#b58900;">Running </span><span>`</span><span style="color:#b58900;">probe-rs</span><span> run</span><span style="color:#268bd2;"> --chip</span><span> STM32F407VGTx target/thumbv7em-none-eabihf/debug/stm32-to-blinky` </span><span> </span><span style="color:#b58900;">Erasing</span><span> ✔ </span><span style="color:#859900;">[</span><span>00:00:01</span><span style="color:#859900;">] [</span><span>##########################</span><span style="color:#859900;">]</span><span> 16.00 KiB/16.00 KiB @ 12.20 KiB/s (eta 0s ) </span><span> </span><span style="color:#b58900;">Programming</span><span> ✔ </span><span style="color:#859900;">[</span><span>00:00:06</span><span style="color:#859900;">] [</span><span>###########################</span><span style="color:#859900;">]</span><span> 16.00 KiB/16.00 KiB @ 2.51 KiB/s (eta 0s ) </span><span> </span><span style="color:#b58900;">Finished</span><span> in 7.904s </span><span style="color:#b58900;">Hello,</span><span> world! </span></code></pre> <p>Done! Part 2 is complete! Building on part 1, we’ve now got a toolchain installed, the hardware connected, a project built, the binary flashed to the chip and a message returned via the debugger. In part 3 we need to access the GPIO pins on the STM32F407VGT6 and blink the attached LEDs on the STM32F407G-DISC1 Discovery board.</p> #NewMusicMonday – Cheap Dirty Horse, The Masochists and a (dis)honourable mention 2024-06-24T13:37:00+00:00 2024-06-24T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/newmusicmonday-cheapdirtyhorse-themasochists/ <p>This week’s <a href="/categories/new-music-monday/">#NewMusicMonday</a> is all about punk, politics and protest in the UK. In the UK we’re gearing up for our next General Election, where it looks like our incumbent blue coloured right-wing political party is going to be ousted by our red coloured right-leaning political party; <em>plus ça change…</em> Leaders and representatives of both parties have spent the last few weeks <a rel="noopener" target="_blank" href="https://web.archive.org/web/20240621203412/https://www.theguardian.com/politics/live/2024/jun/21/uk-general-election-live-updates-tories-labour-bets-gambling-rules-rishi-sunak">telling Scotland what the UK intends to do</a> with us, rather than how they can actually work with <a rel="noopener" target="_blank" href="https://web.archive.org/web/20240618152702/https://www.thenational.scot/news/24395650.scotland-not-country-region-uk-tory-adviser-tells-bbc/">the devolved nations</a>.</p> <p><a rel="noopener" target="_blank" href="https://cheapdirtyhorseband.bandcamp.com/">Cheap Dirty Horse</a> have to be on this list with their collection of recent tracks skewering late-stage capitalism and promoting LGBTQ+ issues, since both parties have spent much of the last 6 months tying themselves in knots over these. <a rel="noopener" target="_blank" href="https://themasochistspunk.bandcamp.com/">The Masochists</a> released their EP including The TV News over a year ago, but it still fits the TV’s over-saturation with political party nonsense that we’re currently experiencing.</p> <p>A sneaky little (dis)honourable mention this week goes out to <a rel="noopener" target="_blank" href="https://kuntandthegang.bandcamp.com/">Kunt and the Gang</a> for their 40 seconds long ode to Rishi Sunak. Who knew his name had the same cadence as the Tellytubbies theme?!</p> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-cheapdirtyhorse-themasochists&#x2F;cheap-dirty-horse.jpg" alt="Cover art of Cheap Dirty Horse Raise Funds to Go On Tour by Cheap Dirty Horse." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;cheapdirtyhorseband.bandcamp.com&#x2F;album&#x2F;cheap-dirty-horse-raise-funds-to-go-on-tour">Cheap Dirty Horse Raise Funds to Go On Tour</a> by Cheap Dirty Horse</p> </figcaption> </figure> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-cheapdirtyhorse-themasochists&#x2F;the-masochists.jpg" alt="Cover art of The TV News EP by The Masochists." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;themasochistspunk.bandcamp.com&#x2F;album&#x2F;the-tv-news-ep">The TV News EP</a> by The Masochists</p> </figcaption> </figure> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-cheapdirtyhorse-themasochists&#x2F;rishi-sunak.jpg" alt="Cover art of Rishi Sunak (RFC) by Kunt and the Gang." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;kuntandthegang.bandcamp.com&#x2F;track&#x2F;rishi-sunak-rfc">Rishi Sunak (RFC)</a> by Kunt and the Gang</p> </figcaption> </figure> STM32 to Blinky with Rust – Part 1 – Hardware – STM32F407G-DISC1 2024-06-20T13:37:00+00:00 2024-06-20T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/stm32-blinky-rust-part-1-hardware/ <p>It’s been more than a decade since I last <a href="/intruder-alert/">played with or programmed a microcontroller</a> in anger, and wow, how the world’s moved on! The Arduino and its ATmega328P AVR is no longer king and now you can get <a rel="noopener" target="_blank" href="https://www.st.com/en/evaluation-tools/stm32g0316-disco.html">an Arm Cortex-M0+ complete with a re-usable USB programmer</a> for less than £10. For around £20 you can buy a development board with <a rel="noopener" target="_blank" href="https://www.st.com/en/evaluation-tools/stm32f4discovery.html">an Arm Cortex-M4F and some peripherals</a> including an accelerometer, microphone, DAC, and USB OTG port.</p> <p><img src="https://assets.mikecoats.xyz/stm32-blinky-rust-part-1-hardware/header.jpeg" alt="A close up of an STM32F407G-DISC1 developer circuit board, centred on the ARM MCU and 4 user controllable LEDs." loading="lazy" decoding="async" /></p> <p>Programming languages, compilers and toolchains have moved on too; C and Assembly aren’t the only games in town for low level microcontroller coding anymore. Rust has gained widespread support for many platforms and architectures and offers a memory safe way to run code on the bare-metal processor.</p> <p>This series of blog posts will document my attempts to get a rust toolchain installed, compiling code for Arm Cortex M processors and deploying a small program on an STM32.</p> <p>The first thing we need to do is install the basic rust toolset. There are probably some secure, supported, packages for Debian that you can <code>apt</code> install, but the last time I looked they were 2 years out of date. <em>caveat emptor</em>, running random shell scripts downloaded from websites is potentially dangerous; here be dragons.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> curl</span><span style="color:#268bd2;"> --proto </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">=https</span><span style="color:#839496;">&#39;</span><span style="color:#268bd2;"> --tlsv1</span><span>.2</span><span style="color:#268bd2;"> -sSf</span><span> https://sh.rustup.rs </span><span style="color:#859900;">| </span><span style="color:#b58900;">sh </span></code></pre> <p>The installer will update your shell’s configuration, setting up rust and its tools ready for your next login. Until that time, you can manually update your shell’s environment by ‘sourcing’ rust’s package manager, <code>cargo</code>‘s, script.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> . </span><span style="color:#d33682;">~</span><span>/.cargo/env </span></code></pre> <p>With a proper rust environment set up, we now need to install the programmer/debugger tools. <code>probe-rs</code> integrates with the standard <code>cargo</code> tool to push compiled code to the chip and run it with a debugger. Installing it is similar to rust’s installation method, so the same warnings apply.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> curl</span><span style="color:#268bd2;"> --proto </span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">=https</span><span style="color:#839496;">&#39;</span><span style="color:#268bd2;"> --tlsv1</span><span>.2</span><span style="color:#268bd2;"> -LsSf</span><span> https://github.com/probe-rs/probe-rs/releases/latest/download/probe-rs-tools-installer.sh </span><span style="color:#859900;">| </span><span style="color:#b58900;">sh </span></code></pre> <p>Only the root user will have access to the USB programmer by default. To let regular users connect to the ST-LINK programmer we need add some udev rules.</p> <p>First we need to find the USB ID for the ST-LINK, so we know which device to apply the rule to. There’s a good chance that the ID is 0483:374b, since that seems to be the same across most ST-LINKs I’ve seen, but it’s always safer to check first.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> lsusb </span><span style="color:#859900;">| </span><span style="color:#b58900;">grep</span><span> ST-LINK </span><span> </span><span style="color:#b58900;">Bus</span><span> 004 Device 002: ID 0483:374b STMicroelectronics ST-LINK/V2.1 </span></code></pre> <p>To let local, logged-in, users connect to the ST-LINK programmer we need add a <code>uaccess</code> tag to it with a udev rule. Remote, SSH, users will need to be added to the <code>plugdev</code> group and then we can use the udev rule to apply that group and the <code>660</code> mode to the device.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo usermod</span><span style="color:#268bd2;"> -a -G</span><span> plugdev </span><span style="color:#859900;">$</span><span style="color:#268bd2;">USER </span><span> </span><span style="color:#b58900;">$</span><span> exec sg plugdev newgrp </span><span> </span><span style="color:#b58900;">$</span><span> sudo vim /etc/udev/rules.d/70-usb-stlink.rules </span><span> </span><span style="color:#268bd2;">ATTRS{idVendor}</span><span>=</span><span style="color:#2aa198;">=</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">0483</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">, </span><span style="color:#268bd2;">ATTRS{idProduct}</span><span>=</span><span style="color:#2aa198;">=</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">374b</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">, </span><span style="color:#268bd2;">MODE</span><span>=</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">660</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">, </span><span style="color:#268bd2;">GROUP</span><span>=</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">plugdev</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">, </span><span style="color:#268bd2;">TAG</span><span>+=</span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">uaccess</span><span style="color:#839496;">&quot; </span></code></pre> <p>That rule and it’s clauses will be picked up on the next reboot, but we can force udev to read and apply it to any devices already connected with udevadm.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> sudo udevadm control</span><span style="color:#268bd2;"> --reload </span><span> </span><span style="color:#b58900;">$</span><span> sudo udevadm trigger </span></code></pre> <p>Running <code>probe-rs</code>‘ <code>list</code> command will tell us which ST-LINKs have been detected, and its <code>info</code> command will then interrogate the chip its connected to about its configuration. If both succeed, we’ve probably got all of the permissions lined up to allow our user to program the chip.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> probe-rs list </span><span> </span><span style="color:#b58900;">The</span><span> following debug probes were found: </span><span style="color:#b58900;">[0]:</span><span> STLink V2-1</span><span style="color:#859900;"> --</span><span> 0483:374b:066CFF575377524867052744 (ST-LINK) </span><span> </span><span style="color:#b58900;">$</span><span> probe-rs info </span><span> </span><span style="color:#b58900;">Probing</span><span> target via JTAG </span><span> </span><span style="color:#b58900;">ARM</span><span> Chip with debug port Default: </span><span style="color:#b58900;">[…] </span><span> </span><span style="color:#b58900;">│</span><span> ├── PARTNO: Cortex-M4 </span><span style="color:#b58900;">[…] </span><span style="color:#b58900;">Probing</span><span> target via SWD </span><span> </span><span style="color:#b58900;">ARM</span><span> Chip with debug port Default: </span><span style="color:#b58900;">[…] </span><span> </span><span style="color:#b58900;">│</span><span> ├── PARTNO: Cortex-M4 </span><span style="color:#b58900;">[…] </span></code></pre> <p>That’s it for part 1. We’ve got the toolchain installed, connected the debugger and confirmed we can see the ARM chip ready for programming. Next we need to <a href="/stm32-blinky-rust-part-2-debug/">spin up a small project and send it</a> to the programmer.</p> #NewMusicMonday – Alkanes, Broadsea & PaperSailor 2024-06-17T13:37:00+00:00 2024-06-17T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/newmusicmonday-alkanes-broadsea-papersailor/ <p>I’ve been “off” the birdsite for a little over a year now, and one of my most missed trends from there is <a href="/categories/new-music-monday/">#NewMusicMonday</a>. I loved discovering smaller, less well known, bands and musicians. In an effort to “be the change [I] want to see”, I’m going to do my best to share my own choices with the fediverse every week, and see if I can’t find some likeminded folks.</p> <p>For my first <a href="/categories/new-music-monday/">#NewMusicMonday</a>, I’m going to start with something local and close to my heart. <a rel="noopener" target="_blank" href="https://alkanes.bandcamp.com/">Alkanes</a> are a stalwart of the Inverness music scene, gigging in town and across the country for the best part of a decade. They’ve recently <a rel="noopener" target="_blank" href="https://www.facebook.com/Alkanesband/posts/pfbid024Ar8f8ujpcKdAb44zHfbnGcvttR2mtwpPasBwYoENCzS4QGgwvEtX8vK8neas6qZl">announced that they’re bringing things to an end</a>, and have lined up <a rel="noopener" target="_blank" href="https://www.seetickets.com/tour/alkanes-farewell-show">a farewell show</a>.</p> <p>At that gig they’re going to be supported by Aberdeen’s <a rel="noopener" target="_blank" href="https://broadseaofficial.bandcamp.com/">Broadsea</a> and <a rel="noopener" target="_blank" href="https://papersailor.bandcamp.com/">PaperSailor</a> from Glasgow, so this week’s <a href="/categories/new-music-monday/">#NewMusicMonday</a> playlist is going to be each band’s latest release, available from their respective Bandcamp pages.</p> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-alkanes-broadsea-papersailor&#x2F;broadsea.jpg" alt="Cover art of Anoesis by Broadsea." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;broadseaofficial.bandcamp.com&#x2F;album&#x2F;anoesis">Anoesis</a> by Broadsea</p> </figcaption> </figure> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-alkanes-broadsea-papersailor&#x2F;papersailor.jpg" alt="Cover art of Pictures of Black Holes by PaperSailor." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;papersailor.bandcamp.com&#x2F;album&#x2F;pictures-of-black-holes">Pictures of Black Holes</a> by PaperSailor</p> </figcaption> </figure> <figure> <img src="https:&#x2F;&#x2F;assets.mikecoats.xyz&#x2F;newmusicmonday-alkanes-broadsea-papersailor&#x2F;alkanes.jpg" alt="Cover art of This Is It by Alkanes." loading="lazy" decoding="async"> <figcaption> <p><a href="https:&#x2F;&#x2F;alkanes.bandcamp.com&#x2F;album&#x2F;this-is-it">This Is It</a> by Alkanes</p> </figcaption> </figure> Data loss: A blind-spot in my backup strategy 2024-04-01T13:37:00+00:00 2024-04-01T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/a-blind-spot-in-my-backup-strategy/ <p>Last week I suffered a catastrophic disk failure, leaving my 1TB NVMe drive completely unreadable. Luckily I have backups, I thought.</p> <blockquote> <p>Only wimps use tape backup: _real_ men just upload their important stuff on ftp, and let the rest of the world mirror it 😉</p> <p>Linus Torvalds</p> </blockquote> <p>I don’t, in general, backup my git projects. All of my personal projects are open source, so I always push my code up to an online repository. I don’t feel like paying to backup a couple of dozen python <code>.venv/</code> and JavaScript <code>node_modules/</code> directories full of other people’s code, so I exclude my projects directory from my backup plan.</p> <p>I’m a little bit of a perfectionist and I don’t like publishing something in advance of announcing and publicising it. Therefore, I tend not to create a project for something on an online repository until v1.00 is ready to deploy. I still commit everything to a local git repository so I can roll back breaking changes, try out multiple different approaches, etc. I just wait until I’m completely finished before I push it out to the world.</p> <p>These two choices came to bite me last week. I had three projects under development; one was about 50% there, another was about 90% done and one was 100% finished except for the blog post to announce it. All three were in local git repositories saved to my 1TB NVMe drive. All three had yet to be pushed to an online repository. All three were excluded from my backup plan. All three were lost when the drive failed.</p> <p>I felt sick.</p> <p>Although I’m an IT Manager now, I come from a background of agile software development. Its practice of Radical Honesty, that should be found in trusting teams and retrospectives, is why I’ve written this post. I similarly believe in Throwaway Prototyping and that its use can be one of the best ways to prove an idea works or is worthy of further development. Even still, I can’t bring myself to work on those three projects again, yet.</p> <p>Instead, I’ve revised my backup plan, decided to get over my aversion to “private” projects on online repositories, and started another project to take the sting off things. I still won’t be publishing publicly until I’m ready to publicise it, but at least it’s saved in three different places now!</p> Stacking Loyalty Schemes for Extra Discounts 2024-03-10T13:37:00+00:00 2024-03-10T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/stacking-loyalty-schemes-for-extra-discounts/ <p>This post is very “<a rel="noopener" target="_blank" href="https://www.thedailymash.co.uk/news/media/the-16-things-you-need-to-own-to-be-a-guardian-reader-20190628187014">Guardian Reader</a>” of me, and for that I apologise in advance.</p> <p>One morning I found myself absent mindedly reading the back of my yogurt pot whilst eating my breakfast. This yogurt was made by <a rel="noopener" target="_blank" href="https://www.yeovalley.co.uk/">Yeo Valley Organic</a> and was advertising some kind of loyalty scheme called “<a rel="noopener" target="_blank" href="https://www.yeovalley.co.uk/yeokens/handy-yeokens-guide">Yeokens</a>”. I’d never really thought about being “loyal” to a yogurt brand before, so I checked out their website to see what you could redeem a lifetime’s worth of yogurt eating points towards. There are branded tote bags, an annual pass to visit <a rel="noopener" target="_blank" href="https://www.yeovalley.co.uk/the-organic-garden">their 6 1/2 acre organic garden</a> and – probably the most useful – discount vouchers for other shops.</p> <p>The discount voucher that caught my eye was an offer to save 20% at <a rel="noopener" target="_blank" href="https://rapanuiclothing.com/">Rapanui</a>, a clothing manufacturer who appear to be trying to “<a rel="noopener" target="_blank" href="https://rapanuiclothing.com/journey/">do the right thing</a>”. They focus on organic cotton, promoting cotton as a vegan alternative to wool for knitted items and offer items made from recycled cotton too.</p> <p><img src="https://assets.mikecoats.xyz/stacking-loyalty-schemes-for-extra-discounts/website.jpeg" alt="A screenshot of the Yeo Valley website showing a 20% off Rapanui voucher offer." loading="lazy" decoding="async" /></p> <p>Yeo Valley will charge you 20 Yeokens to get your 20% Rapanui discount, but you don’t have to purchase 20 Yeokens worth of yogurt to build up your balance. You get a 10 Yeoken bonus for simply activating your account and they will give you another 10 Yeokens for banking your first code.</p> <p><img src="https://assets.mikecoats.xyz/stacking-loyalty-schemes-for-extra-discounts/email.jpeg" alt="A screenshot of the welcome email from the Yeokens programme." loading="lazy" decoding="async" /></p> <p>The availability of a partner discount isn’t really worth a blog post on it’s own and would otherwise sound like an advert for a yogurt company, but I’ve discovered an interesting second aspect to improve the deal. Tesco periodically offer Yeo Valley Organic yogurts on their “Clubcard Price” scheme, which means that you can get a pot of yogurt for 50p less than normal.</p> <p><img src="https://assets.mikecoats.xyz/stacking-loyalty-schemes-for-extra-discounts/tesco.jpeg" alt="A screenshot from Tesco showing a pot of yogurt for £1.50 instead of it’s normal £2.00." loading="lazy" decoding="async" /></p> <p>So, is it worth it? Well, it’s only a deal if you were going to buy it anyway. If you are going to spend at least £7.50 at Rapanui then, yes, you should probably buy some yogurt first!</p> What is Gregg Wallace doing? 2024-02-06T13:37:00+00:00 2024-02-06T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/what-is-gregg-wallace-doing/ <p>I think I’ve trumped my previously silliest web app to date with my new one, “What is Gregg Wallace doing?”</p> <p><a rel="noopener" target="_blank" href="https://what-is-gregg-wallace-doing.mikecoats.xyz">what-is-gregg-wallace-doing.mikecoats.xyz</a></p> <p><img src="https://assets.mikecoats.xyz/what-is-gregg-wallace-doing/demo.jpeg" alt="A screenshot of the “What is Gregg Wallace doing?” Web app. At the time of the screenshot it is 22:24 so Gregg is sleeping." loading="lazy" decoding="async" /></p> <p>Based on his <a rel="noopener" target="_blank" href="https://www.telegraph.co.uk/tv/2024/02/03/my-saturday-gregg-wallace-autistic-son-weight-loss-alcohol/">published schedule in the Torygraph</a> which was then ridiculed on social media until <a rel="noopener" target="_blank" href="https://www.theguardian.com/tv-and-radio/2024/feb/06/its-alan-partridge-meets-gwyneth-paltrow-how-gregg-wallace-became-the-ultimate-lifestyle-guru">the Grauniad reported on it</a>, this web app will tell you exactly what Gregg Wallace is doing at any moment in time. Marvelous!</p> The Greg or Ian Calendar 2024-01-11T13:37:00+00:00 2024-01-11T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/the-greg-or-ian-calendar/ <p>This week I published probably my silliest web app to date, The Greg or Ian Calendar.</p> <p><a rel="noopener" target="_blank" href="https://gregorian.mikecoats.xyz">gregorian.mikecoats.xyz</a></p> <p><img src="https://assets.mikecoats.xyz/the-greg-or-ian-calendar/web.png" alt="A screenshot of the Greg or Ian Calendar web app. In the screenshot, it&#39;s Thursday 11th January 2024, making it an &#39;Ian&#39; day." loading="lazy" decoding="async" /></p> <p>Based on the joke that&#39;s endemic across social media, it’s a web app that will tell you whether today is a ‘Greg’ day or if it’s an ‘Ian’ day.</p> <blockquote> <ul> <li>Monday - Greg</li> <li>Tuesday - Ian</li> <li>Wednesday - Greg</li> <li>Thursday - Ian</li> <li>Friday - Greg</li> <li>Saturday - Ian</li> <li>Sunday - Greg</li> </ul> <p><em>The Gregorian calendar.</em></p> </blockquote> <p>To really run the joke in to the ground, I took it one step further and added an <a rel="noopener" target="_blank" href="https://gregorian.mikecoats.xyz/feed.ics">iCal feed</a> that you can subscribe to from within your desktop or mobile Calendar app. Never again be unsure of whether it’s an ‘Ian’ or ‘Greg day again!</p> <p><img src="https://assets.mikecoats.xyz/the-greg-or-ian-calendar/thunderbird.png" alt="A screenshot of Thunderbird on Debian Linux, subscribed to the Greg or Ian iCal feed. The period between Wednesday, 10th January 2024 and Saturday, 13th January 2024 is visible." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/the-greg-or-ian-calendar/ios.png" alt="A screenshot of Calendar on Apple iOS, subscribed to the Greg or Ian iCal feed. Thursday, 11th January 2024 is visible." loading="lazy" decoding="async" /></p> <p>If you’re that way inclined, the source code is <a rel="noopener" target="_blank" href="https://github.com/mikecoats/gregorian">up on my GitHub</a>. Criticism, bug reports and pull requests are all very welcome!</p> Bringing an Apple AirTag Back to Life 2023-11-27T13:37:00+00:00 2023-11-27T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/bringing-an-apple-airtag-back-to-life/ <p>If you’re lazy like me, you’ll probably have a few gadgets laying about the house that have flat batteries. Some of those widgets might have been needing those batteries replaced for more than six months. If one of them is an Apple AirTag, it might not be as simple as just popping in a fresh CR2032 and closing it back up. There’s a chance that your iPhone still won’t recognise or talk to your AirTag at all, even with a new cell inserted.</p> <p><img src="https://assets.mikecoats.xyz/bringing-an-apple-airtag-back-to-life/airtag-pieces.jpeg" alt="Two Apple AirTags, one in pieces with its button cell removed" loading="lazy" decoding="async" /></p> <p>If you naively thought that removing the AirTag from your Apple ID would help reset things, you’d be wrong and possibly in an even worse state than before. If your iPhone cannot connect to your AirTag, even after you’ve inserted a fresh battery, the command to delete the AirTag’s on-board configuration will not go through. You’ll end up in a no-man’s land where your Apple ID &amp; iPhone will deny all knowledge of the AirTag, while the AirTag still thinks it’s ‘owned’ by your Apple ID but it just can’t connect.</p> <p>AirTags that have been without power for a decent amount of time need to be totally reset. This can be accomplished by completing the steps in the following knowledge base article, <a rel="noopener" target="_blank" href="https://support.apple.com/en-us/102577">https://support.apple.com/en-us/102577</a>.</p> <p>Despite what Apple says in their support article, it might take more than five insert and remove cycles for the AirTag to reset. It seems that depending on how long you take, it might forget its position and restart the count. My advice is just to keep repeating the process until you hear a “different” sound and don’t bother counting. In my experience, it takes 6-8 cycles to reset my AirTags.</p> <p><img src="https://assets.mikecoats.xyz/bringing-an-apple-airtag-back-to-life/airtag-pair.jpeg" alt="Two Apple AirTags in keyring cases" loading="lazy" decoding="async" /></p> <p>Once you hear that “different” sound, you’ve successfully reset the AirTag. You can now close the battery lid and re-associate it with your Apple ID by bringing it close to your iPhone and completing the automatic pairing process.</p> <p>P.S. The reason this article is so keyword heavy is so that I can game my own SEO and find this post again in two years time, when I inevitably have to do exactly the same thing.</p> SQLite Adventure 2023-11-23T13:37:00+00:00 2023-11-23T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/sqlite-adventure/ <p>Nearly a decade ago, before the site formerly known as Twitter went to total ratshit, <a rel="noopener" target="_blank" href="https://mastodon.social/@Edent">@edent</a> <a rel="noopener" target="_blank" href="https://shkspr.mobi/blog/2015/01/writing-a-choose-your-own-adventure-story-on-twitter/">published</a> a <a rel="noopener" target="_blank" href="https://twitter.com/wnd_go">choose-your-own-adventure game</a> where Twitter was the game engine.</p> <p>This was perfect <a rel="noopener" target="_blank" href="https://xkcd.com/356/">nerd-sniping</a> bait for my team at work. How far could you bend a common tool and use it to build a game? How small could a game engine be?</p> <p>In response, my colleagues and I set about writing a game engine that could fit within in a single tweet. My attempt from 2015 is lost to the sands of time, now that I’m one employer and several laptops distant, but I was recently inspired to go back and give it another go. All I can remember from my first attempt was that I cheated (slightly) by storing all of the game data within a SQLite database, allowing me to build my game engine with a simple set of <code>select</code> statements.</p> <p>Starting fresh, I was able to build a reasonable experience using <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/sqlite-adventure/blob/main/adventure.ddl">3 tables for game data</a> and <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/sqlite-adventure/blob/main/page">3 <code>select</code> statements within a bash script</a> as the game engine. The tables are arrange as in this diagram, centred around a page entry with multiple lines of descriptions and multiple choices hanging off each.</p> <p><img src="https://assets.mikecoats.xyz/sqlite-adventure/db-diagram.jpeg" alt="A database diagram illustrating the three tables used in sqlite-adventure - page, description &amp; choice." loading="lazy" decoding="async" /></p> <p>I’ve uploaded my code so far to my <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/sqlite-adventure">sqlite-adventure</a> repo. Clone the repo to a *nix-ish environment and run the <code>./build.sh</code> script to create the database and tables and fill them with entries. The game can then be played by running the <code>./page &lt;number&gt;</code> command and following along with the (as yet unwritten) story.</p> <p><img src="https://assets.mikecoats.xyz/sqlite-adventure/screenshot-page-1.png" alt="A screenshot of the result of running the command <code>./page 1</code>" loading="lazy" decoding="async" /></p> <p>It’s at this point, that the code-golf began. The full-fat, unfettered, version of the script comes in at an unreasonably large 335 characters. That’s practically a novel according to Twitter’s old limits. My first step was to <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/sqlite-adventure/commit/d94b583fe5eacbf49683cfaa91f7a5768aa3b459#diff-f28c9ba065aecf3a444447f6056d6e1fb6ba8953b28b78f5ff54525994f6d4be">rename all tables and columns to single characters</a> and to <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/sqlite-adventure/commit/d94b583fe5eacbf49683cfaa91f7a5768aa3b459#diff-148de9c5a7a44d19e56cd9ae1a554bf67847afb0c58f6e12fa29ac7ddfca9940">remove some of the nicer formatting</a> in the command’s output. These two steps allowed me to reduce the file down to 122 characters.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#93a1a1;">#!/bin/bash </span><span> </span><span style="color:#b58900;">sqlite3</span><span> a </span><span style="color:#859900;">&lt;&lt;&lt; </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">select t from p where p = </span><span style="color:#859900;">$</span><span style="color:#268bd2;">1</span><span style="color:#2aa198;">; select c from d where p = </span><span style="color:#859900;">$</span><span style="color:#268bd2;">1</span><span style="color:#2aa198;">; select t, c from c where f = </span><span style="color:#859900;">$</span><span style="color:#268bd2;">1</span><span style="color:#2aa198;">;</span><span style="color:#839496;">&quot; </span></code></pre> <p>That’s small enough to fit within a tweet, but not small enough to satisfy my code-golf urges. There’s still multiple occurrences of relatively long words like “select” and “where” which could be replaced by some single character variables for a naïve form of dictionary compression.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#93a1a1;">#!/bin/bash </span><span style="color:#268bd2;">s</span><span>=</span><span style="color:#2aa198;">select;f=from;w=where </span><span style="color:#b58900;">sqlite3</span><span> a&lt;&lt;&lt;</span><span style="color:#839496;">&quot;</span><span style="color:#859900;">$</span><span style="color:#268bd2;">s</span><span style="color:#2aa198;"> t </span><span style="color:#859900;">$</span><span style="color:#268bd2;">f</span><span style="color:#2aa198;"> p </span><span style="color:#859900;">$</span><span style="color:#268bd2;">w</span><span style="color:#2aa198;"> p=</span><span style="color:#859900;">$</span><span style="color:#268bd2;">1</span><span style="color:#2aa198;">;</span><span style="color:#859900;">$</span><span style="color:#268bd2;">s</span><span style="color:#2aa198;"> c </span><span style="color:#859900;">$</span><span style="color:#268bd2;">f</span><span style="color:#2aa198;"> d </span><span style="color:#859900;">$</span><span style="color:#268bd2;">w</span><span style="color:#2aa198;"> p=</span><span style="color:#859900;">$</span><span style="color:#268bd2;">1</span><span style="color:#2aa198;">;</span><span style="color:#859900;">$</span><span style="color:#268bd2;">s</span><span style="color:#2aa198;"> t,c </span><span style="color:#859900;">$</span><span style="color:#268bd2;">f</span><span style="color:#2aa198;"> c </span><span style="color:#859900;">$</span><span style="color:#268bd2;">w</span><span style="color:#2aa198;"> f=</span><span style="color:#859900;">$</span><span style="color:#268bd2;">1</span><span style="color:#2aa198;">;</span><span style="color:#839496;">&quot; </span></code></pre> <p>With <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/sqlite-adventure/commit/efe27c6ae5a83465e737059c733be03d1e679e39#diff-148de9c5a7a44d19e56cd9ae1a554bf67847afb0c58f6e12fa29ac7ddfca9940">all three occurrences of “select”, “from”, and “where” replaced with single character variables and all unnecessary whitespace removed</a> my “game engine” is now just 106 characters long. The user experience isn’t as nice as the pre-shrunk version, but it’s still passable.</p> <p><img src="https://assets.mikecoats.xyz/sqlite-adventure/screenshot-p-1.png" alt="A screenshot of the result of running the command <code>./p 1</code>" loading="lazy" decoding="async" /></p> <p>There are a couple of places the engine could be further minimised, but I’m happy to leave it here for now.</p> Bringing a VanMoof X3 or S3 Back to Life – Part 2 – Battery Pack 2023-10-13T13:37:00+00:00 2023-10-13T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/vanmoof-back-to-life-part-2-battery/ <p>In <a href="/vanmoof-back-to-life-part-1/">Part 1 of this series</a>, I extracted and deconstructed the Smart Cartridge from my partner’s VanMoof X3 with the goal of charging the small, secondary, battery inside it. That was successful and I managed to charge the small Lithium-Poly cell, bringing the bike back to life. The VanMoof supplied power brick would still not charge the bike’s primary battery and I suspected that some or all of the individual cells have discharged to the point that the safety circuits will not allow a charging voltage to be applied.</p> <p>My plan for this post is to lay out the steps and tools required to remove the primary battery pack from the bike, deconstruct it to the level of individual cells, charge them manually and rebuild the battery and the bike.</p> <p>In <a href="/vanmoof-back-to-life-part-1/">part 1</a> some of the steps were detailed in VanMoof’s technical documents, but unfortunately everything we’re doing in this post is entirely unsupported. These steps have been reverse engineered by tearing down our bike so, caveat lector, they may vary from unit to unit. Luckily, removing the battery pack from the bike is easier than removing the Smart Cartridge and only involves removing three security torx bolts from the bottom of the downtube. The only tricky bolt is the one behind the chainring. Accessing it can be achieved by unclipping the soft plastic chain guard, then gently bending it out of the way.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/easy-bolts.jpg" alt="Down tube end cap, showing two easily accessible bolts." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/hidden-bolt.jpg" alt="The one tricky, partially hidden, bolt." loading="lazy" decoding="async" /></p> <p>Once all three bolts have been undone, the protective cap can be removed. This will reveal the end of the battery pack, complete with a swing-out removal handle. Pop out the handle with a finger tip and you’ll be able to pull the battery pack free with a firm tug. Be careful not to damage the cable you find in the same area; it’s connected to the cadence sensor and without it the bike will be unable to provide pedal assist.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/end-handle.jpg" alt="End cap with swing-out handle." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/end-connector.jpg" alt="End cap with power connector." loading="lazy" decoding="async" /></p> <p>With the battery pack free, it can be placed on the workbench for further break-down. Remove the 5 cross head screws from each end of the battery pack and use an <a rel="noopener" target="_blank" href="https://www.ifixit.com/products/ifixit-opening-tool">iFixit Opening Tool</a>, or similar, to pop the caps off. The cap with the swing-out handle can be completed removed from the battery pack, but the cap with the power connector is wired internally to the circuit and cells within.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/end-handle-removed.jpg" alt="Unconnected end cap removed." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/end-connector-removed.jpg" alt="Connected end cap loosened." loading="lazy" decoding="async" /></p> <p>Having removed the cap with the handle, press firmly on the inside of the battery pack that is now visible. The whole assembly of cells and circuitry should slide out of the other end of the casing tube.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/power-connector-in.jpg" alt="Power connections within casing tube." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/power-connector-out.jpg" alt="Power connections freed from casing tube." loading="lazy" decoding="async" /></p> <p>Like many battery packs assembled from individual cells this whole thing has been spot welded together and then wrapped in glass fiber reinforced tape to provide physical and electrical protection.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/pre-unwrap.jpg" alt="The extracted battery pack of cells." loading="lazy" decoding="async" /></p> <p>Carefully unwrapping the tape reveals the pack’s make-up, including the safety precautions the manufacturer has added to extend the life of the bike and to prevent it from causing house-fires.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/post-unwrap.jpg" alt="The pack of cells with the binding tape removed." loading="lazy" decoding="async" /></p> <p>There are two temperature sensors embedded in the battery pack, each 1/3 of the way along its length. These will allow the charging circuit to detect any uneven charging in the pack and shutdown before a thermal run-away can cause damage or start a fire.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/temp-1.jpg" alt="Temperature sensor." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/temp-2.jpg" alt="Temperature sensor." loading="lazy" decoding="async" /></p> <p>There are also voltage sense wires connected every 1/10 of the pack’s length at each group of four cells. These allow sub-voltages to be measured both before and during charging, preventing power being applied to an overly discharged pack and keeping the pack balanced.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/charge-wire.jpeg" alt="Battery charge/discharge and voltage sense wires." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/sense-wire.jpeg" alt="Voltage sense wires." loading="lazy" decoding="async" /></p> <p>Reverse engineering the pack allows us to build the following crude schematic. We can see that it will not be possible to charge individual cells, but each group of 4 could be charged as a smaller sub-battery.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/diagram.jpeg" alt="A reverse engineered circuit diagram of the battery pack." loading="lazy" decoding="async" /></p> <p>To determine what constant current and constant voltage values we need to set on our power supply, we first need to determine what cells we’re dealing with. Searching the web for the partial product code “R18650MJ1” reveals that this is most likely an LG INR18650 MJ1 Lithium-Ion cell.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/cell.jpeg" alt="A close-up of a cell in the battery pack." loading="lazy" decoding="async" /></p> <p>Consulting the LG datasheet suggests that single cells can be charged at 1.7A constant current followed by 4.2V constant voltage. We’ll be charging 4 cells at a time, in parallel sub-batteries, so we’ll need to use about 6.8A constant current. Since we’re unsure how safe each cell is, I decided to reduce the charge rate slightly to 6.4A total constant current, to give me a little overhead in case anything started to go wrong.</p> <p><a href="https://mikecoats.com/vanmoof-back-to-life-part-2-battery/LG-INR18650-MJ1.pdf">LG-INR18650-MJ1</a><a href="https://mikecoats.com/vanmoof-back-to-life-part-2-battery/LG-INR18650-MJ1.pdf">Download</a></p> <p>For safety’s sake, I de-soldered the positive end of the battery pack and the adjacent sense wire to break the circuit and ensure there would be no path for current to flow during the sub-battery charging. The positive terminal has quite a substantial amount of solder bonding it to the wire, so a fairly robust iron is needed to free the wire and clean the pad without heating up the surrounding cells too much. My <a rel="noopener" target="_blank" href="https://pine64.org/devices/pinecil/">Pinecil v2</a> was just about up to the job, but if you had to repair multiple batteries, I’d suggest picking up something a bit beefier.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/positive.jpeg" alt="A very large solder blob on the positive terminal of the battery pack." loading="lazy" decoding="async" /></p> <p>Crocodile clips can be connected across every sub-battery using the sense wire solder tags. This is easier if you de-solder these sense wires as it will leave you with more of the tag to clip on to. We can then charge the pack as if we just had 10 cells that each needed 6.4A and 4.2V.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/power-cc.jpeg" alt="Power supply in CC mode, supplying 6.400A." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/power-cv.jpeg" alt="Power supply in CV mode, supplying 4.20V." loading="lazy" decoding="async" /></p> <p>Once all of the cells have been charged, the sense wires and the main positive wire can be re-soldered, and the pack can then be taped back up. As you solder the main positive wire back on, there’s a chance of spark, so be aware soldering this connection around flammable fumes or gases.</p> <p>The layout of the pieces of tape indicate the double duty it is being put to. The whole battery pack is wrapped for structural support, and the solder connections are wrapped to prevent the wires from rubbing through and shorting out. Finding a suitable tape proved tough, but eventually I managed to track down the <a rel="noopener" target="_blank" href="https://www.amazon.co.uk/dp/B07K34YN8F">GTIWUNG Filament Strapping Tape on Amazon</a>, that has a similar arrangement of embedded glass fibers to the original tape. Be careful to use no more tape than strictly necessary as squeezing the battery pack back inside the casing tube is a tight fit.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/post-wrap.jpg" alt="A re-wrapped pack of cells." loading="lazy" decoding="async" /></p> <p>Once you’ve reassembled the battery pack, all that’s left is to re-install the unit in the bike’s down tube and plug in the charging lead to see if you’ve brought the bike back to life. If you’re unsure which way round to insert the battery pack, shining a torch inside the down tube will reveal the orientation of the counterpart connector.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/orientation.jpeg" alt="The battery&#39;s counterpart connector at the far end of the down tube." loading="lazy" decoding="async" /></p> <p>If you’ve successfully brought the bike’s primary battery back to life, you should be able to observe the charging brick turn from green to red to indicate charging has started and it should stay red to indicate that it is continuing to charge.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/charge-brick.jpeg" alt="Charging brick indicating charging." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-2-battery/charge-display.jpeg" alt="Matrix display indicating charging." loading="lazy" decoding="async" /></p> <p>The matrix display on the bike’s top tube should also indicate a successful charging cycle has begun by pulsing a lightning bolt above the battery level indicator.</p> <h2 id="">Comments</h2> <blockquote> <p class="byline">lincoln fowler on 2024-05-08 wrote:</p> <p>mike- we have an A5. both small and main batteries were dead. i brought back the BMS by using a USB charger to pins 1 and 4 of the debug port. i am now thinking about manually charging the main battery. another poster (youtube: ebikerepairs) suggested an S5 could be charged with 35v at 0.5a to give it just enough juice to allow the main charge to recognize the pack and take over. does that sound right to you? i’m not willing to tear down the battery pack.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-05-08 wrote:</p> <p>Hi Lincoln, That should work but I’ve no idea how long you should apply that voltage and current for and it’s not something I’d recommend without being extremely careful. It’s pretty much how the VanMoof charges itself once it’s in the CV section of the charge curve, except it’s got voltage monitoring every 1&#x2F;10th of the pack and temperature monitoring every 1&#x2F;3rd of the pack to protect the cells. If any of the cells have discharged themselves to a dangerous level there’s a chance you could get a thermal runaway that could lead to a battery fire. Realistically, as long as your power supply really can supply 35V with a hard limit of 0.5A then I doubt 17.5W would build up sufficient heat quickly enough to cause too much mischief. If you have an IR Thermometer, I’d at least give it a wave over the pack while it’s charging to make sure it doesn’t get away from you. Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">Tom Stewart on 2024-03-11 wrote:</p> <p>Could you possibly have a go at mine, if I ship it to you? Thank you Tom Stewart</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-03-11 wrote:</p> <p>Hi Tom, I’m sorry, but no. I’m more than happy to take on the risk of charging a Lithium-Ion battery pack for myself, when I know the history of the pack. Rebuilding someone else’s pack and shipping it internationally is a risk beyond my comfort level. With the right equipment, particularly the power supply in the post, it’s really not too difficult to do it yourself. I’m happy to answer any questions you have, either here or by email, if you need any assistance. If you do still want someone else to take a look at it, I’d ask around the folks on Reddit. There’s got to be someone closer to you (or at least on the same continent) that will be able to help. Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">Koichi on 2024-04-02 wrote:</p> <p>I’m trying to remove the battery, but I can’t get good access to the screws hidden in the chain cover. Could you please tell me a little more about how you accessed it?</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-04-03 wrote:</p> <p>Hi Koichi, One of the best tools I’ve found for getting access to bolts in hard to reach places is the Wera Zyklop Mini 1. It’s a tiny ratcheting wrench that can get in to places that hex keys or bit drivers can’t quite reach. My only other advice is to remove as much of the chain guard as you can. The only part that can’t be easily removed is attached to the bottom bracket but it’s quite flexible and with a bit of work and persuasion can be made to sit out of the way. In the second photo in the post you can see how I’ve wedged the chain guard on the other side of the chainring to give me access. Cheers, Mike.</p> </blockquote> <blockquote> <p class="byline">Stuart on 2024-04-03 wrote:</p> <p>Thanks for making this website, i have made a VanMoof s3 work again by charging the little single cell in the smart controller. Very helpful and well documented site.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-04-03 wrote:</p> <p>Hi Stuart, Thanks for your kind message. I’m glad you found these posts useful. Cheers, Mike.</p> </blockquote> <blockquote> <p class="byline">Rabi on 2024-04-04 wrote:</p> <p>The problem wasn’t the 4045A or the VMS Chip on the main board? Did you check continuity on the fuse? I’m currently experiencing a flashing skull that the smart controller shows flashing on and off. Before I get into fixing this whole thing, it’s probably going to take some time.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-04-04 wrote:</p> <p>No, I was lucky enough that my problem was just overly discharged cells. Since rebuilding and recharging everything I’ve not had any of the same problems come back. I’ve managed to keep the pack charged since then but I have had the dreaded Err44 pop up recently.</p> </blockquote> <blockquote> <p class="byline">Rabi on 2024-04-04 wrote:</p> <p>Err44, ah man. https:&#x2F;&#x2F;www.reddit.com&#x2F;r&#x2F;vanmoofbicycle&#x2F;comments&#x2F;169tfra&#x2F;swap_your_broken_vanmoof_eshifter_for_a_3dprinted&#x2F; This might be worth checking out. You might be able to 3D print a new gear shifter. As for my charging issue, I guess it’s going to take some attempts for me. I hope my issue is similar to yours, since I really don’t want to have to replace the fuse. I’m an EE but it’s been a long time since I’ve done circuit soldering.</p> </blockquote> <blockquote> <p class="byline">Stuart on 2024-04-08 wrote:</p> <p>Have got the bike out of shipping? mode now with the lock code, unfortunately i am now getting error 20 message. Does anyone have any ideas?</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-04-08 wrote:</p> <p>Hi Stuart, Yes, once I got my bike out of shipping mode, I also found further battery problems. All of this second post is how I overcame those issues. Without manually refreshing all of the cells within the primary battery, I couldn’t get it to charge at all. Since I’ve done the refresh, I’ve had no further problems charging the bike. Cheers, Mike.</p> </blockquote> <blockquote> <p class="byline">stuart on 2024-04-08 wrote:</p> <p>Also charger stays with a green light when plugged in.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-04-08 wrote:</p> <p>That definitely sounds like the controller’s not happy with the primary battery.</p> </blockquote> <blockquote> <p class="byline">Andrew Kraut on 2024-06-01 wrote:</p> <p>Mike, were you able to figure out a rough pinout for the connector on the battery? The specifics I’m trying to suss out are what each of the 4 large&#x2F;high-current pins are for.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-06-01 wrote:</p> <p>I’m afraid I never beeped them out or traced the wires, sorry. I always just assumed one pair was for connecting the charger to the battery and the other pair was for the connection between the battery and the motor. I’ll have a dig through my photo roll to see if I have any extra pictures from when I had the pack disassembled.</p> </blockquote> <blockquote> <p class="byline">Carlos on 2024-07-12 wrote:</p> <p>I have some cells under 1 volt. Would you recommend trying to bring them back, or is it not worth the risk? Thank you so much!</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-07-17 wrote:</p> <p>Are all the cells below 1 volt, or just some of them? If they’re uniformly low, then you might be able to charge the pack, just very carefully. Figure 2 in this article implies you can use a 0.1C charge current to pre-condition the battery until you get it up to 3 volts. https:&#x2F;&#x2F;www.digikey.com&#x2F;en&#x2F;articles&#x2F;a-designer-guide-fast-lithium-ion-battery-charging If you have only a few cells, or 4-packs of cells, that are that low, then they’ve discharged themselves unusually in comparison the whole pack. That’s indicative of something more serious with the cells and I’d be looking to identify and replace any that are damaged. That’s way beyond my skills though.</p> </blockquote> <blockquote> <p class="byline">javier on 2024-09-09 wrote:</p> <p>Hello. Great guide! thanks! I have a problem, maybe somebody can help me: I have a X3,very few km (450km). I have left it unused for a year and when I want to use it again it does nothing. I disassemble the smart cartridge and change the Li-Po battery. It comes back to life, it connects to the app but now it tells me error 19 and the battery in the app flashes. I understand that it is the main battery so I remove it, and with the source I take it to 34v. When I insert it back, it detects it and starts charging, I leave it all night and it charges to 100% and it does not give me ANY ERROR. I think everything has been solved but now when I want to start it, the lock appears, and the 5 second countdown, and when I move forward, it does nothing, and it locks when it reaches 5. No matter what I do, I cannot unlock it, it is as if the engine does not start. Does anyone know what it could be? Also, I am surprised that the speaker volume is super high. Thanks.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-09-09 wrote:</p> <p>Hi Javier, that sounds like your bike’s rear wheel is locked with its kick-lock. That’s the “button” you can find on the rear wheel that interfaces with the castellated ring attached to the brake disc. Normally you unlock it with the app or using the three-digit button press code on the handlebars. Once unlocked, you need to move the bike forward enough for it to detect rotation of the wheels. As long as you spin both wheels within the countdown, it should stay unlocked. I’ve no advice for problem solving the kick-lock, unfortunately, it’s always just “worked” on our bike.</p> </blockquote> <blockquote> <p class="byline">Wouter on 2024-10-19 wrote:</p> <p>Hi Mike, Thanks for sharing all this information, much appreciated! Then a question, which charger (brand&#x2F;model) for the single cells do you recommend? Br, Wouter</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-10-28 wrote:</p> <p>Hi Wouter, I used the same power supply to charge these cells as I did to charge the cell in the Smart Cartridge in my other blog post. It’s a “NANKADF” brand 30V 10A unit, available from Amazon. I’m sure a proper “branded” power supply would be better or more reliable but this one worked well for me. https:&#x2F;&#x2F;www.amazon.co.uk&#x2F;gp&#x2F;aw&#x2F;d&#x2F;B09C8LWV9W&#x2F; Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">Victor on 2024-11-02 wrote:</p> <p>Hi Mike, During one of the repairs I came across a battery that was deeply discharged. The cells were just above 0.3V per group. I brought them back to 30V very slowly by 50mA, using also a lab supply. May I ask how discharged your cells were? I’m kind of hesitant to charge these deeply discharged cells back to 42 volts. What do you think? Thanks in advance! Victor</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-11-03 wrote:</p> <p>Hi Victor, None of my cells had gotten that low, I think all of mine were still about 1v. Was the whole pack uniformly discharged, or was it just one section that was as low as 0.3V? If the whole pack was at the same level, and it all seemed to charge back up to 30v without any thermal problems, I’d keep going to 42v. It sounds like you’ve followed the “pre-conditioning” processes detailed in this paper, so you should be fine to step up the current and do a normal CC&#x2F;CV process up to final voltage. https:&#x2F;&#x2F;www.digikey.com&#x2F;en&#x2F;articles&#x2F;a-designer-guide-fast-lithium-ion-battery-charging Obviously, keep an eye on the temperatures just in case any of the cells are damaged. Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">Carlos on 2024-12-13 wrote:</p> <p>Hello Mike, I&#x27;ve noticed you have commented that there&#x27;s a e-shifter simulation circuit to overcome the error 44 on Vanmoof bikes. Can you give me more information on it? Is it commercially available as a drop-in replacement somewhere? Thanks!</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-12-13 wrote:</p> <p>Hi Carlos, I&#x27;m afraid I&#x27;ve no more information at this stage. I&#x27;ve only recently picked up this work again, so I&#x27;ve not made much progress yet. I&#x27;ve started reverse engineering the e-shifter and I&#x27;ll hopefully have a rough prototype ready in a couple of weeks. Getting something ready for wider use will take a while longer. Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">Austin on 2024-12-18 wrote:</p> <p>Hi Lincoln&#x2F;Mike, I have a VanMoof A5 with a dead battery. Were you ever able to figure out how to charge the battery? Thank you! Austin</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-12-21 wrote:</p> <p>Hi Austin, I&#x27;m afraid I&#x27;m not going to be much help. I&#x27;ve never even seen an A5 in person, let alone taken one to pieces. If you can work out how to remove and dismantle the battery, charging it will be broadly similar my experience here with an X3. Give the folks on Reddit a shout. They&#x27;re normally pretty helpful folks. Cheers, Mike</p> </blockquote> Bringing a VanMoof X3 or S3 Back to Life – Part 1 – Smart Cartridge 2023-09-08T13:37:00+00:00 2023-09-08T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/vanmoof-back-to-life-part-1/ <p>The VanMoof X3 was a perfect city bike for my partner but, after being stashed over winter, the battery had gone flat and it refused to take a charge. When this happens it’s possible that the bike’s charging circuits are detecting an “overly” discharged battery and are trying to protect the Lithium cells from damage. VanMoof’s bikes are technically clever but many of the components are proprietary and options for replacing or repairing parts are pretty thin on the ground.</p> <p>In this <a rel="noopener" target="_blank" href="https://support.vanmoof.com/en/support/solutions/articles/44002140542-my-bike-is-not-turning-on-or-not-waking-up">technical support document</a>, VanMoof let slip that there is a small secondary battery responsible for the electronics in addition to the primary battery used for driving the motor. I had a theory that perhaps this battery had gone flat which was in turn preventing the electronics in the Smart Cartridge from kickstarting the charging circuit for the main battery.</p> <p>In this post I will lay out the steps and tools required to extract and deconstruct the Smart Cartridge from an X3 and recharge it’s small internal battery. These instructions, while not identical, will be understandable for owners of a VanMoof S3 not just the VanMoof X3 we own.</p> <p>Before you start, follow the first half of <a rel="noopener" target="_blank" href="https://support.vanmoof.com/en/support/solutions/articles/44002092311-how-to-replace-the-vanmoof-s3-x3-s-smart-cartridge">this document from VanMoof</a> to remove the Smart Cartridge from your bike. The video below is taken from that page. If you’ve removed all of the screws shown below but the Smart Cartridge still doesn’t want to slide out, remember there is one screw behind the seat tube that’s very easy to overlook.</p> <p>Once you’ve got the Smart Cartridge free from the bike frame, you can take it over to your workbench for dismantling.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/smart-cartridge.jpeg" alt="A Smart Cartridge from a VanMoof X3." loading="lazy" decoding="async" /></p> <p>The real bulk of the Smart Cartridge is forward of the seat tube, so we first need to remove the extension that holds the rear light. A small flat-bladed screw driver can be used to turn the two plastic screws by 90 degrees, freeing the housing that hides the rear light’s power connector. The rear light can now be unplugged and removed. The rest of the seat tube extension can then be removed using a T6 torx screwdriver. There are two screws holding the top and bottom of the extension together and another two that hold the extension to the Smart Cartridge.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/rear-light-assembly.jpeg" alt="The rear-light assembly from a VanMoof X3&#39;s Smart Cartridge." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/rear-light-disassembled.jpeg" alt="A partially disassembled rear-light assembly from a VanMoof X3&#39;s Smart Cartridge." loading="lazy" decoding="async" /></p> <p>Now the Smart Cartridge has been stripped down to just the “brain”, you can open it up by removing the 8 T6 torx screws around its perimeter. When you separate the top from the bottom be careful as there are two sets of quite fine cables connecting the halves.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/cartridge-circuit.jpeg" alt="An opened VanMoof X3 Smart Cartridge showing it&#39;s circuit board and internal battery." loading="lazy" decoding="async" /></p> <p>The main circuit board is connected to the dot-matrix display via the ribbon cable. This can be disconnected to give you more room to work, but is not necessary as long as you are careful not to damage the cable. The small Lithium-Poly cell is connected by the bundle of red, yellow and black wires. It can be removed from the top case by releasing the four clips. Unplug the connector from the main circuit board and you can remove the cell from the Smart Cartridge.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/battery-installed.jpeg" alt="The internal battery of a VanMoof X3 Smart Cartridge in-situ." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/battery-removed.jpeg" alt="The internal battery of a VanMoof X3 Smart Cartridge removed for inspection." loading="lazy" decoding="async" /></p> <p>Carefully pull back the piece of foam and you’ll reveal the cell and you can read off its details. We will need this information to find a datasheet that can tell us how to charge the cell safely.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/battery-spec.jpeg" alt="The internal battery of a VanMoof X3 Smart Cartridge with the protective foam removed revealing the battery&#39;s specification." loading="lazy" decoding="async" /></p> <p>Searching the web for “iGreen Tech” didn’t give me any decent leads. There appear to be a number of companies around the world with that name, none of whom seem particularly relevant. Searching for “iGreen Lithium” does find <a rel="noopener" target="_blank" href="https://www.igreenlithium.com/">one company</a> that might be close, but it still didn’t get me any closer to a datasheet.</p> <p>Instead, I tried a brute force search for all of the numbers on the cell, “922543 1050 3.7 3.89”. That was much more useful. It appears that 922543 is some sort of standard reference number for this kind of cell as many different “manufacturers” have sold 3.7V 1050mAh Li-Po cells with this code. I say “manufacturers” as it appears that a company called <a rel="noopener" target="_blank" href="https://bakth2020.en.made-in-china.com/product/DOwxQfBTgJVv/China-Rechargeable-Lithium-Polymer-Customized-3-7V-1050mAh-Lithium-Battery.html">Shenzen BAK Technology will white-label their cell with your brand</a> if you buy enough of them.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/stock-battery.jpeg" alt="Stock photography of a Shenzen BAK battery which matches the specification of a VanMoof X3 Smart Cartridge&#39;s internal battery." loading="lazy" decoding="async" /></p> <p>Further digging reveals that the <a rel="noopener" target="_blank" href="https://www.worldwayelec.com/pro/cornell-dubilier-electronics-inc/lp-922543-1s-3/9890388">Dubilier 922543 is compatible with the BAK 922543</a>, so the datasheets should be similarly compatible. Finally, with all that in hand, I managed to track down a Dubilier datasheet for their 922543 Li-Po cell which matches our 3.7V 1050mAh specifications.</p> <p><a href="https://mikecoats.com/vanmoof-back-to-life-part-1/922543.pdf">922543.pdf</a><a href="https://mikecoats.com/vanmoof-back-to-life-part-1/922543.pdf">Download</a></p> <p>With that datasheet available, it’s time to figure how to use the information contained within to charge the cell. This video from Dave Jones of EEVblog goes over all of the details on how to charge Lithium-Ion and Lithium-Polymer cells.</p> <p>Having consumed all the theory behind lithium charging another video, again from EEVblog’s Dave Jones, has practical advice on charging Li-Ion and Li-Po cells with a bench-top power supply.</p> <p>I have a NANKADF 30V 10A programmable supply, so following those videos combined with the datasheet, I set the power supply to 0.53A CC and 4.20V CV mode and connected the cell. At this point there is a danger that the cell may have been damaged by the excessive discharge and recharging it this way could result in a battery fire. I monitored the power and temperatures over the recommended 3.5 hours charging period and observed nothing untoward, indicating that the cell was good to put back in to operation.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/power-cc.jpeg" alt="Power supply in CC mode, supplying 0.530A." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/power-cv.jpeg" alt="Power supply in CV mode, supplying 4.20V." loading="lazy" decoding="async" /></p> <p>Now the small Lithium-Poly cell is charged, reverse the tear-down process and rebuild the Smart Cartridge. If everything’s gone to plan the Smart Cartridge should spring back to life and indicate that it’s in “Shipping Mode”. There’s a good chance your bike decided to enter this mode when it originally detected the battery draining to try to prevent the batteries from getting “dangerously” low.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/shipping.jpeg" alt="A repaired Smart Cartridge from a VanMoof X3 indicating it is in Shipping Mode." loading="lazy" decoding="async" /></p> <p>To get the Smart Cartridge out of shipping mode, follow the 3 step instructions in <a rel="noopener" target="_blank" href="https://support.vanmoof.com/en/support/solutions/articles/44002140542-my-bike-is-not-turning-on-or-not-waking-up">this VanMoof support document</a>. That is, after re-inserting the Smart Cartridge in to the bike’s top tube, plug in your charging lead, press and hold the bike’s power button for two seconds, then short press any of the bikes handlebar or power buttons.</p> <p>If an “unsafely” discharged Smart Cartridge was your only problem, your bike should reboot and begin to charge the primary battery in the bike’s down tube. If, however, your primary battery was similarly discharged to an “unsafe” level you’ll see the status light on the charging brick pulse red as it tries to start charging, but quickly return to green when the charge fails.</p> <p>In <a href="/vanmoof-back-to-life-part-2-battery/">Part 2 of this series</a>, I detail the steps required to extract, dismantle and refresh the main battery pack.</p> <h2 id="">Update — 2023-11-13</h2> <p>When I wrote this post, I didn’t want to include anything about how I physically connected my power supply to the battery as it was a bit of an off-the-cuff hack. However, I’ve since received a couple of comments asking from more information.</p> <p>I just snipped the legs of a spare LED, bent them 90°, then inserted them in to the positive and negative places. The square profile of the LED legs meant they wouldn’t spin once connected, removing the risk of them shorting against each other.</p> <p>I didn’t connect the centre pin as I didn’t have any facility for monitoring or logging that data. Since I was already using a CC/CV process and monitored the temperature of the cell during charging, I didn’t feel it was worth the extra effort.</p> <p><img src="https://assets.mikecoats.xyz/vanmoof-back-to-life-part-1/connectors.jpeg" alt="A close up of the crocodile clips from a power supply connecting" loading="lazy" decoding="async" /></p> <p>I was worried that the “random” size of the legs might have subtly damaged the spring terminals within the plug, so I didn’t want to recommend that as a solution more widely. It appears that those batteries use standard Molex PicoBlade connectors which have an aperture of at least 0.5-0.7mm, so a standard LED leg of 0.5mm should fit just fine without any worry.</p> <h2 id="-1">Comments</h2> <blockquote> <p class="byline">David Ellington on 2023-11-13 wrote:</p> <p>Hi Mike, great blog, I found it really informative and helpful. One question though – when you connected to the Lab power supply, how did you connect it – through the fitted plug? or did you bare the red and black wires and connect crocodile clips to them?</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2023-11-13 wrote:</p> <p>Hi David, Thanks for your feedback and question. I’ve included an update at the end of the post to answer your question. Luckily, I still had a photo in my archives to provide some more detail too. Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">Elwyn Davies on 2023-11-13 wrote:</p> <p>Hi. Could you tell me which wires you connected to the charger please. I assume it was the positive and negative, and did not connect the third wire?</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-11-13 wrote:</p> <p>Hi Elwyn, I’ve updated the end of the post to address your question. I only connected the positive and negative terminals, leaving the centre pin unconnected. Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">David Ellington on 2023-11-14 wrote:</p> <p>Awesome, thanks – based om the information on your blog I have successfully recharged the battery in my smart cartridge. Unfortunately I’m still getting an error (ERR 6) so I guess I’m now going to move onto your Part 2.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2023-11-14 wrote:</p> <p>Congratulations and commiserations! Good luck with the battery pack. It’s no more involved than the smart cartridge; it’s just a bit bigger so it takes a good bit longer to charge. Unfortunately, I’m now dealing with the dreaded Err44. If I had a spare&#x2F;working e-shifter I’d be inclined to run a datalogger on the wires and try to reverse engineer the communications between it and the smart cartridge to see what I could come up with. Unfortunately, they’re like hen’s teeth.</p> </blockquote> <blockquote> <p class="byline">Chet on 2024-02-22 wrote:</p> <p>Hi Mike excellent write-up. It has given me the confidence to attempt repair. I got a similar bench power supply as in the picture. When charging, it started with cc-mode at 4.2v &amp; 0.53A. How do I change to cv-mode as shown in the other picture? Thank you</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-02-23 wrote:</p> <p>Hi Chet, Thanks for your kind words, I’m glad you found this useful. To answer your question, the power supply takes care of the switch-over from CC mode to CV mode automatically. When you set the voltage and current values you’re in effect setting the maximum values the power supply will drive. When the cell is flat the voltage will be low, so the current draw will be pinned against your 0.53A current limit which puts you in constant-current mode. Once the cell reaches 4.2V the voltage limit has been hit so then it’s in constant-voltage mode. Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">Jon Bonesteel on 2024-07-25 wrote:</p> <p>Hello Mike, These two posts have been very informative and helpful. I have disassembled my smart cartridge and it’s clear that the lipo battery is bulging and damaged. I’d like to replace it, but have not been able to find a source in the US. I only see the two wire variants on Amazon for example. Could you provide a tip on where I may be able to find one? Thanks in advance.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-07-26 wrote:</p> <p>Hi Jon, It’s good to hear you found the posts useful. I’ve had a dig about and you’re right – US stockists of that battery are hard to come by. I found my datasheet on Farnell’s website, who are a part of Avnet, but it appears that Newark&#x2F;Avnet don’t stock the same items. I couldn’t find anything from the usual suspects, either – Digikey, etc. It does look like something similar is available on AliExpress, which might be worth a try? https:&#x2F;&#x2F;www.aliexpress.com&#x2F;item&#x2F;1005006129387074.html In another listing the same company says, “If you are not sure whether the battery is suitable for your model, you can contact our customer service. Tell us your battery parameter model and size, we can customize the battery you need for you, thank you.” They might be able to put one together for you using the dimensions and values from the datasheet? Apart from that, I’m out of answers. Hopefully you get it sorted, good luck! Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">Jon Bonesteel on 2024-08-07 wrote:</p> <p>Thank you for the reply Mike. I was able to locate the proper battery on eBay, charged it correctly, and sadly no luck. Installed the battery in the smart cartridge and it flashes the skull icon, then turns off. A reset of the system results in the same outcome. If I plug in the charger while the skull is flashing that icon flash turns off, but will not present the charge icon. I suspect the smart cartridge is damaged in some other way. Unless you have any insight on this, I believe I will just need to part out the bike. I am glad this has worked for some and really appreciate what you posted here.</p> </blockquote> <blockquote> <p class="byline">Arie on 2024-08-21 wrote:</p> <p>Hello Mike, Great explanation and really good instruction. Top Job!. I have a similar situation: a friend of mine asked if I could look at his battery (as I am the only one he knew who had a multimeter :-). I understand your explanation, but I do not own such a professional power supply. only a 12 Volt 20 amps Siemens unit. Would there any other way to do this?</p> </blockquote> <blockquote> <p class="byline">Danny on 2024-08-31 wrote:</p> <p>Hey, thanks for publishing this, it’s super approachable. After reaching out to several e-bike shops in the area, none of them are willing to try and fix the battery, so I’m going to try and do it myself. The part of this that I’m most concerned about is connecting the battery to the power supply. Would a molex 3-pin like this (https:&#x2F;&#x2F;www.amazon.com&#x2F;Davitu-Electrical-Equipments-Supplies-PicoBlade&#x2F;dp&#x2F;B089K2WQJ5&#x2F;ref=mp_s_a_1_24) hook up to the battery to charge a bit more securely? Thanks!</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-09-09 wrote:</p> <p>Hi Jon, Sorry for taking so long to reply – I’d missed the notification that you’d left a comment. It does sound like something more serious has happened to your smart cartridge. I’m afraid, I’ve never had to deal with a damaged smart cartridge, so I’m out of advice. If it’s of any consolation, I’m sure there will be people desperate to buy parts from your bike; the e-shifter in particular.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-09-09 wrote:</p> <p>Hi Arie, Right… I whole heartedly DO NOT recommend this but, in theory, it might work. I would hook up the cell and aim for getting its voltage up to about 2.5V – 3V. You could then transfer the cell back to the smart cartridge and hope that’s within the range that the bike might recognise it and carry on charging – safely. 12V @ 20A is really dangerous though. I’d be worried about going much above 1A, maybe 2A, if you have a thermometer you can use to keep an eye on the cell’s temperature. You’re really abusing the cell at that point, so explosions and fires are possible if not likely. I’d try cannibalising an old USB power supply&#x2F;cable; that would be much safer since you’d restricted to the 5V and 0.5A – 2A range.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-09-09 wrote:</p> <p>Hi Danny, sorry for taking so long to reply – I’d missed the notification that you’d left a comment. Yes, that looks like it’s got the right sort of connector on it to let you plug in the battery. I hope it goes well for you, good luck!</p> </blockquote> <blockquote> <p class="byline">Max on 2024-09-27 wrote:</p> <p>Hi Mike, i had a go at manually charging this module myself, and I think I found an easier way. when you supply 24v and 1.5 A to the bike, instead of the normal 42 V, somehow the power goes directly to the battery, which needs 29000 mAh to let current pass through the battery. Vanmoof used to sell these power supplies to repair shops, but I made one myself by sacrificing a charger and soldering the end plug’s two wires to a 24v power supply, should save some effort!</p> </blockquote> <blockquote> <p class="byline">Nicola on 2024-10-25 wrote:</p> <p>Hello Mike, thanks for your guide, that is very useful. My 2023 Vanmoof S3 has a problem located in the smart cartridge. I’m pretty sure because the battery and the BMS have just been restored thanks to a reparations shop in Tilburg. I’m lucky because I have two smart cartridges: the new one (the only one connected with my phone) while plugged in, shows the shipping mode’s boat led blinking (charger LED is green). The second smart cartridge gets connected to the main battery but error 45 is shown and isn’t linked with my phone. The same shop doesn’t repair the smart cartridge and VM customer support is bouncing me… I’m looking for a technical help near Verona, Italy, because I’m not able to do it by myself. Any suggestion? Thanks in advance. Best regards. Nicola</p> </blockquote> <blockquote> <p class="byline">Nini on 2024-10-27 wrote:</p> <p>Hi Mike, my Van mood S3 has an error 41 which does not respond to a reset. I am non techno. Do you think it is fixable? Thanks</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-10-28 wrote:</p> <p>Hi Nicola, I’m afraid I live in the north of Scotland, so I don’t have any advice about getting help near Verona. My only suggestion is that Error 45 means the Smart Cartridge cannot talk to the wheel motor so I’d try disconnecting the cable to the wheel from within the fork and trying to clean the electrical contacts. It’s possible that road grime or water has gotten in there and is causing your problems. Sorry I couldn’t be more help, good luck! Mike</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-10-28 wrote:</p> <p>It looks like Error 40 and Error 41 are due to the bell and the boost buttons failing, usually due to rainwater getting in them. Someone’s written up a guide on iFixit on replacing the buttons, https:&#x2F;&#x2F;www.ifixit.com&#x2F;Guide&#x2F;VanMoof+S3+Bell+Button+Replacement&#x2F;138539 and they’ve also given a link to a replacement button on Amazon https:&#x2F;&#x2F;www.amazon.co.uk&#x2F;Gebildet-Latching-Lockable-Trumpet-Doorbell&#x2F;dp&#x2F;B07XQSBW7Q I’ve not tried, but it sounds like this should do the trick. Cheers, Mike</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-10-28 wrote:</p> <p>Thanks Max, that’s really useful to know!</p> </blockquote> <blockquote> <p class="byline">Nini on 2024-10-28 wrote:</p> <p>Thanks Mike, looks too technical for me, so its off to Holy Spokes! I do hope it’s fixable. As an aside can water get into the smart cartridge from the cross bar and cause damage, or are those little holes sealed? My bike got really wet the other day and that’s when the error 41 started.</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-10-31 wrote:</p> <p>Water shouldn’t really be able to get in to the Smart Cartridge. There aren’t any holes in the top, the dot-matrix display has a reasonably decent seal and only lets light through. If you rode through a **really** deep puddle, there’s a chance you could spray some up around the power button&#x2F;charge port area but you’d have to be really unlucky to get enough water up there to do any damage.</p> </blockquote> <blockquote> <p class="byline">Nicola on 2024-11-10 wrote:</p> <p>Thank you, Mike! You are very kind. Happy Sunday</p> </blockquote> <blockquote> <p class="byline">Mariusz on 2024-12-10 wrote:</p> <p>Cześć, Zwracam się z prośbą o pomoc w sprawie mojego Vanmoof S3 który nie chce połączyć się z aplikacją w telefonie… Czy macie pomysł czym to może być spowodowane? Z góry dziękuję za pomoc. Pozdrawiam, Mariusz</p> </blockquote> <blockquote> <p class="byline">Machine Translation of Mariusz on 2024-12-10 wrote:</p> <p>Hi, I&#x27;m asking for help with my Vanmoof S3 which won&#x27;t connect to the app on my phone... Do you have any idea what could be causing this? Thanks in advance for your help. Best regards, Mariusz</p> </blockquote> <blockquote> <p class="byline">Mike Coats on 2024-12-12 wrote:</p> <p>I&#x27;m sorry Mariusz, but I don&#x27;t. There are too many variables at play when it comes to the app communication. It could be the Bluetooth connection. It might be the bike&#x2F;app encryption keys. The bike might have lost its memory and lost its pairing information. It&#x27;s almost impossible to diagnose. You just kind of have to try everything and hope for the best. Maybe ask on Reddit and see if anyone there can help?</p> </blockquote> Radxa Rock 5B as a Raspberry Pi replacement? 2023-09-01T13:37:00+00:00 2023-09-01T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/radxa-rock-5b-power/ <p>In December 2022, the Raspberry Pi Foundation set about burning a large amount of the good will they had built up in the hacker community. They announced that <a rel="noopener" target="_blank" href="https://www.buzzfeednews.com/article/chrisstokelwalker/raspberry-pi-hired-ex-cop-mastodon-controversy">they had hired an ex-cop</a> who had used their products to spy on members of the public.</p> <p>For those outside of the UK, or the left-leaning section of its citizens, this probably seemed fairly harmless. However, UK policing has been undergoing some pretty damning self-reflection after <a rel="noopener" target="_blank" href="https://www.theguardian.com/uk-news/2023/jun/29/what-is-spy-cops-report-about-public-inquiry-undercover-policing">overstepping the mark</a>, by quite a distance, on <a rel="noopener" target="_blank" href="https://www.theguardian.com/uk-news/series/spy-cops-scandal">more than one occasion</a>.</p> <p>When challenged on the tone of their announcement, the Raspberry Pi Foundation’s social media and PR teams <a rel="noopener" target="_blank" href="https://www.makeuseof.com/raspberry-pi-hires-former-police-officer-for-surveillance-tech/">doubled down, attacked their critics, and started spreading straw-men</a> to the press covering the outrage.</p> <p>I, like many <a rel="noopener" target="_blank" href="https://oblique.systems/log/dear-raspberry-pi">others</a>, decided we could no longer support that organisation and that it was time to look elsewhere for our single-board computers and hackable hardware.</p> <h2 id="enter-radxa-rock-5b">Enter Radxa Rock 5B</h2> <p>In early 2023, the Rock 5B from Radxa seemed to poised to take the Raspberry Pi’s <a rel="noopener" target="_blank" href="https://www.youtube.com/results?search_query=radxa+rock+5b">crown for the best SBC for Linux on desktops</a>. I promptly ordered one from China and eagerly awaited its delivery. Life “got in the way” for a bit and it wasn’t until this summer that I managed to sit down and get to grips with it. Immediately, I stumbled on to this devices biggest caveat – powering it on.</p> <p>As can be found all over Radxa’s forums, people have a tough time powering this board. According to <a rel="noopener" target="_blank" href="https://www.armbian.com/rock-5b/">the Armbian project’s documentation</a>, almost all Rock 5Bs in the wild have a firmware that isn’t able to negotiate USB Power Delivery and then hand over to Linux during boot up, resulting in an infinite brown out and reboot loop. There are updated firmware blobs and instructions available that once installed do appear to correctly handle USB-PD, but these cannot be installed until the board boots. Catch 22!</p> <p>The solution to this is to boot the device using a “dumb” power supply that just provides the volts and amps without negotiation, flash the new boot firmware, then use a proper, standards-abiding power supply. I did not have any USB-C power supplies that acted in “dumb” mode, so instead I set up my <a rel="noopener" target="_blank" href="https://www.amazon.co.uk/dp/B09C8LWV9W">NANKADF 30V/10A Programmable Power Supply</a> to supply 5V at 3A and hooked it up to the Rock 5B via a <a rel="noopener" target="_blank" href="https://www.amazon.co.uk/dp/B0C3VR9181">USB-C break-out cable</a>.</p> <p><img src="https://assets.mikecoats.xyz/radxa-rock-5b-power/power-supply.jpg" alt="Programmable Power Supply, set to 5V and 3A." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/radxa-rock-5b-power/usb-c.jpg" alt="USB-C break-out cable." loading="lazy" decoding="async" /></p> <p>Using this combination lets the board start up and get all the way through to a log in prompt. During boot, the board peaked at a draw of almost 2A, but once it all settled down it dropped to less than 1A.</p> <p><img src="https://assets.mikecoats.xyz/radxa-rock-5b-power/draw-boot.jpeg" alt="During boot, the Rock 5B peaked at almost 2A." loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/radxa-rock-5b-power/draw-running.jpeg" alt="Once booted to a text login prompt, current draw dropped to less than 1A." loading="lazy" decoding="async" /></p> <p>Now that the system’s up and running, the firmware can be upgraded by <a rel="noopener" target="_blank" href="https://wiki.radxa.com/Rock5/install/spi">downloading a new image and following the instructions from the Radxa wiki</a>. Once the new firmware’s flashed, you should be able to shutdown and restart the board with a proper USB power supply. Initially, I used a Samsung EP-TA200, a QuickCharge/Adaptive Fast Charging adapter which gives 15W, but only at 9V. At 5V, it will only supply 2A which was fine for most tasks, but once I’d logged in to a KDE desktop session, started a full <code>apt update</code> and begun editing some files in VSCode it must have pushed the supply beyond 2A and reset. Subsequently, I’ve moved over to an Anker A2039 which can supply the full 3A at 5V and since then I’ve never had a single hiccup.</p> <p><img src="https://assets.mikecoats.xyz/radxa-rock-5b-power/plug-samsung.jpg" alt="Samsung EP-TA200" loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/radxa-rock-5b-power/plug-anker.jpg" alt="Anker A2039" loading="lazy" decoding="async" /></p> <h2 id="">Would I buy one again?</h2> <p>No.</p> <p>Just don’t. It’s not worth the hassle.</p> <p>If you find yourself in my shoes and have already bought the board but don’t have a fancy desktop power supply and various connectors, I’d skip all the compatibility lists on the Wiki and buy a <a rel="noopener" target="_blank" href="https://www.amazon.co.uk/dp/B0BGGV97KT">barrel-plug 5V 3A power supply</a> and a matching <a rel="noopener" target="_blank" href="https://www.amazon.co.uk/dp/B07XZ7Q2N7">barrel-socket to USB-C plug</a> like these two available from Amazon.</p> <p><img src="https://assets.mikecoats.xyz/radxa-rock-5b-power/dc-supply.png" alt="UK 230v AC to 5.5x2.1mm 5v 3A DC power supply" loading="lazy" decoding="async" /> <img src="https://assets.mikecoats.xyz/radxa-rock-5b-power/dc-usb.png" alt="5.5x2.1mm DC to USB-C adapter" loading="lazy" decoding="async" /></p> <h2 id="-1">What would I buy instead?</h2> <p>When I picked up the Rock 5B, it was the only RK3588 based board that came with 16GiB of RAM. A similar board which I use as my router running OpenWrt is the <a rel="noopener" target="_blank" href="https://www.friendlyelec.com/index.php?route=product/product&amp;product_id=289">NanoPi R6S from FriendlyElec</a> which is also RK3588 based but only comes with 8GiB of RAM. It’s been flawless in operation and a breath of fresh air to flash, install and maintain compared to the Rock 5B. FriendlyElec have since come out with the <a rel="noopener" target="_blank" href="https://www.friendlyelec.com/index.php?route=product/product&amp;product_id=292">NanoPC-T6</a> which has an option to buy it with 16GiB of RAM. That’s what I’d pick instead of the Radxa Rock 5B.</p> Installing OpenWrt on a Unifi AP AC Lite in 2023 2023-07-25T13:37:00+00:00 2023-07-25T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/openwrt-on-unifi/ <p>OpenWrt is a very powerful OS and can be configured to be much more useful and capable than just an access point, but these instructions assume you have other devices on your network handling those duties. Our goal here is just to replace the OS on the Unifi AP AC Lite with OpenWrt, leaving the AP to only handle wireless connectivity to the rest of your network.</p> <h2 id="before-you-start">Before you start</h2> <p>Make sure you’ve got some empty IP addresses.</p> <ul> <li>Unifi APs use 192.168.1.20 if they are not assigned addresses by DHCP</li> <li>OpenWrt uses 192.168.1.1 regardless of DHCP and any existing equipment, so you might get a clash with an existing router</li> </ul> <p>Make sure you take a note of your Unifi network’s SSH username &amp; password. Once you begin the process, you will not be able to manage them from the Unifi controller. Out of the box, they’re <code>ubnt:ubnt</code>, but either through upgrading or twiddling over the years, mine were not that anymore. You can find them in the System Settings page, under Network Device SSH Authentication.</p> <p><img src="https://assets.mikecoats.xyz/openwrt-on-unifi/unifi-ssh.png" alt="The SSH settings in Unifi&#39;s control panel." loading="lazy" decoding="async" /></p> <h2 id="">Downgrade the Unifi AP AC Lite</h2> <p>According to the OpenWrt Wiki, recent versions of the Unifi AP firmware have disabled write access to certain flash sections and removed some of the tools necessary for installing OpenWrt. These updates will hinder the installation process of OpenWrt. The easiest workaround for these restrictions is to downgrade the firmware to a version that predates the implementation of these protections. However, it is unclear which specific protections were introduced in which firmware versions. Therefore, to ensure a comprehensive approach, I recommend downgrading to the earliest available firmware version. As of July 2023, the earliest firmware version available on <a rel="noopener" target="_blank" href="https://www.ui.com/download/unifi/unifi-ap-ac-lite/default/unifi-firmware-3758-uap-ac-litelrproedumm-proiwiw-pro">Ubiquiti’s site is v3.7.58</a>. Click the Download button and then click through the EULA popup to view the download URL for the firmware. At the time of writing, it can be found at <a rel="noopener" target="_blank" href="https://dl.ui.com/unifi/firmware/U7PG2/3.7.58.6385/BZ.qca956x.v3.7.58.6385.170508.0957.bin">https://dl.ui.com/unifi/firmware/U7PG2/3.7.58.6385/BZ.qca956x.v3.7.58.6385.170508.0957.bin</a>.</p> <p>You now need to find your Unifi AP AC Lite in your Unifi console and navigate to the Location URL field in the Settings &gt; Manage menu. Paste the link to the .bin file in the Location URL field, hit Update, and follow the prompts. The Unifi console will display a scary looking popup that asks you to confirm you’re happy to install firmware from a custom location and that there may be compatibility issues after installation. Hit Confirm on this message and the Unifi AP will reboot automatically once it completes installing the firmware.</p> <p><img src="https://assets.mikecoats.xyz/openwrt-on-unifi/unifi-wrt.png" alt="A warning dialogue box in Unifi&#39;s control panel." loading="lazy" decoding="async" /></p> <p>The “compatibility issues” mentioned in that message appear to include a problem where the old firmware is unable to connect to the up to date console, so from this point on the Unifi console will be unable to adopt or configure the Unifi AP AC Lite. All configuration from this point on will be via SSH. Once installation has complete and the Unifi AP AC Lite has rebooted, if you have a DHCP allocation set , it will start up and respond on that address, otherwise it will use its default address, 192.168.1.20.</p> <h2 id="-1">Install the OpenWrt firmware</h2> <p>To install the OpenWrt firmware, connect to the Unifi AP with SSH. The SSH server that’s included in v3.7.58 is pre-<a rel="noopener" target="_blank" href="https://en.wikipedia.org/wiki/Logjam_(computer_security)">LogJam</a>, so it only offers old, out of date key algorithms. Depending on how up to date your SSH client is, it may not support these algorithms by default. If so, you’ll see the following error message.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> ssh ubnt@192.168.1.20 </span><span style="color:#b58900;">Unable</span><span> to negotiate with 192.168.1.20 port 22: no matching host key type found. </span><span style="color:#b58900;">Their</span><span> offer: ssh-rsa,ssh-dss </span></code></pre> <p>To connect to an older SSH server such as this, you have to manually specify an acceptable key algorithm, using the <code>HostKeyAlgorithms</code> option. If successful, you should see a number of messages or banners scroll by and be presented with a <code>BZ</code> prompt that includes the downgraded firmware version.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> ssh</span><span style="color:#268bd2;"> -oHostKeyAlgorithms</span><span>=+ssh-dss ubnt@192.168.1.20 </span><span style="color:#b58900;">[...] </span><span style="color:#b58900;">BZ.v3.7.58# </span></code></pre> <p>Once connected, you’ll need to download the latest suitable OpenWrt firmware to the Unifi AP. According to the <a rel="noopener" target="_blank" href="https://openwrt.org/toh/ubiquiti/unifiac">OperWrt hardware wiki</a>, as of July 2023 the latest version is <a rel="noopener" target="_blank" href="https://downloads.openwrt.org/releases/22.03.5/targets/ath79/generic/openwrt-22.03.5-ath79-generic-ubnt_unifiac-lite-squashfs-sysupgrade.bin">22.03.05, compiled for the ath79 target and packaged for the AC Lite</a>. You can download it using the <code>curl</code> command, which I have split over multiple lines for readability here.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">BZ.v3.7.58#</span><span> curl </span><span style="color:#dc322f;">\ </span><span>&gt; https://downloads.openwrt.org/releases/22.03.5/targets/ath79/generic/</span><span style="color:#dc322f;">\ </span><span>&gt; openwrt-22.03.5-ath79-generic-ubnt_unifiac-lite-squashfs-sysupgrade.bin </span><span style="color:#dc322f;">\ </span><span>&gt; &gt;openwrt.bin </span><span> </span><span style="color:#859900;">%</span><span> Total </span><span style="color:#859900;">%</span><span> Received </span><span style="color:#859900;">%</span><span> Xferd Average Speed Time Time Time Current </span><span> </span><span style="color:#b58900;">Dload</span><span> Upload Total Spent Left Speed </span><span style="color:#b58900;">100</span><span> 6208k 100 6208k 0 0 339k 0 0:00:18 0:00:18 --:--:-- 602k </span></code></pre> <p>APs like the Unifi AP AC Lite do not have storage in the form of HDDs or SSDs, but as embedded flash chips known as MTDs. This means we’re not going to write our image to a partition such as <code>/dev/sda</code> like you would when flashing an SD card for a Raspberry Pi. Instead, we deal with MTD character devices such as <code>/dev/mtd4</code> or we can use the <code>mtd</code> tool to operate on the devices for us. To see the equivalent of the MTD’s partition table, you can look in the <code>/proc/mtd</code> file.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">BZ.v3.7.58#</span><span> cat /proc/mtd </span><span style="color:#b58900;">dev:</span><span> size erasesize name </span><span style="color:#b58900;">mtd0:</span><span> 00060000 00010000 </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">u-boot</span><span style="color:#839496;">&quot; </span><span style="color:#b58900;">mtd1:</span><span> 00010000 00010000 </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">u-boot-env</span><span style="color:#839496;">&quot; </span><span style="color:#b58900;">mtd2:</span><span> 00790000 00010000 </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">kernel0</span><span style="color:#839496;">&quot; </span><span style="color:#b58900;">mtd3:</span><span> 00790000 00010000 </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">kernel1</span><span style="color:#839496;">&quot; </span><span style="color:#b58900;">mtd4:</span><span> 00020000 00010000 </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">bs</span><span style="color:#839496;">&quot; </span><span style="color:#b58900;">mtd5:</span><span> 00040000 00010000 </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">cfg</span><span style="color:#839496;">&quot; </span><span style="color:#b58900;">mtd6:</span><span> 00010000 00010000 </span><span style="color:#839496;">&quot;</span><span style="color:#2aa198;">EEPROM</span><span style="color:#839496;">&quot; </span></code></pre> <p>Unifi APs have two “partitions” that can have images written to them, <code>kernel0</code> and <code>kernel1</code>, to allow Ubiquiti to be clever and roll-back to a previous image if an upgrade fails. OpenWrt does not have this ability and is can only boot from the first of the two “partitions”. We will therefore use the <code>mtd</code> tool to write our <code>openwrt.bin</code> file to the <code>kernel0</code> partition.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">BZ.v3.7.58#</span><span> mtd write openwrt.bin kernel0 </span><span style="color:#b58900;">Unlocking</span><span> kernel0 ... </span><span> </span><span style="color:#b58900;">Writing</span><span> from openwrt.bin to kernel0 ... </span></code></pre> <p>At this point, you have successfully written OpenWrt to the first “partition”, but Unifi’s OS will still be installed to the second one. We can use the <code>mtd</code> tool to erase the second “partition”, <code>kernel1</code>, leaving only OpenWrt installed on the AP.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">BZ.v3.7.58#</span><span> mtd erase kernel1 </span><span style="color:#b58900;">Unlocking</span><span> kernel1 ... </span><span style="color:#b58900;">Erasing</span><span> kernel1 ... </span></code></pre> <p>Finally, we need to let the Unifi AP AC Lite know which “partition” to boot in to. This is done by writing a single <code>0</code> byte to the boot selector or “bs” partition in order to indicate <code>kernel0</code>. In an earlier command we saw that the <code>mtd4</code> character device contained the “bs” partition, so we can use <code>dd</code> to write the single <code>0</code> byte.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">BZ.v3.7.58#</span><span> dd if=/dev/zero bs=1 count=1 of=/dev/mtd4 </span><span style="color:#b58900;">1+0</span><span> records in </span><span style="color:#b58900;">1+0</span><span> records out </span></code></pre> <p>The only piece of Unifi’s OS that’s left is what remains in the Unifi AP AC Lite’s RAM. Rebooting the AP now will restart the device and load OpenWrt as the AP’s OS.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">BZ.v3.7.58#</span><span> reboot </span><span style="color:#b58900;">Connection</span><span> to 192.168.1.20 closed by remote host. </span><span style="color:#b58900;">Connection</span><span> to 192.168.1.20 closed. </span></code></pre> <h2 id="-2">Configure OpenWrt</h2> <p>Your Unifi AP AC Lite has now rebooted, launched OpenWrt and is listening on the IP address 192.168.1.1 over it’s ethernet port.</p> <p>To begin setting up your AP, log in to LuCI, OpenWrt’s web-based admin panel, on <a rel="noopener" target="_blank" href="http://192.168.1.1/cgi-bin/luci/">http://192.168.1.1/cgi-bin/luci/</a>. Your new admin username is <code>root</code> and, by default, the password is simply <code>password</code>. This can and should be changed in the “Router Password” tab on the the <a rel="noopener" target="_blank" href="http://192.168.1.1/cgi-bin/luci/admin/system/admin">System &gt; Administration page</a>.</p> <p>The next step is to change your AP’s IP address as it will probably either be clashing with your normal router or be entirely incorrect for the rest of your network; this can be set in the <a rel="noopener" target="_blank" href="http://192.168.1.1/cgi-bin/luci/admin/network/network">Network &gt; Interfaces page</a>. Choose the <code>lan</code> device on that page and press the “Edit” button. On the dialog that pops up you can either replace the static IP address with one of your own choosing or switch to an allocated address by selecting “DHCP client” from the “Protocol” drop down.</p> <p>As OpenWrt is designed to support many different powerful router configurations, Dnsmasq is installed, enabled and started by default and provides DNS and DHCP for IPv4 networks. Odhcpd is also installed, enabled and started by default and provides RA and DHCP for IPv6 networks. Since we want to use the Unifi AP AC Lite as just an access point, we must stop and disable both of these services.</p> <p><img src="https://assets.mikecoats.xyz/openwrt-on-unifi/wrt-dnsmasq.png" alt="dnsmasq controls within OpenWrt." loading="lazy" decoding="async" /></p> <p><img src="https://assets.mikecoats.xyz/openwrt-on-unifi/wrt-odhcpd.png" alt="odhcpd controls within OpenWrt." loading="lazy" decoding="async" /></p> <p>The last part to configure your AP is to add the SSIDs you want to broadcast. Unifi AP AC Lites have two radios onboard, an 802.11a/c/n radio broadcasting on 5GHz and an 802.11b/g/n radio broadcasting on 2.4GHz. Adding SSIDs is accomplished by pressing the “Add” button next to each and filling out the required fields. If you want to broadcast the same SSID on both frequencies to allow clients to migrate between them, you’ll need to create two identical SSIDs, one for each radio.</p> <p>Finally, you’ll need to save and apply the changes your made above. This can be done by pressing the “UNSAVED CHANGES” link found in the top-right of the web admin panel. This will present you with a text based version of all the changes you have made, allowing you to confirm you’ve not made any typos and apply the configuration.</p> <h2 id="-3">What next?</h2> <p>Sit back and enjoy your AP, you weird nerd! Alternatively, start to dig in to the OpenWrt documentation to expand on the ideas in this post. For example, I hope to cover band steering, 802.11r, 802.11k &amp; 802.11v fast transition roaming and 802.1X access controls in subsequent, upcoming blogposts.</p> Weeknotes for 9th December 2019 – 15th December 2019 2019-12-21T13:37:00+00:00 2019-12-21T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/weeknotes-004/ <h2 id="failte-agus-tapadh-leibh-a-charaid">Fàilte agus tapadh leibh, a charaid!</h2> <p>This one’s a little late, but it’s finally here; another <a rel="noopener" target="_blank" href="https://twitter.com/hashtag/weeknotes">#weeknotes</a> report! This week was a different one for me as it was focused on learning rather than building.</p> <h2 id="sepa-visit">SEPA Visit</h2> <p>On Monday and Tuesday, a group of us from <a rel="noopener" target="_blank" href="https://www.nature.scot/">NatureScot</a> went to visit one of our sister agencies, <a rel="noopener" target="_blank" href="https://www.sepa.org.uk/">SEPA</a>, as they’ve just released three new applications.</p> <ul> <li><a rel="noopener" target="_blank" href="https://privatesewage-services.sepa.org.uk/">Register your private sewage treatment system</a></li> <li><a rel="noopener" target="_blank" href="https://radioactivesubstances-services.sepa.org.uk/n-lowrisk/GDPR">Notify SEPA about the management of radioactive sources</a></li> <li><a rel="noopener" target="_blank" href="https://wastemanagementexemption-services.sepa.org.uk/">Register your exempt waste activity</a></li> </ul> <p>They’ve developed these applications following the <a rel="noopener" target="_blank" href="https://resources.mygov.scot/standards/digital-first/">Digital First Service Standard</a> and as we’re about to build and release our own applications under it’s successor, the <a rel="noopener" target="_blank" href="https://resources.mygov.scot/alpha/service-standard/digital-scotland-service-standard/">Digital Scotland Service Standard</a>, we were interested in discovering any lessons they had learned along the way. It was a really useful visit and we left with a lot to think about and to take in to our planning sessions.</p> <h2 id="tha-thu-sgoinneil-is-toil-leam-ceic-agus-cofaidh">Tha thu sgoinneil! Is toil leam cèic agus cofaidh.</h2> <p>The rest of the week, as if you couldn’t guess, has been taken up with learning <a rel="noopener" target="_blank" href="https://www.duolingo.com/course/gd/en/Learn-Scottish%20Gaelic">Gaelic with Duolingo</a>. <a rel="noopener" target="_blank" href="https://twitter.com/DailyGael">The Daily Gael</a> set up <a rel="noopener" target="_blank" href="https://twitter.com/ScotsGaelicDuo">a campaign</a> back in February to get Duolingo to support Gaelic learners with a course, and in November they <a rel="noopener" target="_blank" href="https://twitter.com/duolingo/status/1199779779278491651">went live</a>. Ever since, I’ve been logging in everyday to get my practice in. Hopefully, I’ll soon be able to impress my office mates from <a rel="noopener" target="_blank" href="https://twitter.com/bordnagaidhlig1">Bòrd na Gàidhlig</a> at the tea point with a bit of “Am bu toil leat cofaidh?”</p> Weeknotes for 2nd December 2019 – 8th December 2019 2019-12-08T13:37:00+00:00 2019-12-08T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/weeknotes-003/ <p>In an effort to keep my blogging momentum up, here’s my third consecutive <a rel="noopener" target="_blank" href="https://twitter.com/hashtag/weeknotes">#weeknotes</a> post. As in past weeks, I’ll be rounding up any of the interesting things I’ve been searching for on the internet.</p> <h2 id="mag-right-advent-of-code">🔎 advent of code</h2> <p>For the <a rel="noopener" target="_blank" href="https://adventofcode.com/events">past few years</a>, every December, <a rel="noopener" target="_blank" href="http://was.tl/">Eric Wastl</a> has run a series of coding challenges called <a rel="noopener" target="_blank" href="https://adventofcode.com/">Advent of Code</a>. These work like an Advent Calendar, with a new challenge becoming available each day. They can be solved using any tools or programming languages you want, so I have chosen NodeJS &amp; regular JS to keep the tooling to a minimum. I started off strongly, but I’ve not had enough time to dedicate to it yet and I’ve started running behind. To keep an eye on my progress, you can follow my <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/advent-of-code">GitHub repo, here</a>.</p> <h2 id="mag-right-mdn-reduce-mag-right-mdn-biggest-number">🔎 mdn reduce &amp;&amp; 🔎 mdn biggest number</h2> <p>One of the <a rel="noopener" target="_blank" href="https://adventofcode.com/2019/day/3">Advent of Code challenges</a> had us searching for the smallest number in a list of results. My favorite aspect of programming in JavaScript over many other languages is it’s ability to work with lists and arrays, gained from its functional programming roots. This lets us process arrays using concepts like <code>map</code> and <code>reduce</code> giving us the following snippet for retrieving the smallest item from a list.</p> <pre data-lang="js" style="background-color:#fdf6e3;color:#657b83;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#859900;">console</span><span>.</span><span style="color:#859900;">log</span><span>(</span><span style="color:#268bd2;">[</span><span style="color:#6c71c4;">1</span><span>, </span><span style="color:#6c71c4;">2</span><span>, </span><span style="color:#6c71c4;">3</span><span>, </span><span style="color:#6c71c4;">4</span><span>, </span><span style="color:#6c71c4;">5</span><span style="color:#268bd2;">]</span><span>.</span><span style="color:#b58900;">reduce</span><span>( </span><span> (</span><span style="color:#268bd2;">prev</span><span>, </span><span style="color:#268bd2;">curr</span><span>) </span><span style="color:#268bd2;">=&gt; </span><span style="color:#859900;">Math</span><span>.</span><span style="color:#859900;">min</span><span>(</span><span style="color:#268bd2;">prev</span><span>, </span><span style="color:#268bd2;">curr</span><span>), </span><span> </span><span style="color:#859900;">Number</span><span>.MAX_VALUE) </span><span>); </span></code></pre> <p>I used <code>Number.MAX_VALUE</code> as the <code>initialValue</code> in the reduce call as that’s the largest number JavaScript can hold; any other given value must be smaller than it, making it an ideal starting point for finding minimums in a list. <code>Number.POSITIVE_INFINITY</code> is also available, and could even be a better choice due to the <code>Number.isFinite()</code> method to make sure we’ve actually found a smaller number.</p> <h2 id="mag-right-mdn-spread-operator-array-copy">🔎 mdn spread operator array copy</h2> <p>When I was prototyping an answer for another challenge, I found myself needing to create a copy of an array, not just a reference to it, and the <a rel="noopener" target="_blank" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax">spread operator</a> is great for that. It creates a new array with all of the original array members contained within.</p> <pre data-lang="js" style="background-color:#fdf6e3;color:#657b83;" class="language-js "><code class="language-js" data-lang="js"><span style="color:#268bd2;">const testArray1 </span><span>= </span><span style="color:#268bd2;">[</span><span style="color:#6c71c4;">1</span><span>, </span><span style="color:#6c71c4;">2</span><span>, </span><span style="color:#6c71c4;">3</span><span>, </span><span style="color:#6c71c4;">4</span><span>, </span><span style="color:#6c71c4;">5</span><span style="color:#268bd2;">]</span><span>; </span><span> </span><span style="color:#268bd2;">const testArray2 </span><span>= </span><span style="color:#268bd2;">testArray1</span><span>; </span><span style="color:#268bd2;">testArray2</span><span>.</span><span style="color:#859900;">push</span><span>(</span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">Everywhere</span><span style="color:#839496;">&#39;</span><span>); </span><span> </span><span style="color:#268bd2;">const testArray3 </span><span>= </span><span style="color:#268bd2;">[</span><span style="color:#859900;">...</span><span style="color:#268bd2;">testArray1]</span><span>; </span><span style="color:#268bd2;">testArray3</span><span>.</span><span style="color:#859900;">push</span><span>(</span><span style="color:#839496;">&#39;</span><span style="color:#2aa198;">Just Here</span><span style="color:#839496;">&#39;</span><span>); </span><span> </span><span style="color:#859900;">console</span><span>.</span><span style="color:#859900;">log</span><span>(</span><span style="color:#268bd2;">testArray1</span><span>); </span><span style="color:#93a1a1;">// [ 1, 2, 3, 4, 5, &#39;Everywhere&#39; ] </span><span style="color:#859900;">console</span><span>.</span><span style="color:#859900;">log</span><span>(</span><span style="color:#268bd2;">testArray2</span><span>); </span><span style="color:#93a1a1;">// [ 1, 2, 3, 4, 5, &#39;Everywhere&#39; ] </span><span style="color:#859900;">console</span><span>.</span><span style="color:#859900;">log</span><span>(</span><span style="color:#268bd2;">testArray3</span><span>); </span><span style="color:#93a1a1;">// [ 1, 2, 3, 4, 5, &#39;Everywhere&#39;, &#39;Just Here&#39; ] </span></code></pre> <h2 id="mag-right-git-change-old-commit-messages">🔎 git change old commit messages</h2> <p>Working as a full-stack developer often means fixing bugs or adding features that cross both the front-end and back-end systems. At work, we follow <a rel="noopener" target="_blank" href="https://chris.beams.io/posts/git-commit/">Chris Beams’ seven rules</a> for git commit messages, which means ending our messages with references to the issues that the piece of work was for. Jumping between different repos for front and back-ends can result in the front-end commits being tagged with the back-end issues and vice-versa. To fix this, and re-write a series of commit messages, you can take advantage of git’s interactive rebase mode, and rewrite the last, say, 3 messages by using the following command.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">$</span><span> git rebase</span><span style="color:#268bd2;"> -i</span><span> HEAD</span><span style="color:#d33682;">~</span><span>3 </span></code></pre> <p>You will then be presented with a summary listing those commits.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">pick</span><span> 55f9d3e Advent of Code 2018, Day 1, Stars 1 </span><span style="color:#859900;">&amp; </span><span style="color:#b58900;">2 </span><span style="color:#b58900;">pick</span><span> d577864 Advent of Code 2019, Day 2, Star 1 </span><span style="color:#b58900;">pick</span><span> 62de55e Advent of Code 2019, Day 2, Star 2 </span></code></pre> <p>Changing each of those <code>pick</code>s (which would just use the commit as-is) to <code>reword</code>s tells git to accept the code changes, but re-write the commit messages, allowing you to fix your incorrect issue references.</p> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#b58900;">reword</span><span> 55f9d3e Advent of Code 2018, Day 1, Stars 1 </span><span style="color:#859900;">&amp; </span><span style="color:#b58900;">2 </span><span style="color:#b58900;">reword</span><span> d577864 Advent of Code 2019, Day 2, Star 1 </span><span style="color:#b58900;">reword</span><span> 62de55e Advent of Code 2019, Day 2, Star 2 </span></code></pre> Weeknotes for 25th November 2019 – 1st December 2019 2019-12-02T13:37:00+00:00 2019-12-02T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/weeknotes-002/ <p>For my second <a rel="noopener" target="_blank" href="https://twitter.com/hashtag/weeknotes">#weeknotes</a> blogpost, I’ve continued with last weeks “theme” of collecting up my browser searches and publishing the interesting bits. I spent most of my time this week either in meetings or at home waiting for a heating engineer, so I didn’t actually have much time to do any searching for cool projects and instead I just ticked along some existing projects.</p> <h2 id="mag-right-vscode-browser-mag-right-code-server">🔎 vscode browser &amp; 🔎 code-server</h2> <p>As part of my <a href="/weeknotes-001/">ongoing effort</a> on turning an iPad into a ‘proper’ computer for development, I continued searching for a way to run an IDE like vscode remotely from my home server. This week I came across the <a rel="noopener" target="_blank" href="https://github.com/cdr/code-server">code-server</a> project which runs a ‘full’ copy of vscode in a browser tab. Interestingly, it can be run in a docker container and connected over an HTTPS reverse proxy, providing a secure connection to an editor hosted on my own server.</p> <p>To test this program out, I set it up as a container on my laptop and launched it in Firefox. I then put the tab in fullscreen mode and used it to edit this blog post. It worked! It’s not quite the same as running the editor on my server and editing on the iPad, but it’s getting pretty close. I even managed to bring up the interface on my iPhone and enter some text, but the small display, coupled with the on-screen keyboard meant it wasn’t <em>really</em> a go-er.</p> <h2 id="5-mag-right-logitech-slim-folio-mag-right-brydge-keyboard">[][5]🔎 logitech slim folio &amp; 🔎 brydge keyboard</h2> <p>The iPad’s on-screen keyboard takes up a fair amount of vertical space and isn’t really suitable for long typing sessions, especially for coding with all of the required alternative characters, etc. Really, I’m on the lookout for a physical keyboard to go along with the iPad. I’ve pretty much dismissed Apple’s Smart Keyboard straight away as it has zero key travel. I thought it felt terrible after 1-2 minutes use in the Apple Store, so I can only imagine I’ll hate it even more after any serious coding sessions. I also spend a lot of my time typing on my lap and I don’t like how flimsy the ‘laptop’ mode appears. It just seems a bit too ‘floppy’ for my liking.</p> <p>To work around this, I’ve done some searching and discovered the <a rel="noopener" target="_blank" href="https://web.archive.org/web/20190929181831/https://www.logitech.com/en-gb/product/slim-folio-pro">Logitech Slim Folio Pro</a> and the <a rel="noopener" target="_blank" href="https://web.archive.org/web/20200403011653/https://www.brydge.co.uk/products/brydge-pro-keyboard-for-ipad-pro">Brydge Pro</a>. Both of these devices appear to do what I want, but both appear to have some tradeoffs when put head-to-head with one-another. I’ll need to see if any shops near me have some in stock and get a hands-on test with them, but until then I’ll just have to check out any reviews I can find.</p> Weeknotes for 18th November 2019 – 24th November 2019 2019-11-25T13:37:00+00:00 2019-11-25T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/weeknotes-001/ <p>I’ve followed a few digital government folk like <a rel="noopener" target="_blank" href="https://twitter.com/edent">@edent</a> on twitter for a while and seen their <a rel="noopener" target="_blank" href="https://twitter.com/hashtag/weeknotes">#weeknotes</a> approach to blogging, so I’ve decided to dip my toe in and write a little post about what I’ve been up to this past week.</p> <p>It can be difficult to remember everything you did if you don’t deliberately keep notes, but the one thing that was keeping notes all week was my browser history. Reviewing your browser searches can be quite enlightening, revealing things that tripped you up or that you needed help with.</p> <h2 id="mag-right-roku-sdk-mag-right-brightscript-mag-right-windows-95-star-field">🔎 roku sdk, 🔎 brightscript &amp; 🔎 windows 95 star field</h2> <p>On the 17th <a rel="noopener" target="_blank" href="https://twitter.com/nicolehe">Nicole He</a> posted up <a rel="noopener" target="_blank" href="https://twitter.com/nicolehe/status/1196193647244578816">a thread on twitter</a> showing the truly bizarre and <strong>kinda-probably-NSFW</strong> collection of screensavers available for installation on Roku devices. I didn’t even realise that screensavers for your Roku were a thing or that custom ones were available. I immediately signed up for a developer account, of course, and started work on bringing a late 90’s classic back from the dead and on to my TV screen.</p> <h2 id="mag-right-oracle-regular-expression-mag-right-regexp-like">🔎 oracle regular expression &amp; 🔎 regexp_like</h2> <p>We’re continually updating and upgrading our applications at <a rel="noopener" target="_blank" href="https://www.nature.scot/">NatureScot</a>, and an integral part of that work is data migration and management. I had to wrangle some ‘stringly-typed’ numbers this week in our Oracle database and so I got an opportunity to flex my regular expression muscles. Luckily for me, it didn’t turn in to <a rel="noopener" target="_blank" href="https://regex.info/blog/2006-09-15/247">two problems</a>.</p> <h2 id="mag-right-angular-template-reference-variable-mag-right-ngcontent-mag-right-controlvalueaccessor-htmlinputelement">🔎 angular template reference variable, 🔎 ngcontent &amp; 🔎 ControlValueAccessor&amp; HtmlInputElement</h2> <p>At <a rel="noopener" target="_blank" href="https://www.nature.scot/">NatureScot</a>, we develop our applications like <a rel="noopener" target="_blank" href="https://sitelink.nature.scot/">SiteLink</a> using the <a rel="noopener" target="_blank" href="https://angular.io/">Angular framework</a>. To keep our UIs and UXs consistent between applications, I’ve been looking at what’s required to put our commonly re-used components in a library available for re-use across the organisation.</p> <h2 id="mag-right-httpstatuses">🔎 httpstatuses</h2> <p>Creating and working with RESTful services mean you need to have a pretty decent knowledge of the HTTP status codes. I can never keep track of the higher-numbered 4xx errors, which is why I like <a rel="noopener" target="_blank" href="https://httpstatuses.com/">this HTTP Status Codes</a> site.</p> <h2 id="mag-right-metalsmith-mag-right-handlebars-stringify-mag-right-json-stringify-circular">🔎 metalsmith, 🔎 handlebars stringify &amp; 🔎 json stringify circular</h2> <p>I had previously used Metalsmith for this blog – then WordPress and it is now built with Zola. I’ve written this blog using the static site generator <a rel="noopener" target="_blank" href="https://metalsmith.io/">Metalsmith</a> and to get to grips with the data structures it creates on its journey from markdown to HTML I was looking at a way to dump the current page’s object to the HTML. This brought to light an interesting point that <code>JSON.stringify</code> cannot handle javascript objects that contain circular references, but <code>console.log</code> can. Something to remember for the future!</p> <h2 id="mag-right-unicode-arrow-mag-right-html5-unicode-mag-right-html5-semantic-elements-mag-right-mdn-date-localedate">🔎 unicode arrow, 🔎 html5 unicode, 🔎 html5 semantic elements, 🔎 mdn date localedate</h2> <p>As part of my blog’s redesign I was looking at ways to embed forward and backward arrows for post-to-post navigation and how to get some ‘pretty’ looking dates displayed on the home page. I can never remember the proper way to embed a hex reference to a unicode character in HTML. It’s <code>&amp;#x????;</code>, by the way.</p> <h2 id="mag-right-remount-usb-rw-mag-right-linux-mount-hfsplus-ignore-user">🔎 remount usb rw &amp; 🔎 linux mount hfsplus ignore user</h2> <p>I was doing some tidying one evening and came across a couple of old USB memory sticks that I had no idea what, if anything, was on them. It turned out they were from back when I ran OS X/macOS on my laptop, so getting access to them to back up the files and wipe the drives took a little bit of effort.</p> <h2 id="mag-right-jess-frazelle-docker-firefox-mag-right-vscode-x11-forwarding-mag-right-web-vnc">🔎 jess frazelle docker firefox, 🔎 vscode x11 forwarding &amp; 🔎 web vnc</h2> <p>I’m a techie at heart, and I’m always on the look-out for new, hacky, ways to set up my environment. I’d love to be able to work properly from an iPad, and I’m 90% there with <a rel="noopener" target="_blank" href="https://www.panic.com/prompt/">Prompt</a> connecting to my Linux server, but I’ve not got a good solution for very interactive GUI applications yet. I’ve followed <a rel="noopener" target="_blank" href="https://twitter.com/jessfraz">Jessie Frazelle</a> on twitter for a few years and I could remember that she had written a blog post about running <a rel="noopener" target="_blank" href="https://blog.jessfraz.com/post/docker-containers-on-the-desktop/">desktop programs like Firefox in docker containers</a>. If I could spin up an application in a container then I could deploy that to my Linux server and use it as headless terminal.</p> <p>I managed to get <a rel="noopener" target="_blank" href="http://www.mochasoft.dk/iphone_x11.htm">Mocha X11</a> on my iPhone to connect to my Debian laptop and launch vscode from there to display on my phone’s screen. It didn’t appear to work at first, as each time I connected using <a rel="noopener" target="_blank" href="http://www.mochasoft.dk/iphone_x11.htm">Mocha</a>, the new copy of vscode appeared on my laptop screen and not my phone screen. I eventually realised that vscode was sharing some sort of ‘session’ and launching subsequent copies as extra windows on the first copy – not as wholly new processes. Killing all vscode instances and trying once again from the phone worked a treat.</p> <p>It turns out that vscode isn’t a great application to run over X11, probably because it’s an electron app, so rendering is handled in a very non-X11-efficient way. I tried launching <a rel="noopener" target="_blank" href="https://keepassxc.org/">KeePassXC</a> instead and it performed a lot better.</p> <p>In my further searching around the idea of <code>web vnc</code> I came across <a rel="noopener" target="_blank" href="https://guacamole.apache.org/">Apache Guacamole</a>, which looks quite promising as a method of accessing applications remotely. If I can set it up as some kind of gateway to allow access in to my dockerised desktop applications, I might be on to something.</p> Missing Maps #mapathon @ NatureScot 2018-12-31T13:37:00+00:00 2018-12-31T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/snh-missing-maps-mapathon/ <p>One of the perks of working for <a rel="noopener" target="_blank" href="https://www.nature.scot/">NatureScot</a> is that they encourage you to take a day per yea and volunteer on a charitable project. This year some of the staff from our <a rel="noopener" target="_blank" href="https://twitter.com/NatureScotMaps">GIS team</a> organised a number of <a rel="noopener" target="_blank" href="http://www.missingmaps.org/">Missing Maps</a> events, one of which I was lucky enough to find a place on.</p> <p>In case you’ve never heard of Missing Maps or the work they do, I’ve included a link to a short video here.</p> <p><a rel="noopener" target="_blank" href="https://www.youtube.com/watch?v=1TWsCt0Yx_g"><img src="https://assets.mikecoats.xyz/snh-missing-maps-mapathon/video.png" alt="The YouTube page for the Missing Maps introductory video" loading="lazy" decoding="async" /></a></p> <p>We kicked off the event by getting everyone set-up with accounts on <a rel="noopener" target="_blank" href="https://www.openstreetmap.org/">OpenStreetMap</a> and watched a couple of quick video tutorials to get us up to speed with the kind of tasks we’d be spending the day completing. We then looked at the beginner level projects available on the <a rel="noopener" target="_blank" href="https://tasks.hotosm.org/">HOT Tasking Manager</a> and decided to work together on <a rel="noopener" target="_blank" href="https://tasks.hotosm.org/project/5529">Project #5529</a>. When we picked it, it was a high priority project with very little mapping completed on it. We felt we could make a real impact here by dedicating 19 volunteers’ time across both morning and afternoon sessions.</p> <p>Tina from the GIS team posted to their twitter account throughout the day, including the following photo of us all deeply focussed on spotting and annotating buildings across Tanzania.</p> <p><img src="https://assets.mikecoats.xyz/snh-missing-maps-mapathon/tweet.png" alt="@nature_scot in Great Glen House are having a #missingmaps #mapathon. We are mapping project 5529, come join us! NatureScot Maps, December 12, 2018" loading="lazy" decoding="async" /></p> <p>As a team, we managed to contribute a 12% boost to the project’s mapping goal and even more importantly there are an extra 19 trained and engaged volunteers out there contributing to the project in their spare time.</p> <p>The idea of contributing to OpenStreetMap can be quite intimidating to new users, particularly when you know people will be referring to your maps in the field to assist in their humanitarian works, but it’s really not that difficult and you get to make a real difference.</p> <p>If you’re still apprehensive, Missing Maps have a <a rel="noopener" target="_blank" href="http://www.missingmaps.org/events/">list of public events</a> similar to our day on their website.</p> Personal Weather Forecast 2018-12-29T13:37:00+00:00 2018-12-29T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/personal-weather-forecast/ <p>tl;dr Personal Weather Forecast <del>is</del>was a small JavaScript program that mashes together Dark Sky’s API with Twilio’s APIs to produce a personally tailored weather forecast and sends it to you as a text message every morning. The source code is available as <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/personal-weather-forecast">a project on GitHub, here</a>.</p> <p><img src="https://assets.mikecoats.xyz/personal-weather-forecast/demo.png" alt="A demonstration of the sort of output the bot generated" loading="lazy" decoding="async" /></p> <p>If you want to receive your own personal weather forecast every morning…</p> <ul> <li>check out the repository to a computer you’ll leave switched on—such as <a rel="noopener" target="_blank" href="https://www.mythic-beasts.com/servers/virtual">a virtual server from Mythic Beasts</a>, or a <a rel="noopener" target="_blank" href="https://pine64.org/documentation/STAR64/">RISC-V based Single Board Computer</a></li> <li>create a script similar to the one below</li> <li>save it somewhere accessible from your logged-in user on your server, such as <code>~/bin/forecast-sms</code>.</li> </ul> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#93a1a1;">#!/bin/sh </span><span> </span><span style="color:#586e75;">export </span><span style="color:#268bd2;">DARKSKY_KEY</span><span>=</span><span style="color:#859900;">[</span><span style="color:#2aa198;">your</span><span style="color:#859900;">-</span><span style="color:#2aa198;">darksky</span><span style="color:#859900;">-</span><span style="color:#2aa198;">key</span><span style="color:#859900;">] </span><span style="color:#586e75;">export </span><span style="color:#268bd2;">DARKSKY_LATLONG</span><span>=</span><span style="color:#859900;">[</span><span style="color:#2aa198;">the</span><span style="color:#859900;">-</span><span style="color:#2aa198;">location</span><span style="color:#859900;">-</span><span style="color:#2aa198;">for</span><span style="color:#859900;">-</span><span style="color:#2aa198;">the</span><span style="color:#859900;">-</span><span style="color:#2aa198;">forecast</span><span style="color:#859900;">] </span><span style="color:#586e75;">export </span><span style="color:#268bd2;">TWILIO_ACCOUNT</span><span>=</span><span style="color:#859900;">[</span><span style="color:#2aa198;">your</span><span style="color:#859900;">-</span><span style="color:#2aa198;">twilio</span><span style="color:#859900;">-</span><span style="color:#2aa198;">account</span><span style="color:#859900;">-</span><span style="color:#2aa198;">sid</span><span style="color:#859900;">] </span><span style="color:#586e75;">export </span><span style="color:#268bd2;">TWILIO_TOKEN</span><span>=</span><span style="color:#859900;">[</span><span style="color:#2aa198;">your</span><span style="color:#859900;">-</span><span style="color:#2aa198;">twilio</span><span style="color:#859900;">-</span><span style="color:#2aa198;">auth</span><span style="color:#859900;">-</span><span style="color:#2aa198;">token</span><span style="color:#859900;">] </span><span style="color:#586e75;">export </span><span style="color:#268bd2;">TWILIO_FROM</span><span>=</span><span style="color:#859900;">[</span><span style="color:#2aa198;">your</span><span style="color:#859900;">-</span><span style="color:#2aa198;">twilio</span><span style="color:#859900;">-</span><span style="color:#2aa198;">phone</span><span style="color:#859900;">-</span><span style="color:#2aa198;">number</span><span style="color:#859900;">] </span><span style="color:#586e75;">export </span><span style="color:#268bd2;">TWILIO_TO</span><span>=</span><span style="color:#859900;">[</span><span style="color:#2aa198;">your</span><span style="color:#859900;">-</span><span style="color:#2aa198;">destination</span><span style="color:#859900;">-</span><span style="color:#2aa198;">phone</span><span style="color:#859900;">-</span><span style="color:#2aa198;">number</span><span style="color:#859900;">] </span><span> </span><span style="color:#b58900;">pushd</span><span> /</span><span style="color:#859900;">[</span><span>repo</span><span style="color:#859900;">-</span><span>location</span><span style="color:#859900;">]</span><span>/personal-weather-forecast &gt;/dev/null </span><span style="color:#b58900;">node</span><span> main.js </span><span style="color:#b58900;">popd </span><span>&gt;/dev/null </span></code></pre> <p>To make it run every morning, you should edit your user’s crontab which with the following command…</p> <pre style="background-color:#fdf6e3;color:#657b83;"><code><span>$ crontab -e </span></code></pre> <p>…and add the following line…</p> <pre style="background-color:#fdf6e3;color:#657b83;"><code><span>0 7 * * 1-5 ~/bin/forecast-sms </span></code></pre> <p>…which references the location of the previously saved script. This cron entry means that every Monday to Friday morning at 7am you’ll receive your very own personal weather forecast!</p> <p><img src="https://assets.mikecoats.xyz/personal-weather-forecast/example.jpg" alt="A full weather forecast giving a summary text for the day and an hour by hour temperature and condition forecast" loading="lazy" decoding="async" /></p> Trump is Big Boss 2017-07-18T13:37:00+00:00 2017-07-18T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/trump-is-big-boss/ <p>tl;dr Trump Is Big Boss <del>is</del>was a silly little program that randomly generates fake tweet screenshots that attribute quotes from Metal Gear’s Big Boss to @realDonaldTrump.</p> <p><img src="https://assets.mikecoats.xyz/trump-is-big-boss/demo.png" alt="A demonstration of the sort of output the bot generated" loading="lazy" decoding="async" /></p> <p>The source code for the bot is <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/trumps-bigboss-bot">shared as a project on GitHub</a>, and the resulting tweets <del>are</del>were <a rel="noopener" target="_blank" href="https://twitter.com/TrumpIsBigBoss">posted to the parody account here</a>.</p> <p>This script is forked from my first proper Twitter bot, <a href="/jeremy-kylebot/">The Jeremy Kylebot</a>. The quotes the bot uses to generate tweets were originally ripped by the user <a rel="noopener" target="_blank" href="https://www.gamefaqs.com/community/NekuraHoka">Nekura_Hoka on GameFAQs</a>, whose complete rip can be found on <a rel="noopener" target="_blank" href="https://www.gamefaqs.com/msx/578853-metal-gear/faqs/30618">the GameFAQs website</a>.</p> <h2 id="">Examples</h2> <p><img src="https://assets.mikecoats.xyz/trump-is-big-boss/example-1.png" alt="Crooked who-now? #maga #covfefe #ego" loading="lazy" decoding="async" /></p> <p><img src="https://assets.mikecoats.xyz/trump-is-big-boss/example-2.png" alt="This tweet. From the President of the United States? #protest #news #gop" loading="lazy" decoding="async" /></p> <p><img src="https://assets.mikecoats.xyz/trump-is-big-boss/example-3.png" alt="How can he possibly say this? #potus #impeach #snowflake" loading="lazy" decoding="async" /></p> Fork Your Own Repo on GitHub 2017-07-04T13:37:00+00:00 2017-07-04T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/fork-your-own-repo-on-github/ <p>A common time-saving ‘hack’ of mine when building ‘new-but-related-to-old’ projects is to fork an existing code base and use it as the boot-strap for the new project. GitHub, however, does not provide the functionality on their website to fork your own project; you can only fork other people’s projects. Here’s how to work around that.</p> <ul> <li>Create the <a rel="noopener" target="_blank" href="https://github.com/new">new <em>destination</em> project on GitHub</a>.</li> </ul> <p><img src="https://assets.mikecoats.xyz/fork-your-own-repo-on-github/create-new-github.png" alt="A screenshot of GitHub showing MikeCoats creating a repo called test-project-2." loading="lazy" decoding="async" /></p> <ul> <li>Create a new directory on your local computer to hold the new repository.</li> </ul> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#d33682;">~</span><span style="color:#b58900;">$</span><span> mkdir test-project-2 </span><span> </span><span style="color:#d33682;">~</span><span style="color:#b58900;">$</span><span> cd test-project-2 </span><span> </span><span style="color:#d33682;">~</span><span style="color:#b58900;">/test-project-2$</span><span> git init . </span><span style="color:#b58900;">Initialized</span><span> empty Git repository in /home/mike/test-project-2/.git/ </span></code></pre> <ul> <li>Add the new project as the <code>origin</code> remote and the old project as the <code>upstream</code> remote.</li> </ul> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#d33682;">~</span><span style="color:#b58900;">/test-project-2$</span><span> git remote add origin https://github.com/MikeCoats/test-project-2.git </span><span> </span><span style="color:#d33682;">~</span><span style="color:#b58900;">/test-project-2$</span><span> git remote add upstream https://github.com/MikeCoats/test-project-1.git </span></code></pre> <ul> <li>Pull the code from the <code>upstream</code> project.</li> </ul> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#d33682;">~</span><span style="color:#b58900;">/test-project-2</span><span> $ git pull upstream master </span><span style="color:#b58900;">remote:</span><span> Enumerating objects: 3, done.remote: Counting objects: 100</span><span style="color:#859900;">%</span><span> (3/3)</span><span style="color:#b58900;">,</span><span> done. </span><span style="color:#b58900;">remote:</span><span> Total 3 (delta 0)</span><span style="color:#b58900;">,</span><span> reused 3 (delta 0)</span><span style="color:#b58900;">,</span><span> pack-reused 0 </span><span style="color:#b58900;">Unpacking</span><span> objects: 100</span><span style="color:#859900;">%</span><span> (3/3)</span><span style="color:#b58900;">,</span><span> done. </span><span style="color:#b58900;">From</span><span> https://github.com/MikeCoats/test-project-1 </span><span> </span><span style="color:#b58900;">*</span><span> branch master -&gt; FETCH_HEAD </span><span> </span><span style="color:#b58900;">* </span><span style="color:#859900;">[</span><span>new branch</span><span style="color:#859900;">]</span><span> master -&gt; upstream/master </span></code></pre> <ul> <li>Push it to the <code>origin</code> project.</li> </ul> <pre data-lang="sh" style="background-color:#fdf6e3;color:#657b83;" class="language-sh "><code class="language-sh" data-lang="sh"><span style="color:#d33682;">~</span><span style="color:#b58900;">/test-project-2</span><span> $ git push origin master </span><span style="color:#b58900;">Enumerating</span><span> objects: 3, done. </span><span style="color:#b58900;">Counting</span><span> objects: 100</span><span style="color:#859900;">%</span><span> (3/3)</span><span style="color:#b58900;">,</span><span> done. </span><span style="color:#b58900;">Writing</span><span> objects: 100</span><span style="color:#859900;">%</span><span> (3/3)</span><span style="color:#b58900;">,</span><span> 881 bytes </span><span style="color:#859900;">| </span><span style="color:#b58900;">881.00</span><span> KiB/s, done. </span><span style="color:#b58900;">Total</span><span> 3 (delta 0)</span><span style="color:#b58900;">,</span><span> reused 0 (delta 0) </span><span style="color:#b58900;">To</span><span> https://github.com/MikeCoats/test-project-2.git </span><span> </span><span style="color:#b58900;">* </span><span style="color:#859900;">[</span><span>new branch</span><span style="color:#859900;">]</span><span> master -&gt; master </span></code></pre> <p>Congratulations! You’ve successfully forked your own repo on GitHub!</p> Jeremy Kylebot 2017-06-27T13:37:00+00:00 2017-06-27T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/jeremy-kylebot/ <p>tl;dr Jeremy Kylebot <del>is</del>was a silly little program that randomly generates title cards in the style of The Jeremy Kyle Show and posts them to Twitter.</p> <p><img src="https://assets.mikecoats.xyz/jeremy-kylebot/demo.png" alt="A demonstration of the sort of output the bot generated" loading="lazy" decoding="async" /></p> <p>The source code for the bot is <a rel="noopener" target="_blank" href="https://github.com/MikeCoats/kyle-bot">shared as a project on GitHub</a>, and the resulting tweets ~are~were <a rel="noopener" target="_blank" href="https://twitter.com/JeremyKylebot">posted to the parody account here</a>.</p> <p>I tried to make the format of the show titles match the look-and-feel of genuine episodes, while at the same time I made the subject of the titles as ludicrously “British” as possible.</p> <h2 id="">Examples</h2> <p><img src="https://assets.mikecoats.xyz/jeremy-kylebot/example-1.png" alt="Coming up: Their Kestrel Soiled My Pint Of Bitter. I Want A DNA Test! #jeremykyle" loading="lazy" decoding="async" /></p> <p><img src="https://assets.mikecoats.xyz/jeremy-kylebot/example-2.png" alt="Coming up: My Step-Son Stole His Gate. I Want A Lie Detector To Prove It. #jeremykyle" loading="lazy" decoding="async" /></p> <p><img src="https://assets.mikecoats.xyz/jeremy-kylebot/example-3.png" alt="Coming up: His Boyfriend Urinated On My Front Door. Is The Baby Mine? #jeremykyle" loading="lazy" decoding="async" /></p> Intruder Alert 2013-05-09T13:37:00+00:00 2013-05-09T13:37:00+00:00 i.am@mikecoats.com (Mike Coats) https://mikecoats.com/intruder-alert/ <p>For a quick lunch-time electronics project, I’ve constructed an intruder alarm that can alert you any time somebody comes too close to your desk or cubicle. This project took me 45 minutes from start to finish including all coding, wiring and testing so—provided you’ve already got your lunch and eat it quickly—you too can be alerted to approaching co-workers before your lunch hour is up.</p> <p><video controls autoplay disablepictureinpicture loop playsinline src="https://assets.mikecoats.xyz/intruder-alert/demo.webm" alt="A demonstration of the Intruder Alert ultrasonic sensor detecting presence and alerting via an LED"></video></p> <p>Using an Arduino for this project is overkill, as we could easily accomplish the same result without using a micro-controller at all, but it is a good introduction to using analogue electronics with the board.</p> <p>In this project I’ve connected an ultrasonic range finder, a potentiometer and an LED to an Arduino to form a basic intruder alarm. The potentiometer acts as a threshold so you can easily tune the performance of the ultrasonic range finder to your local environment.</p> <p>Very simply, every time the value from the ultrasonic range finder drops below the potentiometer’s threshold value, an alarm is triggered. In this case the alarm is an LED, but it could easily—if you wanted to annoy your cubicle neighbours—be something like a buzzer. If your Arduino is connected to a PC, you can also use the serial monitor to read the debug information of the two analogue devices.</p> <p>This isn’t really a tutorial on how to use an Arduino but more of an example of what can be quickly accomplished by connecting a few simple components together and throwing a wee bit of code at a problem. If you want to build your own—or just look closer at how I built mine—keep reading for a BOM,  Source Code and Schematic.</p> <h2 id="bill-of-materials">Bill of Materials</h2> <table><thead><tr><th>Qty</th><th>Item</th><th>Source</th><th>Cost</th></tr></thead><tbody> <tr><td>1 no.</td><td>Arduino Uno – R3</td><td>CoolComponents</td><td>£19.98</td></tr> <tr><td>1 no.</td><td>Ultrasonic Range Finder</td><td>CoolComponents</td><td>£24.48</td></tr> <tr><td>1 no.</td><td>10 kΩ Linear Pot</td><td>CoolComponents</td><td>£ 1.44</td></tr> <tr><td>1 no.</td><td>5mm Red LED</td><td>CoolComponents</td><td>£ 0.40</td></tr> <tr><td>1 no.</td><td>220Ω Resistor</td><td>CoolComponents</td><td>£ 0.06</td></tr> <tr><td>12 no.</td><td>Jumper Wires</td><td>CoolComponents</td><td>£ 3.59</td></tr> <tr><td>2 no.</td><td>Mini Bread-board</td><td>CoolComponents</td><td>£ 1.44</td></tr> </tbody></table> <h2 id="fritzing-schematic">Fritzing Schematic</h2> <p><img src="https://assets.mikecoats.xyz/intruder-alert/fritzing.png" alt="A fritzing schematic of the IntruderAlert project." loading="lazy" decoding="async" /></p> <h2 id="">Arduino Sketch Source Code</h2> <p><a rel="noopener" target="_blank" href="https://gist.github.com/MikeCoats/df5969b87f739aaefda892576a22bb75#file-intruderalert-ino">GitHub Gist</a></p>