Mini-Review: Glowforge Plus

Glowforge Plus

This unit was purchased by us; no demo units, remuneration, etc. took place. Which is a bummer, considering the results of our Glowforge usage. A hint of things to come.

To match the hardware of a local Maker education classroom we decided to purchase a Glowforge Plus (plus our Epilog needs a new tube–any excuse to buy new hardware!) Here’s our high-level overview.

Overview

Glowforge Plus
Glowforge Plus

The Glowforge Plus is a 45W unit (compared to 65W for our Epilog) for $3995 without a filtration unit. We already had venting set up so we didn’t feel the need, and $6k for the Pro was $2k too much for what it offered.

It has a fairly small cutting area, approximately 19.5″w by 11″ deep, although the bed itself is 18″ deep. This is perfectly fine for small projects as long as your material isn’t very thick–there’s ~2″ clearance, although we haven’t yet used any material over 0.5″. Again, perfectly adequate for much of what people use laser cutters for, but a non-starter for many small laser businesses.

Also note that the Glowforge Tech Specs page states both:

  • Maximum material height: 2″ (50mm)
  • Completely Internal — Lens moves internally up and down inside the head to print on materials up to 0.5” (13mm) thick

It’s unclear to us what, then, they actually mean.

Using the Glowforge

The Glowforge connects to its home base via WiFi–but 2.4GHz only. This is a major problem for us as our 2.4GHz network suffers from arbitrary, but consistent, interference–and the machine is very bad at reconnecting during an outage. Strike one.

There is no way to use the device without WiFi. No USB, no wired ethernet, no nothing. So if we’re suffering from higher-than-normal 2.4GHz drops, we simply cannot use this $3k piece of equipment: it’s a brick. Strike two.

In addition, there is zero way to determine what types of problems the machine may be having with a given print job. For example, our workspace is very hot, and while we can use fans to cool the area around the printer, if it’s a thermal problem stopping a print, the only way to know that’s what happened is by contacting Glowforge’s customer support, where they’ll troll your logs looking for information.

While their support was prompt (at least when we had our most current issue) and courteous, not providing any local insight into potential issues is a time-suck, potentially a major time-suck if no customer service representative is available, and, at least in our opinion, antithetical to the Maker Movement in general. Putting up artificial barriers to the creative process is out of line.

When the Glowforge is working, while it’s not particularly fast (compared to both more powerful lasers, and somehow lasers like the Glowforge Pro that boasts of 20% increased cut speed with the same 45W laser), it’s pretty decent. We’re not sure what else we can say beyond that–there’s nothing terribly remarkable about the unit.

Our materials of choice are cardboard (for prototyping and models), 3mm and 0.14″ (~3.5mm) MDF and plywood, paper, and the other typicals like fabric, acrylic, etc. So far we’ve only been doing cuts on 0.14″ MDF and their supplied “Proofgrade” materials (draftboard, which seems to be just MDF) and maple plywood. Almost all of our cuts have been very precise and clean, as one would expect from a 45W machine.

The Software

That’s a bit of a stretch: it’s really just a way to communicate with the machine, and (roughly) move pieces around in the workspace. You can translate X/Y, resize (but with no feedback, so no real work in here), rotate (hold down <SHIFT> to lock to 45 degree increments), bring in additional artwork, and… maybe there’s more, but we can’t find much else.

There’s a maintenance page, or at least one for the fan, but if there are others, there’s no direct link from the default UI. The only reason we found out about that fan one is because their support person determined (by looking at the logs, which we cannot do ourselves) that it was too hot. There may also be others linked to in the support docs, but we haven’t looked.

If you go through the Glowforge forums you’ll find multiple posts regarding “Stuck in Focusing” or “Stuck in Centering” or “Stuck in Homing” or “Stuck in Scanning”. There’s a reason for that–it gets stuck in all those things frequently enough that it’s very annoying. In fairness, with our 2.4GHz network issues, and the Glowforge’s lack of 5GHz support (what year is this again?!), it may be due in no small part to networking–which is one reason why networking-only devices and apps are antithetical to Making.

The resolution to those problems are largely cargo-cult-ish, black-magic, animal sacrifices: turn it off, wait 30 seconds, turn it back on. Or move the head to the home position. Or make sure everything is clean (that one actually makes sense, but has never solved our problems). Or leave the lid open for twenty minutes… or overnight.

TL;DR

When this machine was Kickstarted (that’s when our nearby Makerspace ordered theirs, they received it last fall, a few years late) we didn’t buy one (you know how hardware Kickstarters go), and after using it, we’re glad we didn’t: the letdown after such a long wait would have been even more irritating than this closed machine has turned out to be.

We want to like it. We want to like the company. And maybe someday we will–but until then, our workhorse will continue to be our Epilog and some upgraded cheap 40W+ Chinese units.

The Epilog cost much more (~5x as much, but we have much better depth capacity and a rotary unit), and have at least some ability to diagnose problems, repeat jobs (which we do a lot), and so on.

The cheaper Chinese units are just that: cheap, and they can be fiddley–but we have total access and the ability to actually diagnose and fix problems, and run our own software, at about one-half the cost.

It’s just too much work for a device that pretty much just moves around and throws light onto a surface, especially considering code that does all that is trivially available. As are boards that support 5GHz networking and can actually reliably reconnect when the network is being stupid/

So Who’s This Machine For?

That’s an interesting question. It’s too pricey for the merely-curious. The non-merely-curious are probably fiddle-capable. The merely-curious could buy a cheaper, smaller unit, likely without cutting capabilities.

It’s a curious proposition: pay a premium price for a system you can’t diagnose, won’t work without WiFi, and cannot hack (trivially–more to come on that front). Schools, perhaps, that want a closed system with daytime support? But as a Makerspace tool, with their host of… well, Makers, it’s a tough sell–at least for us.

Followup with Props to Glowforge

Glowforge reached out with a brief email indicating that they’d found and fixed a big regarding vague error messages–I’m not sure which one was addressed (or if they all were), but props to Glowforge for their QRF (that’s Quick Reaction Force for the non-military types out there) helping users figure out what’s gone wrong when. We’ll definitely update the software and heap further abuse on the machine.

Resources

Print: Another Paintbrush Holder!

Front View, Loaded

Our first paintbrush holder got enough attention that we figured we’d discuss another one we designed and printed. This one has a more-organic shape, holds a few more brushes (and in our opinion it’s easier to get at them), and presented an interesting problem with Fusion 360 regarding patterns on path, and how to make sure you can process edges they way you want. (TL;DR: Press/Pull a bit more than the space you need to take up.)

Patterns on Path

Taking a (modified) version of our “kidney bean” shape we want to create a bunch of “slots” for our brushes:

Pattern on Path Idea
Pattern on Path

There’s a quick look at this process in our Youtube Video, or you can keep on readin’.

We’ll use a symmetric direction, so they wrap around both sides of the path at once, use the “Spacing” direction to the outsides of each “slot” are about 0.625″ apart. One thing to keep in mind is that our Orientation setting needs to be “Path Direction” so each extruded “bar” is tangential to the path:

Fusion 360 Pattern on Path "Orientation" Setting
Choose Your Orientation Wisely!

Here’s the thing: even though our original extruded bar is making good contact with the inner and outer walls in the center location, we run into a problem on the sharp inside wall curves:

Bodies don't meet flush
Uh-oh.

If we try to fillet the edge of the extruded bar it won’t join with the inner wall:

Bad fillet
Just No.

What we need to do is Press/Pull the extruded “bar” before patterning so it makes contact with both the inner and outer walls, all the way around the path:

Press/Pull Into the Walls
Press/Pull Into the Walls

Now when we pattern even those tight-radius inner-wall corners will have full contact with our separator “bars”. After patterning we can combine all the separate bodies, and create a fillet the way nature intended:

Resources

Print: Simple Paintbrush Holder

Front view, loaded with brushes

Disclaimer: This post contains an affiliate link to a product we bought, then promptly printed a version we liked more. But go buy one via the affiliate link anyway–it’s not that it didn’t work, it was just held too many brushes.

We needed additional brush holders that we mostly like this one we bought off Amazon:

Brush holder
Inspirational brush holder

It actually holds too many brushes for what we typically need, so we thought we’d spin out a customized one all quick-like, and use some 1/2″ ID copper tubing for supports. Since it wouldn’t be too wide, we figured a single, central support pillar would be sufficient (it is, but we also filled one with sand capped with silicon for extra heft just in case).

Here’s what we ended up with:

Design

We whipped up a screencast real quick that covers the bulk of the design process. IRL we parameterized everything so we could use other support material (like dowels or acrylic) and change the wall thickness (these are 1.5mm which is a little thin, but worked well on our MakerBot Method). We didn’t parameterize the number of slots; we’ve had mixed results doing that in Fusion 360.

TL;DR: Make a hole the size of your supports. Make the circles for each “row” of brushes you want. Draw a line from the center point to the edge of the outside circle. Offset those on each side by 1/2 the wall thickness you want. Make a circular pattern of those.

Then extrude up the height of the holder (we used 1cm). Duplicate all this for the bottom, and extrude around 2mm so the base is solid (keeps those drips off your table!) and you’re basically done. We filleted and chamfered everything as well.

Lessons Learned

This was about a four hour print on our MakerBot Method with the default settings. Faster than Amazon Prime delivery, and it suited our immediate needs better.

Overall we’re pretty pleased with how it turned out, although a little more slop for the support piece might have been beneficial–it was a tight fit, but the dimensional accuracy of the Method saved us from all sanding whatsoever, and the PLA it shipped with was plenty strong to stand up to a bit of pushing.

Resources

Products

Mini-Review(s): Box Cutters

Disclaimer: Contains affiliate (and direct) links to some reviewed products.

Have boxes? Want to open them? Get you a box cutter for a great good! TSA aside, box cutters are great: yes, we all have our multitools and combat knives strapped liberally around our body, but for all-round utility, it’s hard to beat the simple box cutter. (And they make serviceable weapons in an emergency, and raise fewer eyebrows than a wave-opening Emerson Karambit, our go-to death-dealer of choice.)

From simple to elaborate, from light to heavy–we’ll give a quick overview of the ones we have laying around the shop, and discuss why some are better than others, what our daily carry is, and why.

(We won’t discuss scrapers, which are something different, but maybe some other time–they’re also super-useful, but not as fun for… cutting boxes.)

MOAR BOXCUTTERS!!1!

A pile of box cutters
Pile o’ Box Cutters

These are a few of our around-the-shop cutters (tragically this is not all of them; we have more tools than sense). There are three main “classifications” of box cutters: flippers, sliders, and “always-on” (those come with sheathes). We’ll work in reverse-order.

Always-On

Pull them out of the sheath, cut a box: it’s as simple as that. The problem with them is that they essentially require a sheath or container. They may be suitable for bench mounting (and we have one stuck on to one of our CNC tables) but for daily carry it’s a non-starter. Why?

Sheathes add bulk, and unless it’s sized exactly right, and it’s a pretty tight clip, you either need to hold on to the sheath to deploy, or push down on the sheath while you draw the cutter. So why would you want one at all?

Rigidity: these are easily the solidest variety of box cutters. With a heavy frame and no moving parts you’ll break the blade before you have any other mechanical problems. They’re worth having on hand for that reason alone, but if you’re anything like us, you’ll use them only rarely, or only when you’re near where one is mounted. They’re also fine in a toolbox or tote, but in general, we think they’re more specialized.

We only have a few varieties of these, but we like the Stanley for overall durability, even if the sheath feels a bit cheap, and the belt clip… well, there’s a clip, it’ll go on a belt, but deploying is a bit of a pain.

Sliders

These push the blade out the front. They’ll either use a standard box cutter blade (the trapezoidal ones) or a custom blade, often with snap-off blades to get to the sharp part.

Mechanically they’re generally quite sound, and less prone to failure and breakage than a flipper (up next) because they just run on a track–there’s no pivot to get gunked up and even a pretty dirty one can be easily forced open.

The snap-off blades are a bit more specialized; they’re wonderful for foam cutting (hard to beat, in fact) especially if you sharpen them. For this variety we like the DeWalt (they’re yellow!) and a Kershaw sharpener. The downside is that they take only specific blade types and shapes. They’re also (generally) thinner than their “conventionally-bladed” counterparts so for heavy-duty use you’re better off elsewhere. That said, the blades are much longer (good for foam, bad for cardboard)/

Either type is fine in a pocket (as long as you remember to retract the blade… be safe, kids!) but don’t always include a belt clip. Without the belt clip, at least for us, their use as a daily carry is limited, but they’re great in a toolbox. The DeWalt snap-off cutter has a pocket clip that’s quite robust, and while we don’t carry one daily, when we need to, we can, with confidence.

For “conventionally-bladed” sliders we like the Stanley Fat Max.

For snap-off we like the DeWalt DWHT10038, and use a Kershaw stowable sharpener between cuts. (“Between all cuts?!” you ask? Depends; for foam, pretty much–but we have dedicated units for foam. For general cutting, not really. But for foam it’s arguably worth the extra effort.)

Flippers

For daily carry we like flippers, in particular this Milwaukee flipper, because it’s a one-handed opener, basically a gravity-operated switch blade: it swings really easy. This is also its greatest weakness–there’s a bit of rattle-and-shake when it’s in the locked position. But the convenience and extra functionality (a strap cutter and wire stripper) make up for it for most uses.

We’ll give an honorable mention to our pals at Home Depot for their Kobalt stainless steel flipper. Make no mistake–it does not flip; you have to want to open this bad boy. But when it’s locked it’s nearly as solid as any always-on box cutter, and we have several, mostly in toolboxes and glove boxes.

Products

The Stanley fixed-blade as we have it doesn’t appear to be sold any more, and since we don’t own any newer versions, we don’t know if we can recommend it or not, so no link to that for you.

The Kobalt Stainless Steel may be available at your local Home Depot, but searching for it proved… somewhat irritating, so we gave up. It’s not a daily carry anyway (too heavy, too hard to open) but man, is it solid.

Affiliate Links

Direct Links

Refactor: Simple jQuery Code

This is our first code-oriented post and discusses some refactoring issues from a Stack Overflow question. Here we’ll take a quick look at the main issues in the code. The screencast goes into more detail about specific refactorings, and refactoring in general–it’s a bit of a slog at 30+ minutes, but particularly for beginners, it may be instructive.

(Caveats: We don’t use jQuery much any more; there’s likely several additional jQuery-specific changes to be made. The refactored code has not been tested, so there may be a few minor gotchas. We’ve assumed no support libraries (e.g., lodash) and there are often some efficiencies there as well.)

Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.

Here’s the original code as a point of reference. We’ll break down the issues method-by-method and see how we can clean it up and make it easier to understand, test, and think about in the future.

$(function () {
  $('[data-toggle="tooltip"]').tooltip()
})

$(document).on('show.bs.modal', '#BSDOMAddEditEntryModal', function () {
  const $modal = $('#BSDOMAddEditEntryModal')

  if (!$modal.hasClass('show') && !$modal.hasClass('in')) {
    $(".modal-backdrop.fade.in").remove()
    $(".modal-backdrop.fade.show").remove()
  }

  $('.BSDOMInputGrpCalendarIcon').datepicker({
    autoclose: true,
    format:    'mm/dd/yyyy'
  }).on('changeDate', function (e) {
    $(this).parent().find('input:text').val(e.format())
  })
})

function validateBSDOMChkBxL(source, args) {
  const idValue = "[id$='" + source.id + "']"
  args.IsValid = $(idValue).siblings('div').find('table input:checkbox:checked').length > 0
}

function confirmDeleteReportBtn() {
  return confirm("Are you sure you want to delete this record?")
}

function updateUploadStatus(status, message) {
  var $uploadStatusLabel = $("span[id$='lblUploadStatus']")

  $uploadStatusLabel.html(message)

  if (status == "error") {
    $uploadStatusLabel
      .removeClass("BSDOMFileUploadSuccess")
      .addClass("BSDOMFileUploadError invalidMsgCss")
  } else {
    $uploadStatusLabel
      .removeClass("BSDOMFileUploadError invalidMsgCss")
      .addClass("BSDOMFileUploadSuccess")
  }
}

function checkboxErrorVal(source) {
  const idValue   = "[id$='" + source.id + "']"
  const $idObject = $(idValue)

  let displayStyle = "inline"

  if ($idObject.find('input:checkbox:checked').length > 0) {
    displayStyle = "none"
  }

  $idObject.parent().siblings('span').css("display", displayStyle);
}

function BSDOMFileUploadComplete() {
  const id = $("[id$='_DvFileUploadPanel']").attr('id')
  updateUploadStatus("success", "File Uploaded Successfully")

  __doPostBack(id, '')
}

const validFileExts = [ "xls", "xlsx", "pdf", "xml", "doc", "docx", "jpeg", "jpg", "png" ]

const validFileExtMsg = validFileExts.join(", ")

function isFileExtValid(fileExt) {
  return validFileExts.indexOf(fileExt) > -1
}

function createError(name, message) {
  const err   = new Error()
  err.name    = "Upload Error"
  err.message = `* Only accept format in ${validFileExtMsg}`
  return err
}

function BSDOMFileUploadStart(_sender, args) {
  const fileName = args.get_fileName()
  const fileExt  = fileName.substring(fileName.lastIndexOf(".") + 1)

  if (isFileExtValid(fileExt)) {
    return true
  }

  throw createError("Upload Error", `* Only accept format in ${validFileExtMsg}`)
}

function BSDOMFileUploadError(_sender, args) {
  updateUploadStatus("error", args.get_errorMessage())
}

There’s nothing particularly complex about this code, but there are a number of redundancies throughout we thought could be cleaned up.

Duplicate DOM-Ready Handlers

$(document).ready(function () {
  $(function () {
    $('[data-toggle="tooltip"]').tooltip()
  });
});

This runs a DOM-ready block inside another different DOM-ready block; boo-hiss. It’s not going to cause a problem, but it’s redundant and confusing–someone may look at this and think it’s necessary (hint: it’s not). We’ll pull that out:

$(function () {
  $('[data-toggle="tooltip"]').tooltip()
})

Pulling Out jQuery IDs

The validateBSDOMChkBxL method has a dynamic ID in it; we’ll pull that out into a variable. In this case it’s only used once in the method, but if we’re not using string interpolation, it can be confusing to read because of the different quotes:

function validateBSDOMChkBxL(source, args) {
  if ($("[id$='" + source.id + "']").siblings('div').find('table input:checkbox:checked').length > 0) {
    args.IsValid = true;
  } else {
    args.IsValid = false;
  }
}

This turns in to:

function validateBSDOMChkBxL(source, args) {
  const idValue = "[id$='" + source.id + "']"

  if ($(idValue).siblings('div').find('table input:checkbox:checked').length > 0) {
    args.IsValid = true;
  } else {
    args.IsValid = false;
  }
}

If we’re transpiling or targeting a browser(-like) environment that supports string interpolation, we might not pull it out, but if we did, it would be:

  const idValue = `[id$='${source.id}'`

The other potential improvement, since we’re setting a value to true or false based on a logical expression, is to remove the if statement and set it directly:

function validateBSDOMChkBxL(source, args) {
  const idValue = "[id$='" + source.id + "']"
  args.IsValid = $(idValue).siblings('div').find('table input:checkbox:checked').length > 0
}

We’re on the edge on this one because the resulting line is pretty long. Formatting would fix some of that… shrug.

The naming of the method seems off to us, but we’ll leave it alone since we don’t have a complete understanding of the code’s context.

Using ifs To Return the Value of a Logical Expression

Here again we have an unnecessary if statement:

function confirmDeleteReportBtn() {
  if (confirm("Are you sure you want to delete this record?") == true) {
    return true;
  } else {
    return false;
  }
}

We already have a true/false value; returning a different one is redundant:

function confirmDeleteReportBtn() {
  return confirm("Are you sure you want to delete this record?")
}

Pulling Out jQuery Objects and Setting Variables

Here we’re building an ID again, then referring to it multiple times. jQuery caches so there’s no performance penalty, but we believe there’s a readability penalty: we must read the code to understand that we’re operating on the same object throughout. The less reading and thinking we have to do the happier we are.

function CheckboxErrorVal(source) {
  if ($("[id$='" + source.id + "']").find('input:checkbox:checked').length > 0) {
    $("[id$='" + source.id + "']").parent().siblings('span').css("display", "none");
  } else {
    $("[id$='" + source.id + "']").parent().siblings('span').css("display", "inline");
  }
}

The other thing to note is that the only thing this method actually does is set a single CSS value (display) to a string, and that string is determined by how many checkboxes are actually checked (none, or at least one): instead of duplicating the code we’ll just set a variable to "none" or "inline".

We’ll again pull out the constructed ID. We’ll also create a reference to the jQuery object so it’s immediately clear we’re always operating on the same jQuery object. We like to preface jQuery object references with a $ (because that’s a jQuery-ish variable)–this is a matter of preference, but we think it makes it obvious that we (a) don’t need to re-jQuery it by wrapping it in a $(), and (b) that we’re explicitly dealing with things in jQuery-land.

function checkboxErrorVal(source) {
  const idValue   = "[id$='" + source.id + "']"
  const $idObject = $(idValue)

  let displayStyle = "inline"

  if ($idObject.find('input:checkbox:checked').length > 0) {
    displayStyle = "none"
  }

  $idObject.parent().siblings('span').css("display", displayStyle);
}

Isolating Functionality

The only other major irritation with the original code is how it validates the filetypes and reports the error message back. The original code is this:

function BSDOMFileUploadStart(sender, args) {
  var fileName = args.get_fileName();
  var fileExt = fileName.substring(fileName.lastIndexOf(".") + 1);
  if (fileExt == "xls" || fileExt == "xlsx" || fileExt == "pdf" || fileExt == "xml" || fileExt == "doc" || fileExt == "docx" || fileExt == "jpeg" || fileExt == "jpg" || fileExt == "png") {
    return true;
  } else {
    //To cancel the upload, throw an error, it will fire OnClientUploadError
    var err = new Error();
    err.name = "Upload Error";
    err.message = "*Only accept format in xls, xlsx, pdf, doc, docx, jpeg, jpg, png";
    throw (err);

    return false;
  }
}

We have a bunch of file extensions we need to check in a gigantic long line, and an error message if it’s an unacceptable file extension. There are (at least) two problems with this:

  • Adding or removing extensions requires changes in two places.
  • There’s already a mismatch between acceptable types and the error message.

Did you see the mismatch? We allow XML files, but the error message doesn’t indicate it. So we’ll create a few utility methods and variables to encapsulate this:

const validFileExts = [ "xls", "xlsx", "pdf", "xml", "doc", "docx", "jpeg", "jpg", "png" ]

const validFileExtMsg = validFileExts.join(", ")

function isFileExtValid(fileExt) {
  return validFileExts.indexOf(fileExt) > -1
}

The only other thing we might change is how we construct our Error object: since we’re using a non-standard error property (name) we probably want to have an application-specific Error sub-class, but for now we’ll wrap it up in a simple method:

function createError(name, message) {
  const err   = new Error()
  err.name    = "Upload Error"
  err.message = `* Only accept format in ${validFileExtMsg}`
  return err
}

Now our start-upload method looks like this:

function BSDOMFileUploadStart(_sender, args) {
  const fileName = args.get_fileName()
  const fileExt  = fileName.substring(fileName.lastIndexOf(".") + 1)

  if (isFileExtValid(fileExt)) {
    return true
  }

  throw createError("Upload Error", `* Only accept format in ${validFileExtMsg}`)
}

So What?

We added about ten lines of code to the original codebase. It’s important to understand that refactoring isn’t (necessarily) about reducing the amount of code: it’s about (at the very least):

  • Reducing the cognitive load necessary to think about the code.
  • Making it easier to change (extend, enhance, modify).
  • Making it easier to test.

Our codebase got larger: we’re trading a slightly-larger codebase for code that’s just easier to deal with. Even assuming you (the reader) don’t agree with some of the stylistic choices (or naming, or usefulness of the refactors, etc.) in general it’s easier to read. (And certainly easier to handle acceptable file extension changes!)

Sometimes refactorings will greatly increase the codebase: that’s a tradeoff. We might have more files, more classes, more lines–but if we’re doing it “right” (for various values of “right”) our mainline code will be much easier to reason about, and our codebase will be easier to maintain, extend, and test.

Quick Look: Makerfocus NodeMCU Board with 18650 Battery On-Board

Disclaimer: This post has affiliate (and direct) links to the reviewed product on Amazon.

As fun as adding a voltage booster (for 5V projects) and/or voltage regulation and a charge circuit is to our designs, sometimes it’s nice to just have everything done for us. We’re big fans of the ESPxxxx/NodeMCU ecosystem: they’re cheap, they’re WiFi-enabled out-of-the-box, they’re small, and they’re usable in the Arduino ecosystem if that’s how you roll.

We found this little guy on Amazon:

It’s a NodeMCU-compatible board with a built-on 18650 battery holder and charge control circuit: it may end up being one of our go-to designs when we need a battery-operated, low-IO WiFi-enabled part (a disturbingly-frequent ask), barely larger than the battery itself. Slick. Let’s take a look, and do a sanity programming check.

Major plus: MOUNTING HOLES. Boards without mounting holes are a real pain. Minor drag: the 18650 battery holder is on the back, so you’re not going to pop this bad boy in a breadboard any time soon, unless you put some longer headers on the top of the board, and don’t mind not being able to hit the switches or see the lights.

The Guts

The brain is an ESP-WROOM-02 (see links in Resources) with a Silicon Labs CP2102 handling USB comms. An Advanced Monolithic Systems AMS1117 chip regulates the volts, charging is controlled with a Nanjing Extension Microelectronics TP5400 (whose datasheet I could only find in Chinese 🙁

The LED near the charge controller will be red when charging, green when charged or under USB-only power. You can charge and run the board at the same time. We see solar in this board’s future.

The Setup

There are a number of steps to make this work in the Arduino environment; you may want to go a different route–but we needed to keep in the Arduino ecosystem to keep our classes as uniform as possible.

  • Install USB Driver
  • Add Board Manager URL
  • Pick a board, any board
  • Set the RESET method

Install USB Driver

You may need to install a driver for the Silicon Labs CP2102. Drivers are available on the Silicon Labs CP2102 Driver Downloads page for major environments.

For OS X (the only one we’ve tested so far) you’ll get a ZIP file with a disk image (DMG). Unzip, open the image, and run the package file Silicon Labs VCP Driver.pkg, and do the normal package install thing.

Add Board Manager URL

If you haven’t already been using ESP-ish boards you’ll need to add a board manager URL to the board manager. In the Arduino IDE open the preferences via Cmd-, or the menu. The list of board manager URLs is a comma-separated list.

Board manager dialog box with board manager URL text box highlighted
Board Manager Dialog

To it add:

http://arduino.esp8266.com/stable/package_esp8266com_index.json

Pick a Board, Any Board

For this mini-review we stuck with a generic 8266 board. Under Tools ⇨ Board choose “Generic ESP8266 Module.”

Board selection drop-down menu
Board Selection

Set the RESET Method

To upload our sketches to the board we need to set the RESET method under Tools ⇨ Reset.

Shows board drop-down method with board reset method
Reset Method

We didn’t need to make any other changes to get the basics working.

The Glory

The “documentation” on the Amazon product page states that the on-board LED is on GPIO16, so our go-to example, Blink, will use pin 16 for the LED:

#define LED 16

void setup() {
  pinMode(LED, OUTPUT);
}

void loop() {
  digitalWrite(LED, 0);
  delay(250);

  digitalWrite(LED, 1);
  delay(250);
}

If you’ve followed all the steps above (and do let us know if we’ve left anything out) you should see an attractively-blue blinking LED.

We’ll be revisiting this board both here and in our upcoming Maker’s End Inspiration Series book, ESP Inspiration, so stay tuned–this little guy is going to get some projects!

Resources

Products

Build: Arduino Tachometer (Reflective Style)

Disclosure: This post contains affiliate (and direct) links to some parts off Amazon for making a (relatively) hand-held device as well as the commercial product we replaced.

In the first book of the Maker’s End Inspiration Series, Arduino Inspiration, we’re controlling some computer fans to make a pleasant desk fan. To get some data on various fans, we needed a tachometer. So we wrote a different book. Because reasons.

Thus the first Maker’s End MEMO (Maker’s End Mini-Overview) publication, MEMO#001: Arduino, Interrupted, in which we provide a quick (but thorough) introduction to using interrupts on Arduinos of various flavors. As preparation for releasing a kit of the tachometer we started prototyping case designs in case we wanted to print the case instead of building a carrier plate for a commercially-available case (jury’s still out).

In this post we’ll take a quick look at the prototype case and code, discuss miniaturizing designs (spoiler: don’t!), and see where we go from here. There’s actually a video showing some of the design and wiring process, but it turned out to be really boring, so we didn’t post it :p

We whipped up a quick-and-dirty tach using a reflective sensor (read: when light bounces off of something back into the sensor it’s a “pulse” and we count it as a revolution) using the STEMTera for prototyping, then moved it as-is onto an Arduino Nano clone with USB power bank. (The kit will use a simple battery pack or LiPo.)

We 3D-printed a small mounting plate so we had something we could use and to start exploring the design space of such a small tool that still uses off-the-shelf parts without a custom PCB so we had a tool we could use around the shop. It’s roughly equivalent to a cheap “laser”-based reflective tachometer we also got off of Amazon as a simple way to test performance. Not quite as pretty, but customizable, accurate-enough, and a cute little project.

Any of these parts can be substituted. We built one with a different IR sensor and a white monochrome OLED, with essentially no code changes. We also have one that uses a 18650 or LiPo batter with a built-in charger and voltage converter–or just use a board with a built-in LiPo charger like an Adafruit Feather: we’ll post a followup article that uses a Feather that’ll send the data to your phone or to a computer for data storage as well.

Build the Circuit

There are two circuit “chunks”, both quite simple. First is hooking up the reflective sensor, second is hooking up the OLED. The Adafruit part comes with the resistors necessary for operation–we went ahead and made a slightly more “self-contained” sensor unit by soldering the resistors onto the sensor and running breadboard-ready wires for the initial prototype.

The OLED hookup, since it’s an I2C OLED, is as easy as hooking up Vcc and Gnd to the appropriate pins, and SCL/SDA to (in our case) the Uno (or Nano) A4/A5, the default I2C pins.

While we prototyped on an Uno, the “final” version uses a Nano (clone), so we’ll show that circuit–it’s the same on the Uno, though, in case you want a giant tachometer (or also go the smart route and prototype on something large).

Write the Code

The code is (roughly) as simple as you’d expect. There’s not a whole lot to talk about, but we’ll break it down anyway. The code for the kit version will be available on Github; the code here is just the prototyping to make sure it was accurate-enough.

Administrivia

It’s just a bunch of library headers (we don’t need all of them for our initial example, but the kit code uses them).

#include <Wire.h>

#include <gfxfont.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SPITFT.h>
#include <Adafruit_SPITFT_Macros.h>

#include <Adafruit_SSD1306.h>

// Useful constants
#define IR_LED  12    // Turns on IR emitter
#define IR_IN    2    // Input from IR sensor
#define DELAY  500    // Milliseconds between display updates

// Initialize OLED
// OLED_RESET is connected to SDA on OLED.
// NOTE: It's *analog* Pin 4 on the Arduino.
#define OLED_RESET 4  // Pin for OLED SDA connection
Adafruit_SSD1306 display(OLED_RESET);

// Revolution tick/pulse counter.
// NOTE: Volatile!
volatile unsigned int rev;

// For calculations/results.
unsigned long int rpm;      // Current RPM
unsigned long int maxRpm;   // Maximum calculated RPM
unsigned long int time;     // ... time.
unsigned long int prevTime; // The time before!

Interrupt Service Routine (ISR)

The ISR is the “meat” of the code even though it’s a single line long. ISRs can be confusing to new programmers, which is why MEMO#001: Arduino, Interrupted will exist–to demystify them. The only thing we do in the ISR is increment a value–the count of how many times we received a signal (pulse) from the IR sensor, which in turn pulses on each revolution of what we’re measuring.

void rpmCount() {
  rev++;
}

We’ll put up a few more posts on interrupts in general. Suffice to say that in Setup (below) we’ll tell the Arduino to run rpmCount every time we get a pulse, which just tells the code to add one (increment) the number of revolutions. We’ll see the rev value later, because we’ll clear it out when we display the current revolutions per minute (RPM).

Setup

Most of the setup method is self-explanatory: we do a bunch of stuff to the OLED so we can display the current RPM. We set a bunch of values to known startup values (zero in this case), set our IO pin directions, and turn on the IR emitter.

void setup() {
  Wire.begin();
  display.begin(SSD1306_SWITCHCAPVCC, 0x3c);

  display.clearDisplay(); // clearing the display
  display.setTextColor(WHITE); //setting the color
  display.setTextSize(1); //set the font size
  display.setCursor(0, 0); //set the cursor coordinates
  display.print("Tachyons are Go!");
  display.display();
  delay(500);

  // Start clean!
  rev    = 0;
  rpm    = 0;
  time   = 0;
  maxRpm = 0;

  // Set up IO
  pinMode(IR_IN, INPUT);
  pinMode(IR_LED, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);

  // Set up interrupt handler
  attachInterrupt(digitalPinToInterrupt(2), rpmCount, FALLING);
  
  digitalWrite(IR_LED, HIGH);   // turn the LED on (HIGH is the voltage level)
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
}

The only thing of note is this:

attachInterrupt(digitalPinToInterrupt(2), rpmCount, FALLING);

attachInterrupt “hooks up” an Arduino (really the processor interrupt) to a given pin, runs the provided code (rpmCount), every time the signal rises, falls, or changes: here we care when the pin changes from high (5V) to low (0V) because that’s what the sensor is wired to do.

The digitalPinToInterrupt function allows us to provide the Arduino pin number instead of knowing what CPU-level interrupt is used for a given pin. This can be different across Arduino boards (and non-Arduino boards programmed in the Arduino environment), so it’s a safer bet to use this convenience method.

Loop

The “meat” lives in loop as with many an Arduino sketch. And yes, we’re using delay here–sorry! You’ll undoubtedly will read (or have read already) that using delay is generally a bad practice, and for the kit code with a UX, it’ll go away (and it has to!). But for prototyping a simple tach with no UX it’s fine. We’ll be discussing this, and how to avoid delay, in MEMO#001: Arduino, Interrupted and in subsequent posts.

void loop() {
  delay(DELAY);

  detachInterrupt(digitalPinToInterrupt(2));

  long currTime = millis();
  long idleTime = currTime - prevTime;

  rpm = 30 * 1000 / (millis() - time) * rev * 2;

  if (rpm > maxRpm) {
    maxRpm = rpm;
  }

  time = millis();
  rev = 0;

  display.clearDisplay(); // clearing the display
  display.setTextColor(WHITE); //setting the color
  display.setTextSize(1); //set the font size
  display.setCursor(0, 0); //set the cursor coordinates
  display.print(rpm);
  display.display();

  prevTime = currTime;

  attachInterrupt(digitalPinToInterrupt(2), rpmCount, FALLING);
}

Most everything is related to talking to the OLED, but note we detach the interrupt via detachInterrupt before performing the RPM calculation and write to the OLED: this results in missed pulses for the duration of that code, but we don’t want to interrupt writing to the OLED or update the rev count during the calculation.

Usage

Because it’s a reflective tach whatever you’re measuring will need something shiny. We stuck on some reflective tape that came with the cheap reflective tach we purchased on Amazon.

Fan showing how to place shiny tape
Fan with Shiny Tap

Some things won’t need additional shiny, like a disc with holes in it, or may need to work the opposite way, e.g., you’ll need to stick black tape on in order to break the beam. Some measurements would require handling multiple pulses-per-rev. The code handles none of this, nor is there a way to switch modes–when the kit is released we’ll have UX to handle at least some of these concerns.

How Accurate Is It?

We tested against a cheap reflective tach (link in Products section) and were consistently within 1-5%, so it’s “accurate enough” for what we needed. With minor code tweaking it should be pretty much dead-on, but for quick checks of computer fans, an electric drill, and a Dremel tool, we were pleasantly surprised with how close it was to a cheap (albeit trashy) commercial model. Plus we can customize it to our heart’s content, which is never a bad thing.

Is It Worth Building?

Debatable, but ultimately, we think so. Our cheap commercial tachometer was $20 and the only thing our hacked version doesn’t have (but is trivial to add) is storing the max, min, and last measurement (although we’d need to add some non-volatile storage, or keep the device on in a power-sip mode).

Our build was roughly $10 (we over-spent on the OLED, but it was what we had laying around when we needed it) and could probably be even cheaper. If you bump up the cost to $20, we could add more functionality:

  • More storage (via an SD card)
  • Wireless connectivity (via WiFi, BLE, or other radio)
  • Swappable inputs (via a jack for various sensors, like break-beam)
  • Software continuously hackable
  • Multiple power options

We might switch to a different board for some of this, which would increase the price, but something like an Adafruit Feather or a random ESP board with on-board WiFi is still really cheap, and opens up a world of remote monitoring possibilities.

BUILD FTW!!1!

Resources

Products

Why we post what we post

There will be a wide range of posts on this blog, from absolute beginner stuff (like our Building Custom Cables post) to product reviews (like our mini-review of the ShopVac Micro Vacuum) to build videos (like the Pimoroni Keybow build) and many things in-between.

Sometimes we’ll post affiliate links to purchase items used in a post (and will disclose as much, not just because we’re required to by law, but because it’s the right thing to do) along with direct links, sometimes we’ll post links to purchase our own products, sometimes we’ll just talk about things related to Making a wide variety of things–from code to 3D printing to laser cutting to designing to… you get the idea: all things Make-y.

Let us know if there’s something you’d like us to cover: we like building, explaining, teaching, and yakking about this stuff. We do it all the time anyway–we’d like others to join in the conversation and create tons of wonderful stuff.

If you like our posts you might be interested in our upcoming book series, the Maker’s End Inspiration Series. Right now there are at least four books planned:

  • Arduino Inspiration (covers “typical” Arduino parts)
  • Adafruit Inspiration (covers Feather and Gemma boards and parts)
  • Sparkfun Inspiration (covers some QWIIC parts)
  • Building Inspiration (kind of a catch-all at this point)

Each book will include 4-6 projects designed to both teach, and inspire moving beyond what’s shown and taught. Along with these projects, the books will include a bunch of “general” advice around coding, organization, and creative tools used in the projects. We’d like to think we’re capturing the “on-site” Maker’s End “look and feel” in written form. We’ll also be producing a series of accompanying videos to further illustrate and demonstrate things in the book.

We’ll also have parts kits available with enough parts and materials to create each book’s projects–these kits probably won’t be suitable for folks that already have most of the parts just laying around (like us), but for schools, teaching camps, or seminars, they might be just the ticket to get people “up and running” without having to source everything personally.

Stay in touch!

Skill: Custom Breadboard/Molex Connectors

Disclosure: There are both affiliate and direct links to some of the tools and consumables used in this project at the bottom of the post.

We connect a lot of things to a lot of other different things. When prototyping, we use breadboards. A wire salad may look pretty, but it’s hard to debug and diagnose problems: let’s clean some of that mess up by creating some custom connectors to keep things a little bit neater.

(Okay, this is a contrived example, but when we start adding multiple components on a full-size breadboard, it all starts to make sense. Trust us.)

As part of our upcoming Maker’s End Inspiration Series of books we’re going to be doing a lot of prototyping on breadboards. Since many of the parts will be the same across projects, we thought it might be cool to have some pre-made jumpers for some of those parts. So we made some!

We’ll turn some pins, wires, pin holders, and heat-shrink tubing into a cable that’s exactly what we need: a double-ended three-pin jumper.

We’ll also need the usual suspects of tools:

  • Wire cutter
  • Wire stripper
  • Crimper
  • Pliers (maybe two)
Tools used
Tools

The operation will proceed much as you’d expect (but we made a video anyway, because we’re chatty, and sometimes it’s more inspiring to hear someone blab on instead of just looking at some pictures.

The pins themselves vary by manufacturer. These pins looked to be press-stamped, so instead of a “pin” it’s actually more of a U-shaped “channel”. This isn’t ideal: we’d prefer a higher-quality actual pin. They’re easier on the breadboard, stronger, and just all-round more pleasant to work with. But these aren’t awful, and they get the job done.

When you look at the non-pin end of the pin you’ll see some flared triangles: they grab on to the insulation and keep the wire from coming out. They often come too flared, though: we sometimes “pre-process” the pins so they’ll grab the insulation a little easier (makes the crimp go easier) and actually fit into the crimping tool.

Fixing flared pin by squeezing it
Fixing flared pin by squeezing it

Each pin is inserted (once it’s attached to a wire) into a “receiver” (the chunky part of jumper cables) that look roughly like this (we’ll get a better image).

pin receiver, what the pins are inserted into
Pin receivers

Let’s wire one up. We’ll also wire up a single-pin jumper in parallel in case you’d like to try your hand at a less-complex attempt first, before jumping into a complete jumper: basically a breadboard jumper wire instead of a set of wires.

Step One

Cut the wires to whatever length you want your jumper to be. Note that if you want to “bundle” up the cables (as we’ve done here with bits of heat-shrink tubing) you may want to make the “inside” wires a bit shorter, but it’s not that important.

Step Two

Strip the wires so they’ll make contact with the pins. Every pin has its own specification for how much wire to strip; these particular pins were about 1/4″ or so, but we mostly just eyeballed it.

After stripping the wires you’ll need to twist them together, tightly, especially if you’re working with one of the super-flexible “crazy” wires, otherwise it’ll be difficult to seat the wire in the pin correctly.

Step Three

Lay the wire in the pin. The large “wings” on the back of the pin grip the insulation, keeping the wire in the pin. The electrical contact is made further up the pin by the next, smaller “wings”.

The wire should be inserted into the “channel” near the end of the pin. This ensures good contact (and is where the precision stripping comes in to play). It’s often easier to strip a little bit extra and trim to fit.

Step Four

Crimp it! The channel side should be in the “receiver” of the crimping tool: it has bends in it that force the “wings” down into the insulation and over the wire. It’s basically a stapler, where the pin is the staple, and the wire is the paper.

Just squeeze the handles to crimp.

If you see what’s in the image below, try to remove the wire and pin from the tool–here the wire has crept out of the pin; this will lead to a bad crimp, and poor (if any) connection, and the wire is likely to pull out.

shows the wire falling out of the pin right before crimping
The wire is falling out 🙁

Revel in your handiwork. The black wire was a good crimp, the red wire got inserted a bit, and some insulation has gotten into the connection area. When this happens the connection may or may not be solid (in this case it was).

Step Six

Once you have pinned all your wires they can be inserted into the connector. The connectors (usually) have a little arrow showing which direction to insert the pins. Sometimes the pins stop where they’re supposed to, sometimes they don’t.

We usually start them by hand, possibly nudging with a pair of pliers, then often (usually) need to pull them the rest of the way through by gripping the receiver with pliers, gripping the pin with pliers (gently, especially these cheap ones), and pulling until we see the “channel” part of the pin in the little window.

If you’re making a simple jumper wire you might want to add some heat shrink tubing. It provides a little bit of strain relieve, provides a handle to grab on to, and just looks nicer. Here the size we chose is probably a little large.

For the connector block you can add some heat shrink tubing at strategic locations to help keep things neat.

completed jumper cable
A little heat shrink tubing keeps wires together

Lather, rinse, repeat. We made a half-dozen three-pin jumpers that we can use for NeoPixels or servos and several four-pin connectors for I2C devices. The four-pin I2C connectors only have a connector on one end (power, ground, SDA, SCL) while the other end are normal breadboard pins: this lets us hook up I2C devices all neatly on the component side, and hook up the Arduino side where we need to: on the component, already breadboard-ready, the pins are right next to each other. On the Arduino side the pins are separate.

Product Links

We’ve used all of the products below. The Hakko tools come highly recommended. The connector sets are adequate (and cheap) but we prefer higher-quality pins. For the price they’re okay. The affiliate links come first, followed by direct links, and are labeled appropriately.

Affiliate Links

Direct Links

Review and Build: Pimoroni Keybow, a blinky auxiliary keyboard

We love our keyboards, from mechanical to split to Apple Magic. We also love our shortcuts, and even with a zillion macro programs, we still sometimes “need” dedicated keys for our most-used apps and application shortcuts (here’s looking at you, every CAD program ever).

We’re purchased a multitude of commercial keyboard “extensions” over the years, from industrial-strength over-priced 16-key strips to products designed for video editing. We’ve put together several cobbled systems using touch screens and Arduinos and Raspberry Pis. They’ve all worked, to some extent (we’ll detail some touchscreen builds in an upcoming post since they’re quite similar to this product, and provide extra blinkage).

We were attracted to this product because it’s a mechanical solution, and its underlying platform has extra capabilities and versatility: this is a 12-key matrix with an RGB matrix under each key, powered by a Raspberry Pi Zero WH. This opens up a world of keyboard possibilities and hacks.

It’s (marginally) pricey at £49.98 (about $65USD plus post) so it won’t be saving you much money over commercial solutions, but the open platform and trivial hackability are attractive. That said, if you already have a keyboard that suits your needs, you might be better off hacking in the RPi yourself. But we love Pimoroni and throw money at them often, so yep.

What’s in the Box

PARTSES. There’s the Pi0, a PCB for the keys, the keys, the keycaps, two acrylic plates, rubber feet, and connection hardware.

You’ll need a micro-SD card, so be prepared. We were not, and had to wait.

But it can be a small one, they claim a 1G card is sufficient. We used 8G because we had them.

The problem with micro-SD cards? They’re micro, and we’re old.

The board includes an I2C breakout for adding peripherals (we’ll use this in an upcoming build) and runs a mini-Raspbian OS called Keybow OS to get things done. Interface customization is handled by Lua, an embeddable scripting language with great functionality.

The Build

This is not a tricky build: there’s no soldering, just press-fitting and screwing a few tiny screws and bolts that our fat fingers occasionally dropped: it wouldn’t hurt to have a few extra M2.5 parts on-hand just in case (we buy them by the mixed-case every now and then; we’ll throw up a post about keeping the shop stocked some day).

The assembly docs on the Pimoroni site do a great job of piecing it together; we’ll add a few details, but generally just follow their docs.

Prepare Baseplate

  • Remove protective sheets from acrylic parts (we hate this part; be careful on the thinner spacer piece).
  • Stick on little rubber feet. (Unless you’re going to mount it some other way, like in a 3D-printed stand–see below!)
  • The “Keybow” text should be on the lower-left, facing you, not backwards. This is the bottom of the device.

Attach Raspberry Pi Zero WH to Baseplate

  • Use two M2.5 screws and nuts on the front of Pi0 (the long edge with the USB ports) to attach to the thicker baseplate.
  • Attach it to the side without the rubber feet, e.g., the “inside”.
  • Don’t over-tighten the screws; acrylic loves to crack.

Attach Keybow PCB

  • Remove standoffs’ protective film on pCB ferrules
  • Mount standoffs on the PCB
  • Line up the Pi0’s GPIO pins with the header on the PCB.
  • Press to fit; support whichever side you’re pushing against.

Mount Switches

  • Push each switch through the “gold leaf” PCB, from the gold leaf side down. Make sure each key is seated evenly.
  • The “gap” on the underside of the keys (where the pins are, and where the plus-shaped plunger/switch is not) should be oriented to the top of the gold leaf PCB.

Mount Keycaps

  • Press each keycap onto the switch. They’re all the same, and symmetrical, so orientation doesn’t matter.
  • Support the bottom of the switch when you press: it’s a tight fit.

Mount the keys to the PCB

After the pins are all aligned just push it into the PCB, firmly and evenly.

The Software

The Keybow runs a tiny version of Linux based on Raspbian. To install it onto the SD card download the latest ZIP or TAR file from the Keybow OS GitHub Releases page. Un-archive it and copy the files from the sdcard directory to the root level of your SD card.

To restate: everything under the sdcard directory (highlighted in the image at right) should be at the top level of the SD card’s root directory.

The Default Layout

It’s a numeric keypad like on a PC keyboard, e.g., 7-8-9 are on the top row, and 0-.-[ENTER] are on the bottom row, when the USB cable is sticking out the right-hand side of the Keybow.

It also comes with some sample macros; we’ll discuss updating them, various ways to poke the RGB LEDs, and some more fun stuff. We’ll be using it horizontally, and will detail macros, customizations, and using the Pi0 in our next (and final?) Keybow post.

Smoke Test

  • Put the MicroSD card into the Raspberry Pi
  • Plug in a USB cable
  • You should see some blinky lights

Any Problems?

If we have any complaints it’d be around the USB cable location and cutout. We would have preferred the RPi be firing towards the back so the USB cable would have a straight shot out the rear. Barring that, a larger cutout for the USB plug itself to support right-angle connectors so the cable could run out the side might have been a better choice.

Additionally, we had to hunt around to find a Micro USB cable that would seat properly in the keyboard due to the small size of the cutout. The problem was at least partially solved when we broke the thinnest piece of the spacer acrylic by accident: you may want to break it on purpose.

Going Further

3d printed keybow stand
3D Printed Keybow Stand

Naturally we built a small box for it; the design is available on Thingiverse. The initial version for sizing is a bit tight, and keeps the entire build visible. We might do a version with taller walls with a slot for the USB cable. The rubber feet were stuck on the bottom of it so it wouldn’t slide. (It’s not hollow; hollow might be better to allow adding some weight.)

Instead of mounting it on its rubber feet you might prefer to drill out some holes, countersink the board-side’s holes, and screw it onto a platform (like at an angle). Or omit the rubber feet and 3d print a small angled platform with some spaces for the screw heads. Like we did.

Resources