Normal view

There are new articles available, click to refresh the page.
Yesterday — 3 July 2024M0YNG.uk Feed

Modding the Dell DA-2 into a radio power supply

3 July 2024 at 20:24

The Dell DA-2 is a power supply for, I don't actually know. A Dell computer of some sort I assume. They're popular for running external GPUs, and can be easily modified to supply 12v at up to 18 amps, aka 200 watts with no fan and (in theory) decent performance etc.

So, let's modify one to use as a radio power supply! (Please ignore the continuity errors, I took the photos after doing the mod and whilst putting it back together.)

To open it up you'll need a "security" bit, S2 T10, it's a 6 pointed star with a bit in the middle. The screws are all under the rubber feet.

Closeup of the screw with the bit I used to unscrew it

Inside the case you'll find everything wrapped up in metal which is held with tape, and shimmed into the case with two flat bits down the sides. Slide those out and the rest should come free easily.

The metal wrapped power supply, held closed by kapton tape

Cut the tape and you can unfold the metal and slip the thing out.

The metal unfolded but still containing the circuit

There is a big flat copper thing that connects the earth/ground/0v from one side to the other. You'll need to unsolder this to remove it.

A big copper rectangle on top of a black plastic wrap

Yet more unwrapping! Unwrap the plastic thing and you'll finally see the actual circuit.

This is where you need to tin your soldering iron and make sure it's hot as there are two bundles of wires that need removing and it'll take a lot of heat. Be patient and make sure all the solder is liquid before pulling them out.

There are also two smaller wires that need disconnecting.

back of the circuit board with the things that need de-soldering highlighted, it's the two big chunky mountains but you knew that already

Decision time. The supply can be switched by connecting pin 5 to ground. Although not any more as we just removed that wire... If you want it to always be on you can just short the through hole marked "remote" to ground. Or you can do what I did and wire in a switch.

I've no idea what "red" is for. I didn't connect anything to it.

Time to get the soldering iron to work again and connect your +ve and -ve wires. Use something chunky enough to handle 18 amps! I used some of the cable that came with my Icom IC-706 which I'd already cut in bits and fitted Power Poles onto.

Other side of the circuit board with chunky red and black wires and thinner wires added

Now, put it all back together! Don't forget the copper thing. Plastic wrap, copper, metal, tape, shims down side, case, screws. Be careful you don't trap any of your new wires on the screw posts.

Before yesterdayM0YNG.uk Feed

Solar Panels and Battery and Heat Pump Info Dump

24 March 2024 at 21:27

I got solar panels, and a battery, and a heat pump! Be prepared for an info dump.

I've been writing this blog post for a month now, so if there are things missing or questions unanswered ... sorry? Maybe I'll do an update next year.

What do I have, what is it?

  • 11 x 400w Solar Panels
  • 1 x 3.6kW Inverter
  • 1 x 9.6kWh LiFePO4 Battery

It makes and stores electricity, the battery can be charged from and discharge to the grid.

Panels are West facing in Gloucester, UK.

No "Isolation" from grid. Grid goes offline, we go offline.

  • 8kW (output) Heat Pump
  • 9 new radiators
  • 1 x hot water cylinder

It's an Air Conditioner, running backwards, and dumping heat into either hot water, or hot water ... for radiators. Some new radiators needed as HP works at lower temperatures than gas boiler - lower delta between radiator and air means increased surface area needed to transfer the same heat.

Why?

Would prefer to avoid murdering the planet.

Cost, I guess? Having our own generation insulates us from the cost (a bit) and heat pump is more efficient than gas boiler.

Getting it

  • All from Octopus Energy
  • Solar was fairly easy
  • Heat Pump was more involved / annoying

Checking my privilege

Lucky to be able to do this.

  • We have space!
  • We have time!
  • We own the house!
  • We had money thanks to inheritance from Granddad!

Solar

  • Initial enquiry
  • Phone call to discuss what we want and what they offer
  • Survey
  • Proposal / plan with specifics and costs
  • Book install date
  • Scaffolding! Came a few days before
  • Installation took a week
  • Solar panels on roof - quite noisy having mounting hardware drilled into the rafters!
  • Battery and Inverter in garage - quite noisy having holes drilled in the wall for cables!
  • Needed upgraded smoke alarms - we now have a connected detector in the garage that will alert us if anything goes badly wrong.
  • Scaffolding removal, left a few days after

The panels are only on the West roof, we did get a quote including the East roof but it would have been disproportionately more money for little (although not nothing!) extra generation. It wasn't in budget at the time, but maybe we can add more later.

Total cost of install about £11k.

It was installed in October 2023, so I only have about 5 months experience with this, and all of that was in the "low" season.

Heat Pump

  • Initial enquiry
  • Phone call to discuss
  • Survey
  • Proposal / plan with specifics and costs
  • ! Need "confirmation" from council that we're "allowed" a pump because the Heat Pump is "highway facing" (it's not, there is a wall) this was a pain, no one at the council thinks it's an issue so getting a "confirmation" was hard. Apparently the rules are relaxing.
  • Various issues getting confirmation as no one at the council thinks this needs their permission
  • Eventually get someone from planning to "officially" point me at permitted development
  • Book install (months later!)
  • Pre-install visit to tweak plan
  • Installation took a week and was very invasive
  • HP out front - pretty easy
  • Hot water tank, buffer tank, controller all in garage - we did move them to be more out of the way, which was worth it.
  • 9 out of 11 radiators replaced - this is fairly invasive as basically every room had a radiator replaced.
  • old boiler removed
  • old hot water cylinder removed
  • Floor taken up to route new pipes from garage to existing pipes - this was absolutely worth doing, but quite disruptive during the work and it's ruined the underlay.
  • no heating or hot water for most of the week - it was cold. The canal froze over. Would not recommend.
  • system issue a few days later! Engineer visit.
  • post-install check-up / filter clean 4 weeks later

Total cost £10,500 - but government "Boiler Upgrade Scheme" paid £7,500 of that = total "out of pocket" £3000 (aka the cost of a new boiler.)

The pump was installed in Mid January 2024, at this point we've had it less than 2 months, although they are the coldest months of the year with my garden temperature not getting above 15 and averaging around 10.

(Note: the BUS is only available for heating, if you want a system that can also cool you in summer - that isn't eligible. Or so I'm told. Although good luck cooling with convection heaters (aka "radiators".))

Living With it

Heat Pump

Boring.

It just works.

Seriously, I barely think about it.

The only reason I do think about it is because it uses electricity and I'm obsessed with optimising the solar + battery usage.

Rooms are warm, water is hot, it blows freezing air even when the air is already freezing.

We've got the thermostat set to 19°C, but it's by the front door and a bit colder than average, which keeps the whole house a comfortable 20°C ish.

One downside of having the hot water tank in the garage is the hot water is now further away from the taps, so you have to wait a bit longer for it to come through. Previously the tank was in the geographic center of the house so there wasn't much distance to travel for the hot water, but now it has to come from the back of the garage, under the floor of the study, and the landing, before it gets to the starting point of the old tank. However, I'm assured that water costs less than electricity and the reduction in efficiency would be significant enough that this works out the better solution.

Sometimes the hot water runs out, unlike the old gas boiler that would wait for the next scheduled time to make more the heat pump will kick in and get heating. This is good, because we have more hot water, but it's bad if we're running low on stored power and it means we draw expensive power from the grid.

The water is set to the higher of the two default presets, due to the longer run, and we could probably save a bit / improve the efficiency by using the lower setting. Maybe in the summer!

Does it work when it's cold? Yes. Obviously. The week we had it installed the canal froze over. We had the hot water enabled first and we could have hot showers that same day, whilst it was cold enough for the canal to freeze from side to side. We had the house heating turned on in the last afternoon and were back to normal internal temperatures that same evening.

Lifetime COP (Coefficient Of Performance) is 3 (so far), for heating and water. For every 1kWh of electricity put in it has pumped 3kWh of heat. Hot water is less efficient than heating - but the combined total is 3.

I've done the maths and it is costing less to run than a gas boiler.

Looking at a bill from December, we used 443.7kWh of gas, at 6.49p/kWh for a total of £28.81. At 90% efficiency (an A-Rated boiler) that's 399.33kWh of heat output. At COP of 3 the Heat Pump would use 133.11kWh of electricity to do the same. We're paying 17p/kWh (on "Cozy Octopus") or £22.63.

Heck, even if the gas boiler was 100% efficient the heat pumt would cost £25.14 for the same heat output. Which is still less than gas!

Admittedly we did use some gas for cooking, but it will not be £6 worth.

We've also scrapped the gas hob and installed induction. We're completely off the gas grid. Not paying the standing charge will save over £95 a year, and nearly £115 with the new price cap come April. https://www.ofgem.gov.uk/energy-price-cap

Stop Press: The April prices have been announced. Gas will be 6p and "cozy" electric will be 11.676p, which is less than 2 times the unit cost of gas. That same example would be £26.62 as gas and £15.54 with the heat pump.

Not to mention we're now not putting all the combustion by-products of gas into the kitchen when we cook.

And on a sunny afternoon we pay £0. Yes, this does happen, even in February. It's worth a reminder that the Heat Pump and Solar are different systems, we didn't get one to support the other. They do overlap and help each other (well mostly the solar helps the pump!) but running costs etc. should be considered in isolation because yes, solar won't produce enough to run the pump when we most need it in winter.

The financial break even point on this is negative, it's already about the same as buying a new boiler and the running costs are less. Admittedly we didn't NEED a new boiler...

The climate break even point is harder to know, but still encouraging. I asked Octopus sales, and the installation team, and the manufacturer, none can tell me the embedded emissions of manufacturing this system and installing it. HOWEVER, given the worst common situation where most of the electricity on the grid is generated by gas - we're still emitting less CO2 by using that electricity to pump heat rather than burn it for heat. I don't know exact numbers, but the losses in the electric system would have to be massive to counteract a 300% efficiency. Plus, we're with Octopus so (at least in theory) 100% of our electricity is renewable, and when the sun shines it's our own solar being used.

I'm a massive nerd so you might expect me to have connected this to my Home Assistant system, but I haven't. The options are all bad, there is no open source integration because the manufacturer has an NDA (Non-Disclosure Agreement) before you get access to the API, so it's impossible to do an open source anything with it. Plus the app has options for logging in with Facebook ... and fuck that, if you're going to even give that as an option I do not trust you with my home heating.

However, I have got a clamp energy monitor on the power supply so I can at least monitor the power separately from the rest of the house, in Home Assistant.

The control panel in the garage does give some data, for example kWh used, kWh heat generated, etc. which is where the figures here have come from.

Checking my privilege again

Given that 9.6 million households in the UK are living on incomes below the minimum income standard and in poorly insulated homes.

And these households have most to gain from more efficient heating.

But the government grant is only available to homes with a valid energy performance certificate for the property which does not recommend installing loft or cavity wall insulation.

And it's not just heating for comfort.

The Building Research Establishment Group (BRE) has calculated that the very worst housing in England – approximately 720,000 homes with a category one hazard of excess cold (typically insulated to bands F or G) cost the NHS £0.5 billion per year in first-year treatment costs alone.

That's probably more than a million people (assuming more than one person lives in a home, on average) who are so cold they end up needing medical treatment.

We urgently need to insulate Britain and roll out heat pumps.

Solar + Battery

I am a massive nerd and obsess over this ... a lot. I suspect that if I'd lived in the past I'd have been the one obsessing over having enough fire wood, and water, etc. for the village.

There are a few different ways to live with solar and storage, one is to basically ignore it and carry on as normal. This is somewhat pointless as you'll inevitably end up using power at different times to when it's being generated. This is mitigated somewhat by having storage, now you can store power for use later, especially important for things that can't really move about much, like cooking dinner. Some care is needed for this to work - the inverter can only provide 3.6kW, which is plenty to boil the kettle, run the oven, dishwasher, etc. but not at the same time and this is where you can start drawing from the grid for example if you boil the kettle whilst cooking, or the tumble dryer at the same time as the washing machine. I've started to get cleaver with this and monitor the energy draw of the washing machine as once it has heated the water it doesn't use much power, then I can turn the tumble dryer on and run them at the same time. It's risky, there isn't any overhead if someone turns on the toaster!

In some ways this is the least impressive thing because I really enjoy the days when it's overcast, even raining, and we're generating more electricity than the house is using. It's great to sit at my desk with music playing, lights on, computers running, and be doing it all from solar. In the rain. In December.

Most of the time we don't worry much about this, and with some care to make the most of the generated solar and charge when it's cheap, and not overdo things when cooking dinner, it's just like a normal house. But with the nice realisation that you're watching TV at night on the electric you generated that afternoon.

The battery has a 9.6kWh capacity, so enough to run the house for 24 hours, although admittedly not with the heat pump! Although in the summer I expect we'll be able to operate without using the grid at all, we've already done in multiple days in October!

It's really intuitive, if the suns out we're making power. It's even possible to have a sense of exactly how much and if it will last, the brighter it is the more we're making, and the cloudier it is or if the weather is blowing in we'll probably not have it for long. Clouds make a real difference, even small fluffy ones can drop generation from 2.2kW to 400w whilst they pass.

Because the panels are all West facing there is a significant delay in the mornings before we start generating the big watts, today (3rd March 2024) generation started around 7am, and by 10am it was still only making 275w. But then the sun was in a position to illuminate the panels directly and generation shot up to over 2.2kW by 12. It will stay high until late afternoon / evening (weather permitting) until it falls off a cliff when the sun goes behind some tall trees across the road. This is noticeable in winter, but a near vertical drop in summer.

Data and control of Solar + Battery

The battery and inverter are made by GivEnergy, I was made a cloud account for the system when it was installed (as in, the engineer handed me my username and password!) but it also has complete local control via the GivTCP software / Home Assistant integration. As a result I have a lot of data and full control of the system.

I also use Forecast.Solar to get predictions of generation which helps me plan usage and charging, especially when combined with the Unofficial Octopus Energy integration.

I have this data almost constantly visible, I've curated a dashboard on Home Assistant which gives me quite a lot of detail, including power flows to/from the solar, battery, grid, house, and heat pump. Forecast generation, "running totals" of generation, usage, import, export, tariff, cost, grid carbon, "benefit" (saving in money/carbon) etc.

I've got an e-ink display on my desk than updates every minute with key bits (plus other house stuff like temperature) and I've also got some widgets on my phone home screen so I can pretty much always know power going to/from the grid, solar, and battery, plus battery charge level, grid carbon, top grid fuel, and outside temperature.

One of my favourite (but also I have complicated emotions about it) is the "cost per kWh generation". It's a simple "sensor" which divides the total system cost by how many kWh we've generated - so far every single kWh of solar generation has cost £18.82 which is quite a lot! This is a kind of "break-even-o-meter" as once this gets to the same cost per kWh as buying from the grid, we've broken even.

I've also got a bunch of notifications that are sent over Matrix.

This one is sent at the start of "cheap times"

Electric import is about to be £0.168739 Target charge 72% (currently 40%, 4.7 kWh solar left today) and/or forcast generation in the next hour is 1.5 kWh starting at 13:00:00 and ending 16:00:00

At the end of the (solar) day we'll get one like this

☀️ production today 13.5kWh 🔋 battery is 88% charged. 🔮 Forcast generation tomorrow 5.897kWh

At midnight the daily summary will be delivered (the numbers often don't add up, I blame rounding)

Saved £3.13 and 3085.34gCO2 today by using the battery (£2.12 | 8.6kWh | 1577.60gCO2) and solar (£1.30 | 11.0kWh | 1612.31gCO2), plus exported 2.5kWh.

Making Money with the Battery

Yeah, really.

Well, kind of.

For a couple of months we were on Octopus' "Intelligent Flux" tariff which takes control of the battery and charges it overnight when electricity is cheap / carbon is low, and then tops it off during the day using solar or the grid. It then pays extra for your exports during peak time, by about 10p per kWh. With a 9kWh battery you can in theory make 90p per day, just by charging and discharging the battery. In reality the charging algorithm they use isn't great, and it never discharged as much or as fast as I thought it could, and I never managed to get someone to talk to me about it.

There are other tariffs that are "manual" but also work in a similar way.

Exporting usually pays about 15p per kWh, which is a lot less than buying it costs, so it's not usually worth charging and then discharging it later on normal tariffs. However, Saving Sessions / Demand Flexibility are times it can be worth it, especially if you've got some solar in the store. I've got my system set to automatically dump the battery to the grid at full power during these sessions. We don't usually draw from the grid during peak times any more but it still gives us points as negative "use" is still a "reduction" on nothing!

We're now on the "cozy" tariff which is specifically for Heat Pump users, it charges the normal amount most of the day, with two 3 hour periods of 40% cheaper power for running the heat hump. BUT we also charge the battery in this time, which means we're saving 40% all day long. This is especially useful because there is also a peak period which costs more, during the early evening. Cooking off battery is saving us paying 45p per kWh during that time! My rudimentary sensor in Home Assistant says we've saved about £35 per month by charging when cheap and running off battery the rest of the day, although admittedly some of that power was from the solar - not much though as this was late January to late February. During the same period we saved nearly £19 by using solar generation straight into the house.

We could possibly save more by using the "Agile" tariff, which changes per half hour. But I've not done anything to work out for sure.

Other suppliers have tariffs that vary by time of day and/or grid capacity so I'm pretty sure most people could save money with just a battery. Even the old Economy 7 tariffs would work here as you could charge the battery up over night and reliably have power during the day.

Stop Press: The new prices for April have been announced and we'll be paid more to export (15p) than import during the "cozy" times (11.676p) so this completely changes the calculations on what's "worth" exporting (everything, all the time.)

Return / Break Even

The financial return on this system depends a lot on how we use it. During planning the prediction was we would generate about 53% of our yearly usage, which doesn't sound great. But cutting an annual bill in half doesn't sound like a bad idea! Our average usage in February was 17.4kWh per day (including the heat pump), and the estimated generation in summer is 17kWh so I suspect we'll be exporting quite a lot too.

17.4kWh per day for a year is 4623.8, at our cozy rate we're looking at a worst case total year cost of £786 (excluding standing charge.)

The design pack predicts 3627kWh generation per year, which is 78.4% of that figure. Or about £616 of electricity we're not paying for every year.

On average days we use up to 10kWh excluding the heat pump, meaning we could be exporting 7kWh a day in summer, netting an amazing £1.19 per day! That's £71 over 60 days of peak generation!

One issue is that peak production is during summer and peak use is winter, BUT Octopus pay us a fixed 15p / kWh, and we pay 17p / kWh on the cozy times, so on average, we're paying 2p / kWh.

Design pack predicts a break even point of 6 years 6 months. But we'll have to see how this adjusts based on our real world usage.

The battery has a guaranteed 10 year life, and the solar panels are predicted to last 25 years before performance degrades below 80%. Even then I expect the battery will keep working, and could be replaced and/or upgraded at much reduced cost in 10 years, and even 80% efficiency isn't that bad. Point being, all of this is way past that 6.5 year prediction.

But we didn't do this for the money, although it is nice to have lower and more predictable expenses.

Like the Heat Pump, it's impossible to know the embedded environmental cost of the system. The panels were made in China and no one knows anything about this process. We even had a visit from Octopus France who are starting to supply solar soon and wanted to see how it was being done here, and I was being told that in France they have a legal requirement to know the whole like carbon cost, and the Chinese manufacturing is causing problems because no one knows! Then there is the issues of lithium cells and the significant issues of the mining of the raw materials used in them. All I can really do here is trust that Octopus have done the work and the entire supply chain is slavery free, low carbon etc. GivEnergy claim the battery is "Ethically sourced and cobalt-free." I did ask, and it's the same story as the Heat Pump, no one knows.

In the last week (in February 2024) we've avoided 4kg of CO2 emissions by using solar directly rather than from the grid. Or 7.2 seconds of Private Jet flight. (See my post on High Draw and Low Carbon Automation for the details of that maths.) At time of writing we've generated 600kWh, which would have prevented 145kg of CO2 emissions at the current generation cost of 234gCO2/kWh. It's not much, but it's part of my rebellion against extinction.

Checking my privilege again again

There was zero chance of my affording this system on my own. We could only do it due to inheritance from my Granddad. I think he's be fascinated by the technology (he was always keen to show me his new computer, laptop, gadget, etc.) and the knowledge that he's helping provide our power - just like he did when he delivered coal back when he was younger than I am now.

I'm also very aware that I live in and own a house we can modify like this, that has a roof suitable, and space in the garage. We're (disappointingly, everyone deserves somewhere affordable and safe to live) very fortunate to be in this position.

Optimising Battery Charge

We're in the most difficult period of the year for optimising this (writing in February/March), as the solar generation isn't very predictable, but it also isn't nothing.

The charging is a bit stupid, if the battery is in "eco" mode any solar we don't use immediately in the house goes to the battery, and if it's full, the spare goes to the grid. But if it's in charge mode it draws from the solar AND the grid, up to a set target. Once it reaches the target it will stop adding more to the battery, even if there is capacity and the solar is generating.

SO, I want to maximise the solar we capture, but also not end up running from the grid when it's expensive. I want to set that target to shove as much solar as possible in there, and top it off with cheap grid, even though we often have to grid charge first. But doesn't put so much grid power in there we run out of storage and send some back to the grid.

This is my current "algorithm" that runs at 59 minutes past every hour.

[100  - (float(states.sensor.energy_production_today_remaining.state) / 9.6 * 90)
      + (float(states.sensor.energy_next_hour.state) / 9.6 * 100)
      | round,
      20] | max

What?

  • Start with 100%, a fully charged battery
  • take away the forecast solar production left today, but only 90% of it, we're pessimistic
  • add back the forecast solar production in the next hour, we want that going into the battery not the grid.
  • round that to a whole number
  • pick whichever number is largest, the calculated value or 20

In tandem I have a script which looks at the next period, and if the cost of import is low (below 20p) it will schedule the charge at that cheap time. But it also checks if the current charge is lower than the target, because if it is we don't want to enable "charging" because the solar will go into the grid not the battery. Which is pointless. But it also checks the forecast generation for the hour, if that's very low or nothing we'll still enable "charging" even if it means we just run from the grid for that time, because any high loads (i.e. the heat pump) will quickly drain the battery leaving us with nothing for when the price goes back up.

It's not perfect and I suspect that this is a situation where some sort of self-adjusting model would work well, especially if it can also consider the average or likely demand for power (is it cold? The we'll need heat.) and even the forecast carbon intensity of the grid (can we charge later in the cheap period and save some CO2 emissions?) but I have no idea where to start with this.

Stop Press: With the April prices it's going to be pointless to do any of this, I may as well stuff the battery from the grid during "cozy" time and export everything I generate, getting "paid" 3.324p for the difference.

Energy isn't Fungible

Like money or monkeys, we've dealt with electricity for a long time like it hasn't mattered where it comes from or what it does. But it does. 1kWh from coal isn't the same as 1kWh from wind. And it doesn't matter how green the generation is if the 1kWh is used to mine crypto when it could have been used to heat someone's home.

Ultimately I think we have the tools to mitigate many climate issues, and now I've got a bunch of them in my house. Heat Pumps are more efficient, they cost less to run and emit much less / or no / carbon. Batteries enable us to flatten the curve on demand, meaning we can minimise the most polluting generation and store the least polluting for later.

We need a significant shift in attitudes from everyone, especially politicians, to make this change. But my house shows we can do it.

Beepy charge mods for infinite battery life

3 September 2023 at 16:03

Well, effectively infinite anyway.

I <3 my Beepy and use it a lot, something which is hindered by the fact that the stock charging current is limited to 100mA due to a 10k resistor on the TP4054 charging IC.

So, I ordered a bunch of tiny resistors and got the soldering iron out.

Increasing charge current

The charge IC is limited to 500mA so although the 2000mAh cell could in theory take a full 2A of charge current, we're going to stick with around 500mA for now.

You will need:

  • A working Beepy
  • A 2k 0603 surface mount resistor
  • Soldering iron
  • Flux, solder, etc.

The resistor to change is R4 just by the charge controller.

A tiny resistor highlighted with a pink ring, it's on the positive side of the battery connector above a 5 pin component and is marked 103

Simply (ha!) add some flux, heat the pads, and slide the component off with a little force - not too much, we don't want to damage the pads.

Clean it up however you want, if you want, and maybe add some fresh solder and flux ready for the new resistor.

Use tweezers to position the new resistor and melt the solder on one end, then the other, hopefully it lines itself up with the pad and you're done. Just clean up the flux with some IPA.

A tiny resistor marked 308

It was at this point I took a photo and noticed the resistor I'd got from a multipack wasn't actually the right value and I didn't have a 2k resistor in the set! I fudged it with the closest value I had to hand and repeated the process. No photos, it wasn't as pretty the second time!

EDIT: I've been told that it is actually a 30B resistor which IS 2k at 1%, but I've never worked with components like this before and that's my excuse. Now you hopefully won't make the same mistake. (p.s. this edit made on the beepy!)

Beepy in a bright pink case connected via USB to a power monitor reading 0.5A

It works!

"Infinite" battery life

Ok, great, now we can charge it faster than it discharges itself at idle, pretty good.

But, what if, Qi?

Qi is the name of the standard most of us know as "wireless charging" and you can buy bare bones receiving coils and circuits quite cheaply, and small enough to hide in the Beepy.

I ordered a 1cm by 1.5cm circuit and coil for £6.65 - it can deliver 0.6A and charge the Beepy fine when it's turned off, but struggled and stops working after a few minutes when the Beepy is running so you may want one that can do around 1A. If they exist.

Back of the Beepy PCB facing towards the USB C connector. Arrows point to small wires soldered to the far right pin of the USB C port which is ground and the bottom of a resistor which is 5v

Solder the ground wire to any ground pin, I used the end pin of the USB C Port.

Solder the 5v wire to the top of Diode 1 (D1) this has a direct connection to the USB C port's 5v line so DO NOT CONNECT Qi AND USB AT THE SAME TIME.

That's basically it, I wrapped the Qi PCB in heat shrink (yes, I had to unsolder it because I forgot!) and tucked it all into the case.

The coil is placed on the battery, which probably isn't ideal due to heat, but there isn't much space and you need the coil in the middle so when you naturally place the Beepy down on the charger it is in the right place. You WILL need the little black pad behind the coil for it to work (in my experiments.) Maybe a suitable case could place the battery lower down and have a slot of the coil in the middle?

Now you have basically infinite battery life! Just pop the Beepy down on the charger when not using it and it will charge, the pick it up to use without having to faff with wires.

Beepy in a bright pink case on a Qi charging thing with a power monitor reading 0.78A

(This reads more than over USB due to the inherent inefficiencies of Qi charging.)

High draw low carbon automation with Home Assistant

7 August 2023 at 21:49

I'll admit this is a bad a title, but there isn't a snappy way to describe this.

Why do?

Not all electricity is created equal

Maybe you've never thought about this, but not all electricity is created the same way, and every method has a different environmental impact. Plus the way the electricity delivered to your home is generated changes throughout the day, week, month, season, and over years.

For example, as I write this, Scotland is generating 68% of its electricity from Hydro, 11.4% from wind, 10% nuclear, and the rest is imported - a mixture that emits 21 gCO₂ equivalent per kWh of electricity.

In contrast, South Wales is generating 96.9% by gas and 3.2% by wind, emitting 381 gCO₂eq/kWh.

(You can see the real time info for loads of countries at Electricity Maps)

So, if I have a dish washer to run or a load of washing to do right now, it would be best to do that using the Scottish electricity and not the South Wales stuff.

But you can't just plug into different bits of the grid at different times, but you can wait until the local generation is producing as little CO₂eq as possible.

Think of this as "flattening the curve" (remember that from Covid?) we aren't trying to use less electricity, but we are trying to use it when it's most abundant so we don't need to use costly methods of generation. The National Grid experimented with this last winter, paying people for every kWh they didn't use during peak times (compared to that home's average usage at that time of day) when the grid was at risk of having to bring coal plants online.

How do?

There are some problems to solve here:

  1. How do I know how much CO₂eq is being emitted by the grid right now?
  2. How do I know how much is likely to be emitted in the future, so I can decide if I should wait or do it now?
  3. How do I communicate that information?

1 - CO₂eq NOW

This is fairly easy, Carbon Intensity API exists and will give you this info for the current 30 minute chunk and it super easy to use, you can just give it a postcode!

For example, pop over to https://api.carbonintensity.org.uk/regional/postcode/RG10 and you'll get a JSON blob with the CO₂eq value, an "index" from "Very Low" to "Very High" and even a breakdown of what generation sources are in use.

(Electricity Maps also have an API if you're not in the UK, it's not as specific as the National Grid data, and you need a free API key - it's called CO2 Signal)

I have a python script which is run by cron to grab this data and shove it into MQTT where Home Assistant does stuff with it - more on that later.

'''
Gets the current Carbon Intensity data for the region
'''
import requests
import configparser
import paho.mqtt.publish as publish
# load the config
config = configparser.ConfigParser()
config.read('config.ini')
# we need this for every request to the API
headers = {
  'Accept': 'application/json'
}
# a request for the current carbon data
r = requests.get(f"{config['ngci']['baseURL']}regionid/{config['ngci']['regionid']}", headers = headers)
# convert that to something we can use
currentData = r.json()
# print out the data
print(currentData)
# the "messages" we're going to send to MQTT (aka the data from the API)
messages = [
# basic intensity info
        {
            'topic': f"{config['mqtt']['topic']}current/gco2",
            'payload': currentData['data'][0]['data'][0]['intensity']['forecast']
        },
        {
            'topic': f"{config['mqtt']['topic']}current/index",
            'payload': currentData['data'][0]['data'][0]['intensity']['index']
        }
]
# add a message for each of the generation types
for fuel in currentData['data'][0]['data'][0]['generationmix']:
    messages.append({
            'topic': f"{config['mqtt']['topic']}current/{fuel['fuel']}",
            'payload': fuel['perc']
            })
# show me the messages!
print(messages)

# Send the messages off to MQTT
publish.multiple(messages, hostname=config['mqtt']['server'], auth={'username': config['mqtt']['username'], 'password': config['mqtt']['password']})

2 - Future

OK, great, but what if it's currently "Medium" intensity, do I run the dishwasher now because that's as good as it will get, or wait because it's going to get lower later?

It's important for the National Grid to predict demand so they can match it with supply, and to do that they need to be able to predict supply too so there is some pretty good data for this.

Most renewables can be forecast pretty accurately, wind will use weather forecast data for the wind, solar will use a mix of weather, time of day, time of year, etc. and the same API also has endpoints for predicted CO₂eq per 30 minute chunk for the next 24+ hours.

I have another python script which checks this API each morning and sends the data via MQTT to Home Assistant, which sends it on to me via a Matrix room.

'''
Gets the forecasted Carbon Intensity data for the next 24 hours
'''
import configparser
import requests
from datetime import date
from datetime import datetime
from dateutil import tz
import paho.mqtt.publish as publish

config = configparser.ConfigParser()
config.read("config.ini")

to_zone = tz.gettz('Europe/London')

forcastAPI = f'{config["ngci"]["baseURL"]}intensity/{datetime.utcnow().replace(microsecond=0).isoformat()}Z/fw24h/regionid/{config["ngci"]["regionid"]}'

lastHalfIndex = ''
combinedText = ''

r = requests.get(forcastAPI)
for halfhour in r.json()['data']['data']:
    if lastHalfIndex != halfhour['intensity']['index']:
        dtUTC = datetime.strptime(halfhour['from'][:-1], '%Y-%m-%dT%H:%M')
        combinedText += halfhour['intensity']['index'] + ' from ' + dtUTC.astimezone(to_zone).strftime('%H:%M') + '\n'
        lastHalfIndex = halfhour['intensity']['index']

print(combinedText)

publish.single(f"{config['mqtt']['topic']}forecast", payload=combinedText, hostname=config['mqtt']['server'], auth={'username':config['mqtt']['username'], 'password':config['mqtt']['password']})

3 - Communicate

There are two main ways I communicate this, a bot that sits in a Matrix room, and switches attached to the "high draw" machines I want use at different times.

Matrix is triggered by Home Assistant automations looking for changes to those MQTT topics, if the forecast changes, it tells me, if the index goes High or Low it tells me that too. I'm sure there are many ways you can tell yourself stuff and you'll probably already have a favourite.

Each "high draw" machine (dish washer, clothes washing machine, tumble dryer) has an energy monitoring connected switch attached. These are all dumb appliances and I want to keep it that way.

I'm using these tasmota energy monitor switches from Local Bytes as they came pre-flashed, from the UK, and I had already destroyed an identical looking and more expensive device trying to flash ESPHome onto it before.

The main way these switches communicate is by turning the devices on, or off, depending on the index value. The basic logic is simple.

IF index is High or Very High:
    turn switches off
ELSE IF index is Low or Very Low:
    turn switches on
You'll notice there is no consideration for "Medium" intensity, I don't feel this is a "bad enough" or "good enough" index to cause a change, and often the index will go back to what it was before anyway.

This makes the logic slightly more complex because we can't just slam it on or off regardless, so I made a "helper toggle" in Home Assistant called "High Draw Active" and the logic checks it before doing anything, this also prevents it turning off switches that are already off if the index goes from High to Very High.

IF index is High or Very High:
    and "High Draw" is active:
        turn switches off
        turn "High Draw" off
        tell me it was turned off
ELSE IF index is Low or Very Low:
    and "High Draw" is off:
        turn switches on
        turn "High Draw" on
        tell me it was turned on

I could probably break this out into three bits that all listen to or change the "high draw" toggle, but I haven't felt the need ... yet.

It also checks to see if a machine is running before turning it off. I have some "Dropdown" helpers that store the state of each machine (running, finished) and the energy monitoring switches allow me to detect when the machine is running, and when it has finished (and notify me via matrix) Obviously I don't want to turn off a machine that is running so the automation won't do that, but it will tell me that it hasn't turned it off because it is running.

What do?

It has adjusted our habits, more than I thought it might.

The motivation for, and implementation of, this is supported by other people in the house and it has a good "spouse approval rating" - which is helped by the fact that all switches can be overriden if needed.

What impact does it have though?

Some numbers

My dishwasher seems to use around 1kWh per cycle, which is helpful for doing maths.

Since I installed my monitoring sockets the dishwasher has used about 50kWh, so if we use the numbers from before the emissions would be:

  • 19,050 gCO₂eq for 381g in South Wales
  • 1,050 gCO₂eq for 21g in Scotland

That's an 18kg difference in emissions.

Now, I'll admit that I don't have actual numbers for my real usage, in theory I could work them out but I haven't got my head around the maths for that yet (I have dyscalculia) but I think it makes the point!

Completely Pointless

However, this exercise is completely pointless, depending on how you look at it, because:

  • I'm with Octopus Energy so all my electricity is renewable anyway
  • I don't pay anything different for using low carbon electricity

Completely and utterly pointless

Seriously, it's completely and utterly pointless.

The average private jet emits two tonnes of carbon an hour Flying shame: the scandalous rise of private jets - The Guardian

Therefore that 18kg hypothetical difference is 54 seconds of a private jet.

I'd have to run the dish washer 5,249 times at 381gCO₂eq/kWh to make up 1 hour of private jet use.

If I didn't care and only used the dish washer when the grid is incredibly carbon intensive, and did it every day, at 381gCO₂eq/kWh, it would take 14 years and 4 months and 17 days to emit as much CO₂eq as one hour of private jet usage.

It's the billionaires, there is no way I can make even the smallest scratch in the tiniest dent on the climate crisis when billionaires are emitting a million times more greenhouse gases than the average person - we cannot capitalism our way out of this.

What's next?

We're in the process of getting solar panels and battery storage.

I expect I'll swap the triggers for this to be when the panels are generating, at least in the summer.

Another aspect is cost, if we go on an "economy 7" tariff we get 7 hours of cheaper electricity overnight, which we could use to charge up the battery (especially useful during winter) and run things like the dish washer.

Maybe there will be three triggers to balance, solar generation, electricity cost, and the emissions of the grid.

I'd like to make a "smart carbon meter", like the smart energy meters that show you how much electric/gas you've used and give a budget for the day/week/month it might be interesting to set a "carbon budget" and have a display that shows the emissions, the budget, and the status of the switches and the grid generation intensity. In theory I have all the data, and the hardware (a Lilygo T-Display) but I lack the time/skills to get this working.

TPU with the Anycubic Vyper

16 July 2023 at 12:27

The Anycubic Vyper is a pretty good 3d printer, in my experience of only ever using two 3d printers. It has a bowden tube, which means the extruder (that pushes the filament into the hot end) is a good 30+ cm away from the hot end so, can it print using TPU, a flexible and soft plastic?

YES!

Vyper mid TPU print, with pink filament dropping slightly as it heads into the extruder

Go slow

This seems to be key, print slow, very slow. I'm finding 20mm/s works well (20 being the top speed, the outside is going at just 10mm/s)

I'm using Overture TPU which says it's suitable for "direct extrusion" - oops! The "shore hardness" is apparently 95A which is very flexible, maybe you can go faster with a more firm filament.

I'm mostly using the Cura preset "Generic TPU 95A" with 0.2mm layer height, 2 walls, 4 bottom and top layers, zig-zag pattern, 228 hot end, heated bed off, part cooling fan at 100%, retraction enabled, and a skirt.

I also find the experimental / hidden settings of "Infill travel optimisation" and a "combing mode" of "Not on Outer Surface" help avoid stringing from nozzle oozing as it travels between areas - because these both reduce or hide that travel.

With these setting the tyres for this tractor model have basically no stringing in the middle and only the odd hair on the outside.

A stack of four pink tractor tyres

The cali dragon doesn't do quite as well, but to be fair it is designed to highlight issues like this! I find a gentle heat from a lighter or hot air gun can tidy up the strings pretty well.

A small pink dragon with some blobby bits of filament on the horns and some blackened bits where the flame got too close

Size can also help, I find larger models print much cleaner, possibly because the layers have time to cool. For example this owl model prints perfectly until the top of the head where holes usually appear.

The number one tip for quality 3d printing seems to be "keep the filament dry" and this is even more the case with TPU. It is stupidly hygroscopic and will start spitting and causing issues really quickly when exposed to the air. An initial dry in the filament dryer helped a lot and acted really quickly, I've been keeping mine in the filament dryer box since with some silica gel packets (and no heat) and it seems to be keeping dry enough.

Clogs

Clogs seem to be different with TPU, and much more common. With a hard filament if the hot end gets clogged the force get's passed to the extruder which will slip on the filament, making clicking sounds and breaking off bits of the filament where the teeth have bit in then slipped. I've only had one with PLA so far, and that was due to running a bunch of prints at a temperature that was too cool for the filament.

TPU doesn't transfer the force and there will be no audible warning of a clog. The things to look for are under extrusion (obviously, but harder to spot) and corkscrewing of the filament in the bowden tube near the extruder. Because the extruder will keep pushing and the filament has nowhere to go it will start twisting around and bunching up inside the tube, looking like a corkscrew. This seems a good reason to use a clear bowden tube to me!

Failed owl print due to a clog, the deposited filament becomes less and less dense with increasing voids especially visible in the infill

Luckily I've found clogs to be easy to clear, prodding the nozzle with the thing poking thing the printer came with will spray molten plastic onto your hand and free up the backlogged filament to ooze out into a blob on the build plate.

Squishy Models

The tractor tyres are a little bit squashy, and a clog let me feel them only partly printed, and I liked the feel!

The owl prints great at 10% grid infill and is super squishy!

It seems to always end up with holes in the top though, and these can be good as it lets air in and out when you squash - giving a very satisfying feel. But I like to rub a 300 degree soldering iron over the holes (maybe with some spare oozed filament) to "weld" them shut which gives more resistance to a squeeze and a more robust feel. Although finding and welding all the holes seems impossible 🤷

A pink owl with welded patched on the head, although they aren't obvious unless you're looking for them

Mycle Charge Fat Tyre Folding Electric Bike

11 December 2022 at 15:35

I've had this bike for a few months and covered around 160km (100 miles) so thought I'd share some thoughts.

What is it?

It's a bike of many features! I won't copy the entire sales pitch but the things that sold it to me are:

  • It's electric
  • It has good range - claimed 65km!
  • Fat tyres for various terrain and bad roads
  • Folding

It retails for £1,599 but I got it on the Cycle To Work scheme, which means it costs me less and I pay over 12 months, but I don't actually OWN it until I buy it outright after 12 months OR extend the "hire" for 5 years at no cost.

What's it like to ride?

It's very different to a normal bike, which comes from a combination of factors.

It's HEAVY at 26kg.

It has fat tyres, which give you confidence on rough or loose ground, but also feel weird and are noisy on the flat road, I don't feel like I can lean into corners as much.

The wheels are 20 inches, which means it can go on a train without booking when folded (in the UK.) small wheels usually mean you can speed up and change direction easier, as they have less mass and less inertia, BUT these wheels are fat and heavy, which undoes all of that I think.

Front tyre next to an Anytone 878 radio, the tyre is nearly twice as wide

I feel like I need to have it on the first level of power assist all the time just to overcome the rolling resistance of the fat tyres and heavy bike. It does feel like you're constantly fighting the motor at this level and it's hard to get the bike going faster by peddling alone. Generally I ride with level two assist, it is a good speed for shared pedestrian and cycle paths, and I can push it faster if I want most of the time.

Level three assist is the "sod it" mode (2 is "Let's go already"), it easily keeps up to the legal limit of 25km/h even up reasonable hills. I have taken it up a 1 in 4 ony then did it fail to get me up there, even with me peddling in first gear.

At the top end of speed, the fat tyres increase rolling resistance, the small wheels limit top speed, and the assist is limited to 25km/h, so it has a very obvious limit to how fast you can go. Much more than 30km/h on the flat is very hard work.

To reiterate the "very different" feel, the first ride I went on, I wiped out and dislocated my shoulder. I think the combination of weight, disk breaks (which were new to me), and suspension (which moved the weight around in a way I wasn't used to) conspired against me and it just went from under me. I'm still recovering from that accident, and would not recommend dislocating your shoulder.

On a train?

YES!

Well, mostly.

On the UK you can take a folding bicycle with maximum 20" wheels on a train without booking.

Mycle says it folds down in 10 seconds, which is not true.

It also weighs 26kg, and you probably have a bag or two as well.

26kg is a lot to heft on and off a train, from the platform, and through a door, whilst also holding a bag or two. There is no obvious place to hold the bike when doing this, and it doesn't clip shut or anything so you have to balance it such that it doesn't swing unfolded again.

When folded it isn't that small either, I could only just fit it into the bottom of a large luggage cubby - that's a lie, it stuck out. So no, you can't really use it like a "normal" folding bicycle. Mycle say it's great for "folding into your car on weekend trips away" ... and that might work if you don't also have bags or anyone else wanting to bring their bike...

Folded bike taking up the whole of a car boot

But, bicycle reservations are free! On most journeys so far I've (tried to) book a cycle reservation. There are two types of on train cycle space I've encountered so far.

One is in the rear of the train and the staff have to unlock it for you, inside there are maybe four of five places where you can wedge the front wheel of the bike. But not with this! The fat tyres are too big to fit, so you have to improvise, put the stand down, and hope it doesn't fall over. It always falls over.

The other is in the main train, and you're supposed to store the bike vertically and hang the front wheel from the hook provided. WHich this bike also can't use. So you end up with it sticking out of the designated space. To be fair, the spaces aren't that well designed and others have commented on how they must have been designed by someone who has never actually taken a bike on a train.

Bike that doesn't qute fit in the train cubby

Utility cycling

I think this is where the bike shines, it can do a lot of different stuff and can replace a car for most things.

The rack can carry 25kg, which is a fair bit of shopping in pannier bags, or a child in a seat. It's not as good as an actual cargo bike, but it can do a lot of common journeys easily.

On that note, rack mounted child seats should absolutely be the normal thing! The ones that attach to the seat tube are a right faff, wobble all over, and generally suck. The ones that mount onto the rack are really sturdy, really simple to fit and swap between bikes, and are better in every way. And yes, you can fit a child seat like this to a 20" bike, don't let the people in the shop say otherwise.

Bike with child seat fitted and child in it, their face is covered with a grinning Alex emoji

I also have a trailer that can carry 30kg and has a larger physical space than just bags. With this you can do loads! A week of shopping? No problem. A bunch of stuff (including an old single mattress) for the local municipal recycling center (aka Tip)? Can do.

Bike with trailer attached, a rolled mattress is strapped to the top of the trailer

Does it do 65km? Maybe? I've done nearly 20km with little regard for looking after the battery and was still showing 2 of 3 lights on the battery itself.

Things it lacks

When I ordered the bike the only option for the control was a basic unit with some LEDs on it, 3 indicate the boost level, and some others show the battery level. It has an on button and buttons to reduce and increase boost, hold up and it turns the lights on or off. That's it.

You can now get a fancy display for £100 extra, which does ... something? Mycle don't really say. But it would be great to have a display showing speed, distance, etc. I have a wireless CAT "computer" that does this, but I have to remember to turn it on, it has different batteries, and doesn't work reliably with the bikes electrics.

It would be great if there was a way to interface with the bikes data, I assume it has some, in an open way.

The battery also uses a 3-pin XLR connector for reasons I don't understand at all. That's a connector used for microphones and lights. It makes absolutely no sense to me to use a connector like this for that.

It's clear that all the stuff in this bike is just off the shelf components from China, combined to make this specific bike. The instructions for the charger are clearly white-label (they don't mention Mycle at all) and the controller says Mycle on it, but the manual also doesn't mention Mycle.

The controller is apparently made by lsdzs, but I can't find it on their website. It says it is:

  • 36V
  • 17A max
  • 8A rated current
  • 30V low voltage protection
  • Throttle adjustment voltage between 1.2 and 4.4V

Close photo of the controller which is a metal rectangle with diagonal details and a damaged sticker

Should you get one?

It depends?

If you're ok with the compromise (and there are a few) and it suits your needs, maybe!

However, if you need something for the train, maybe get a bike more focussed on small folding. If you want to carry stuff, get a cargo. If you want to ride around town, get a traditional (electric) bike. If it's going off road, maybe a mountain or trail bike. BUT if you want something that can do most of these things fairly well, then the charge might be good for you.

Overall, I'm pretty happy with it!

December update on TwitterMigration

10 December 2022 at 10:01

It's been around a month since my "Thoughts on the TwitterMigration" post and things seem to have calmed down again, so I thought I'd take some time to pull together some thoughts and numbers for you all.

Previously ...

After 4 years of fairly steady increase in users mastodon.radio got an influx.

We went from less than 100 new users per month to 111 joining in the final 4 days of October, or 28 per day.

I wasn't enjoying the constant barrage of notifications and requests.

Some numbers

In November 2022 mastodon.radio added 1071 new accounts. That's more than double the total accounts that joined in the entire previous 4 years. It's 34.5 per day.

Impressively 64% of those new accounts have logged in during the first 10 days of December, which is pretty impressive "retention" considering we're early in the month.

We now have nearly 1,700 "Active users".

New users create a lot of email, before we'd comfortably sent fewer than 1000 emails each month, in November we sent nearly 14,000

Also on the backend is Sidekiq, it does all the fetching and sharing of posts, likes, boosts, etc. and it took some tweaking to get working well under the new load.

For the previous months Sidekiq processed around 50-60k jobs per day, now it hovers between 750k and 1.2 million jobs per day.

We've also seen a HUGE increase in donations. I have nearly £1,500 in an account ready to pay for all those emails and server upgrades.

Server upgrade

This was inevitable, even if we hadn't had any more users there is now a lot more traffic on the network and sidekiq is busy, so we needed a server upgrade to keep up.

Mythic Beasts have hosted us from the start and when I emailed to ask what they could offer I got a pretty quick reply and a very good offer. We were basically able to double the server power (now we have 4 CPU cores!) and pay only a little bit more.

Some quick maths shows it costs just under 20p a year per user to pay for the server and domain and a modest amount of email.

Process updates

There were a few things that made the initial influx very hard work. For one I personally welcome every new user to the server, and approve all the accounts.

I made some changes to the way I do this, the main one being to make our mascot, Alex, send the welcome messages. This freed up my personal timeline and mentions to be usable and not just a flood of "welcome to mastodon.radio!" messages. I also made some changes to the script to streamline the actual process of approving the accounts.

This helped a lot, I still get emails when a new account is requested, but I'm much more lax about dealing with them now and don't jump on approving every one as soon as I can.

I've also turned off a lot of notifications, so I don't get pings for every boost or like etc. which makes the experience of not using mastodon.radio (you know, the time I'm NOT on the laptop or phone) much more pleasant.

More instances

One of the big changes was a bunch of other instances starting up, including mastodon.hams.social, qth.social, and hackers.radio. I hear rumours of national radio associations also looking to set up instances for their members.

This is great as is spreads the community our and stops mastodon.radio being THE mastodon server for radio people.

Behind the scenes we've got a good alliance of admins who talk about issues, moderation, etc. which is very helpful.

We've also got a relay set up which ensures posts from mastodon.radio get to hackers.radio, and vice versa, without everyone having to follow everyone on every server. It also helps posts with hashtags be reliably "findable" across the servers connected to the relay.

The relay server also runs LibreTranslate so we have some basic machine translation of toots.

You can find a list of radio related servers, where they are located, and if they are on the relay at fediverse.radio

How I'm feeling now

Much more comfortable, if mastodon.radio collapses in flames then so what? It's social media not a bank. I'm confident I could stand it back up again, even if we didn't keep all the data for whatever reason (the daily database backup files are now 2.5G gzipped) plus there are other servers that people could jump to if they REALLY can't not have mastodon for a bit.

It's really good to see new instances coming online and sharing the ethos.

It's still just me running the server, which isn't ideal, but it's absolutely not just me running amateur radio fediverse servers.

Thoughts on the TwitterMigration

4 November 2022 at 20:08

It's been about a week since "The Bird Site" was purchased, lots has been written about that, and lots has been written about Mastodon, but I've not spotted much about the impact of the influx on servers like mastodon.radio, so here are some thoughts. A ramble, so beware.

Brief History

Mastodon.radio started in 2018, for a long time it ran on a single core VPS with 4Gb ram. Then when an influx happened in the past I finally upgraded it to the current specification, which is 2 CPUs, 8Gb ram, and 160Gb storage.

For a long time I've had it set to require approval for new accounts, this is mostly to stop spam and people who aren't actually wanting a radio amateur focused server (maybe they are a DJ, etc.), and it works fairly well. A week ago we had 807 accounts, and around half were active, the statistics say we have 44% of our users still using the site after 6 months (on average) which is pretty good!

For the most part we got a handful of new users each week, and the community was pretty stable. I follow everyone and could keep up with the local timeline easily.

Chart showing a couple of peaks in 2020 and 2021 but 2022 is significantly higher overall

When I was clearing out my emails at the start of October I grabbed this chart from Thunderbird showing the number of emails the server sent me about new accounts per month. You can see it's mostly stable with a couple of spikes, and a large increase in 2022 when Smitty mentioned the server on the Ham Radio Workbench Podcast - also around the time Musk was talking of buying the bird site. Then it trails off back to the usual lower level by October - presumably as people catch up with the podcast!

The specific numbers are:

  • May 109
  • June 65
  • July 64
  • August 42
  • September 27

The Eternal September of October 2022

It's a little tricky to get the maths right, but I think Musk paid approximately $111.11 per monthly active user on the bird site.

Apparently people didn't like this.

In October we had 138 new users.

Reader - I hated it. It has completely ruined my relationship with mastodon.radio.

It used to be a cozy space where I could read about cool things people were doing, see fluffy pets, I could read EVERYTHING - I could refresh my home timeline and there be no new posts.

But then people wanted accounts. If we do some quick maths and assume we would have had as many people join in October as September without the bird situation and compare that to what we had:

138 - 27 = 111 extra new users.

Over four days.

That's nearly 28 per day, on average, and there are only 24 hours in a day.

Remember that I get an email every time I need to approve an account. Even though I've streamlined the approval process quite a lot with my "mastoWelcomer" script I still have to:

  1. get a notification
  2. notice if it's an account request
  3. dismiss the notification
  4. open up my ssh client/app
  5. connect to my home server
  6. go up to the last command
  7. run the last command
  8. wait for it to load in the account info
  9. scroll back and read the username, reason, check the callsign, etc. and make a decision
  10. type the number of the account(s) to approve
  11. confirm I want to approve those accounts
  12. close the app
  13. go to mastodon and check it worked
  14. refresh the local timeline every so often to see if they have posted an introduction
  15. boost their introduction
  16. notice, and dismiss, people liking/commenting/etc. on the welcome post

By which point I'd sometimes have had another two or three account requests come in!

After a few days I'd stopped trying to approve accounts as fast as possible and I'm now only checking and doing it if I'm either already at the computer or every few hours, e.g. morning, lunch, before dinner, again in the evening, before bed. I'm still getting around 10 accounts whilst I sleep.

I've also modified my "welcomer" script to only show me accounts that have confirmed their email address. There is no point approving an account if they won't know its been done. Some people confirm really quickly, some hours or days later, some seem to enter invalid addresses that I then try and track down a correction for if it seems easy, e.g. they have a callsign I can look up elsewhere.

I've always introduced everyone to the server, and although I've automated it a bit, I still actively care about and welcome every new users. This has however made my account somewhat useless for me recently, as I have over 200 direct messages and quite a lot of "please welcome" public posts. I can't even find messages I posted myself a few days ago. I'm sure many people who follow me have probably stopped or muted me by now!

The "problem" (for want of a better word) is that none of this is "chunky". It happens all the time. People don't request accounts only between 0900 and 1700, they do it whenever suits them, at least one every hour. And people respond to these new accounts when they see them, not immediately when I post, so there is a long tail of notifications that never seems to stop. I've turned off notifications on my phone, they're still there but it doesn't beep or vibrate. This helps a little, but I've also missed phone calls so it's not a perfect system.

The rest of my life hasn't stopped either, so balancing this with that has been ... difficult at times.

Some statistics?

We've had 225 new user, up 704% on the average number of new users, pushing us over 1000 accounts on the server.

We now have 562 "active users", up 90% (although will they stick around?)

15,602 interactions, up 309%, although that's probably mostly me mentioning all the new users.

We've also had zero reports - which is ... good but worrying? Have I curated our block list and membership that well? Maybe! I hope so!

The daily database backup is now over 1.6G with gzip compression!

We've also had a massive increase in donations. The server and domain renew in August but we've already got everything we need to pay for it sitting in a savings account. Which is amazing!

Although we might need that money soon as the next version of the mastodon software has translation capabilities, if paired with a translation service so that's an extra cost for either a service or another VPS (because I think we need to spread the load a bit)

So what?

I don't know.

If the pace of new users keeps up, and they all stick around, we're going to have a very different server than we had before. But that's not a bad thing.

What is a bad thing is if the server crashes, I can't fix it, something else happens.

I feel a lot of pressure to look after the experience of 1.03k users (so says the about page as I write this.)

It's the same install of Debian I put on the server in 2018, it's been updated sure, but I still worry about things going wrong and my ability to bring it back. I've never done a full clean install and recover from backups, even on a test server. I probably should, and I'm sure Smitty would lend me one, but then do I want to send all our data to the USA?

To be clear, moderation is not a burden, approving accounts at the usual cadence is not a problem. But I do worry that people think this is a proper enterprise and there is a team of people who know what they're doing. Maybe it should be. But I've struggled to work out how I make the leap from "I own everything, I trust me" to "I can die tomorrow and know a team will keep the server running." Who do I trust with the keys to the kingdom? Who do the users of the server trust? Everyone just trusts me to keep hold of the money they donate and spend it on the server, but no one knows if I do or not. Wouldn't it be better to have a coop run this? Hold the money, be a legal entity that can be trusted with everything?

But I have NO IDEA how to make that a thing.

Plus, I don't have the time! I don't discuss my personal life online, but I have people who depend on me IRL, and a full time job, and I'm the editor for my IRL radio club newsletter, and at some point I might want to actually DO some radio. How do I fit "set up mastodon.radio as a cooperative, find, vet, and induct people to help, etc." into everything else?

Mastodon.radio was a fun idea 4 years ago, now it's a burden, and I don't know how to share the burden.

Do I regret starting mastodon.radio? Absolutely not.

Do I regret not having a more robust technical architecture that could more easily scale etc. yes.

Do I regret not sharing the admin burden sooner? Kind of. I still feel like I'd need someone who I know IRL, has the skills required, is interested and committed to help, and can be trusted - I'm not sure I know anyone who fits that specification.

I've had three, no, four account requests since I started writing this post.

Pending Projects and ideas 2022

18 June 2022 at 22:06

I have a few project ideas currently pending parts, budget, time, etc. and I wanted to write some of them down so they can get out of my head! There is a lot of ESP stuff in here ...

Page Content

Penkesu Computer

This is documented elsewhere on the site, but as it stands I've not got any further mostly due to the lack of Raspberry Pi availability, and my falling out with the 3d printer I was baby sitting (which has since been returned)

I'd still like to make it ... but the MNT Pocket Reform seems to be becoming a reality and might be a better idea

so... do you want a cute open hardware 7" computer with mechanical keyboard and optional LTE/5G connectivity?! what would you do with it? minute CEO of MNT Research

ESP Solar Server

For some time I've had a 100% solar powered APRS setup. But it's possible to have an ESP act as a web server, and they use basically no power, and you can get MicroSD card things for them, and this entire site is currently +checks+ 21M so could easily fit on an microSD. There also exists a "wide input" thing that would happily take the variable 12-ish-volts from the solar system to run the ESP.

I could then get my VPS to connect back into my house, check if the ESP is online or not. If so, proxy that, if not, fall back to serving the site in the same way it does now. Could easily have a solar subdomain that always (tries) to serve from the ESP.

I could even add Gemini using Astrrra / ESP8266GeminiServer

Sure, I could use the raspberry pi that's doing the APRS thing ... but ...

Fix the APRS Solar Setup

The APRS station isn't actually online right now.

A few things failed, and I haven't got it all back (yet / ever?)

Firstly, the 12v -> USB converter seems to have failed in a way that kills raspberry pis - which isn't ideal. The Pi was working, until I had to (re)boot it, then it wasn't. I assumed SD card issues, which was the case. New SD card, still no boot. "Spare" Pi, new SD, boot success! But it only stayed on the WiFi for a few minutes, then vanished, and then wouldn't boot anymore.

The SD was ok, and when I connected it to the only Pi I had left not already doing something (although I did have plans for it, just not this) it booted OK on a different power supply. neither of the other PIs would boot with the same SD and power supply.

When I connected up the third Pi to the solar system I didn't use the pi-killing-converter and went for a wide-input-shim I'd got for using with the 3d printer and octopi. So far this has worked fine and I would absolutely recommend using something like that over an ebay sourced buck converter.

I've also set up log2ram so hopefully that SD card and Pi will survive for longer than the last two. Although to be fair, the original solar powered pi did run consistently (but not constantly, night time and winter in the UK isn't great for 100% solar things) for at least a year but was let down by the power supply.

If anyone knows how I might bring the other two Pis back to life, that would be great!

One key thing the solar setup is missing now is some way to monitor it all. I was using three INA260s which worked really well. But now they seem to have decided to stop working. One has a short to ground on the clock pin and the other two don't respond to i2cdetect (and make it run super slowly) my eBay basket has some INA226 modules - but they are nearly £7 each and I'm not sure I care enough about this specific project right now.

However, it would be super good to get this working again, and combined with the ESP Server idea I could do something like LOW<-Tech Magazine and show the battery charge level somehow, like how they use the background.

ESPHome Video Doorbell

This one is potentially really easy to do, and also useful!

  • LILYGO make an ESP32 camera device with PIR, camera, button, etc. It's basically screaming to be made into a video doorbell
  • petrepa/ESPHome-VideoDoorbell exists and uses this device

"Smart" Laundry basket

I started this and it half works...

  • Take an ESP8266
  • Connect an HC-SR04 ultrasonic distance measuring module
  • Connect a tilt switch to the reset pin
  • attach it to the inside of the laundry basket lid
  • code it to read the distance and send that over MQTT then go into deep sleep
  • Make HomeAssistant show that distance and alert me when the basket is getting full

Some problems, probably of my own making...

  • Ultrasonic distance sensors work by using sound. Which is absorbed by fabric. Which leads to unreliable distance readings (especially when the distance is short / basket is getting very full)
  • Doesn't seem to actually ... do it more than once? The tilt-to-reset doesn't seem to work as expected and deepsleep for 60 minutes never seems to wake up (yes I have the pins connected)
  • I should probably just use ESPHome
  • not everyone is careful when shoving stuff into the basket and the pins have been bent and connections pulled out quite often

Remote "HomeScreen"

I have a PiZeroW powered e-paper display in the hall. It runs various python scripts on a cron defined schedule and can do things like:

  • Show everyone's calendar and weather forecast in the morning
  • Provide random Aeropress recipies at 10am
  • Show charts of energy use, temperature, solar power (if it was working), grid fossil fuel %, etc. every hour during the day
  • Various info about stuff to summarise the day in the evening

It can do all of this on-demand too using a 6 key mechanical keyboard (each key runs a differnt script, some depend on the time of day), plus some other stuff only on-demand:

  • Show a random image from pexels.com
  • Show an image uploaded to it
  • Check and show if my computers/Pis/servers are online and if they have updates
  • fetch car Tax/MOT status from DVLA
  • tell me how many visitors my website has had recently (on Gopher, Gemini, and HTTP)

It would be very cool to have a remote screen that can display some/all of this but from another location, for example a friend or relative's house (yes, I'm being vague for privacy reasons.)

LILYGO (them again!) make a 4.7 inch e-paper screen, which has an ESP32 stuck on the back, and buttons, and the ability to run off (and charge!) a 18650 cell.

trombik/esp_wireguard exists, which would allow me to VPN back into my house and download pre-made images for the remote display to display. I could even add an environment sensor to get temperature etc. and scan for devices on the remote WiFi to determine if people I care about got home safely after work and send it all to my Home Assistant! (with their permission of course.)

I got as far as making the display connect to WiFi and download a PNG... but the LILYGO screen is a higher resolution than my Pi-powered one! So it can't just take the image file and display it, some conversion seems required, and I haven't worked out how to do that yet. Plus my C skills are lacking, and the edit-compile-upload-run-debug cycle is VERY SLOW. I could pre-convert the image on the pi, but I have no idea how to make it download and display the .h file the example code produces. I've not tried putting WireGuard and the other stuff on yet.

Remote WLED

On a similar thread, I have modified a small dinosaur light and shoved some controllable RGB LEDs in it, and connected it up with an ESP8266 and WLED. It's great! I can control it from Home Assistant!

BUT - I want to give this to a friend, so they can have it in their home and it sync with/be controlled by my Home Assistant.

I've repurposed a PiZeroW to use RaspAP to abuse AP-STA so I can connect to their WiFi AND provide an AP for the WLED device to connect to, plus WireGuard to connect everything on that AP back into my home.

But, although it seems to work (I can connect to devices in my home via the AP) I can't work out how to do it the other way around and get Home Assistant to "discover" the remote WLED device. The only suggestion I've had so far it to run another Home Assistant instance and make it a "remote" to my local one, but as I've killed two Pi 3b+ I don't have the spare hardware for that! Until issue 2634 is implemented I don't think I'll be getting this working any time soon.

Like the remote display, it's really hard to debug and work this stuff out when you have multiple devices and complex webs of connections. My at home debugging setup was

  • Laptop connected to RaspAP WiFi
  • PiZeroW connected to my phone's WiFi hotspot, which connects to Mobile network
  • PiZeroW using Wireguard to connect to my home network

It's REALLY EASY to not spot when something has re-connected to the better WiFi - aka your home WiFi and is no longer on the Pi's AP. Or the Pi needs to restart something and the AP goes away. Or the phone has weak signal strength so the 3g is very slow. or etc.

DishWasher Monitor

I have a dumb dishwasher, I like this. But it is "integrated" so has no external sign that it is running, finished, or whatever. I bought an energy monitoring "smart socket" that has an ESP8266 hiding in it, it was an absolute pain to open up and get to the ESP. Once I had it out I can't get it into flash mode.

I'd LIKE to do something like Giovanni did with their washing machine - but I'd really like the slim profile of something round and about the size of the plug. Not that it matters, it's all hidden in the cupboard anyway 🤷

A more compact device would be more acceptable for devices that have visible plugs though, like a bread machine on the kitchen counter...

ESPHome Flashing Service

ESPHome is great, and I've been using it a lot with commercial devices that have ESPs in them.

But getting ESPHome onto the device is often a pain, involving disassembly and sometimes soldering. Once installed all the updates and configuration can be done over WiFi.

I suspect there is a market for people who want to use ESPHome but don't want to do the initial hardware flashing work. That was me when I first started playing!

I wonder how much luck I'd have with an eBay store selling sonoff and other devices I'd purchased in bulk for less than retail price + a service to flash it with ESPHome and your provided WiFi details and charge people about retail price for the bundle.

ESPHome environment light switches

Lets say I have some ESPHome powered light switches (I do!) and I wanted a neat way to know the temperature in every room of my house.

Well, I've already got most of the solution stuck to the wall, right?

Can I add a small i2c temperature sensor to the ESP inside my light switches?

mastoWelcomer

22 May 2022 at 10:20

Page Content

Background

I run mastodon.radio, and I have it set to require approval for new accounts. I also like to welcome every new user.

This can result in delays, as I need to do a complex dance, something like:

  • look at pending accounts
  • confirm email is confirmed (not visible on approval page!)
  • confirm I'm happy with reason
  • maybe check the callsign too
  • select for approval
  • copy usernames (using snippet in dev console)
  • approve
  • copy old welcome toot
  • paste usernames into welcome toot
  • send toot
  • find accounts again
  • follow the new accounts

I wrote a snippet of JavaScript to run in the dev console to copy out the selected usernames, which helped but for security reasons you can't put stuff on the clipboard without user interaction so I still had to manually select and copy the string... the entire process is a faff.

Well, mastodon has an API, and there exists Mastodon.py

Guess what comes next?

That's right! SCOPE CREEP!

Proof of concept

I wrote a small python script to fetch a list of pending accounts, show me the info, and let me pick which ones to approve. It then does that, and follows each account, and toots a welcome message.

It worked well!

Script output listing 3 pending accounts, details redacted

But ...

  • It was hard to use on my phone (copying long ID strings)
  • It wasn't obvious that the email was confirmed
  • The date time thing is too long
  • It didn't check if the resulting welcome toot is too long, so it could fail to send

Current version

So, I extended it a bit.

Now it also:

  • formats the relative date/time
  • tries to find callsigns in the username, email, and request text
  • looks for my "rules canary" (more below)
  • highlights if email is confirmed
  • checks QRZ.com for any found callsigns
  • lets me type single digits to select accounts!
  • sends individual welcome messages, plus a public welcome

Script output listing 3 pending accounts, with the above improvements

The Code

It's probably not much use to anyone else, but if you remove the callsign and QRZ lookups it might be a good base for your own?

Note I've kept the permissions as minimal as possible, but they are still ADMIN tokens lying around on your computer. Due caution is required.

from datetime import datetime, timezone
from mastodon import Mastodon
from random import choice
from qrz import QRZ
import humanize
import pprint
import re

callsignRegex = '[a-zA-Z0-9]{1,3}[0-9][a-zA-Z0-9]{0,3}[a-zA-Z]'
ruleCanary = 'hippo cornflakes'

qrz = QRZ(cfg='./settings.cfg')

pp = pprint.PrettyPrinter(indent=4)

''' create app - do this once
Mastodon.create_app(
     'mastoWelcome',
     scopes=['admin:read:accounts', 'admin:write:accounts', 'write:statuses', 'write:follows'],
     api_base_url = 'https://mastodon.radio',
     to_file = 'mastoWelcome_clientcred.secret'
)
'''

''' authorize - do this once
mastodon = Mastodon(
    client_id = 'mastoWelcome_clientcred.secret',
    api_base_url = 'https://mastodon.radio'
)
'''

''' log in - do this as often as needed
mastodon.log_in(
    'username',
    'password',
    scopes=['admin:read:accounts', 'admin:write:accounts', 'write:statuses', 'write:follows'],
    to_file = 'mastoWelcome_usercred.secret'
)
'''

# connect with saved credentials
mastodon = Mastodon(
    access_token = 'mastoWelcome_usercred.secret',
    api_base_url = 'https://mastodon.radio'
)

pendingAccounts = mastodon.admin_accounts(status='pending')

print(str(len(pendingAccounts)) + ' Pending accounts')
i = 0
for pAccount in pendingAccounts:
    # easy to type ID
    print(f'\n{i}')
    # attempt to find callsigns
    allPossibleCallsigns = re.findall(callsignRegex, pAccount['username'])
    allPossibleCallsigns.extend(re.findall(callsignRegex, pAccount['email']))
    allPossibleCallsigns.extend(re.findall(callsignRegex, pAccount['invite_request']))
    # tell me about the account
    print(f"Username: {pAccount['username']}")
    print(f'{humanize.naturaltime(datetime.now(timezone.utc) - pAccount["created_at"])} {pAccount["email"]}')
    print(f'{pAccount["invite_request"]}')
    if ruleCanary in pAccount["invite_request"]:
        print(f'\033[93mRule Canary: {ruleCanary in pAccount["invite_request"]}\033[0m')
    else:
        print(f'\033[95mRule Canary: {ruleCanary in pAccount["invite_request"]}\033[0m')
    if pAccount["confirmed"]:
        print(f'\033[93mEmail confirmed: {pAccount["confirmed"]}\033[0m')
    else:
        print(f'\033[95mEmail confirmed: {pAccount["confirmed"]}\033[0m')
    # dedupe list
    possibleCallsigns = []
    for call in allPossibleCallsigns:
        lowerCall = call.casefold()
        if lowerCall not in possibleCallsigns:
            possibleCallsigns.append(lowerCall)
    print(f'Possible Callsigns: {possibleCallsigns}')
    # get info from QRZ
    print('QRZ info')
    for call in possibleCallsigns:
        result = qrz.callsign(call)
        pp.pprint(result)
    i+=1

# ask me what accounts look OK
approveWhat = input("Which accounts to approve?: ")
# split the space separated list
approveWhat = approveWhat.split()

print('\nYou want to approve these account:')
for aAccount in approveWhat:
    print(pendingAccounts[int(aAccount)]['username'])

# make a case sensitive YES to make sure I'm paying attention
confirmYES = ''.join(map(choice, zip('yes', 'YES')))
confirm = input(f"type '{confirmYES}' to confirm: ")

# store the usernames in "@ + name" format
userNames = ''

if confirm == confirmYES:
    print('Approving the accounts...')
    for aAccount in approveWhat:
        updatedAccount = mastodon.admin_account_approve(pendingAccounts[int(aAccount)]['id'])
        print(f'Approved {updatedAccount["username"]}!')
        mastodon.account_follow(pendingAccounts[int(aAccount)]['id'])
        print('Followed them!')
        userNames += f'@{updatedAccount["username"]}\n'
        # YOU'LL WANT TO CHANGE THIS MESSAGE (I assume)
        mastodon.toot(f'Hello @{updatedAccount["username"]}\nPlease toot an introduction (using # introductions) so we can get to know you :alex_grin: add some hashtags too so we know what interests you\n\nYou can find more people on our server using the local timeline & directory\nhttps://mastodon.radio/web/directory\n\nThis list of radio amateurs on mastodon, may be of interest: https://pad.dc7ia.eu/p/radio_amateurs_on_the_fediverse\n\nI recommend the third-party apps\nhttps://joinmastodon.org/apps\nRemember to describe your images (ask for help!) + fill out your bio before following people', visibility='unlisted')
        print('Tooting to welcome them!')
    templateToot = f'Everyone, please welcome\n{userNames}\n\n#introductions'
    print('Tooting bulk welcome message...')
    mastodon.toot(templateToot)
else:
    print('\nOk, DOING NOTHING.')

Rules Canary?

mastodon.radio isn't a big corporate server, we have rules that we enforce. Some are pretty common (don't be a dick) some are important for the community (describe your images) and I see people not describing their images. Which makes me think they haven't read the rules. So I wondered how I could check up on this.

Maybe you've heard of a warrant canary? The idea being a message says that "The FBI has not been here" and is removed if they have been, even if an organisation is forbidden from saying, for example, that the FBI HAS been there.

Based on this idea I added "rule 7" to mastodon.radio, it reads

When apply for an account, if you've actually read these rules, please end your "Why do you want to join?" with the phrase "hippo cornflakes"

Very few requests mention cornflakes, or hippos. But that's not really the point, I'm not denying entry to people who don't. Yet. But it is interesting to see, from the administrator's view, who actually read the rules AND bothered to follow them.

Some workflow optimisations [November 2022]

November 2022 has seen a lot more applications for accounts than ever before to mastodon.radio, as a result I've made some changes to the script which remove some of the manual checks and speed up the process.

  • Only show confirmed emails - before I had to eyeball which accounts had confirmed their email address, now it only shows me accounts with a confirmed email address.
  • Rule Canary - the script now creates an "Auto Selected" list of accounts which have confirmed their email AND have included the rule canary text (plus I made it not case sensitive!)
  • Stop showing if email is confirmed and rule canary present, as we're automatically deciding so don't need to see them
  • Added some try/except statements to avoid the entire script failing part way through

I could automate the decision further, specifically if a callsign is found and gets at least one result in QRZ lookup. But I've held off that for now because I'd have to make more significant code changes to move that lookup around, AND I'm not convinced that a bad actor couldn't just pop in a valid callsign and then I'm automating the approval of spam. I know the rule canary could have this issue too, but it's easy to change that and it remain effective (for a time) but the only mitigation to "callsign stuffing" would be to remove the automation again.

Make Alex do it [November 2022]

For four years I've welcomed everyone personally, when I wrote this script I was able to easily send a direct message to every new account with some tips.

This worked fine, until we had literally hundreds of new users and my mentions and direct messages became impossible to use.

The solution? Make Alex do it! Alex is the mastodon.radio mascot and has had an account for a while which has always been restricted, with no followers, and not posting anything. But I've opened that up and now it's Alex who welcomes every new user.

It adds some complexity to the script because now it has to log in twice, but it makes my life a lot easier and my use of mastodon.radio a LOT nicer. It also means that it doesn't matter who does the approval, everyone gets a consistent welcome and everyone else has one place to look for these welcome messages.

I'm logged in as Alex on my phone so I can spot anyone asking questions to them, and so far it has been very well received - with replies like:

A mascot! Now that's what I'm talking about!

Who's a good bot!? You're a good bot!

Plus now more people get to see our cool mascot! If you want to know about new arrivals on mastodon.radio give Alex a follow.

The updated code [November 2022]

This is the newly refined code with more Alex

'''
ALL THE SAME STUFF AS BEFORE BUT WITH TWO ACCOUNTS
'''
# connect with saved credentials
mastodon = Mastodon(
    access_token = 'mastoWelcome_usercred.secret',
    api_base_url = 'https://mastodon.radio'
)
# THIS IS NEW
mastodonMascot = Mastodon(
    access_token = 'mastoWelcome_usercredMascot.secret',
    api_base_url = 'https://mastodon.radio'
)
pendingAccounts = mastodon.admin_accounts(status='pending')

print(str(len(pendingAccounts)) + ' Pending accounts')
autoSelected = []
i = 0
for pAccount in pendingAccounts:
    if pAccount["confirmed"]:
        # THIS IS NEW
        if ruleCanary in pAccount["invite_request"].casefold():
            autoSelected.append(i)
        else:
            print(f'\n{i}')
            allPossibleCallsigns = re.findall(callsignRegex, pAccount['username'])
            allPossibleCallsigns.extend(re.findall(callsignRegex, pAccount['email']))
            allPossibleCallsigns.extend(re.findall(callsignRegex, pAccount['invite_request']))
            print(f"\033[93mUsername: {pAccount['username']}\033[0m")
            print(f'{humanize.naturaltime(datetime.now(timezone.utc) - pAccount["created_at"])} {pAccount["email"]}')
            print(f'\033[95m{pAccount["invite_request"]}\033[0m')
            # dedupe list
            possibleCallsigns = []
            for call in allPossibleCallsigns:
                lowerCall = call.casefold()
                if lowerCall not in possibleCallsigns:
                    possibleCallsigns.append(lowerCall)
            print(f'Possible Callsigns: {possibleCallsigns}')
            # get info from QRZ
            print('QRZ info')
            for call in possibleCallsigns:
                try:
                    result = qrz.callsign(call)
                    pp.pprint(result)
                except:
                    print(f'{call} not found')
    i+=1
# THIS IS NEW
print(f'Auto Selected {len(autoSelected)} accounts')
for aAccount in autoSelected:
    print(pendingAccounts[int(aAccount)]['username'])

approveWhat = input("Which accounts to approve?: ")

approveWhat = approveWhat.split()

print(f'\nYou want to approve these {len(approveWhat)} accounts:')
for aAccount in approveWhat:
    print(pendingAccounts[int(aAccount)]['username'])

# make a case sensitive YES to make sure I'm paying attention
confirmYES = ''.join(map(choice, zip('yes', 'YES')))
confirm = input(f"type '{confirmYES}' to confirm: ")

userNames = ''

if confirm == confirmYES:
    print('Approving the accounts...')
    for aAccount in autoSelected + approveWhat:
        updatedAccount = mastodon.admin_account_approve(pendingAccounts[int(aAccount)]['id'])
        print(f'Approved {updatedAccount["username"]}!')
        mastodon.account_follow(pendingAccounts[int(aAccount)]['id'])
        print('Followed them!')
        userNames += f'@{updatedAccount["username"]}\n'
        try:
            mastodonMascot.status_post(f'Welcome to mastodon.radio @{updatedAccount["username"]}!\nAny questions, your admin is M0YNG\n\nPlease toot an #introduction with topic hashtags so we can get to know you :alex_grin:\n\nYou can find more people on our server using the local timeline\nhttps://mastodon.radio/web/public/local\nPlease fill out your bio before following people.\n\nThis list of radio amateurs on mastodon may be of interest\nhttps://pad.dc7ia.eu/p/radio_amateurs_on_the_fediverse\n\nI recommend the third-party apps\nhttps://joinmastodon.org/apps\n\nRemember to describe your images!', visibility='direct')
            print('Tooting to welcome them!')
        except:
            print(f'Failed to welcome {updatedAccount["username"]}')
    templateToot = f'Everyone, please welcome\n{userNames}\n\n#introduction'
    print('Tooting bulk welcome message...')
    mastodonMascot.toot(templateToot)
else:
    print('\nOk, DOING NOTHING.')

Brain transplant on CALEX 4-way to ESPHome

18 April 2022 at 21:17

A while ago I spotted some "Smart" 4-way power plugs "Reduced to Clear" in Tesco from £30 to £7.50 - so I bought two.

They are named "CALEX Holland Smart Power Plug 4-way / 4 USB", and are still listed on the Tesco website at £30

A black box with an image of the device and various logos

They use tuya - as so many things these days do, and I excitedly began the process of using Tuya-Convert to convert them to use the ESPHome firmware. Sadly these devices don't work with Tuya-Convert (at time of writing) due to the known issue of a New PSK Format and/or that they use a "WB3S" microcontroller which is not an ESP device but a custom controller tuya have started using (possibly to thwart people like me, probably for cost reasons.)

I read (somewhere) that the WB3S and the ESP8266 have the same pinout so it is theoretically possible to perform a "brain transplant" by removing the WB3S and replacing it with an ESP8266.

Before we start I need to remind you that this device has MAINS VOLTAGE in it, and you should only do this if you are confident you know what you are doing. Don't come crying to me if you die in the process, etc.

TL;DR

  • I works
  • It's a good job I bought two

Performing surgery

This is a fairly simple process, but has some nuances

  1. Unscrew case
  2. Identify WB3S
  3. Remove WB3S
  4. Replace with pre-installed ESP8266
  5. Close up case

Let's go through it all step by step!

You will need

  • An CALEX device
  • A Tri-Wing screwdriver or bit
  • A drill with countersink bit
  • A precision screwdriver / set
  • An ESP8266
  • Equipment to desolder - e.g. hot air gun, desoldering braid, soldering iron, flux, tweezers, spudger
  • Equipment to solder - eg. soldering iron, solder
  • A clamp to hold stuff still
  • Multimeter or similar to test connections

Programme the ESP8266

Before I began this I installed a very basic ESPHome firmware on the ESP8266 using the USB development board it came on.

ESP8266 developlment board connected via USB

I was able to confirm the ESP was working, and booting up, connecting to the WiFi, etc. before I did anything nasty to it.

I'll put a full configuration example below for you to start from.

Unscrew the case

Two problems here, one is that the case uses tri-wing screws and the other is that the screws are sufficiently recessed that my bit wouldn't reach. NOTE: you do not need a precision bit, these are fairly chunky screws. I had to buy a set of "security" bits and use the smallest which is much bigger than my largest precision tri-wing.

Bottom of case with six screw holes highlighted

To gain access to the screws, remove the six rubber feet. I couldn't reach the screws so used a countersink bit to drill out the top a bit, enough to reach the screws but not enough to prevent me fitting everything back together. Go slowly, too fast and you will melt the plastic rather than cutting it away. I do want to use this after I'm done and as it has mains voltage inside I don't want the cover hanging off!

Close view of drilled out screw hole - like a small impact crater

Once you can undo the screws it is simply a case of unscrewing them all and separating the cover halves. The PCB is attached to the back.

The case is open and we can see the PCB, bus bars, etc.

Identify the WB3S

The WB3S is located on the back of the PCB, when we first see the board. Remove the four small screws holding it in place and free the earth and neutral bus bars, and the four live connections from the case. You may find it helpful to unscrew the cable retainer which is using the same tri-wing screws as the case.

Turn the PCB over and look for the white rectangle overhanding the main PCB on the opposite end to the USB sockets.

The pcb is out of the case and the WB3S is highlighted

Remove the WB3S

This is a little tricky and I don't know the best way. I DO know that prying it off whilst trying to heat each pad with a soldering iron will damage the traces (possibly tearing them off the board) and destroy the main PCB, so don't do that. From the factory these boards are mostly attached with solder UNDER, not to the side, so it is very hard to get it to all melt.

I was successful by using desoldering braid and a soldering iron to remove the majority of the solder, then a heat gun to simultaneously melt the solder on all the pads and lift the entire blue PCB off the main green PCB very carefully using tweezers.

The PCB is in a clamp and the WB3S is no longer attached

I then had to repeat the process on the ESP8266 as it seems to be much easier and cheaper to buy them on development boards than as just the ESP (at least in the UK, if you don't want to wait for shipping directly from China.) Mine didn't have an overlap so I used a spudger to gently get in between the ESP's PCB and the development board and extremely carefully lifted it off whilst applying hot air.

The PCB is in a clamp and the ESP8266 is no longer attached

Replace the WB3S with the ESP8266

Hopefully this is harder to get wrong!

I placed some solder onto the now empty pads and placed the ESP8266 roughly in position. I then did a mix of melting pads directly with the soldering iron, and heating with the heat gun and pressing down with the tweezers.

This seemed to work ok, but I also flowed a little extra solder onto each pad to be sure, looking for it to appear inside the little hole in the pad.

The PCB is in a clamp and the ESP8266 has replaced the WB3S

I then went around and checked for continuity from each pad to the main PCB - ideally to the next component or connection I could see.

Close up the case

I know it's bad to close the case up before testing, but as we have mains voltage and bus bars held in place using only flexible wires I STRONGLY recommend you put everything back, screw it up, and not plug anything in until you are SURE there is no risk of the live and neutral shorting out or shocking you.

Now is the time to plug it in! If it worked you should see the LED by the USB light up, and one or two of the sockets might also have a lit LED (depending on the configuration (or lack of) you installed). It should also show up in ESPHome / on your WiFi - just like it did before when you tested it (you did test it, right?)

The rubber feet should still fit, and you could probably file/sand the rough plastic to make a nice flat finish.

Don't forget to add labels to remind you this one has ESPHome in it, and to identify the socket numbers.

Back of the closed case, it now has a Dymo label reading "ESPHOME" Front of the closed case, each socket has a Dymo label number on from 4 to 1

ESPHome configuration

It worked! Now what?

I had a "spare" PCB so I was able to trace the relays back to the controller and identify which pins control which relay. I don't think it is possible to control the USB ports, but I didn't ever try the tuya firmware so maybe I'm missing something.

You will have noticed the ribbon cable, it has seven wires. Four are used to control relays, one is ground, one is VCC, one is possibly also (a different) VCC. So there doesn't seem to be be one available to control the USB, and there isn't a relay for it, and looking at the PCB the USB ports seem to get a direct feed which is shared with the relays.

Each socket has an LED, which is tied to the relay so we don't need to control these in addition (like the Sonoff switch) and the LEDs around the power button are all hard wired too, so we can't control that either.

Your basic configuration could look something like this:

esphome:
  name: calex-4gang

esp8266:
  board: esp01_1m

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: "set this / auto generated"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Calex-4Gang Fallback Hotspot"
    password: "set this / auto generated"

captive_portal:

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO16
      mode:
        input: true
      inverted: true
    name: "CALEX Button"
    on_press:
      - switch.toggle: CALEX_Relay_1
      - switch.toggle: CALEX_Relay_2
      - switch.toggle: CALEX_Relay_3
      - switch.toggle: CALEX_Relay_4

switch:
  - platform: gpio
    name: "CALEX Relay 1"
    pin: GPIO14
    id: CALEX_Relay_1
  - platform: gpio
    name: "CALEX Relay 2"
    pin: GPIO5
    id: CALEX_Relay_2
  - platform: gpio
    name: "CALEX Relay 3"
    pin: GPIO15
    id: CALEX_Relay_3
  - platform: gpio
    name: "CALEX Relay 4"
    pin: GPIO4
    id: CALEX_Relay_4

It showed up in Home Assistant looking like this (after it was discovered and "configured")

Home Assistant UI showing four CALEX Relays and a button

Having Lights

I'm going to use two of these sockets to control lights, so I'd like Home Assistant to see them as lights, not switches.

To do that you need to alter the configuration slightly, remove the sockets you want to have controlling lights from the "switch" section, create a new "outputs" section and put them in there, don't give them a name (so they don't show up individually in HA). Then add a "light" section which calls on the outputs by ID, and does have a name. Like this:

switch:
  - platform: gpio
    name: "CALEX Relay 1"
    pin: GPIO14
    id: CALEX_Relay_1
  - platform: gpio
    name: "CALEX Relay 2"
    pin: GPIO5
    id: CALEX_Relay_2

output:
  - platform: gpio
    pin: GPIO15
    id: CALEX_Relay_3
  - platform: gpio
    pin: GPIO4
    id: CALEX_Relay_4

light:
  - platform: binary
    output: CALEX_Relay_3
    id: cloud
    name: "Cloud lights"
  - platform: binary
    output: CALEX_Relay_4
    id: stars
    name: "Star Lights"

Now it looks like this in Home Assistant

Home Assistant UI showing two CALEX Relays and two lights named Cloud and Star

Conclusion

What have we learned from all of this?

  • It is possible to replace the tuya controller with an ESP8266 and use ESPHome
  • Tuya probably don't want you to (they did use "security screws")
  • You probably don't want to do this unless you are willing to potentially kill a working bit of kit
  • I should have bought a hot air gun years ago

Is it worth it? Only you can answer that for yourself really. For me, the device was £7.50 (ok, I killed one but I can possibly patch it back together) and I like the privacy and control of having everything on my own network and not talking to random servers just to turn lights on and off. Plus I've learned a lot, had success, and some fun, and now I have a working useful device to replace multiple individual remote sockets in one room and a USB charger. So yes, for me, it was worth it.

koda keyboard

12 March 2022 at 14:29

I've wanted to build a keyboard for ages now, and the Penkesu computer needs a keyboard, so my first forray into the custom keyboard world was to build a koda keyboard by odd-rocket

Small 4 row by 12 keyboard with black square key caps with white lettering and a pink glow

Sourcing components

PCB

I needed to get PCB(s) made, which isn't something I've done before. I went with JLCPCB on the basis that people like Big Clive dot com use them. The process wasn't too complex, but I did end up guessing on some options (like surface finish) or just picking based on price. In the end I ordered:

  • 15
  • Purple (because why not?)
  • HASL(with lead)

Now, when Great Scott says his JLCPCB order came within the week, I suspect he gets the special sponsor treatment.

My timeline was:

  • Place order 18/02
  • Begin production 19/02
  • Complete production 23/02
  • Packed for delivery 24/02
  • Picked up by FedEx 25/02 (after cut off!)
  • Delivered 04/03

The PCBs themselves seem to be of good quality, and the one I have used works fine. I'd buy from them again.

The PCBs themselves cost £18.74 and the cheapest shipping was £19.15 for a total of £37.89 or about £2.53 per board. Did I need 15? No. Did it change the total price much? Also no. On that note, if you are in the UK and want one, let me know!

Everything else

I got the rest of the stuff from mechboards in the UK, and it wasn't cheap!

The total with shipping was £115!

I then got to test their support. The first set of keycaps I received had two "D" keys, and no "C". They asked me to post the set back and replaced it once they received them, which was slow but ok I guess.

However, when I went to install the keycaps I realised that I had no down key! In the pack it looked like a down key, but it was actually a left/right that had been rotated 90, and as the keycaps can only be installed one way (well, also upside down) I don't currently have a down arrow key. Luckily the set has a few dots that I can borrow, whilst I wait for mechboards to decide what to do next.

Building

I don't have many photos of this because I was doing it between other things, and after waiting this long I was keen to get it working!

The process is fairly simple,

  1. Snip the socket into just the side rails so we can fit the controller
  2. Shove the pins into the socket and position them into the controller - with the USB side towards the PCB
  3. Solder the pins into the controller
  4. Solder all the diodes to the under side of the PCB
  5. Solder the socket to the underside of the PCB
  6. Solder all the switches - beware of bent pins!

Later I used some brass M2 standoffs and screws with another PCB and some sticky feet to make a more robust (no exposed diodes and microcontroller) setup.

Flashing

I had to do the initial flash with the microcontroller not installed, so I could reach the reset button, or you can short RST to GND using a screwdriver.

I cloned the penkkesu repo and copied the keyboard stuff into ~/qmk_firmware/keyboards/penkesu

From there I could cd into ~/qmk_firmware/keyboards/penkesu/keymaps/default and run

qmk compile
qmk flash

Once you have QMK installed getting the keyboard into flash mode is simply a case of holding the top left and bottom left keys when connecting it to the computer, in this case esc and the bottom left dot.

Mods

Obviously I had to make some tweaks! So far just two...

RGB

Odd-rocket says

koda has no leds. two extra data pins with GND and VCC were broken out for RGB underlighting. 

These are the four through holes in a line below the controller.

  • TOP - Data
  • VCC
  • GND
  • Bottom - Data

underside of the keyboard PCB showing LED tape lit up in the middle and most of the width of the keyboard

I connected some addressable RGB LED tape to the top data connection and made the following configuration changes;

# config.h

/* Define pins etc. for RGB lights */
#define RGB_DI_PIN C6 /* specify physical data pin */
#define RGBLED_NUM 12 /* specify number of LEDs */
#define RGBLIGHT_ANIMATIONS
#define RGBLIGHT_SLEEP /* turn LEDs off when host sleeps */
# rules.mk

BACKLIGHT_ENABLE ?= no # Enable keyboard backlight functionality
RGBLIGHT_ENABLE ?= yes # Enable RGB LEDs
# keymaps/default/keymap.c
# In the third KEYMAP (the "raise" key on penkesu, to the right of space) on the second row (tab, a, s, d, f)

RGB_TOG, RGB_MOD, RGB_HUI, RGB_SAI, RGB_VAI,

This lets to turn the RGB on/off RGB_TOG, change the mode RGB_MOD, then change the Hue, Saturation, and Intensity/Brightness.

These settings are remembered between use.

It looks good, making the middle of the board glow, but it doesn't help identify keys in the dark.

the keyboard in the dark with purple glow, some letters are slightly visible

Keymap

Since writing this post I've added a few more convenience keys, including an entire layer for function keys and "forced" keys that should type what I want regardless of the keyboard layout. For example the tilde (~) is all over the place between mac and windows keyboards, US and UK layout, etc. Now I can type it with confidence. But also ‽ … 🐍 ` # ~ | €

  • make backspace perform a forward delete when holding raise
  • up/down be page up/down when holding raise
  • left/right be home/end when holding raise
  • up/down be volume up/down when holding left raise
  • (and the RGB controls)

My full keymap file now looks like

#include "koda.h"

enum unicode_names {
    BANG,
    EURO,
    SNEK,
    TILDE,
    PIPE,
    HASH,
    GRAVE,
    ELIP
};

const uint32_t PROGMEM unicode_map[] = {
    [BANG]  = 0x203D,  // ‽
    [SNEK]  = 0x1F40D, // 🐍
    [EURO]  = 0x20AC,  // € 
    [TILDE]  = 0x007E,  // ~
    [PIPE]  = 0x007C,  // |
    [HASH]  = 0x0023,  // #
    [GRAVE]  = 0x0060,  // `
    [ELIP]  = 0x2026,  // …

};

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {

    KEYMAP(
        KC_ESC, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_BSPC, 
        KC_TAB, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, 
        KC_LSFT, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_UP, KC_ENT, 
        MO(3), KC_LCTL, KC_LALT, KC_LGUI, MO(1), KC_SPC, KC_SPC, MO(2), KC_SLSH, KC_LEFT, KC_DOWN, KC_RGHT),

    KEYMAP(
        KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_TRNS, 
        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_MINS, KC_EQL, KC_LBRC, KC_RBRC, KC_SCLN, KC_QUOT, 
        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_BSLS, KC_COMM, KC_DOT, KC_TRNS, KC_TRNS, 
        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_SPC, KC_SPC, MO(2), KC_SLSH, KC_TRNS, KC_TRNS, KC_TRNS),

    KEYMAP(
        KC_TILD, KC_EXLM, KC_AT, KC_HASH, KC_DLR, KC_PERC, KC_CIRC, KC_AMPR, KC_ASTR, KC_LPRN, KC_RPRN, KC_DELETE, 
        RGB_TOG, RGB_MOD, RGB_HUI, RGB_SAI, RGB_VAI, KC_NONUS_HASH, KC_UNDS, KC_PLUS, KC_LCBR, KC_RCBR, KC_COLN, KC_DQUO, 
        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_PIPE, KC_LT, KC_GT, KC_PAGE_UP, KC_TRNS, 
        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, MO(1), KC_TRNS, KC_TRNS, KC_TRNS, KC_QUES, KC_HOME, KC_PAGE_DOWN, KC_END),

    KEYMAP(
        KC_F12, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, 
        KC_TRNS, KC_TRNS, KC_TRNS, X(EURO), KC_TRNS, KC_TRNS, KC_TRNS, X(SNEK), X(GRAVE), X(HASH), X(TILDE), X(PIPE),
        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, X(ELIP), KC_KB_VOLUME_UP, KC_TRNS, 
        KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_TRNS, KC_SPC, KC_SPC, MO(2), X(BANG), KC_TRNS, KC_KB_VOLUME_DOWN, KC_TRNS)

};

Review

I'm using it now, as a stand-alone keyboard. I've never used an ortholinear keyboard before so it is taking some getting used to the layout.

I like the switches, they feel good and sound good. I like the keycaps, they look and feel nice. But they should for the price!

I've also not used QMK before, and I didn't find it easy to get started. There seems to be a lot of older info out there, and there are many different keyboards and configurations possible, so it can be hard to pin down specifics. I've found the official documentation to be the best.

My build is pretty solid and looks nice, and I could pop it into a bag to bring with me easily enough, but it is going to take time to o get my typing speed back up!

After using it for even more longer, the main issue is that it's too light and moves around the desk when typing. Maybe more grippy feet would help this.

Ultimately I still want to install this into the penkesu when I build it, but it is a nice keyboard on its own. Maybe I can have a passthrough on the penkesu so I can connect it to a host computer and just use the keyboard.

GEEETech i3 3d printer

27 February 2022 at 10:33

I'm currently babysitting a 3d printer, it's a "GEETech i3 Pro C" - apparently.

The end goal is to make the Penkesu Computer, but first I need to get the printer working as well as possible so I can print the case.

It's a fairly basic device from what I understand, manual bed levelling for example, but has a heated bed and two extruders. Aligning the nozzles for these to be exactly the same height off the bed is ... not fun.

After quite a few small burns (and one that really hurt and still hurt after 4 hours under water) I think I'm finally getting it dialled in to work well with both extruders simultaneously.

This is the latest "XYZ calibration cube" printed with extruder 0 (purple) for the perimeters, and extruder 1 (yellow) for the infill.

The slicer preview looks like this A purple sided cube with yellow top. X and Y on the sides and Z on the top

The resulting cube looks like this A purple sided cube with threads of yellow poking out of the Y side and some gloop on the X side

Photos of all the sides are at the end of the page, if you care or want to help me improve the results!

I think I'm trying to do something quite hard here, use both extruders on every layer, but I really want to have my computer case have purple edges and yellow middle, because if I'm making it as a one off just for myself why shouldn't I have it look how I want? Other than that being hard.

To answer the inevitable "yeah, but can it even print with just one extruder anyway?" - Yes, it can. These two cubes were printed with exactly the same settings but using only one extruder, the purple loaded into extruder 0 and yellow into extruder 1. The only difference is that I grabbed a nearby fan to aid general cooling for the yellow cube as the purple one had droopy soft warm infill.

Purple and yellow cubes, both are good quality prints on the sides. The purple has a messy top but the yellow is fine

I'm using "PrusaSlicer" with the following changes to the default printer configuration - using "expert" mode because that is required to unlock multiple extruders.

PrusaSlier Printer Settings

General

  • Capabilities - Extruders - 2

Extruder 1

  • Extra length on restart - 0.1mm (ensures the extruder is extruding when it re-starts on the next layer)
  • Retraction when tool is disabled - 15mm, 0.2mm extra on restart (ensures the extruder is extruding when it re-starts on the next layer)

Extruder 2

  • Extruder offset - x: 33mm, y: 0.5mm (because the firmware doesn't handle this)
  • Extra length on restart - 0.1mm
  • Retraction when tool is disabled - 15mm, 0.3mm extra on restart

PrusaSlicer Print Settings

I find these (nominally per print) settings essential too

Layers and Perimeters

  • Vertical Shells - Perimeters - at least 3 (to prime the extruder before it starts the first layer)
  • Quality - Avoid crossing perimeters - enabled (avoid ooze from the nozzles being dragged onto the model)

Infill

  • Fill pattern - Gyroid
  • Top/Bottom fill pattern - Octagram Spiral

Skirt and Brim

  • Loops (minimum) - 6 (primes both extruders before they start working on the first layer)

Multiple Extruders

  • Perimeter extruder - 1
  • Infill and Solid infill extruder - 2

Photos of the test cube

Every side of the cube, if you like that sort of thing.

Base (opposite Z) of the cube, a star pattern is visible in yellow with some gaps and splodges

X side in purple with a blob of yellow in the bottom left, some layers are uneven or have blobs or strings

Y side in purple with 19 little bits of yellow poking out and some strings, the purple is the same as on the X side

Back side (opposite X) in purple, a few layers are wavy and some are misaligned so you can see yellow

Left (opposite Y) side in purple has lots of strings of extra purple

Sonoff S26 R2 + ESPHome

10 December 2021 at 14:22

Who wants a smart home that stops working if the internet is down, or the OEM goes bust, or wants to charge you extra, or ... Not me!

I have a couple of TP-Link Kasa "Smart Plugs" because they were fairly cheap and they can be controlled from the command line by using python-kasa or Thomas Breydo's thing however, they still (try to) talk to their OEM a lot and at the time I needed to use the app to do the initial configuration.

Then I discovered that ESPHome supports pretty much every Sonoff product, and that they are fairly cheap - I paid £32.82 for four sockets (£8.205 each) delivered from the UK.

But what I got was the S26 R2 - which I haven't seen documented elsewhere, and there are some slight differences compared with the older version.

The documentation for S20 is basically the same - so do check that too: Sonoff S20 ESP Documentation

Open it up

WARNING This is a mains powered device. DO NOT OPEN IT WHEN IT IS PLUGGED IN.

Pop off the ring around the plugs, and remove the three screws, the case should come off into two parts.

S26 R2 with cover removed

If you look closely at the writing on the chip on the daughter board you may worry that it is not an ESP8266 - but don't panic, it still works.

Flashing

There are no handy holes for the UART connections, so you WILL need a soldering iron for this.

Pull out the circuit board and look on the bottom by the daughter board which is sticking up and you should see three pads marked "V", "TX", & "RX". "GND" is on the other side of the daughter board.

You will need to solder some suitable wires to these pads (or try holding four wires very still!)

S26 R2 with wires soldered to the UART pads

On my devices TX needed to connect to RX on my serial interface, and vice versa. (yours might be different, but there it won't break anything if you get this wrong initially)

Note: don't be scared off by the term "UART", I was successful using a normal USB->Serial interface This USB to RS232 at £6 on ebay

To get the device into "flash mode" hold down the button when you connect it to the computer - no lights should come on, if they do it didn't work and you can just try again. But it is worth giving it power without flash mode to check if the device is dead on arrival!

I am using ESPHome with Home Assistant and generated the firmware from there. I named each one "s26-last four characters of serial number" and used "Pick specific board" -> "Generic ESP8266 (for example Sonoff)" as the device type.

If you are using a chromium based browser you can just flash straight from the browser! But I found it more reliable to download the firmware image and use "esphomeflasher" to shove it over to the device.

Once the flash is complete, unplug from your computer and plug it back in again. Hopefully it will boot up and connect to your network and appear in ESPHome and/or Home Assistant.

Configuration

In my experience, the generated configuration file lacked some basic functionality. Sure - you can see if the button is pressed, turn the relay on and off, and the LED on and off, but that isn't quite a complete switch!

I want to be able to:

  • Turn the relay on and off remotely
  • Turn the relay on and off by pressing the button
  • Have the state correctly relayed to Home Assistant
  • Have the LED turn on and off depending on the relay state

My configuration files look like this:

###
# All your normal WiFi Stuff here
###

# The button
binary_sensor:
  - platform: gpio
    pin:
      number: GPIO0
      mode:
        input: true
        pullup: true
      inverted: true
    name: "Sonoff S26 Button"
    # what to do when it is pressed
    on_press:
      - switch.toggle: relayandled
    # tell Home Assistant what is going on
  - platform: status
    name: "Sonoff S26 Status"
  - platform: gpio
    pin: GPIO2
    name: "Sonoff S26 Sensor"

# This is the relay
switch:
  - platform: gpio
    name: "Sonoff S26 Relay"
    pin: GPIO12
    id: s20_relay
    # the thing we do when the button is pressed
  - platform: template
    name: "Penguin Relay"
    optimistic: true
    id: relayandled
    turn_on_action:
    - switch.turn_on: s26_relay
    - light.turn_on: s26_status_led
    turn_off_action:
    - switch.turn_off: s26_relay
    - light.turn_off: s26_status_led

output:
  # Register the LED as a dimmable output ....
  - platform: esp8266_pwm
    id: s26_led
    pin:
      number: GPIO13
      inverted: true

light:
  # ... and then make a light out of it.
  - platform: monochromatic
    name: "Sonoff S26 LED"
    output: s26_led
    id: s26_status_led

TIP you can add this to your configuration BEFORE initial flashing, to avoid doing everything twice.

Tidying up Home Assistant

Each switch should now offer you some "things"

  • The relay itself (switch)
  • The LED (light)
  • The Button (sensor)
  • The state (sensor)
  • The template we called "Penguin Relay" (switch)

If this is too much clutter for your dashboard (it is for me!) you can disable most without any issue (in my experience)

  • Go to "Configuration"
  • Go to "Devices"
  • Find your switch and click to open its configuration
  • Find the thing you want to disable, for example "binary_sensor.sonoff_s26_button"
  • Click to open the Settings modal dialogue
  • Toggle the "Enable entity" switch
  • Cick UPDATE

Enable entity is disabled

Solar power in autumn

2 November 2021 at 20:47

Note: This is not a full write-up of my solar power APRS radio, I will do that one day, honest!

I've been running my APRS RF iGate purely off solar power for a few months now, and we're at an interesting time of year for solar stuff, so I thought I'd quickly note down some observations as the seasons change.

wet solar panel basking under a rainbow

Quick history

Some time ago I wondered if I could press a 20w solar panel and 7.5Ah SLAB into service doing APRS, the answer was YES - but only just.

I was happy for stuff to turn off when it runs out of power, reasoning that APRS traffic is light when there is least light so it wouldn't matter too much.

However, the 20w panel could only just keep up with the power demands of the system on a slightly dull summer day, and the 7.5Ah SLAB could only just keep it all running overnight in summer after a sunny day.

I could have left the experiment there, but I spotted a reduced-to-clear 100w panel on eBay and bought it, then bought a 55Ah battery on sale at the local motor factors (yea, I know it's the wrong type of battery.)

Since getting all that I've had everything running fine 24/7 from the moment I connected it all up.

Quick setup overview

It's not optimised, but it's also not fancy.

  • 100w solar panel
  • Charge controller
  • 55Ah battery
  • Raspberry Pi 3b+
  • TNC-Pi HAT
  • Small woxum mobile radio running low power
  • 2/70 Collinear
  • APRX software

So, Autumn?

Up until now, even with three or four rainy days in a row the system has never run low enough to power off, until this last week. We had a few VERY dark days, which ran the battery down enough that the charge controller shut off the output. Even after a sunny day there isn't enough juice going into the battery to keep everything alive overnight in early November.

Let's look at a graph/chart, and see if you can spot what is going on (answers after the break!)

Chart of amps and volts, discussed in detail below

This graph/chart is from today, so the data doesn't extend to midnight, because it isn't midnight yet. However, there is another big gap between around 0300h and 0900h where the system was offline. Which is fine, that's part of the design and "expected operation".

We can see that from some point in the morning the panel has been generating power and charging the battery, as we're at around 12.5v before we get any data (because the Pi is monitoring its own voltages) and it is coming it a respectable (if not amazing) 2 Amps. The panel is rated 100w at 5A, so we're running around 40% here, which for an autumn morning isn't too bad.

Can you guess what happens next though? From around 1000h to 1300h we have basically no solar generation. Any ideas why? Yup, my garden is north facing and even though I have the solar panel as far away from the house as possible there is still a big house shaped shadow that falls on the panel for these three hours. I suspect a lot of potential power is being lost here because it is the hight of the day and when the solar panel could be generating the most power.

This theory is supported by the nearly 4 Amps generated almost immediately the shadow moves off the panel. (The data is read every 30s but grouped up to 15 minutes for the chart.)

If we look at my historic solar data we can see how much power is generated and used and stored every day since late June 2021. The highlights are:

  • Good days produce 18Ah of solar power
  • Average days use 8Ah
  • Once we get to days that start with 0 stored power the table is not helpful

This is because if we start with 0 power, and the next day also starts with 0 power, clearly the system is only able to use the power generated that day. On that point, it's also not that useful for days where there is lots of sun because the battery can only store solar power in the "space" made by using it overnight.

Compared with summer

Let's look at another graph/chart and see how this compares with a summer day, this is from September

Chart of amps and volts, it looks like you'd think it should!

In this chart you can see the voltage is following the "proper" cycle of charging upto 14.25 volts (maybe there was a cloud around 1000?), then dropping off to hold at 13.8 volts for the rest of the day. We also start earlier with the voltage picking up from 0700 and dropping off again at 1700. These times are mostly dictated by neighbouring houses and the shadows they cast. The panel was also closer to my house at this point, but as the sun was higher in the sky it didn't matter, unlike now!

The raw power generated is actually less, but that's because we're just topping up what was used overnight, and providing some to run directly, rather than sucking up every single amp available.

A dark day

Chart of very few amps and volts

If I Remember Correctly [IIRC] this was a dark day that rained a lot, amazingly the solar panel still managed to produce 2.77 Ah that day, but barely managed to prevent the battery voltage from ending the "sun lit day" lower than it started.

What am I going to do?

We're always going to struggle with the triangle of power, to keep this thing up overnight I need to do one or more of:

  • Increase storage
  • Increase production
  • Decrease usage

Decreasing usage isn't that easy, other than tuning the pi a little by turning off USB+Ethernet, turning off HDMI, and turning off LEDs that I'm not using. I could also underclock the CPU, but it is already running the ondemand governor which turns down the clock speed when it is idle. I can't reduce the frequency it does APRS stuff either, as that is the entire point of it existing.

Increasing production could be done by adding more panels, but that's overkill for most of the year and expensive. I am considering moving the panel again so it gets sun later in the morning, but isn't shadowed during the peak of the day. This should help boost production a little. But at the expense of a later "start up"? I could move the panel to the house roof, but a) I'm not climbing up there, b) I don't have any way to attach it, c) I don't have long enough cables, d) the roof faces East and West, not South so we'd still have the shadow problem.

Increasing storage probably ins't useful in this context, we're not making enough to keep going until the next sunrise so more storage wouldn't do much.

What was the point of all this?

Nothing really, I just thought it was interesting and I look at these graphs every day so wanted to share!

Complex 19 + Licences

7 October 2021 at 19:43

Today I made the source of Complex 19 available, so I thought it would be a good time to write a little bit about Complex 19 itself, and why it took me so long to release the code.

What is Complex 19?

Complex 19 is a Static Site Generator which takes in Markdown and outputs Gopher, Gemini, and HTML.

It can do:

  • Blog posts (like this one)
  • Pages (that you want to stick around for longer)
  • Make a page showing all the posts by Tag
  • Make a page showing all the posts by date
  • Make RSS and Atom files
  • Do all of that for Gopher format
  • Also do it for Gemini format
  • It does HTML files too!
  • Cope with some static files, e.g. your gpg key
  • Optimise images

It relied on many FLOSS projects and I'd like to acknowledge the awesome shoulders I stand on every time I can pip install something.

Why? Well because I could and because I wanted to. Will it be any use to you? Probably not.

Want to play? I've also installed gitea on my VPS to host it (I have gitea on my home network too so updates may be lumpy.)

Complex 19 git repo

A quick history

I've had a website since mumble years ago, and I've used a lot of things to make websites, from Micro$oft Publisher, Front Page, Dreamweaver, Drupal, Wordpress, and most recently hexo.

I wanted to try making my site available via Gopher, and as I already had my content in markdown it should be easy, right? Well as things tend to do with me it spiralled out of control. If you spot any references to "gopherStaticMaker" in the code, this is why.

I also wanted to support Gemini, and it was pretty easy. Yay for Open Source.

At this point I wondered why I was still using hexo to make HTML files, so I extended it to do that too.

I've been using it for a while now and think it does everything I need and want it to.

Picking a licence

I've been holding off releasing the code for one main reason, what licence?

I'm very aware of the ethics of software, I've gained massively from FLOSS over the years and Complex 19 is built on it. However, I also cannot feel comfortable releasing software that could be used for harm.

I run mastodon.radio, and like many instances it is a great place with friendly and supportive people. However, there are other instances that also use mastodon for things I find abhorrent. As I write this post the exploitative practices of Facebook are (yet again) being laid bare and parallels to the tobacco industry are obvious. Facebook is built on FLOSS, Gab is built on FLOSS.

I cannot feel comfortable releasing code that could be used in ways that harm people without any protection against that. Maybe there is a vanishingly small chance anyone would use Complex 19 for their hate site, but it's still not zero. I have snippets of code on my site that could more directly cause harm, for example python scripts that interface with mastodon, and I have been considering how to licence these too.

I know licences don't fix the problem, I know a bad actor will just ignore it, but I can try, right? We know that Google won't use AGPL licenced software so it can have an impact.

I'm not alone, there are quite a few Ethical Licences, (Organization for Ethical Source have a few options.)

After much consideration, deliberation, and procrastination, and having published the code I decided to "Just pick a license!111!!!" (thanks @erebion@chaos.social) and have picked The Hippocratic License 3.0 (or later.) Because it seems "good enough" for what I want, and has been around for a while and still seems to be cared about. I can always change it later, right?

I'm probably going to keep using the GNU GPL for some stuff, but I'm using the Hippocratic License for Complex 19, the code snippets on my site, and probably most of the other code I release in the future.

Talking IRC via XMPP

5 September 2021 at 14:10

mastodon.radio has an XMPP server, which is mostly there because I wanted to see if I could. WIth some help from Mike it even authenticates using the user's mastodon account (mastodon xmpp config file) but none of that is relevant to this!

Danie van der Merwe wrote

Matrix is interesting in that it bridges to lots of other networks. So we have a #hamradio-za IRC channel at Libera.Chat, but it can be fully and easily accessed from Matrix by just joining the room from there. IRC Libera.Chat does already have a global #hamradio channel going too.

And I wondered if we could do XMPP to IRC and things looked hopeful because mod_irc exists. But I couldn't find any documentation on it, and Харпер in the XMPP Service Operators MUC said it was depreciated and the modern option was biboumi.

I patched together some bits from the biboumi documentation and this post by copyninja to get it working, and here follows an idiot's guide to remind me what to do next time (if there is one.)

A quick note on why

There isn't really any obvious advantage to doing this rather than just directly connecting via IRC, but some benefits to me are:

  • Extra privacy, as the connection goes via the mastodon.radio server rather than showing my home connection IP to the IRC server
  • Persistance, as long as I'm connected to XMPP somehow the IRC connection stays alive
  • Persistent authentication, identify with nickserv once on my laptop and I'm identified on my phone too (because the IRC server only sees one connection)
  • Seamless multiple devices, I can jump between laptop and mobile and miss nothing
  • Battery, blabber uses very little power on my phone (3% over 20 hours)
  • I can just keep using the same clients

Configure ejabberd

We need to tell ejabberd about our "external component", which we add to the listen section

listen:
    -
        port: 5347
        ip: "127.0.0.1"
        module: ejabberd_service
        access: all
        hosts:
            "biboumi.mastodon.radio"
                password: yourpasswordgoeshere

This password is for biboumi to talk to ejabberd and is needed in the biboumi configuration file.

You can restart ejabberd now and hopefully it will start up happy.

Database configuration

I already have postgres as mastodon uses it, so it was easy to just add a new user and database for this.

First get a root prompt on postgres:

sudo -u postgres psql

Then create our user, give it a password, and create a database:

CREATE USER biboumi CREATEDB;
ALTER USER biboumi WITH PASSWORD 'yourDBpasswordHere';
CREATE DATABASE biboumi OWNER biboumi;
\q

Configure biboumi

Mine is very mich like the example...

hostname=biboumi.mastodon.radio
password=yourpasswordgoeshere
xmpp_server_ip=127.0.0.1
port=5347
admin=m0yng@mastodon.radio
db_name=postgresql://biboumi:yourDBpasswordHere@localhost/biboumi
realname_customization=true
realname_from_jid=false

At this point I would run biboumi manually to see if it works and look for any issues and errors (I had a few and this is the final working version.)

sudo biboumi /etc/biboumi/biboumi.cfg

Assuming everything is ok, we can enable and start the service

sudo systemctl enable biboumi.service
sudo systemctl start biboumi.service

Connecting to an IRC channel

From the XMPP side of things we are joining a Multi User Channel (MUC) but the format is slightly verbose, for example to join the hamradio channel use this magic string:

#hamradio%irc.libera.chat@biboumi.mastodon.radio

#hamradio <- is the channel name
%irc.libera.chat <- is the IRC server
@biboumi.mastodon.radio <- is the local transport we set in the configuration

From there everything just sort of worked for me, I'm even able to send and receive messages on my laptop (using dino) and mobile phone (using blabber.im) and a terminal (using profanity.)

In order to speak in the #hamradio room on libera.chat you need to have registered your nickname. Which is fairly easy.

You can't do the usual /msg NickServ so you need to start a chat with NickServ directly. Their "address" is basically the same as we used to join the channel:

nickserv%irc.libera.chat@biboumi.mastodon.radio

(note the lack of a #, 'cos it's not a channel.)

Then you can just message them directly with the usual commands

REGISTER <password> <email>

which will hopefully send you an email with instructions to confirm the registration. Then you need to identify the next time you connect - directly messaging NickServ like you did to register.

IDENTIFY <nick> <password>

If you have any questions, and think I might be able to help, feel free to ping my via XMPP m0yng@mastodon.radio or mastodon

Printing my eQSLs

5 August 2021 at 14:22

eQSL is a cool idea, I have some reservations about it (most useful features are paid for, control over cards is limited, card size is small, the site is clunky) but it provides all the thrill of QSL cards without the delay ... or something.

Anyway, I can see my cards in cloudlog, which is great, but there isn't an easy way to just browse the cards (that I know of) or get a copy of them. When you view the cards in the eQSL website there is an option to print them (which doesn't work when I tried) but all that does it show just the image, and it is all very manual and labour intensive with lots of clicks.

I had a rummage around and found an old python script to download the card images (eqsl-downloader-python) but couldn't get it working, I think some of the URLs have changed. CloudLog can fetch the cards too, so by mixing up the code from both I was able to hack together a script that would download the cards and send them off to my thermal printer. Some time later, I'd run out of paper and only done half of the QSL cards!

two reels of paper, one unwrapped to show eQSL cards in black and white

You could obviously just use the script to download all your eQSL cards, but then they still only exist as data on your computer and not on a long reel of paper!

The eQSL "API"

eQSL doesn't have a proper API for getting cards, there are the following steps:

  1. Call a URL with your username and password as query parameters
  2. Get an HTML page with a link to the ADI or TXT file of your log
  3. Download the log file
  4. Parse the log
  5. For each log entry call a url with the QSO's date year, date month, date day, time hour, time minute, mode, band, and remote call (oh, and your username and password)
  6. Get an HTML page with a link to the image
  7. Download the image

Printing the cards

This was fairly simple, Adafruit have a nice library for the printer that can handle printing suitably sized images so all I needed to do was rotate the file to better fit the paper and resize it (which wasn't actually needed, because the cards are such low resolution already!)

You may not want to rotate the image, as you can see here the card from E7TT is still readable and takes up much less space than the full size card from OH5BM.

E7TT and OH5BM cards printed onto paper

You will also notice that many of the cards don't look great, obviously a thermal printer designed for text doesn't handle images with lots of colour and detail very well. Plus, I vastly overestimates the paper I had available / underestimated how many cards and how much paper they would need!

two strips of paper showing cards from EAGLE OM5XX F1RUM F4GUK S53AK

The script

This is far from clean code, but it does work, for me at least.

import requests
import re
import adif_io
import os
# you can cut these two if you don't want to print them out
from Adafruit_Thermal import *
from PIL import Image


BASE_URL = 'http://www.eqsl.cc'
baseURL = 'https://www.eQSL.cc/qslcard/DownloadInBox.cfm?'
username = ''
password = ''

# you can cut this bit if you don't want to print them out
printer = Adafruit_Thermal("/dev/ttyUSB0", 19200, timeout=5)

eqslPageURI = baseURL + 'UserName=' + username + '&Password=' + urllib.parse.quote(password)
eqslPage = requests.get(eqslPageURI)
adiURI = re.search('<A HREF="(.*)">.ADI file</A>', eqslPage.text)

adiData = requests.get('https://eQSL.cc/qslcard/'+adiURI.group(1))

qsos, header =  adif_io.read_from_string(adiData.text)

for qso in qsos:
    outputFile = 'cards/' + qso['QSO_DATE'] + '-' + qso['TIME_ON'] + '-' + qso['BAND'] + '-' + qso['MODE'] + '-' + qso['CALL'].replace('/','-') + ".png"
    print(qso)
    if (not os.path.isfile(outputFile)) :
        card_url = 'https://www.eqsl.cc/qslcard/GeteQSL.cfm?Callsign=' + qso['CALL'] + '&CallsignFrom=' + qso['CALL'] + '&QSOYear=' + qso['QSO_DATE'][0:4] + '&QSOMonth=' + qso['QSO_DATE'][4:6] + '&QSODay=' + qso['QSO_DATE'][6:8] + '&QSOHour=' + qso['TIME_ON'][0:2] + '&QSOMinute=' + qso['TIME_ON'][2:4] + '&QSOBand=' + qso['BAND'] + '&QSOMode=' + qso['MODE'] + '&UserName=' + username + '&Password=' + urllib.parse.quote(password)
        cardPage = requests.get(card_url)
        cardImageURL = re.search(' src="/CFFileServlet/_cf_image/([a-zA-Z0-9_\-.]*)"',cardPage.text).group(1)
        print(cardImageURL)
        with requests.get('https://www.eQSL.cc/CFFileServlet/_cf_image/' + cardImageURL, stream = True) as incoming:
            with open(outputFile, 'wb') as outgoing:
                for chunk in incoming.iter_content(chunk_size=16*1024):
                    outgoing.write(chunk)
        # you can cut this to the end if you don't want to print them out
        image = Image.open(outputFile)
        image = image.rotate(90, expand=True) # only if you want them bigger
        image.thumbnail([384,100000])
        image.save(outputFile+'-thermal.png')
        printer.printImage(outputFile+'-thermal.png')

Conclusion?

This is almost certainly pointless and a waste of paper, BUT it is a fun wa to review past contacts in a tactile way (I only keep electronic logs) and it is cool to see SWL reports too which I don't get any other way (that I know of...)

I'm sure there are things I could do to enhance the quality of the prints, probably by some sort of processing of the image before but I'm not sure exactly what that would be.

I'm also unsure what to do with the tens of meters of printed out QSL cards I now have, maybe decorate the shack somehow?

Raspberry Pi Pico Display Pride

22 July 2021 at 12:22

I couldn't resist the raspberry pi pico any longer, and I also got a pico display to do with it. This is a pico sized hat that sits directly onto the pico and provides a nice full colour screen, RGB LED, and 4 buttons.

After playing with the demo code I wanted to try something myself, so I thought I'd try making a name badge of sorts, and using the pride flag as the background. Then I wanted to use the buttons, so made each button show a different flag. The LED also changes, trying to show some key colours from the flag.

It can show:

  • Pride rainbow
  • Trans
  • Intersex
  • Bi

In this quick demo I've made it run through the flags automatically.

GIF of Pico display showing the flags

Code uses the Pimoroni Pico Firmware

import time
import utime
import picodisplay as display

width = display.get_width()
height = display.get_height()

display_buffer = bytearray(width * height * 2)  # 2-bytes per pixel (RGB565)
display.init(display_buffer)

display.set_backlight(1.0)

display.set_led(255,255,0)
display.set_pen(255,255,0)
display.clear()

def clear():
    display.set_pen(0, 0, 0)
    display.clear()
    display.update()

def draw_trans():
    clear()
    stripe_height = int(height/5)
    display.set_pen(91,207,250)
    display.rectangle(0,0,width,stripe_height)
    display.rectangle(0,stripe_height*4,width,stripe_height)
    display.set_pen(245, 171, 185)
    display.rectangle(0,stripe_height,width,stripe_height)
    display.rectangle(0,stripe_height*3,width,stripe_height)
    display.set_pen(255, 255, 255)
    display.rectangle(0,stripe_height*2,width,stripe_height)
    display.set_led(255,0,255)
    display.set_pen(0,0,0)

def draw_pride():
    clear()
    stripe_height = int(height/6)
    display.set_pen(229,0,0)
    display.rectangle(0,stripe_height*0,width,stripe_height)
    display.set_pen(255, 141, 0)
    display.rectangle(0,stripe_height*1,width,stripe_height)
    display.set_pen(255, 238, 0)
    display.rectangle(0,stripe_height*2,width,stripe_height)
    display.set_pen(0, 129, 33)
    display.rectangle(0,stripe_height*3,width,stripe_height)
    display.set_pen(0,76,255)
    display.rectangle(0,stripe_height*4,width,stripe_height)
    display.set_pen(118, 1, 136)
    display.rectangle(0,stripe_height*5,width,stripe_height)
    display.set_led(100,100,100)
    display.set_pen(0,0,0)

def draw_intersex():
    clear()
    display.set_pen(255, 217, 0)
    display.clear()
    display.set_pen(122, 0, 172)
    display.circle(int(width/2), int(height/2), int(height/3))
    display.set_pen(255, 217, 0)
    display.circle(int(width/2), int(height/2), int(height/4))
    display.set_led(122, 0, 172)
    display.set_pen(0,0,0)

def draw_bi():
    clear()
    stripe_height = int(height/5)
    display.set_pen(214,2,112)
    display.rectangle(0,stripe_height*0, width, stripe_height*2)
    display.set_pen(155,79,150)
    display.rectangle(0,stripe_height*2, width, stripe_height)
    display.set_pen(0,56,168)
    display.rectangle(0,stripe_height*3, width, stripe_height*2)
    display.set_led(155,0,150)
    display.set_pen(0,0,0)


def draw_details():
    display.set_pen(0,0,0)
    display.text("Christopher", 5, 10, 100, 4)
    display.text("M0YNG", 20, 40, 100, 7)
    display.update()


while True:
    if display.is_pressed(display.BUTTON_A):
        draw_trans()
        display.text("Trans Rights!", 20,40,100,7)
        display.update()
        utime.sleep(2)
        draw_trans()
    elif display.is_pressed(display.BUTTON_B):
        draw_pride()
        display.text("Stonewall was a riot", 10,10,100,4)
        display.update()
        utime.sleep(2)
        draw_pride()
    elif display.is_pressed(display.BUTTON_X):
        draw_intersex()
        display.text("", 5, 10, 100, 7)
        display.update()
        utime.sleep(2)
        draw_intersex()
    elif display.is_pressed(display.BUTTON_Y):
        draw_bi()
        display.text("", 5, 10, 100, 7)
        display.update()
        utime.sleep(2)
        draw_bi()
    draw_details()
    utime.sleep(0.1)
❌
❌