Rusty all-sky cameras

All-sky cameras are a lovely idea, especially if you’re someone like me who enjoys hiding in the warm most of the time and letting the computers look after the telescope in the depths of winter. I’ve been enjoying some time (when the clouds permit) this summer looking at things directly, but deep-space-object imaging in summer is brutal – short periods of darkness mean all the setup and teardown only for an hour or two of adequate darkness.

An all-sky camera is just a camera with a fisheye lens looking up 24/7/365, taking exposures in darkness of typically over 30 seconds to see stars. You can analyse these exposures to find out about sky conditions, use them as a quick visual guide, and spot meteors. And since they live outside all the time, there’s no setup or teardown!

So I figured I’d have a go at doing an all-sky camera, from scratch. There were a few reasons I wanted to do this. My mirror project is still going (slowly) when I get the headspace, but it takes a lot of messy setup and teardown, so is hard to dip into in the evenings when I have time. But mostly I was curious to dig a bit more into INDI, the protocol I use to control my telescope.

INDI is the instrument-neutral device interface, and it is XML over TCP. It’s quite an old design, using fairly old technologies. However, it’s well-liked by manufacturers as it has a quite simple C API, a very flexible abstraction model, and works well over networks as well as for local use. indiserver is the reference server implementation and has dozens of drivers for cameras, mounts, observatory dome motors, and everything inbetween. I can write C, but if I can avoid it, I always try to – so rather than use the libindi library or the pyindi libraries (which depend on the C library) I thought I might have a go at writing a new implementation from scratch.

I’ve been tinkering with Rust for a while now, and wrote my first serious project in it last month – a parser for Telcordia’s slightly-obscure optical time domain reflectometry (OTDR) format. I found it quite jarring coming from Python, even having written C/C++ in the past, but after a while I realised I was making better structural decisions about my code than I was in Python et al. The Rust compiler is aggressively studious, and forces you to think things through – but after a while getting used to the style, this becomes something quite conversational, rather than adversarial.

Aside from writing some Rust I needed some hardware, so through a half-dozen eBay orders and bits I had lying around, I assembled a “v1” camera. This contained a Pi 4, a WiFi radio with a high-gain antenna, an ASI120MC camera I was using for guiding, and a 12V to 5V step-down board. The power was supplied by a 12V PSU in a waterproof box sat next to this.

To look out, the enclosure got a dome thrown on top – an old CCTV camera dome, with silicone sealant around the rim to make a good watertight fit. I didn’t get the hole position spot on, but it was close enough and seals up fine.

Armed with some hardware, I was ready to test. My first test images revealed some horrendous hot pixels – a known issue with un-cooled cameras – and some clouds. An excellent first step!

One frame from the camera
One frame, gently post-processed

Taking frames over the course of an evening and assembling into a video yielded a fairly nice result. To drive the camera I used KStars, with indiserver running on the Pi4 to control the hardware.

I assembled the above video in PixInsight, having done hot pixel removal and a few other bits of post-processing. Not really a viable approach for 24/7 operation!

v2 – Brown Noctuas – not just for PCs! They also do tiny ones.
Inlet and outlet, with grilles to keep the smaller bits of wildlife out (hopefully)

Hot pixels are more or less guaranteed on uncooled cameras, but the box was getting quite hot. So I’ve now wrapped it in shiny aluminium foil to increase its solar reflectance index, and added a 40mm fan to circulate air through the enclosure (with a 40mm opening on the far side, camera in the middle – some quick CFD analysis suggested this as a sensible approach).

This definitely helps matters somewhat, though in winter a dew heater will be required, and rain removal is something that bears further study – my initial approach involves some G Techniq car windscreen repellent.

I’ve started on an INDI client implementation in Rust, now. This has been a challenge so far. For starters, Rust doesn’t have many XML parsers/generators, and those that exist aren’t well documented. However, having gotten the basics working, the way that INDI works presents some challenges. The protocol essentially shoves XML hierarchies around and then makes updates to elements within that hierarchy, and expects clients to trigger events or make changes to their state based on state changes within that hierarchy. There’s very little protocol-defined convention and a lot of unwritten expectations.

This makes for a very flexible protocol, but a very uncertain client. This doesn’t map into Rust terribly well, or at least requires a more complex level of Rust than I’ve used to date! It does also explain a great deal about some of the challenges I’ve had with stable operation using INDI clients of various forms. There’s such a thing as too much rigidity in abstractions, but there’s definitely such a thing as too little.

So, next step is getting the basic client working and stable with good and verifiable handling of errors. I’m intending to fuzz my client in the same way I’ve fuzzed otdrs, but also to have extensive test cases throughout to ensure that I can replicate well-behaved servers as well as the full gamut of odd network errors and conditions that can arise in INDI ecosystems. Hopefully I’ll end up with a client library for INDI which is fairly bulletproof – and then I can start writing an application!

My initial plan is to write something nice and basic that just does a fixed exposure and stores the raw FITS files on disk in a sensible layout. But once that’s done, I want to tackle image analysis for auto-exposure and dark frame processing. This will involve parsing FITS frames, doing some processing of them, and writing either FITS or PNG files out for storage.

It’s definitely an interesting challenge – but it feels tractable to extend my Rust knowledge. I’ve been really taken with the language as a tool for systems programming, which overlaps quite well with astronomy software. We generally want high levels of reliability, do plenty of maths and processing which benefits from low-level language performance, and increasingly do networking or work with remote hardware. It’s a good fit, it feels, and just needs some more time invested in it.