Wednesday, November 29, 2023

Return of the Christmas Tree Project (ch 5): More LEDs!

It's been two years since I started my Christmas tree project. Last year, I didn't work on the project at all, but this year, we're back in full force. And I bought more led lights to put in my tree. In fact, we'll be doing five times as many LEDs as before!

Because it had been so long and because I had moved since I worked on it last, the first thing was to get the project up and running again. And the first thing to work on was my Raspberry Pi. Unfortunately, the sd card in the Pi that I was using before had gotten corrupted.

I spent quite a bit of time looking for my card reader, but all it did was confirm that the sd card did not work at all. Luckily having found the card reader also made it very easy to get a new sd card ready. The next thing was to find the code. It wasn't on my desktop, so it had to be on my private git server. However, that server failed a while ago, so I had to track down the storage I used in that server. This took some effort, but I found it eventually. And then it turned out that the code wasn't on there either...

The code had apparently only been on the sd card, so I would have to write it again. The amount and complexity of the code was such that that was feasible enough, but it was still a bummer. I took a look at the code from the project that had been the inspiration for this one to look at what libraries had been used. Looking at that repository made me remember that I had shared my own code with the world as well. And that was where the code was! You see, I only kept my private projects on my private git server. My public projects were all on GitHub. And indeed, the lost code was right there on GitHub.

It seems that I did have some code that was only on the sd card. In particular, I seem to be missing the three dimensional effects that I wrote. One was a rainbow effect and that should be easy enough to recreate, especially because the one effect that I still have does have much of the general structure I'd want to use. The other effect was an effect that never really worked that well, and isn't really that much of a loss.

With the pi running again and the code being back in my possession, I could get to working on the thing again. I was unsure if I would be able to run all the LEDs from one power source, so that was the first thing to test. I connected all my new LEDs and set them to white light at 25% brightness.

Several interpretations of white...

To be fair, all lights turned on and all of them being white is the worst case. But, 25% brightness is roughly 75% below the worst case. And the results were pretty bad. However, this looked like the power adapter was handling it just fine. Instead, it looked like voltage drop to me, which is not related to the adapter but to the lights and the wires between them.

So, I rigged up a quick test and saw that indeed, things looked a lot better if I connected the power source to each string of lights separately, even if it was still the same single adapter. The light strings do have extra wires that help you do this, but the bare wires aren't too easy to use. So, I decided to take some of my Dupont cables, cut them in half and splice the halves onto those wires to make them much easier to use. Using these tools and this skill set was a lot of fun, which surprised me because I had forgotten how much I liked doing this.

A bit of solder
Some tape
And some more tape

I added a breadboard to map from one input coming from my micro-usb breakout boar to multiple wires going to the different strings. This actually made for a surprisingly good setup.

Now, that's what I'd call white

I also worked a bit on my code. I took some of my old one-dimensional code and cleaned it up. I also wrote a simple rainbow effect. It's not as good as an effect that includes a tree and coordinates of the LEDs, of course, but it's still quite nice. And I did use some of the lessons I remembered from making the rainbow effect for in the tree two years ago.

The next step will be to try to cram all these LEDs into my small tree. (I'm at half the number of LEDs Matt Parker used, but my tree is also a lot smaller.) And then I can start using the most essential and complex code I wrote last year to map all the LEDs. I'm happy I won't have to rewrite that code!

Saturday, September 30, 2023

Unopiniated variable binding

I'm a bit crazy. I like vanilla front-end work. I just like it when you do not need a compile step and what you've written is what is executed. I've always been like that, even when it made a lot less sense than it does right now, like in the times of Internet Explorer and browsers having lots of incompatibilities and some not supporting standards well at all.

In the light of using vanilla javascript, seeing the technology making progress is really nice. Sometimes, that means that you get shiny new tools built into the browser. Other times it means that you no longer need a library to do what you were already doing. I loved learning about document.querySelector(), as it covered one of the two things I used jquery for. The other thing I used it for was later covered by the fetch api. Learning about css variables was far more exciting to me than learning about the css preprocessors of the time.

When I do use a framework (for personal projects), I like using something that isn't very opinionated. The big front-end frameworks generally tell you how to set up your whole project. I prefer something that I can introduce in that one place if I want. And if the project grows big enough, I will refactor it to have a better structure. I probably end up with something similar to what the frameworks prescribe. Or I might end up with something slightly better or a lot worse. In any case, it'll probably be more tailored to my project.

In the past few years, my goto front-end framework has been Knockout.js. When I was first introduced to it at a previous job, I thought it was just another AngularJS with a slightly different syntax, but in the years after that I started appreciating it for how unopinionated it was. There was one major part of its usage that I didn't like, but adding knockout-es5 solved that. The es5 library doesn't work well with knockout's component functionality, though, which is essential once you start refactoring. So I wrote a script that worked around that. The script was a quintessential hack: it does some pretty ugly manipulation and leaves things in a state where it usually works. Together, all of these basically gave me what I wanted: unopinionated variable binding.

The time to let go of knockout has been steadily approaching for a while, though. There really hasn't been much development of the project for years. The project's website has had trouble several times recently. There is tko.js which aims to be the next version of knockout, but development seems stalled in an alpha state and it doesn't actually address most of what I want to see changed. I just don't really see a path towards not needing the knockout-es5 or my own script on top of it, and both of those feel more like temporary fixes than true solutions. For projects that I expect to live for a long time, it just doesn't seem to be sustainable enough to keep using it.

Recently, I have been working on a very old project. This project is built around the google maps API. Because of that, it doesn't actually do a lot of html manipulation. So, even when redoing most of the project six years ago or so, I just did that little bit of manipulation in vanilla javascript. But now I'm adding features, and some of those do require a lot more manipulation.

That sent me into a bit of a panic. Not using a framework was starting to weigh down the project. Using knockout doesn't feel like a sustainable option, but other frameworks are much more opinionated than I like. Even the ones that are minimal and/or proclaim not to be opinionated require you to work in components and thus don't just drop right into your project to do that one task you need from them. I felt like I was at a crossroad with none of the paths leading where I wanted to go.

I think I have a solution, though. I started looking at WebComponents recently. It's a collection of three javascript APIs that are mostly used by frameworks. That's because using them without one is complicated and doesn't really manage to compete with using a framework. I have been using two of those APIs to prototype what is basically my own framework. It provides the unopinionated variable binding that I was looking for and not much more than that.

It's only some three hundred lines of code, but it's able to bind variables to text, attributes and events, and it's able to repeat parts of your html or remove them. About a third of the code is dedicated to a solution for tables, as the default looping solution clashed with the way those are special in html. And because it is built on top of WebComponents, the system to upgrade to when refactoring is built right in.

There's definitely more to be done. When you use the prototype incorrectly, it doesn't tell you what you need to change. There will probably also end up being more things that the library needs to do to. But I do believe this pretty small prototype already covers most of what I want.

I compared total byte size of my prototype to Alpine.js (just because it came up in a search for small frameworks) and the byte sizes are about the same. That's more impressive than it sounds, because we're comparing a minified and gzipped version of Alpine with the plain sources of my prototype. While size comparisons aren't actually that useful, size does matter. It matters in how sustainable maintaining the code base is. It's also a part of how close it is to vanilla javascript, which is a part of how it stays unopinionated and how it can interoperate with existing codebases and other frameworks. For neither of those it's the whole story, but it is an important part of it.

I think I'll be using this for the time being. And I'm starting to think that a version of this may end up being my tool of choice for a long time to come...

Saturday, December 18, 2021

Christmas Tree Project Step 4: Improving the Mapping

Last time, I got the first 3D mapping of the LED coordinates. I wasn't entirely happy with the results, though, so it was time to improve them. So, I started by working on code that I could use to verify the results of the scan. The idea was to turn a few LEDs on at a time, so you can visually verify that those LEDs are in the right order on one of the axes. However, while the y axis is completely obvious, the x and z axes aren't visible in the real world, so that was the first thing I did: create a gradient from red to blue on one of the axes so you could identify the axis.

The x axis

From there, I turned the lights green one by one according to their order on that axis. My original idea was to turn just the light after it and the light before it on, or perhaps a few, but I ended up just turning everything on, with the ones that were before the green LED being red, and the ones after it blue. Then, you could press either space if the LED appeared to have the right coordinate when it came to that axis, or x if it didn't. The software would then remember the incorrect coordinates and write them to a file when done.

Verifying the green LED

A modified version of the mapper can now also be used to scan only the incorrect coordinates. In that case, it will read which LEDs need to be rescanned from the file. It then scans every such LED from all four directions, but only updates the coordinate components of the axes marked as incorrect. With those changes made, I got back to the physical tree and got working on my new coordinates.

I decided not to use my previous mapping results, but start over instead. I had marked the position and orientation of the tree and the camera stand hadn't moved, but the camera wasn't in it in the exact same way. The way the webcam was mounted on the stand was a bit improvised and the webcam was borrowed from my work setup, so it hadn't stayed in the stand and the result was that I didn't put it back in the exact same way.

Improvised does not mean bad!

So, I took a few pictures with the new setup to get the tree properly in frame again and then started scanning. I alternated between judging the coordinates and rescanning them several times until I ended up with something that I was happy with.

Deciding what LED was wrong and what wasn't always obvious. When two LEDs are in the incorrect order, this method just isn't great for determining if the first one or the second one was wrong. When there are more LEDs involved, it gets even more complicated. I did develop a bit of a feeling for it, I just chose for rescanning more than necessary in a number of cases and I restarted the verifying process without saving the results a number of times. I could probably improve things by allowing you to go back in the LED order as well, so I will probably do that if I end up mapping another tree.

I was also important to learn that the result just wasn't going to be perfect. Slight changes in perspective can have big results, and we're already combining several perspectives into one coordinate. Whenever two LEDs were a close call, it was best to just go with whatever result the scan had. The mapping wouldn't be perfect, but it was never going to be perfect and as long as it's close enough, it'll look good, especially considering that the viewer won't have a fixed perspective either.

Finally, there was one LED left that just didn't get a good scan for its Z coordinate. I checked the photos and saw that it was indeed on, but just wasn't visible from either of the two opposite sides. I decided to just set the coordinate manually, so I lit it up as well as some LEDs around it. I identified which were its closest neighbors and gave it a coordinate between them, which I manually put into the output file.

With the new mapping, the next thing to do was of course to run the test effect again. I think the actual changes were relatively small, but the whole effect looks a lot more smooth to me...

(Well, I promise it does in person, at least.)

As before, all the code I wrote for this project can be found on GitHub.

Is the this the end of this project, then? Not just yet. The basic setup is done now, but I do still need to run effects on the thing. I am planning to both write some of my own and run the ones submitted to project that serves as inspiration for this one...

Tuesday, December 14, 2021

Christmas Tree Project Step 3: Mapping

Spoilers of what I pulled off

With the hardware gathered and the lights working, the next step was to put the lights in the tree. I took a bag of zip ties that I think I bought with the intention of using the for cable management in my computer (I bought way too many, though). For the most part, this was pretty uneventful, but there were a few things that I would do different if I'd do this again. The first is that I would plan it more carefully and use more zip ties and attach in more places. The second is that I dislike the colored wires between the lights, so I think I would sleeve them in something green to make them stand out less.

The colored wires do stand out...

I also wanted wifi on my raspberry pi, so I wouldn't have to run a network cable to it. As I realized last time, the device itself doesn't have wifi hardware, so I went looking for wifi adapters. I found two. Both are old and were probably cheap, but they were worth a shot. The first adapter didn't get recognized by the Raspberry Pi, but the second one worked well and after some messing with the configuration, I was able to freely move the device anywhere I had power.

Getting wifi running

The next part was the real challenge: mapping the LEDs to three dimensional coordinates. I did actually send an email to Matt Parker to see if he could share the code he used for this, but unfortunately, he didn't have it in a usable form. Not wanting to wait, I started working on my own.

Getting the camera to take a picture was both easy and hard at the same time. Basically, copying some code from the internet and saving an image wasn't that hard. However, there were basically three libraries to choose from, and none of them was exactly what I wanted and I definitely didn't like that you have to install non-python dependencies for each of them. In the end, I went with pygame, which uses sdl2 under the hood.

My complete mapping setup

There were also several issues with the camera that I had to solve once I started using it more extensively. The first was that the first photo would often have different brightness at the top than at the bottom, with a clear dividing line about a fifth from the top. However, this didn't show up at first, as it starts happening when you run the program more than once. I solved this by taking an image from the camera on startup, discarding that and then working only with the images taken later on.

The next problem was something I started noticing when I was working on the mapping: the photos taken were often from well before I gave the command, sometimes by ten or more seconds. I got around this by re-initializing the camera every time I wanted to take a picture, as then the picture would always end up being current. To prevent the previous brightness issue, though, I had to discard an image each time I did this.

With the camera working, the next step was to start identifying the LEDs and their location. The idea was simple: turn on a LED, take a picture, identify the brightest pixel in the picture and repeat this for the next LED. I also added a step of drawing some lines on the picture, so I could see the result.

Pretty orange lines

The first result wasn't bad for some of the LEDs, but just going with the brightest pixel didn't quite work for others. So, I changed my code to take the average of several bright pixels. My first implementation was quite bad and allowed a gradual increase in brightness to shift the average towards the top of the image. But even after fixing that, the results weren't to my satisfaction at all. So, I decided to take a different approach. Instead of looking for the brightest pixels, I just look with pixels that are above a hardcoded brightness.

For the most part, that gave pretty good results. However, when a lot of light was being reflected, the reflection of the LED would sometimes throw off the calculation. The way I solved this was by lowering the brightness of the LEDs. Originally, I had assumed that full brightness was a good idea, but in the end about 20% brightness gave better results because there were far fewer reflections that were bright enough to be taken into account.

Next, it was time to do multiple scans and combine the results. Doing multiple scans was straight forward: just put the code in a function and all that function four times, with a bit in between where tell the user to rotate the tree. For combining, I gave each mapping result a score, based on the brightness and on how many pixels were involved. Then, I took the y coordinate from the highest score on any of the four scans, the x coordinate from the best among the front and back scans and the z coordinate from the best of among the left and right scans. And those results I printed to a file.

In order to check the results, I quickly threw together some code that read the file (this was basically a copy-paste job from Matt's code) and then changed the color of each LED in order of either the x, y or z portion of the coordinate. And I must say, I'm not at all displeased with the result. It's not perfect, but it's quite good, especially if you consider this was based on the first time I did a full scan of the tree.

There are still LEDs that pretty clearly have inaccurate coordinates. So, that's what I'll have to do for next time: build the tools to see what LEDs are wrong and correct those...

Oh, also: my code is now on GitHub.