Superdrive SV200 Wheel and Pedals

Playing Forza Horizon 5 on my Xbox Series X made me think about getting a steering wheel controller. (Again.) For this effort, I decided to start cheap. Before I spend a lot of money on this I need to know I enjoy a wheel more than a controller thumb stick and, more importantly, that I actually spend time driving with the wheel. If I actually do both and find the cheap wheel wanting, then I can justify spending more money on a better wheel. I bought the cheapest wheel I could find on Amazon that day, a Superdrive SV200. (*)

It is advertised to be compatible with almost everything out there (PC, Xbox One, Switch, PlayStation 3 and 4 but not 5) which had the unfortunate side effect of multiple labels on each button. The user is expected to figure out which labels applied to their setup, which is a bit annoying but inevitable at this end of the market. The wheel diameter is much smaller than typical of actual cars, but it’s big enough for me to grasp with both hands and more importantly not a tiny joystick with no resemblance to a steering wheel. Said wheel rotates through 180 degrees range of motion smoothly, but there’s quite a bit of play in the plastic mechanism allowing the wheel to flex a few degrees in every direction. Jury is still out on whether that flex is a problem. There are only two pedals. Their range of motion is small, but enough for me to modulate brake and throttle. Which makes them better than certain cheap analog potentiometers I’ve encountered in the past. But more importantly, the pedals feel sturdy enough I’m not worried about breaking them in the heat of a race.

Connectivity is via a USB cable, no wireless connection here. I thought maybe the electronics would present themselves as a USB HID controller of some sort, but it wasn’t quite that simple because I was expected to assemble a chain of devices by plugging my Xbox Core controller into the wheel. (Xbox –(USB Cable)–> SV200 –(USB Cable)–> Xbox controller.) I guess this was a way for SV200 to gain Xbox connectivity without paying Xbox licensing fees?

With Xbox controller plugged in to SV200, which is then plugged into the Xbox console, I brought up the controller information panel. It shows a single controller and not two. I wonder if the SV200 intercepts USB messages between the console and the controller? Whatever the mechanism was, it meant I could use the wheel and the Xbox thinks it’s coming from the controller. For feedback I get generic rumbling effects on the wheel instead of the controller as well. But not totally reliably: sometimes the controller starts rumbling instead of the wheel and I don’t know why. I just know it startled me whenever it happened.

SV200 appears to only convey controller level in-palm rumble, it is not a force feedback wheel and does not convey road texture and traction as expensive wheels advertise of doing. It also lacks the tactile trigger feedback of a standard Xbox One controller. In Forza Horizon, throttle trigger rumble signals traction control is active and brake trigger rumble for anti-lock brakes. I’ve come to appreciate that feedback and I might miss them when they’re gone. On the To-Do list: investigate whether more expensive peripherals include such feedback motors in their pedals.

Before I can decide how I feel about wheel flex or trigger rumbles, I have a more immediate problem. The SV200 is small and lightweight which is good at saving space but bad for staying still on my coffee table. The Xbox360 wheel had a sturdy clamp, but this wheel had only four small suction cups woefully inadequate for holding in place. I can’t really evaluate whether driving with this wheel is worth the effort when I’m constantly distracted working to keep the wheel in place. I need to fasten it more securely.

(*) Disclosure: As an Amazon Associate I earn from qualifying purchases.

Forza Horizon 5 Makes Me Want a Wheel Again

I’ve been a fan of the Forza Horizon series of games since the first one, where we cruised through highlights of Colorado, USA. I recently started playing the latest edition Forza Horizon 5 (*) taking place in a fictionalized Mexico. And as is typical when playing a driving game, I find it somewhat unsatisfying to use a standard (“Core”) Xbox gaming controller. I started thinking about getting a steering wheel controller again. I tried it once before in the Xbox 360 era, but that ended in a teardown treatment as I didn’t use my 360 anymore.

That disassembled steering wheel was a first-party Microsoft accessory with full Xbox 360 branding. For the Xbox One era (including current Xbox Series S|X) Microsoft decided against making a first-party steering wheel peripheral. Instead, that market has been delegated to the existing peripheral ecosystem. We have a whole spectrum of options starting from very inexpensive sets made by manufacturers I’ve never heard of, up through midrange offerings from mainstream peripheral makers like Logitech and Thrustmaster, up through serious simulation components that cost more than the Xbox console itself.

I decided to start all the way at the cheapest end of the spectrum for two reasons: One, I’ve already tried steering wheels once with the Xbox 360 wheel and didn’t use it as much as I thought I would. If history repeats itself at least I’ve wasted less money. And second, going cheap means I’ll be less intimidated about hacking into the wheel for my own customizations. I concede if I wanted a durable wheel to hack on, I should have kept my Xbox 360 wheel, but too late now.

The lowest Amazon bidder of the day was ~$60 for a Superdrive SV200. (*) Just a small wheel, a few buttons, and two minimalist pedals. Looking on Superdrive’s own website, it appears to be a French company which was a surprise. I didn’t know there were French companies in this space, good for them. The product it was still manufactured in China, and that part was no surprise.

I ordered one and waited for its arrival.

(*) Disclosure: As an Amazon Associate I earn from qualifying purchases.

Snow Globe Liqueur LED

This Christmas themed novelty product “Snowglobe Orange & Gingerbread Gin Liqueur” is an alcohol beverage packaged in a globular glass bottle resembling a snow globe. Contributing to the theme is a small quantity of edible gold flakes in the liquid that can float like snowflakes, illuminated by LEDs embedded in the base. I didn’t care about the alcohol, but I asked (and was given) the empty bottle afterwards so I could remove that LED circuit to see an example of low-cost disposable production.

Each press of the bottom toggles LED power on/off. There may be a sleep timer to turn off LED after some period, but I didn’t test that.

The LED circuit board is surrounded by a circle of soft foam, which is in turn glued to the bottle. It peeled off easily.

Four surface-mounted white LEDs are visible, labeled L1 through L4. Each pair (L1+L2, L3+L4) are in series. The two 3V coin-cell batteries are also in series for 6V power supply. Battery positive terminal is connected to both pairs of LED series at their anode (+) and control chip U1. Battery ground is connected to control chip U1 and nowhere else. U1 has six pins: two pins for power and ground, two for LED cathode (one for each pair), and two more for the switch.

Peeling circuit board away from foam, the two switch terminals are visible. Closing this circuit is the job of a small piece of conductive metal, here still held by the bottom sticker. The metal is a thin springy sheet stamped into a dome shape. Pressing and collapsing the dome closes the switch circuit. Releasing the dome lets it pop back up and open the circuit.

Anything that conducts electricity bridged across those circuit board contacts will toggle LED on/off.

This was designed to be thrown away along with the bottle once the alcohol has been consumed. I’ve tossed the bottle into glass recycle bin and I’m keeping the light, even though at the moment I have no use for a dim battery-powered LED circuit with push on/off.

Compass Project Version 1.0 Complete

Along with source code, GitHub code repositories have an “Issues” section for tracking problems. I’ve logged the outstanding problems and their workarounds for my Compass project. Since fixing them are outside of my control, I am going to declare version 1.0 complete. Now to wrap up before moving on to something else.

The primary goal of this project was to practice working with Angular framework, and that goal is a success. I knew going in that Angular wouldn’t be the best tool for the job: its architecture was designed to handle much larger and more complex web apps than my Compass. But I wanted a chance to practice Angular with something small, and Compass succeeded at that.

An entirely expected consequence of my initial decision is a web app far larger than it needs to be. I knew from earlier experiments a bare-bones Angular app doing nothing more than <body><h1>Hello World</h1></body> start at over 200 kilobytes. I wasn’t too impressed by that, but as a practical matter I accept this is not considered huge in today’s web world. I found an online tool which lists download sizes for the given URL, and most of the sites I visit weigh in at several megabytes. Even the legendarily lightweight Google home page downloads over 300 kilobytes. Judged in that context, 200 kilobytes of overhead are unlikely to be a dealbreaker by themselves. Not even in space-constrained situations, thanks to the fact it compresses very well.

Since it’s such a small app, I didn’t touch upon very much beyond Angular core. I created just two components, one (compass-needle) inside the other (capability-check) so there was no routing at all. (It is literally a ‘single-page application’). I created two services who distributed information via RxJS, but I didn’t need to deal with server communication over a network. It’s good to start small to get over beginner hurdles. My next Angular practice project can be more complex to expand my exploration frontier.

Unrelated to Angular, one thing I knew I needed to practice — and I got lots of it here — was CSS layout. This was the first project where I used media queries to react to portrait vs. landscape orientations, and it was great practice towards more responsive layouts in future projects. I know people can do great things with CSS, but I don’t count myself among them. I foresee a lot more hair-pulling in the future as I struggle to declare CSS rules to create what I have in my mind, but that’s the journey I know I need to take.

Compass Web App Workarounds

After giving my Compass web app the ability to go full screen, it’s working pretty much as I had imagined when I started this project. Except, of course, for the two problems that I believe to be browser bugs outside of my control. I’ve always known that real-world web projects have a lot of ugly workarounds for problems hiding across browser implementations, but I had thought I could avoid that by targeting a single browser. (Chrome on Android with experimental magnetometer API.) Sadly, no such luck. It is time to get hacking.

The vertical text issue is merely cosmetic and the easiest to fix. I want sideways-lr but it doesn’t work. Fortunately, vertical-lr is also a vertical text layout, just rotated in the opposite direction from the one I wanted. Because I only have a single line of text, adding transform: rotate(180deg) was enough to get the result I wanted. I believe there would be additional complications if there were more than one line of text, but that’s a problem I don’t have to deal with today. I opened a GitHub issue to track removing this workaround, but as a practical matter, there’s no real harm leaving this workaround in place even if sideways-lr starts working correctly.

The same could not be said of the magnetometer {referenceFrame: 'screen'} problem, where landscape-left receives the result for landscape-right and vice versa. While the workaround is the same, adding transform: rotate(180deg) to flip things around, I can’t leave this workaround the same way as I could vertical text. As soon as the upstream browser bug is fixed, my workaround rotation would cause the display to be wrong again. And even worse, there’s no way for me to determine within the app whether the workaround is applicable. I couldn’t issue a CSS media query for whether this bug is fixed! I don’t know of a graceful way to handle this, but at least I’ve opened an issue for it as well.

And finally for completeness, I opened an issue tracking the fact magnetometer API is a preview experiment. Because it is likely to change, this app could stop working at any time. I expect that magnetometer API would mature to become available across multiple platforms, not just Chrome on Android. Once adoption broadens, this app needs to be updated from experimental to standard API.

Compass Web App Going Full Screen

HTML started as a way to display text within a given width and whatever height is needed to accommodate that content, scrolling if necessary. My desire to create a single-page layout without scrolling feels like a perpetual fight to put a square peg in a round hole. I’ve seen layouts I like out in the world, so I believe it’s only a matter of practice to master all the applicable techniques. A tool I wanted to add to my toolbox is the ability to make my web app go full screen.

When my web app first loads up on Chrome browser for Android, I declared CSS layout to work within full viewport width and height. However, Chrome doesn’t actually show my app on the full viewport, because it has a lot of user interfaces in the way. A bar up top has the address and other related buttons, and a bar at the bottom has my open tabs and additional controls. Together they consume more than one third of overall screen space, far too much for tasks not related to my web app. Fortunately, this situation is only temporary.

As soon as I tap my app, both bars retreat. A small bar is left up top for the phone icons (clock, signal strength, battery) and a bar below reminding me I could drag to unveil more controls. I estimate they consume approximately one eighths of overall screen space. An improvement, but I can do even better by going full screen.

MDN page on fullscreen API makes it seem pretty straightforward, at least for updated browsers that support the official standard. The complication comes from its history: Going full screen was such a desirable browser feature that they were available in vendor-specific ways before they were standardized. To support such browsers, MDN pointed to the fscreen library which packaged all the prefixed variations together. Fortunately, a personal project like my Compass web app doesn’t need to worry about legacy browser support. Besides, they probably wouldn’t have the magnetometer API anyway.

After adding a button for triggering fullscreen request, I can make my app full screen and free of all browser UI. It doesn’t necessarily mean I have the entire screen, though. I noticed that on my phone (Pixel 7) my app was prohibited from the screen region where a hole was punched for the front-facing camera. When my app is fullscreen, that area is filled with black so at least it is not distracting even if it was unusable. Native Android apps can request to render into this “display cutout” area, but as far as I can tell browser apps are out of luck. That’s a relatively trivial detail I can ignore, but I have to devise workarounds for other problems out of my control.

Source code for this project is publicly available on GitHub

CSS Beginner Struggles: aspect-ratio and height

Reviewing CSS from’s “Learn CSS!” course provided a refresher on a lot of material and also introduced me to new material I hadn’t seen before. I had hoped for a bit of “aha” insight to help me with CSS struggles in my project, but that didn’t happen. The closest was a particular piece of information (Flexbox for laying out along one dimension, Grid for two dimensions) that told me I’m on the right track using Flexbox.

A recurring theme with my CSS frustration is the fact height and width are not treated the same way in HTML layout. I like to think of them as peers, two equal and orthogonal dimensions, but that’s not how things work here. It traces back to HTML fundamentals of laying out text for reading text in a left-to-right, top-to bottom languages like English. Like a typesetter, the layout is specified in terms of width. Column width, margin width, etc. Those were the parameters that fed into layout. Height of a paragraph is then determined by the length of text that could fit within specified width. Thus, height is an output result, not an input parameter, of the layout process.

For my Compass web app, I had a few text elements I knew I wanted to lay out. Header, footer, sensor values, etc. After they have all been allocated screen real estate, I wanted my compass needle to be the largest square that could fit within the remaining space. That last part is the problem: while we have ways to denote “all remaining space” for width, there’s no such equivalent for height because height is a function of width and content. This results in unresolvable circular logic when my content (square compass) is a function of height, but the height is a function of my content.

I could get most of the way to my goal with liberal application of “height: 100%” in my CSS rules. It does not appear to inherit/cascade, so I have to specify “height: 100%” on every element down the DOM hierarchy. If I don’t, height of that element collapses to zero because my compass doesn’t have an inherent height of its own.

Once I get to my compass, I could declare it to be a square with aspect-ratio. But when I did so, I find that aspect-ratio does its magic by changing element height to satisfy specified aspect ratio. When my remaining space is wider than it is tall, aspect-ratio expands height so it matches width. This is consistent with how the rest of HTML layout treats width vs. height, and it accomplishes the specified aspect ratio. But now it is too tall to fit within remaining space!

Trying to reign that in, I played with “height: 100%“, “max-height: 100%“, and varying combinations of similar CSS rules. They could affect CSS-specified height values, but seems to have no effect on height change from aspect-ratio. Setting aspect-ratio means height is changed to fit available width and I found no way to declare the reverse in CSS: change width to fit within available height.

From I saw offered ability to have code snippets in a webpage, so here’s a test to see how it works on my own blog. I pulled the HTML, CSS, and minimal JavaScript representing a Three.js <canvas> into a pen so I could fiddle with this specific problem independent of the rest of the app. I think I’ve embedded it below but here’s a link if the embed doesn’t work.

After preserving a snapshot of my headache in Codepen, I returned to Compass app which still had a problem that needed solving. Unable to express my intent via CSS, I turned to code. I abandoned using aspect-ratio and resized my Three.js canvas to a square whose size is calculated via:

Math.floor(Math.min(clientWidth, clientHeight));

Taking width or height, whichever is smaller, and then rounding down. I have to round down to the nearest whole number otherwise scroll bars pop up, and I don’t want scroll bars. I hate solving a layout problem with code, but it’ll have to do for now. Hopefully sometime in the future I will have a better grasp of CSS and can write the proper stylesheet to accomplish my goal. In the meantime, I look for other ways to make layout more predictable such as making my app full screen.

The source code for my project is publicly available on GitHub, though it no longer uses aspect-ratio as per the workaround described at the end of this poist.

Notes on “Learn CSS”

Designing portrait and landscape orientation layouts for my compass web app was the first time I applied my media query lessons. Despite browser bugs out of my control making the app largely useless in landscape mode, it was educational to get some hands-on exercise with CSS. With new appreciation for CSS as well as new questions about how things are supposed to work, I thought it was a good time for a refresher course. Instead of going through Codecademy’s CSS courses again, I decided to get a different perspective and go through “Learn CSS!” on

This course was one of several on, which comes from Google Chrome’s developer relations team. I learned of this site by attending the completely online Google I/O developer conference in 2021 and had marked it as a resource to explore later. Today is the day! For interactive demonstrations, doesn’t have an in-browser development environment of Codecademy. It uses little pieces of embedded playground instead. After this course, I have a slight preference for codepen because I could use it independently.

As of this writing, the course is split across 28 sections, each focused on a topic. There seems to be a podcast series related to this course, though the sections do not necessarily correspond one-to-one with podcast episodes. As a result of this organization, some material is duplicated across multiple sections. For example, 021 Animations had some obvious overlap with 025 Transitions. They tend to show up together, even though not all CSS transitions are animated and not all animations are for transitions. Given this commonality, they both warn to consider users who prefer not to have too many things moving onscreen. Stylesheets should query prefers-reduced-motion and cut back on the flashiness.

Side comments about prefers-reduced-motion is representative of a big difference between this course and the Codecademy course: This course reminds us that not everyone can use a computer the same way and implores us to keep the web accessible to all. So animation sections mention prefers-reduced-motion. Color sections reminds us to keep color-blindness in mind. And the Focus section explains how to make sure a site is usable by someone that has to navigate with tab instead of mouse or touchscreen. These are all good points.

Given my most recent layout experience, the most relevant sections started with 008 Layout. I was very amused by the brief history lesson at the start, where it mentioned early HTML layout were done with <TABLE>. I’m old enough to remember those days! I even used <TABLE> layout for my primitive SGVHAK Rover interface, much to the horror of some people. All of my web development education is part of my efforts to catch up to the latest tools like 009 Flexbox and 010 Grid. From my Google I/O 2021 notes, I also learned Chrome developer tools included a lot of layout debugging assistance. There was even a code lab touring CSS Grid debug tooling.

I’ve come across Flexbox and Grid in the Codecademy course, and it has been fuzzy when one tool would be better suited for another. This course has a rule of thumb that I find valuable: Flexbox is for laying out items along one dimension: our choice of row or column. Whereas Grid is for laying things out across two dimensions: we’re always dealing with both rows and columns.

Coming from the Chrome team, I was not surprised that some of this course gets into implementation details that I wasn’t sure was strictly relevant. In 004 Specificity they describe the point score algorithm for determining how a browser chooses between multiple applicable rules. Informative, but I don’t think it is important to memorize the scoring algorithm, or at least I hope not, because I don’t want to put in the time.

In all this course was packed of a lot of information, much of which were immediately useful for me. Plus other information that I might have to come back later to absorb. There are only a few things — like specificity point score — that I doubt would ever be useful. My future projects may yet prove me wrong, and the best way to know is to dive right back into working with CSS.

Compass Web App in Landscape Exposed Browser Bugs

I got my Compass web app deployed to GitHub Pages and addressed some variations in browser rendering. Once it was working consistently enough across browsers, I tackled the next challenge: creating CSS layout for landscape as well as portrait orientations. Built solely around media query for orientation, it was a great exercise for me to build experience with CSS layouts. Most of my challenges can be chalked up to beginner’s awkwardness, but there were two factors that seemed to be beyond my control.

Sideways Text

While in landscape orientation, I wanted my headline banner to be rotated in order to occupy less screen real estate. This is a common enough desire that CSS has provision for this via writing-mode. My problem is that my desired direction (top of word pointing to screen left) doesn’t seem to work, I could only get vertical text in the opposite direction (top of word pointing to screen right, 180 degrees from desired.) The MDN page for writing-mode had an example indicating it’s not my mistake. It has a table of examples in markup followed by two renderings. A bitmap showing expected behavior (here with my desired output circled in red):

And then the markup as rendered by Chrome 112 (with different behavior circled in red.)

Looking for a workaround, I investigated CSS rotate transform. The good news is that I could get text rotated in the direction I want with rotate(-90deg). The bad news is that only happens after size layout. Meaning my header bar is the sized as if the header text was not rotated, which is very wide and thus defeating the objective of occupying less screen real estate.

I guess I can’t get the layout I want until some bugs are fixed or until I find a different workaround. Right now the least-bad alternative is to use writing-mode vertical-lr, which rotates text the wrong way from what I wanted but at least it is vertical and compact.

Magnetometer Reference Frame

Landscape mode uncovered another browser issue. When initializing the magnetometer object, we could specify the coordinate reference frame. Two values are supported: “device” is fixed regardless of device orientation, and “screen” will automatically transform coordinate space to match screen orientation. The good news is that “screen” does perform a coordinate transform while in landscape mode, the bad news is the transform is backwards: each landscape orientation gives information appropriate to the other landscape orientation.

For reference, here’s my app in portrait mode and the compass needle pointing roughly north. For a phone, this is the natural orientation where “device” and “screen” would give the same information.

After rotating the phone, Chrome browser rotates my app to landscape orientation as expected. Magnetometer coordinates were also transformed, but it is pointing the wrong way!

Still Lots to Learn

These two issues are annoying because they are out of my control, but they were only a minority of the problems I encountered trying to make my little app work in landscape mode. Vast majority of the mistakes were of my own making, as I learned how to use CSS for my own layout purposes. Hands-on time makes concepts concrete, and such experience helps me when I go back to review documentation.

Source code for this project is publicly available on GitHub

Compass Web App Browser Variations

Once deployed to GitHub Pages (made easier by moving the project into its own GitHub repository) I could easily try my web app across more devices and browsers. This compass web app only really works on my Android devices with magnetometers, but the page would come up with placeholder data on every modern browser. And naturally, there are variations between browsers. The differences on iOS Safari weren’t surprising, but I was surprised at the differences between Microsoft Edge and Google Chrome as they both purportedly used the same Chromium engine.

The first and most obvious difference are update rates. All browsers would show compass needle moving in response to either real or placeholder data, but the update rate varies. On Microsoft Edge, the update rate would be on par with Chrome but would drastically slow down after several (~5) seconds without user interactivity. If I touch the needle, response rate picks back up for another few seconds before slowing down. I suspect this is a consequence of aggressive throttling of animation and/or timers in the goal of saving power.

Another difference are in page updates. One example on my is “{{magX | number:'1.2-2'}}” which is supposed to print the value of magX property to two decimal point precision. (Y and Z are handled the same way.) I update magX every time data is received, but that isn’t necessarily shown on screen. Chrome shows as expected but Edge never updates. There’s something different about how Angular runs its change detection between these two browsers. Until I understand how to work within the system, I can work around the problem by manually calling ChangeDetectorRef.detectChanges() to notify that new numbers need to be picked up.

Once I had portrait mode working more or less as intended, I started looking into landscape mode and found… uh… many more learning opportunities.

Source code for this project is publicly available on GitHub

Compass Web App Project Gets Own Repository

Once I got Three.js and Magnetometer API up and running within the context of an Angular web app, I was satisfied this project is actually going to work. Justifying a move out from my collection of NodeJS tests and into its own repository. Another reason for moving into its own repository is that I wanted to make it easy to use angular-cli-ghpages to deploy my Angular app to GitHub pages. Where it will be served over HTTPS, a requirement for using sensor API from a web app.

Previously, I would execute such moves with a simple file copy, but that destroys my GitHub commit history. Not such a huge loss for small experiments like this one, but preserving history may be important in the future so I thought this is a good opportunity to practice. GitHub documentation has a page to address my scenario: Splitting a subfolder out into a new repository. It points us to a non-GitHub utility git-filter-repo which is a large Python script for manipulating git repositories in various ways, in this case isolating a particular directory and trimming the rest. I still had to manually move everything from a /compass/ subdirectory into the root, but that’s a minor change and git could recognize the files were renamed and not modified.

The move was seamless except for one detail: there is a conflict between local development and GitHub Pages deployment in its index.html. For GitHub Pages, I needed a line <base href="/compass/"> but for local development I needed <base href="/">. Otherwise the app fails to load because it is trying to load resources from the wrong paths resulting in HTTP404 Not Found errors. To make them consistent, I can tell my local development server to serve files under the compass subdirectory as well so I can use <base href="/compass/"> everywhere.

ng serve --serve-path compass

I don’t recall this being a problem in my “Tour of Heroes” tutorial. What did I miss? I believe using --serve-path is merely a workaround without understanding the root cause, but that’s good enough for today. It was more important that GitHub Pages is up and running and I could test across different browsers.

Source code for this project is publicly available on GitHub

Magnetometer Service as RxJS Practice

I’m still struggling to master CSS, but at least I got far enough to put everything I want on screen roughly where I want them, even after the user resizes their window. Spacing and proportion of my layout is still not ideal, but it’s good enough to proceed to the next step: piping data from W3C Generic Sensor API for Magnetometer into my Angular app. My initial experiment was a much simpler affair where I could freely use global variables and functions, but that approach would not scale to my future ambition to execute larger web app projects.


The Magnetometer API is exposed by the browser and not a code library, so I didn’t need to “npm install” any code. However, as it is not a part of core browser API, I do need to install W3C Generic Sensor API type definition for the TypeScript compiler. This information is only used at development time.

npm install --save-dev @types/w3c-generic-sensor

After this, TypeScript compiler still complains that it can’t find type information for Magnetometer. After a brief search I found I also need to edit and add w3c-generic-sensor to “types” array under “compilerOptions”.

  "compilerOptions": {
    "types": [

That handles what I had to do, but I’m ignorant as to why I had to do it. I didn’t have to do the same for @types/three when I installed Three.js, why was this different?

Practicing Both RxJS and Service Creation

I’ll need a mechanism to communicate magnetometer data when it is available. When the data is not available, I want to be able to differentiate between reasons why that data is not available. Either the software API is unable to connect to a hardware sensor, or the software API is not supported at all. The standard Angular architectural choice for such a role is to package it up as a service. Furthermore, magnetometer data is an ongoing stream of data, which makes it a perfect practice exercise for using RxJS in my magnetometer service. It will distribute magnetometer data as a Subject (multicast Observable) to all app components that subscribe to it.

Placeholder Data

Once I had it up and running, I realized everything is perfectly setup for me to generate placeholder data when real magnetometer data is not available. Client code for my magnetometer data service doesn’t have to change anything to receive placeholder data. In practice, this lets me test and polish the rest of my app without requiring that I run it on a phone with real magnetometer hardware.

State and Status Subjects

Happy with how this first use turned out, I converted more of my magnetometer service to use RxJS. The current state of the service (initialized, starting the API, connecting to magnetometer hardware, etc) was originally just a publicly accessible property, which is how I’ve always written such code in the past. But if any clients want to be notified as soon as the state changes, they either have to poll state or I have to write code to register & trigger callbacks which I rarely put in the effort to do. Now with RxJS in my toolbox, I can use a Behavior Subject to communicate changes in my service state, making it trivial to expose. And finally, I frequently send stuff to console.log() to communicate status messages, and I converted that to a Behavior Subject as well so I can put that data onscreen. This is extra valuable once my app is running on phone hardware, as I can’t (easily) see that debug console output.

RxJS Appreciation

After a very confused introduction to RxJS and a frustrating climb up the learning curve, I’m glad to finally see some payoff for my investment in study time. I’m not yet ready to call myself a fan of RxJS, but I feel I have enough confidence to wield this tool for solving problems. This story is to be continued!

With a successful Angular service distributing data via RxJS, I think this “rewrite magnetometer test in Angular” is actually going to happen. Good enough for it to move into its own code repository.

Source code for this project is publicly available on GitHub

Angular Component Dynamic Resizing

Learning to work within Angular framework, I had to figure out how to get my content onscreen at the location and size I wanted. (Close enough, at least.) But that’s just the start. What happens when the user resizes the window? That opens a separate can of worms. In my RGB332 color picker web app, the color picker is the only thing onscreen. This global scope meant I could listen to Window resize event, but listening to window-level event isn’t necessarily going to work for solving a local element-level problem.

So how does an Angular component listen to an event? I found several approaches.

It’s not clear what tradeoffs are involved using Renderer versus EventManager. In both cases, we can listen to events on an object that’s not necessarily our component. Perhaps some elements are valid for one API versus another? Perhaps there’s a prioritization I need to worry about? If I only care about listening to events that apply to my own specific component, things can be slightly easier:

  • @HostListener decorator allows us to attach a component method as the listener callback to an event on a component’s host object. It’s not as limited as it appears at first glance, as events like “window:resize” apparently propagates through the tree so our handler will fire even though it’s not on the Window object.

In all of the above cases, we’re listening on a global event (window.resize) to solve a local problem (react to my element’s change in size.) I was glad to see that web standards evolved to give us a local tool for solving this local problem:

  • ResizeObserver is not something supported by core Angular infrastructure. I could write the code to interact with it myself, but someone has written an Angular module for ResizeObserver. This is part of a larger “Web APIs for Angular” project with several other modules with similar goals: give an “Angular-y” way to leverage standardized APIs.

I tried this new shiny first, but my callback function didn’t fire when I resized the window. I’m not sure if the problem was the API, the Angular module, my usage of it, or that my scenario not lining up with the intended use case. With so many unknowns, I backed off for now. Maybe I’ll revisit this later.

Falling back to @HostListener, I could react to “window.resize” and that callback did fire when I resized the window. However, clientWidth/clientHeight size information is unreliable and my Three.js object is not the right size to fill its parent <DIV>. I deduced that when “window:resize” fired, we have yet to run through full page layout.

With that setback, I fell back to an even cruder method: upon every call to my animation frame callback, I check the <DIV> clientWidth/clientHeight and resize my Three.js renderer if it’s different from existing values. This feels inelegant but it’ll have to do until I have a better understanding of how ResizeObserver (or an alternative standardized local scope mechanism) works.

But that can wait, I have lots to learn with what I have on hand. Starting with RxJS and magnetometer.

Source code for this project is publicly available on GitHub

Angular Component Layout Sizing

For my first attempt at getting Three.js running inside an Angular web app, the target element’s width and height were hard coded to a number of pixels to make things easier. But if I want to make a more polished app, I need to make this element fit properly within an overall application layout. Layout is one of the things Angular defers to standard CSS. Translating layouts I have in my mind to CSS has been an ongoing challenge for me to master so this project will be another practice opportunity.

Right now, I’m using a <DIV> in my markup to host a canvas object generated by Three.js renderer. Once my Angular component has been laid out, I need to get the size of that <DIV> and communicate it to Three.js. A little experimentation with CSS-related properties indicated my <DIV> object’s clientWidth and clientHeight were best fit for the job.

Using clientWidth was straightforward, but clientHeight started out at zero. This is because during layout, the display engine looked at my <DIV> and saw it had no content. The canvas isn’t added until after initial layout in AfterViewInit hook of Angular component lifecycle. I have to create CSS to block out space for this <DIV> during layout despite lack of content at the time. My first effort was to declare a height using “vh” unit (Viewport Height) to stake my claim on a fraction of the screen, but that is not flexible for general layout. A better answer came later with Flexbox. By putting “display: flex;” on my <DIV> parent, and “flex-grow:1” on the <DIV> itself, I declared that this Three.js canvas should be given all available remaining space. That accomplished my goal and felt like a more generally applicable solution. A reference I found useful during this adventure was the Flexbox guide from

It’s still not perfect, though. It is quite possible Flexbox was not the right tool for the job, but I needed this practice to learn a baseline from which I can compare with another tool such as CSS Grid. And of course, getting a layout up on screen is literally just the beginning: what happens when the user resizes their window? Dynamically reacting to resize is its own adventure.

Source code for this project is publicly available on GitHub

Angular + Three.js Hello World

I decided to rewrite my magnetometer test app as an Angular web app. Ideally, I would end up with something more polished. But that is a secondary goal. The primary goal is to use it as a practice exercise for building web apps with Angular. Because there are definitely some adjustments to make when I can’t just use global variables and functions for everything.

My first task is to learn how to use Three.js 3D rendering library from within an Angular web app. I know this is doable from others who have written up their experience, I only have to follow their lead.


The first step is obvious: install Three.js library itself into my project.

npm install --save three

Now my Angular app could import objects from Three, but it would fail TypeScript compilation because the compiler doesn’t have type information. For that, a separate library needs to be installed. This is only used at development time, so I save it as a development-only dependency.

npm install --save-dev @types/three

HTML DOM Access via @ViewChild

Once installed I could create an Angular component using Three.js. Inside that component I could use most of the code from Three.js introduction “Creating a Scene“. One line I could not use directly is:

document.body.appendChild( renderer.domElement );

Because now I can’t just jam something to the end of my HTML document’s <BODY> element. I need to stay within the bounds of my Angular component. To do so, I name an element in my component template HTML file where I want my Three.js canvas to reside.

[...Component template HTML...]

  <div #threejstarget></div>

[...Component template HTML...]

In the TypeScript code file, I can obtain a reference to this element with the @ViewChild decorator.

  @ViewChild('threejstarget') targetdiv!: ElementRef;

Why the “!” suffix? If we declared the variable “targetdiv” by itself, TypeScript compiler would complain that we risk using a variable that may be null or undefined instead of its declared type. This is because TypeScript compiler doesn’t know @ViewChild will handle that initialization. We use an exclamation mark (!) suffix to silence this specific check on this specific variable without having to turn on the (generally useful) null/undefined checks in TypeScript.

(On the “To-Do” list: come back later and better understand how @ViewChild relates to similar directives @ViewChildren, @ContentChild, and @ContentChildren.)

Wait until AfterViewInit

But there are limits to @ViewChild power. Our ElementRef still starts null when our component is initialized. @ViewChild could not give us a reference until the component template view has been created. So we have to wait until the AfterViewInit stage of Angular component lifecycle before adding Three.js render canvas into our component view tree.

  ngAfterViewInit() : void {
    this.targetdiv.nativeElement.appendChild( this.renderer.domElement );

An alternative approach is to have <CANVAS> inside our component template, and attach our renderer to that canvas instead of appending a canvas created by renderer.domElement. I don’t yet understand the relevant tradeoffs between these two approaches.

Animation Callback and JavaScript ‘this

At this point I had a Three.js object onscreen, but it did not animate even though my requestAnimationFrame() callback function was being called. A bit of debugging pointed to my mistaken understanding of how JavaScript handles an object’s “this” reference. My animation callback was getting called in a context where it was missing a “this” reference back to my Angular component, and thus unable to advance the animation sequence.


One resolution to this issue (JavaScript is very flexible, there are many other ways) is to declare a callback that has an appropriate “this” reference saved within it for use.


That’s a fairly trivial problem and it was my own fault. There are lots more to learn about animating and Angular from others online, like this writeup about an animated counter.

Increase Size Budget

After that work I had a small Three.js animated object in my Angular application. When I run “ng build” at that point, I would see a warning:

Warning: bundle initial exceeded maximum budget. Budget 500.00 kB was not met by 167.17 kB with a total of 667.17 kB.

An empty Angular application already weighs in at over 200kB. Once we have Three.js in the deployment bundle, that figure ballooned by over 400kB and exceeded the default warning threshold of 500kB. This is a sizable increase, but thanks to optimization tools in the Angular build pipeline, this is actually far smaller than my simple test app. My test app itself may be tiny, but it downloaded the entire Three.js module from a CDN (content distribution network) and that file three.module.js is over a megabyte (~1171kB) in size. By that logic this is the better of two options, we just have to adjust the maximumWarning threshold in angular.json accordingly.

My first draft used a fixed-size <DIV> as my Three.js target, which is easy but wouldn’t be responsive to different browser situations. For that I need to learn how to use CSS layout for my target <DIV>

Source code for this project is publicly available on GitHub

Compass Web App for Angular Practice

I’ve wanted to learn web development for years and one of my problems was that I lacked the dedication and focus to build my skill before I got distracted by something else. This is a problem because web development world moves so fast that, by the time I returned, the landscape has drastically changed with something new and shiny to attract my attention. When I window-shopped Polymer/Lit, I was about to start the cycle again. But then I decided to back off for a few reasons.

First and most obvious is that I didn’t yet know enough to fully leverage the advantages of web components in general, and Polymer/Lit in particular. They enable small lightweight fast web apps but only if the developer knows how to create a build pipeline to actually make it happen. I have yet to learn how to build optimization stages like tree-shaking and minimization. Without these tools, my projects would end up downloading far larger files intended for developer readability (comments, meaningful variable names, etc.) and include components I don’t use. Doing so would defeat the intent of building something small lightweight.

That is closely related to the next factor: Angular framework has a ready setup of all of those things I have yet to master. Using Angular command line tools to build a new project boilerplate comes with a build pipeline that minimizes download size. I wasn’t terribly impressed by my first test run of this pipeline, but since I don’t yet know enough to setup my own, I definitely lack the skill to analyze why and certainly don’t yet know enough to do better.

And finally, I have already invested some time into learning Angular. There may be some “sunk cost fallacy” involved here but I’ve decided I should get basic proficiency with one framework just so I have a baseline to compare against other frameworks. If I redirect focus to Polymer/Lit, I really don’t know enough to understand its strengths and weaknesses against Angular or any other framework. How would I know if it lived up to “small lightweight fast” if I have nothing to compare it against?

Hence my next project is to redo my magnetometer web app using Angular framework. Such a simple app wouldn’t need all the power of Angular, but I wanted a simple practice run while things are still fresh in my mind. I thought about converting my AS7341 sensor web app into an Angular app, but those files must be hosted on an ESP32 which has limited space. (Part of the task would be converting to use ESPAsyncWebServer which supports GZip-compressed files.) In comparison, a magnetometer app would be hosted via GitHub pages (due to https:// requirement of sensor API) and would not have such a hard size constraint. Simpler deployment tipped the scales here, so I am going with a compass app starting with putting Three.js in an Angular boilerplate app.

Source code for this project is publicly available on GitHub

Window Shopping Polymer and Lit

While poking around with browser magnetometer API on Chrome for Android, one of my references was a “Sensor Info” app published by Intel. I was focused on the magnetometer API itself at first, but I mentally noted to come back later to look at the rest of the web app. Now I’m returning for another look, because “Sensor Info” has the visual style of Google’s Material Design and it was far smaller than an Angular project with Angular Material. I wanted to know how it was done.

The easier part of the answer is Material Web, a collection of web components released by Google for web developers to bring Material Design into their applications. “Sensor Info” imported just Button and Icon, unpacked size weighing in at several tens of kilobytes each. Reading the repository README is not terribly confidence inspiring… technically Material Web has yet to reach version 1.0 maturity even though Material Design has moved on to their third iteration. Not sure what’s going on there.

Beyond visual glitz, the “Sensor Info” application was built with both Polymer and Lit. (sensors-app.js declares a SensorsApp class which derives from LitElement, and import a lot of stuff from @polymer) This confused me because I had thought Lit was a successor to Polymer. As I understand it, the Polymer team plans no further work after version 3 and has taken the lessons learned to start from scratch with Lit. Here’s somebody’s compare-and-contrast writeup I got via Reddit. Now I see “Sensor Info” has references to both projects and, not knowing either Polymor or Lit, I don’t think I’ll have much luck deciphering where one ends and another begins. Not a good place for a beginner to start.

I know both are built on the evolving (stabilizing?) web components standard, and both promise to be far simpler and lightweight than frameworks like Angular or React. I like that premise, but such lightweight “non-opinionated” design also means a beginner is left without guidance. “Do whatever you want” is a great freedom but not helpful when a beginner has no idea what they want yet.

One example is the process of taking the set of web components in use and packaging them together for web app publishing. They expect the developer to use a tool like webpack, but there is no affinity to webpack. A developer can choose to use any other tool. Great, but I hadn’t figured out webpack myself nor any alternatives, so this particular freedom was not useful. I got briefly excited when I saw that there are “Starter Kits” already packaged with tooling that are not required (remember, non-opiniated!) but are convenient for starting out. Maybe there’s a sample webpack.config.js! Sadly, I looked over the TypeScript starter kit and found no mention of webpack or similar tool. Darn. I guess I’ll have to revisit this topic sometime after I learn webpack.

Mermaid.js for Diagrams in GitHub Markdown

This blog is a project diary, where I write down not just the final result, but all the distractions and outright wrong turns taken on the way. I write a much shorter summary (with less noise) for my projects in the README file of their associated GitHub repository. As much as I appreciate markdown, it’s just text and I have to fire up something else for drawings, diagrams, and illustrations. This becomes its own maintenance headache. It’d be nice to have tools built into GitHub markup for such things.

It turns out, I’m not the only one who thought so. I started by looking for a diagram tool to generate images I can link to my README files, preferably one that I might be able to embed into my own web app projects. From there I found Mermaid.js which looked very promising for future project integration. But that’s not the best part: Mermaid.js already have their fans, including people at GitHub. About a year ago, GitHub added support for Mermaid.js charts within their markdown variant, no graphic editor or separate image upload required.

I found more information on how to use this on GitHub documentation site, where I saw Mermaid is one of several supported tools. I have yet to need math formulas or geographic mapping in my markdown, but I have to come back to take a closer look into STL support.

As my first Mermaid test to dip my toes into this pool, I added a little diagram to illustrate the sequence of events in my AS7341 spectral color sensor visualization web app. I started with one of the sample diagrams on their live online editor and edited to convey my information. I then copied that Mermaid markup into my GitHub file, and the diagram is now part of my project documentation there. Everything went through smoothly just as expected and no issues were encountered. Very nice! I’m glad I found this tool and I foresee adding a lot of Mermaid.js diagrams to my project README in the future. Even if I never end up integrating Mermaid.js into my own web app projects.

Webpack First Look Did Not Go Well

I’ve used Three.js in two projects so far to handle 3D graphics, but I’ve been referencing it as a monolithic everything bundle. Three.js documentation told me there was a better way:

When installing from npm, you’ll almost always use some sort of bundling tool to combine all of the packages your project requires into a single JavaScript file. While any modern JavaScript bundler can be used with three.js, the most popular choice is webpack.

— Three.js Installation Guide

In my magnetometer test project, I tried to bring in webpack to optimize three.js but aborted after getting disoriented. Now I’m going to sit down and read through its documentation to get a better feel of what it’s all about. Here are my notes from a first look as a beginner.

I have a minor criticism about their home page. The first link is to their “Getting Started” guide, the second link is to their “Concepts” section. I followed the first link to “Getting Started”, which is the first page in their “Guides” section. I got to the end of that first page and saw “Next” link is about Asset Management, the next guide page. Each guide page linked to the next. I quickly got into guide pages who used acronym or terminology that had not yet been explained. Later I realized this was because the terminology was covered in the “Concepts” section. In hindsight I should not have clicked “Next” at the end of “Getting Started” guide. I should have gone back to “Concepts” to learn the lingo, before reading the rest of the guides.

Reading through the guides, I quickly understood that webpack is a very JavaScript-centric system built by people who think JavaScript first. I wanted to learn how to use webpack to integrate my JavaScript code and my HTML markup, but these guide pages didn’t seem to cover my use scenario. Starting right off the bat with the Getting Started guide, they used code to build their markup:

   const element = document.createElement('div');
   element.innerHTML = _.join(['Hello', 'webpack'], ' ');

Wow. Was that really necessary? What about people who wanted to, you know, write HTML in HTML?

    <body><div>Hello webpack</div></body>

Is that considered quaint and old fashioned nowadays? I didn’t find anything in “Guides” or “Concepts” discussing such integration. I had thought the section on “HtmlWebpackPlugin” would have my answer, but it’s actually a chunk of JavaScript code that erased my index.html (destroying my markup) with a bare-bones index.html that loads the generated JavaScript bundles and have no markup. How does my own markup figure in this picture? Perhaps the documentation authors felt it was too obvious to write down, but I certainly don’t understand how to do it. I feel like an old man yelling at a cloud “tell me how to use my HTML!”

I had thought putting my HTML into the output directory was a basic task, but I failed. And I was dismayed to see “Concepts” and “Guides” pages got deeper and more esoteric from there. I understand webpack is very powerful and can solve many problems with modularizing JavaScript code, but it also requires a certain mindset that I have yet to acquire. Webpack’s bare bones generated index.html looks very similar to the generated index.html of an Angular app (which uses both HTML and webpack) so there must be a way to do this. I just don’t know it yet.

Until I learn what’s going on, for the near future I will use webpack only indirectly: it is already configured and running as part of Angular web app framework’s command line tools suite. I plan to do a few Angular projects to become more familiar with it. And now that I’ve read through webpack concepts, I should recognize pieces of Angular workflow as “Aha, they used webpack to do that.”

Fun with Magnetic Field Viewing Film

It was fun to visualize magnetic field with an array of retired Android phones. It was, admittedly, a gross underutilization of hardware power. It was more a technical exercise than anything else. There are much cheaper ways to visualize magnetic fields. I learned from iron filings in school way back when, but that got extremely messy. Ferrofluid in a vial of mineral oil is usually much neater, unless the vial leaks. I decided to try another option: magnetic field viewing films.

Generally speaking, such films are a very thin layer of oil sandwiched between top and bottom sheets, suspending magnetic particles within. The cheap ones look like they just use fine iron particles, and we see the field among slightly different shades of gray caused by different orientation of uniformly colored particles. I decided to pay a few extra bucks and go a little fancier. Films like the unit I bought (*) have much higher visual contrast.

As far as I can tell, the particles used in these films present different colors depending on orientation of magnetic field. When the field is perpendicular to the film, as when one of the magnet poles, the film shows green. When the field is parallel, we see yellow. Orientation between those two extremes show different colors within that spectrum. When there’s no magnetic field nearby, we see a muddy yellow-green.

Playing with a Hall switch earlier, I established that this hard drive magnet has one pole on one half and another pole on the other half. Putting the same magnet under this viewing film confirms those findings, and it also confirms this film doesn’t differentiate between north or south poles: they both show as green.

This was the simplest scenario: a small disc salvaged from an iPad cover shows a single pole on the flat face.

Similarly simple is the magnet I salvaged from a Microsoft Arc Touch Mouse.

This unexpected complex field was generated by a magnet I salvaged from a food thermometer. I doubt this pattern was intentional, as it does nothing to enhance its mission of keeping the food thermometer stuck to the side of my refrigerator. I assume this complex pattern was an accidental result of whatever-was-cheapest magnetization process.

The flat shape of this film was a hinderance when viewing the magnetic field of this evaporator fan rotor, getting in the way of the rotor shaft. The rotor is magnetized so each quarter is a magnetic pole. It’s difficult to interpret from a static picture, but moving the rotor around under the film and seeing it move interactively makes the relationship more visible. It is also higher resolution and responds faster than an array of phones.

This disc was one of two that came from a 1998 Toyota Camry’s stock tape deck head unit. I don’t know exactly where it came from because I didn’t perform this particular teardown.

We can see it laid out on the tabletop in this picture, bottom row center next to the white nylon gears.

And finally, the motor that set me on this quest for magnetic viewing film: the rotor from a broken microwave turntable motor. Actually looking at the plastic hub color, I think this is the broken rotor that got replaced by the teardown rotor sometime later.

And since I’m on the topic, I dug up its corresponding coil from that turntable motor teardown. Curious if I would see a magnetic field, I connected it to my benchtop DC power supply and slowly increased the voltage. I could only get up to 36V DC and didn’t notice anything. This coil was designed for 120V AC, so I wasn’t terribly surprised. I’ll have to return to this experiment when I can safely apply higher voltage to this coil.

(*) Disclosure: As an Amazon Associate I earn from qualifying purchases.