Planet Fitness Cancellation Template

Update: I put this in the mail on Tuesday and got cancellation confirmation less than 48 hours later.

Also, your mileage may vary, some locations are a little more of a stickler about certified mail, but you should get a confirmation from the location within a few days if they’ve accepted.

I needed to cancel Planet Fitness in the middle of the COVID-19 pandemic and I really didn’t want to be showing up at a gym in person right now (time and unnecessary risk) just to cancel.

I called up my local Planet Fitness and was told I could either come in in person *or* send a [yes, snail mail] letter to the Planet Fitness I signed up at.

So, I’ve thrown together a cheap template to help you include everything you need to cancel (it’s just name, date of birth, and signature, according to the person on the phone.)

You can download the doc I put together at: Planet Fitness Cancellation Word Doc template

Creality Ender 3 Pro: Getting Successful Prints and Ending Frustration

Lots of frustration with the Creality Ender 3 Pro

It’s been about a 10 day journey to getting consistent prints with the Creality Ender 3 Pro. (Amazon affiliate link, but I got it from Creality for cheaper during their Black Friday sale.) Manual bed leveling is an art that there are too many opinions on that… well.. they didn’t 100% work. I tried various gaps from paper thickness to card stock thickness, and while they mostly worked, I would have prints fail at random places halfway through.

Creality Ender 3 Pro fail

Initial Success

I had some success with the Creality Filament (affiliate link) with perfect leveling and some glue stick application, but most prints bailed an hour or two in:

The dog and cat prints did eventually work with specifically the separately purchased “official” filament, but not with the sample white filament that came with it.

Tempered Glass Bed: Amazing for Adhesion

The Dawnblade Creality Ender 3 Glass Bed Upgraded (affiliate link) greatly improved my success with the black “official” filament with the included print files and I was able to print a couple of things from Thingiverse like the Doggie Phone Holder if I started with a raft, but I still ended up a bit warped:

Warped raft

With the glass bed, however, I was able to get the Basics 3D filament (affiliate link) to work with the stock prints:

Maneki Neko print in blue

Creality CR Touch Upgrade: Possibly Better than the Glass Bed?

I bought and received the Creality CR Touch (affiliate link) prior to the glass bed, but installed the glass bed first because it’s much easier. (I later also bought the Ender 3 Pro bed clip clamps (affiliate link) which are mostly unnecessary, but provide cleaner clearance on the ends.)

I suspect that I might not have bought the glass bed if I had the CR Touch first, as the adhesion with the bed is almost *too good*:

Tips for Installing the CR Touch

  • The motherboard is the box in the front left of the Creality Ender 3 Pro. There’s one small screw near the front of the motherboard case (and near the bed rail) and three below. You’ll need to remove all three.
  • Remember to note WHICH VERSION your motherboard is *while you have the case open*… I don’t know if it matters, but if it does, you’ll kick yourself if you close it up before noting it:
  • The firmware is in a zip file for your printer model under accessories on the Creality website
  • You’ll have to turn the printer over to get to the motherboard port you need:
The motherboard access
  • The screws go into the mounting bracket up through the CR Touch and then into the bracket (you’ll be really mad if you fight to get them started the other way, but fortunately, the correct way is easier!):
Mount the CR Touch screws upwards, not downwards!

Here’s a link to the YouTube video that helped me with figuring out the CR Touch install:

Now, Off to the Races!

Getting C64Studio to work with C128 BASIC and Assembly

C64Studio for building more complex projects for Commodore build targets

C64Studio is a .NET based IDE which has some nice macros and dependency linking to connect your BASIC and Assembly files together automatically. Using it with Commodore 128 BASIC and linking the Assembly in add a few extra challenges.

Starting with a template

Sample Project 14 – BASIC and Assembly has most of the settings you need to get going. The keys to this setup

The compile target for the BASIC program needs to have the Assembly file checked as “Dependant” and “Symbols” and that D64 should be the output:

C64Studio setting the Compile Target properties on a BASIC file.
Check “Dependant” and “Symbols”

Set up a “Post Build” step on the BASIC file to add the Assembly target to the D64 image $(MediaManager) -d64 "$(BuildTargetFilename)" -import asmloop.prg -renameto "ASMLOOP.PRG". The .asm extension of the file should be changed to .prg for the post build command.

Post Build step on the BASIC file to add the Assembly file.
$(MediaManager) -d64 "$(BuildTargetFilename)" -import asmloop.prg -renameto "ASMLOOP.PRG"

Right-click your main BASIC source file and make it the Active Element

Active element should be bold after right-clicking and selecting “Set as Active Element”

Linking source itself

C64Studio will expose label references in dependencies in BASIC, so for the following Assembly, ASMSTART, HIBYTE, MIDBYTE, LOBYTE are all available (I haven’t tried .loop or .nocarry, but they are intended to be “local labels”)

;startup address
  * = $0c00
;create BASIC startup
ASMSTART
  ldx #$00
  ldy #$00
  lda #$00
  sta $a0
  sta $a1
  sta $a2
  sta LOBYTE
  sta LOBYTE
  lda #$60
.loop
  inx
  bne .nocarry
  iny
  bne .nocarry
  inc HIBYTE
.nocarry
  cmp $a2
  bcs .loop
  stx LOBYTE
  sty MIDBYTE
  rts
HIBYTE
  !byte $00
MIDBYTE
  !byte $00
LOBYTE
  !byte $00

In the BASIC file these symbols can be used within curly braces:

BASIC code referencing Assembly labels/symbols
.

Make sure to use curly braces and select the proper BASIC for your target Commodore. Since I’m targeting Commodore 128, I’ve selected BASIC V7.0. Basic keywords are compiled to two-byte codes, dependent on the version, so if you select the wrong one, some keywords may be treated as variables or use the wrong keyword code and you’ll get bizarre errors. (I initially got “Compile Errors” on my BASIC with no error message and then “Type Mismatch” on my “compiled” BASIC file.)

Compile and Run

You should be able to click “Compile and Run” now if you’ve set up your emulator in the IDE to point at an installed emulator (VICE GTK has been working for me).

URI::InvalidURIError : The scheme mysql2 does not accept the registry part

How you can end up with the URI::InvalidURIError

This error is pretty easy to end up with if you have database URL with embedded username and password, although it’s also possible if you’re using basic auth over https.

Example problem URL

Suppose you have a URL such as mysql2://my-user-name:passwordwith.and#and$init@mymacbook.local:3306/db. The passwordwith.and#and$init will cause an error message:

URI::InvalidURIError (the scheme mysql2 does not accept registry part: my-user-name:passwordwith.and (or bad hostname?))

Solution

The above error message ends before the first problematic character (#). These need to be URL encoded (%23 for '#'). mysql2://my-user-name:passwordwith.Eand%23and$init@mymacbook.local:3306/db should pass.

Using Ruby to Quickly validate the URLs

The URI::RFC2396_Parser in ruby can provide a quicker way to validate vs. trying to boot an application dependent on the URL:

irb(main):013:0> URI::RFC2396_Parser.new.parse('mysql2://my-user-name:passwordwith%2Eand#a
nd$init@mymacbook.local:3306/db')
Traceback (most recent call last):
        8: from /Users/tpowell/.rbenv/versions/2.7.2/bin/irb:23:in `<main>'
        7: from /Users/tpowell/.rbenv/versions/2.7.2/bin/irb:23:in `load'
        6: from /Users/tpowell/.rbenv/versions/2.7.2/lib/ruby/gems/2.7.0/gems/irb-1.2.6/exe/irb:11:in `<top (required)>'
        5: from (irb):12
        4: from (irb):13:in `rescue in irb_binding'
        3: from /Users/tpowell/.rbenv/versions/2.7.2/lib/ruby/2.7.0/uri/rfc2396_parser.rb:219:in `parse'
        2: from /Users/tpowell/.rbenv/versions/2.7.2/lib/ruby/2.7.0/uri/rfc2396_parser.rb:219:in `new'
        1: from /Users/tpowell/.rbenv/versions/2.7.2/lib/ruby/2.7.0/uri/generic.rb:208:in `initialize'
URI::InvalidURIError (the scheme mysql2 does not accept registry part: my-user-name:passwordwith%2Eand (or bad hostname?))
irb(main):014:0> URI::RFC2396_Parser.new.parse('mysql2://my-user-name:passwordwith%2Eand%2
3and$init@mymacbook.local:3306/db')
=> #<URI::Generic mysql2://my-user-name:passwordwith%2Eand%23and$init@mymacbook.local:3306/db>

Commodore Maze Generator on AWS Lambda via API Gateway

The Commodore Maze Generator Program

I needed something “useful” to output from my Commodore VICE Lambda Custom Runtime that also didn’t require much input (if any) from the requester. Otherwise, I’d have to sanitize the input that is being passed to a shell script and potentially validate it if I didn’t want unpredictable behavior. I settled on a Commodore 128 version of the Commodore Maze Generator (the fast and slow will be unnecessary once we launch from the Lambda with the -warp option on VICE, but I never changed the program so it’s left in here. The fast and slow and everything before line 50 is Commodore 128 BASIC 7.0-specific, but the Commodore 64 version just requires substitution of POKE commands instead. (See vice-lambda repo on GitHub for latest version)

5 fast
10 scnclr
20 color 0,2
30 color 4,2
40 color 5,1
50 for y=1 to 25
60 for x=1 to 40
70 print chr$(205.5+rnd(1));
80 next x
90 next y
95 slow
100 goto 100

Making sure that the Lambda returns an image for Lambda

I want to be able to use Lambda Proxy Integration when setting up the API Gateway, so I need to return a format that maps the information properly. The response JSON needs to have:

  • isBase64Encoded set to true (necessary for the translation of the PNG back from base64)
  • headers with "Content-type" and "content-disposition" set (image/png and inline for this example)
  • statusCode of 200 (right now, if we have an error it’s either not caught or happens at the Lambda invocation level)
  • body containing the base64 data. Note the -w 0 argument to turn off line breaks when wrapping… Lambda Proxy Integration doesn’t handle that well.
function handler() {
  cd vice
  SEED=$((0 - `od -An -N2 -i /dev/random`))
  ./x128 -silent -sound -keybuf "
  3 i=rnd($SEED)
  `cat ../maze.bas`
  run
  " -warp -limitcycles 20000000 -exitscreenshotvicii /tmp/$1.png 2>&1 >/dev/null
  cd ..

  RESPONSE="{\"isBase64Encoded\": true, \"headers\": {\"Content-type\": \"image/png\", \"content-disposition\":\"inline\"}, \"statusCode\":200, \"body\":\"`base64 -w 0 /tmp/$1.png`\"}"

  echo $RESPONSE
}

Also note that we’re passing in a SEED value because, otherwise, the Commodore 128 will generate the same maze every time.

Repackage the Lambda and upload as in the Commodore VICE Lambda Custom Runtime blog post.

Setting up the API Gateway

Go to API Gateway in the AWS Console and select [Create API]

APIs Create API

Choose [Build] under “REST API” (we won’t be using OIDC/OAuth2/CORS…)

REST API -> Build

Choose REST, New API, and name your API:

REST, New API, API Name

Create a GET Method and check the checkmark

Create Method
API Method verb selection -> GET
Check the checkmark to commit
  • Choose “Lambda Function” for your Integration Type
  • Check “Use Lambda Proxy Integration”
  • Type the name of the Lambda that you’ve calling and select it and [Save] and confirm that you want to add role/permission for the Lambda.
GET API configuration

Deploy the API

Back on your API view, select the [Actions] dropdown and [Deploy API]

API Actions "Deploy API"

Just create a new dev stage… we’re not going to “production” with this experiment

Create new dev stage

and [Deploy]

It’s broken

If you click the Invoke URL presented, you will get a broken image

Invoke URL
Broken image from the API

Since we’re only sending back PNG files, we can just tell the API that */* is a Binary Media Type by going to Settings on the left sidebar for the API and [(+) Add Binary Media Type]

Add Binary Media Type

Be sure to save the settings, redeploy the API and be sure to use the Invoke URL for the correct API! (I had two identical and was “fixing” the wrong one… hence the different API URL snapshots)

Commodore Maze Generator PNG 1

If you refresh, you should get a new maze from the Commodore maze generator program:

Commodore Maze Generator run 2

Some Troubleshooting

  • Be sure to save your settings
  • Test your API call to Lambda in the Method Execution view (Resources -> GET -> Test [lightning bolt])
  • Be sure to redeploy your API after settings change.

Commodore 128 (VICE) Custom Runtime on AWS Lambda

Why a Commodore 128 Custom Runtime??

The first reason is wanted to do a Commodore 128 Custom Runtime on AWS Lambda is because it’s an absolutely ridiculous thing to do. The Commodore 128 runs at 1 MHz (2 MHz in “fast” mode) natively on the 8502 processor vs. multi-GHz scale.

Another reason is because I’ve had challenges with the slight differences with Amazon Linux vs. Ubuntu for EC2 instance. This seemed like a good way to exercise working through those differences.

The last reason is that I wanted a platform that would require me to dig a little deeper into how things are managed for a custom Lambda. The only directory that’s writable is /tmp. There are specific conventions used to honor the function.handler format. The responses (if you don’t have a supported runtime) are handled through callback URLs.

Compiling VICE

It’s a bit of an adventure getting VICE running on Amazon Linux. Ubuntu provides a package for VICE (of course, you’re on your own for the ROMs themselves.) With Amazon Linux, you’ll need to download the tarball and compile. Here’s my video recreating that process through to getting the Lambda running:

Compile VICE and Building the Custom Runtime

The steps… fire up an Amazon Linux EC2 instance and:

  • sudo yum update
  • sudo yum install links -y
  • links https://vice-emu.sourceforge.io and download the vice-3.5.tar.gz
  • tar zxvf vice-3.5.tar.gz from the home directory
  • sudo yum install -y gcc gcc-c++ flex bison dos2unix libpng-devel.x86_64
  • links https://www.floodgap.com/retrotech/xa/ and download xa-2.3.11.tar.gz or whatever version is available
  • tar zxvf xa-2.3.11.tar.gz
  • cd xa-2.3.11 and make
  • Add xa to the path with PATH=~/xa-2.3.11:$PATH
  • cd ~
  • cd vice-3.5 and ./configure --disable-pdf-docs --enable-headlessui --without-pulse --without-alsa and make

You should be able to cd ~/vice-3.5/data/C128 and run:

../src/x128 -silent -sound -ntsc -keybuf "10 graphic 1
20 scnclr
30 circle 1,100,100,20
run
" -warp -limitcycles 8000000 -exitscreenshotvicii image.png

and be able to scp the image.png and open it and see a screenshot something like (bump cycles to 10,000,000 if necessary): (Edit: use -warp not +warp to enable warp mode)

Testing x128 for Custom Runtime
C128 Circle

Packaging the Necessary Pieces

Make a vice-package directory or similar and grab the contents of vice-3.5/data/C128 (some files can be excluded, such as build files if you want to trim the package down) and the vice-3.5/src/x128 file and tar the vice-package directory (tar cvf vice-package.tar vice-package/*) and scp it down to your local computer.

Get the Sample Lambda for Custom Runtime

Go to Lambda -> Functions -> Create Function -> Author from Scratch and select the “Provide your own bootstrap on Amazon Linux 2” option:

Custom Runtime provide your own bootstrap
Custom Runtime template

Copy the bootstrap.sample to the root of the lambda package you are going to create and name it bootstrap and the hello.sh.sample as function or whatever the first part of your function.handler name is (see the Runtime settings below the code window):

Runtime settings where your function.handler is named.

Constructing the bootstrap

There are a couple of environment variables (XDG_CACHE_HOME and XDG_CONFIG_HOME) that have to be set to /tmp so that VICE can write to them. Be sure the handler in Runtime settings matches <script_name>.<bash_function_name> or else Lambda won’t be able to find it to invoke (actually the bootstrap below won’t… you can skip using $_HANDLER and hard code, but then the AWS console won’t help you for function configuration.) I disabled the -e option because we’re actually going to exit VICE ungracefully on purpose for simplicity. Be aware that this is the ON ERROR RESUME NEXT or “try with empty catch block” in that your code will ignore all the other potential failures along the way.

#!/bin/sh
# set -euo pipefail
# we're going to exit VICE on clock cycles so -e option would fail in this case
set -uo pipefail

# otherwise vice tries to write to the 'home' directory that isn't a [writeable] thing in Lambda
export XDG_CACHE_HOME=/tmp
export XDG_CONFIG_HOME=/tmp

# Handler format: <script_name>.<bash_function_name>
#
# The script file <script_name>.sh  must be located at the root of your
# function's deployment package, alongside this bootstrap executable.
source $(dirname "$0")/"$(echo $_HANDLER | cut -d. -f1).sh"

while true
do
    # Request the next event from the Lambda runtime
    HEADERS="$(mktemp)"
    EVENT_DATA=$(curl -v -sS -LD "$HEADERS" -X GET "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/next")
    INVOCATION_ID=$(grep -Fi Lambda-Runtime-Aws-Request-Id "$HEADERS" | tr -d '[:space:]' | cut -d: -f2)

    # Execute the handler function from the script
    RESPONSE=$($(echo "$_HANDLER" | cut -d. -f2) $INVOCATION_ID "$EVENT_DATA")

    # Send the response to Lambda runtime
    curl -v -sS -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2018-06-01/runtime/invocation/$INVOCATION_ID/response" -d "$RESPONSE"
done

Constructing the handler

The handler for this setup needs to only output what is intended as a response. I’m redirecting stderr and stdout to /dev/null because there are some messages that pop-up in the current state of the emulator. I am also using the -silent option to suppress all the errors about missing disk drive and other device ROMs that I don’t care about for this case.

function handler () {
  EVENT_DATA=$2

  cd vice-package

  ./x128 -silent -sound -ntsc -keybuf "10 graphic 1
    20 scnclr
    30 circle 1,100,100,20
    run
  " +warp -limitcycles 8000000 -exitscreenshotvicii /tmp/$1.png 2>&1 >/dev/null
  cd ..


  RESPONSE="{\"isBase64Encoded\": true, \"headers\": {\"Content-type\": \"image/png\", \"content-disposition\":\"attachment; filename=$1.png\"}, \"statusCode\":200, \"body\":\"`base64 /tmp/$1.png`\"}"

  echo $RESPONSE
}

The response

The above response is intended to output JSON in preparation for API Gateway Lambda integration. statusCode is required, and to convert the image back to an image isBase64Encoded and the headers Content-type is needed. The content-disposition is to tell it to download. All this gets POSTed back by the bootstrap script to the invocation response callback. The body is the base64 encoded png file, but in the current invocation in Amazon Linux, I’m getting newlines in the output, so that’s a problem to debug before attaching to API Gateway.

One more missing piece

We also need to pull libpng from our EC2 instance and place in the root of the lambda function at the same level as the bootstrap file. Just scp ec2-user@ec2IPaddress:/usr/lib64/libpng\* . for that.

Structure of the zip file and Deploy

zip up the following pieces into your lambda.zip (zip file name doesn’t really matter, just the organization of the contents):

  • bootstrap
  • function.sh
  • libpng*
  • vice-package/*

Once uploaded, you should be able to [Test] the function and check the logs. Add a set -x to your bootstrap if things aren’t behaving. You may need to chmod +x your bootstrap if you haven’t tried to run it locally for testing.

Using C to Program the Commodore 64 SID Chip

Why use C to Program the Commodore 64 SID Chip?

If you grew up with an original Commodore 64, you probably had a manual along with it that showed you how to program the SID chip. There was even a multi voice example that took forever to load its data and was impossible to read and debug. This is what it looked like (lines 1 and 2 mine) (GitHub link):

1 rem c64 programmers reference guide
2 rem pages 187-188
10 s=54272: forl=stos+24: pokel,0:next
20 dimh(2,200),l(2,200),c(2,200)
30 dimfq(11)
40 v(0)=17:v(1)=65:v(2)=33
50 pokes+10,8:pokes+22,128:pokes+23,244
60 fori=0to11:readfq(i):next
100 fork=0to2
110 i=0
120 readnm
130 ifnm=0then250
140 wa=v(k):wb=wa-1:ifnm<0thennm=-nm:wa=0:wb=0
150 dr%=nm/128:oc%=(nm-128*dr%)/16
160 nt=nm-128*dr%-16*oc%
170 fr=fq(nt)
180 ifoc%=7then200
190 forj=6tooc%step-1:fr=fr/2:next
200 hf%=fr/256:lf%=fr-256*hf%
210 ifdr%=1thenh(k,i)=hf%:l(k,i)=lf%:c(k,i)=wa:i=i+1:goto120
220 forj=1todr%-1:h(k,i)=hf%:l(k,i)=lf%:c(k,i)=wa:i=i+1:next
230 h(k,i)=hf%:l(k,i)=lf%:c(k,i)=wb
240 i=i+1:goto120
250 ifi>imthenim=i
260 next
500 pokes+5,0:pokes+6,240
510 pokes+12,85:pokes+13,133
520 pokes+19,10:pokes+20, 197
530 pokes+24,31
540 fori=0toim
550 pokes,l(0,i):pokes+7,l(1,i):pokes+14,l(2,i)
560 pokes+1,h(0,i):pokes+8,h(1,i):pokes+15,h(2,i)
570 pokes+4,c(0,i):pokes+11,c(1,i):pokes+18,c(2,i)
580 fort=1to80:next:next
590 fort=1to200:next:pokes+24,0
600 data34334,36376,38539,40830
610 data43258,45830,48556,51443
620 data54502,57743,61176,64814
1000 data594,594,594,596,596
1010 data1618,587,592,587,585,331,336
1020 data1097,583,585,585,585,587,587
1030 data1609,585,331,337,594,594,593
1040 data1618,594,596,594,592,587
1050 data1616,587,585,331,336,841,327
1060 data1607
1999 data0
2000 data583,585,583,583,327,329
2010 data1611,583,585,578,578,578
2020 data196,198,583,326,578
2030 data326,327,329,327,329,326,578,583
2040 data1606,582,322,324,582,587
2050 data329,327,1606,583
2060 data327,329,587,331,329
2070 data329,328,1609,578,834
2080 data324,322,327,585,1602
2999 data0
3000 data567,566,567,304,306,308,310
3010 data1591,567,311,310,567
3020 data306,304,299,308
3030 data304,171,176,306,291,551,306,308
3040 data310,308,310,306,295,297,299,304
3050 data1586,562,567,310,315,311
3060 data308,313,297
3070 data1586,567,560,311,309
3080 data308,309,306,308
3090 data1577,299,295,306,310,311,304
3100 data562,546, 1575
3999 data0

I copied this from a text / pdf version which was in all uppercase (because only one case is valid for BASIC keywords and such). It’s in lowercase above so that you can copy/paste into the VICE emulator, because that “one case” is the lower in lower/uppercase mode, and uppercase pastes in as graphics characters in upper/graphics mode. This example is a fairly impressive one, but it also takes 45 seconds to load all of the data and a couple of typos can mean hours of debugging. I took several hours to find what was ultimately mistakenly typing an I instead of a 1.

Spacing Matters in Commodore BASIC

While this code is not *required* to be so compact in spacing, those in-line spaces almost 100% impact the size of the program in memory and on disk. So if you’re taking up nearly all of the BASIC RAM, eliminating spaces actually matters. So you have code that reads pokes or fori or dimfq.

In BASIC, data has to be part of code but still explicitly read in every time

The DATA statements in BASIC can create a nice little data block, but it’s an implicit data block that requires explicit reading in. So you take up all of the space required for the DATA statements, but then load that data into arrays, and, in this case, take 45 seconds to read in and encode the data used.

Variable names are 2 characters max in Commodore BASIC

PIPE, PIKE, and PINE are the same variable, so if you set PIPE to 2, PIKE to 3, and PINE to 4, you will get 4 from all of them. However Commodore 64 BASIC will let you *think* that up to 5 characters is legal (you get a syntax error if you use something like THINGS=2)

Functions can only receive one argument and be named 2 chars (5 can be specified)

The def fn statement allows you to specify code like def fn xp(x) = x * 300 + 12, which then is required to be invoked by fn xp(x) or (fn xp(x)) + y * 12 in context. (The parentheses are probably unnecessary, but readability can be helped by using 2 extra bytes for the parens.)

Subroutines take no arguments and have to be referred to by line number

If you are good at modularizing your code, you’ll find Commodore 64 BASIC a bit limiting. This isn’t even compared to modern and higher level programming languages. Even a decent Assembler can at least track symbolic locations of the subroutines and variables.

Commodore BASIC is slow

Spacing and variable names have an impact on the performance of Commodore BASIC

Setting up for C programming

Install cc65 for your platform. For macOS, it’s available via brew install cc65 and in Ubuntu via sudo apt install cc65. (My Windows install of cc65 is via WSL2).

You may want to find the include files for reference for your platform. They’re in /usr/share/cc65/include for my Ubuntu install and /opt/homebrew/Cellar/cc65/2.19/share/cc65/include (your version may vary).

I created a c64_note_values.h header file for note #defines for all octave 0-7 notes. They’re proportional to their frequency values, but scaled up so that the low ranges can be defined by integer values. If you want to tweak the values and regenerate, this is the C file I created to generate it.

The rudimentary Canon in D (so far)

Thanks to Recreating the Commodore 64 User Guide code samples in cc65. Part four: Sound, I was inspired to try my own hand at the C code.

#include <stdio.h>
#include <stdlib.h>
#include <c64.h>
#include "c64_note_values.h"

const unsigned int notes[][8] = {
  { D3, A2, B2, Fs2, G2, D2, G2, A2 },
  { Fs5, E5, D5, Cs5, B4, A4, B4, Cs5 },
  { D5, Cs5, B4, A4, G4, Fs4, G4, E4 },
};


int main(void) {
  unsigned int i,d;
  unsigned int measure=0;

  SID.amp = 0x1F;
  SID.v1.ad = 0x0f;
  SID.v2.ad = 0x0f;
  SID.v3.ad = 0x0f;

  while(1) {
    for(i = 0; i < sizeof(notes[0]) / sizeof(int); i++) {
      SID.v1.freq = notes[0][i];
      SID.v1.ctrl = 0x11;
      if(measure) {
        SID.v2.freq = notes[1][i];
        SID.v2.ctrl = 0x11;
      }
      if(measure > 1) {
        SID.v3.freq = notes[2][i];
        SID.v3.ctrl = 0x11;
      }
      for(d=0; d<10000; d++) { }
      SID.v1.ctrl = 0x10;
      if(measure) {
        SID.v2.ctrl = 0x10;
      }
      if(measure > 1) {
        SID.v3.ctrl = 0x10;
      }
    }
    measure++;
    if(measure>2) { break; }
  }
  return EXIT_SUCCESS;
}

In this example, I haven’t actually created any functions yet, beyond the main.

SID is a struct pointer cast of the base memory location of the default SID chip. (See here for a “multiple SID” example if you enable in hardware or VICE emulator)

.v1 through .v3 align to the base address of the voice 1 through 3 controls.

.ad is an attack / decay value… 0-f for the two nibbles. If you want the note to start as quickly as possible, use 0, if you want the note to never fade out, use f for the second nibble. For this case, I’m using just that… 0x0f.

.freq is the scaled frequency value I created earlier from the header file which is in the notes arrays.

.ctrl value of 0x11 turns the note on and 0x10 turns it off. You’re actually setting the triangle waveform bit to true and the gate bit to on and off.

.amp is a volume number (actually volume and mode… location 54296 in the Commodore 64 memory map). LP is 1 and volume nibble is 0xf.

Compiling and running

Assuming that you’ve saved the file to canonind.c, the command to compile and link is cl65 -t c64 -O canonind.c (you may be able to remove the -t c64 parameter… my install appears to assume c64.)

Provided you have a successful build, you should have a canonind binary file. In VICE, you can use “File -> Smart attach disk/tape/cartridge…” or “Autostart disk/tape image…”. For the first version, you’ll want to click the “[Autostart]” button after selecting the extensionless filename. For the second version, you’ll want to look for All Files *.* instead of .d64 etc… Either version should autoplay once loaded.

See my video (choppy sound from VICE and morning voice warning) of the process of using C to program the Commodore 64 SID Chip here:

How to Automate Uptime Display in StatusPage.io via Synthetic Monitors in Datadog in 4 steps

I was given the task of converting a 1/0 metric on our Statuspage.io page from Datadog Metrics using the Route53 healthcheck to an actual percentage uptime display in StatusPage.io, or at least something similarly meaningful to the end user.

First stop: Service Level Objectives

When browsing around our current monitors and dashboards, one thing that stood out was “service level objectives.” In combination with synthetics, they provide an uptime percentage over a period of time that can be embedded on the dashboard. [We’ll come back to synthetics on a different approach]

SLO Synthetics Uptime Display in Datadog

Next stop: Trying to embed those SLOs

The System Metrics integration on the statuspage.io side seems to really only be built for flat queries for a point-in-time, and not aggregated over a period of time of days or weeks. A aws.route53.health_check_status query that produced either a 1 or a 0 at any given point in time was fine, but coming up with a way to “query” for a 24 hour or 90 day up time was a different story (impossible to do via direct integration between the two apps?)

Third stop: UptimeRobot and Similar

Jyll over @ Veracity.net suggested some experimentation with Uptime Robot and similar services with my own free instance of StatusPage, and it was in stripping away the extra configuration and being able to feed a simple up/down email or webhook to statuspage.io that I came back to the idea of looking to see if I could email or webhook synthetic alerts from Datadog to Statuspage. (Spoiler: You can!)

Final stop (and the actual steps needed!) Automating Datadog to Send Status to get Uptime Display in StatusPage.io

  1. Add a component in your statuspage.io account
  2. Click on the “Automation” button to get the automation email. Copy that email:
uptime display in statuspage.io
Click the Automation button to reveal your automation email

3. (Create a synthetic monitor that checks a heartbeat route if you don’t already have one)

4. Go to your synthetic monitor in Datadog… under Step 6 is “Notify your team”. Your monitor name needs to use the template variables {{#is_alert}}DOWN{{/is_alert}}{{#is_recovery}}UP{{/is_recovery}} for statuspage automation to understand the message. The rest of the monitor name is irrelevant (as long as DOWN or UP isn’t a fixed part of that name!)

The automation email needs to be mentioned in the message body with an @ in front of it.

Monitor alert settings
No, that’s not a valid automation email.

Error on an M1 Mac loading Rails: node-sass refusing to compile.

M1 Mac specific error

In trying to transfer and restart development on a Rails 6.1.3 app and get it up-to-date, I got an error on an M1 Mac about node-sass not supporting my current environment (the M1 Mac’s ARM 64 architecture).

ERROR in ./app/assets/stylesheets/application.scss (./node_modules/css-loader/dist/cjs.js??ref--6-1!./node_modules/postcss-loader/src??ref--6-2!./node_modules/sass-loader/dist/cjs.js??ref--6-3!./app/assets/stylesheets/application.scss)
Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
Error: Node Sass does not yet support your current environment: OS X Unsupported architecture (arm64) with Unsupported runtime (93)

Just yarn install?

Unsurprisingly, this didn’t work, but my first instinct was a yarn install: and a few errors presumably unique to the M1 Mac (or maybe Big Sur’s Xcode environment) showed up:

../src/libsass/src/ast.hpp:1616:14: note: use reference type 'const std::basic_string &' to prevent copying
for (const auto denominator : denominators)
     ^~~~~~~~~~~~~~~~~~~~~~

.
.

/Users/tpowell/.node-gyp/16.10.0/include/node/v8-internal.h:489:38: error: no template named 'remove_cv_t' in namespace 'std'; did you mean 'remove_cv'?
            !std::is_same<Data, std::remove_cv_t<T>>::value>::Perform(data);
                                ~~~~~^~~~~~~~~~~
.
.
1 error generated.
make: *** [Release/obj.target/binding/src/binding.o] Error 1
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
gyp ERR! stack     at ChildProcess.onExit (/Users/tpowell/projects/blog-post-debug/self-journal/node_modules/node-gyp/lib/build.js:262:23)
gyp ERR! stack     at ChildProcess.emit (node:events:390:28)
gyp ERR! stack     at Process.ChildProcess._handle.onexit (node:internal/child_process:290:12)
gyp ERR! System Darwin 20.6.0
gyp ERR! command "/opt/homebrew/Cellar/node/16.10.0_1/bin/node" "/Users/tpowell/projects/blog-post-debug/self-journal/node_modules/node-gyp/bin/node-gyp.js" "rebuild" "--verbose" "--libsass_ext=" "--libsass_cflags=" "--libsass_ldflags=" "--libsass_library="
gyp ERR! cwd /Users/tpowell/projects/blog-post-debug/self-journal/node_modules/node-sass
gyp ERR! node -v v16.10.0
gyp ERR! node-gyp -v v3.8.0
gyp ERR! not ok
Build failed with error code: 1

Upgrade to node-sass compatible with the M1 Mac via @rails/webpacker

The const auto denominator error led me to this StackOverflow answer. The last time I had downstream dependency issues, the cleanest resolution was upgrading the @rails/webpacker using outdated dependencies in package.json. The current version is 5.4.3:

9c9
<     "@rails/webpacker": "5.2.1",
---
>     "@rails/webpacker": "5.4.3",

After this a yarn install should work fine. (Your need to delete node_modules may vary). Now the assets are compiling and page rendering properly:

M1 Mac now rendering Bootstrap
I guess I’ve never been so happy to see Bootstrap-style buttons.

Remove an Obsolete Work Account from Email and Accounts in Windows 10

Trying to Delete from Settings -> Email and Accounts

I was trying to “fix a problem with one of my accounts” in Windows when I realized that it was an old work account that was complaining. The problem is that “Email and Accounts” in Windows 10 only provides a “Manage” option, which requires login:

Email and Accounts offers a manage button
Email and Accounts in Windows 10 with a work or school account

The answer: Settings -> Access work or school

This is a pretty simple one but I eventually found the answer here. By going to Settings -> Access work or school, you can [Disconnect]:

Settings -> Access work or school
Settings -> Access work or school

Click on the account that you want to disconnect:

Settings -> Access work or school -> [click account]

Click [Disconnect]:

[Disconnect]

After clicking [Yes], Windows will churn for a bit and then you should be all done:

All better
All done

Lowe’s Service Advantage extended protection plan 20% premium is not Worth It

We bought a 4.2 cubic foot Maytag MVWC565FW washer in September of 2019 and paid $130 for the 5 year extended protection plan (Lowe’s Service Advantage) for major appliances $400-699.

Note: if it takes more than fourteen days from first scheduled appointment to resolution, you may be able to receive a one-time $50 payment from your Lowe’s Service Advantage plan.

Interactions with Lowe’s Service Advantage and their service provider

We called for service on August 24, 2021.

September 1, 2021 – initial visit from contract service provider. They diagnosed the problem and repair person said they scheduled a visit for September 15, 2021 after the part came in.

September 15, 2021 – called contract service provider after no one showed for the appointment I cleared my morning calendar for. They said that couldn’t get one of the parts (shift actuator), but I should call Lowe’s to see if they could locate a part.

September 15, 2021 – called Lowe’s, waited 90 minutes for a call back and requested they look for the part. They said it could be up to five business days before the business unit that did that might respond to them.

September 17, 2021 – called Lowe’s Service Advantage back to follow-up (90+ min wait for call back), the part was supposedly going to be sent out to service provider on 9/20.

September 20, 2021 – called contract service provider to see if they received any update from Lowe’s. They said would be at least September 24 before it would be sent/arrive (was not completely clear which end of the transaction it was).

September 23, 2021 – called Lowe’s Service Advantage back for follow-up (90+ min wait for call back). They said they would ask their supplier to give status on parts, with up to a 5 business day wait to hear back.

September 24, 2021 – reached out to Lowe’s on social media channels, and the response was to call Lowe’s Protection Plan phone number. Called contract service provider instead, and they said that they’re still waiting on a part, but that it supposedly got shipped September 23.

September 27, 2021 – received an email from Lowe’s that the part was delivered to the service provider.

September 28, 2021 – Called contract service provider, but they couldn’t make appointment because their systems were down but they’d call me when they came back up.

September 29, 2021 – Called contract service provider. Their first available appointments were 10/5 or 10/6. Scheduled for 10/6. $50 payment for delay was in today’s mail, which seems like a bit of a slap in the face at this point.

October 5, 2021 – Called service provider to try to confirm appointment for the next day. Robocall came through 15 minutes prior to their leaving the office for the day.

October 6, 2021 – Service technician was able to replace actuator, but hub could not be replaced without ordering a new agitator. Washer is now working otherwise.

It took 43 days to get back to a working washing machine.

So far, I’ve spent 5+ hours waiting for call backs, 4 hours waiting for service appointments that were apparently never made or canceled without my knowledge, and my wife has spent 12-14 hours at the laundromat and well over $100 (maybe even $200?) at the laundromat.

Log of interactions with the Lowe's Service Advantage service provider
Service provider logs.

Not the First Time

This has been the second appliance/equipment purchase in the last two years that has subpar support. We also bought a Craftsman Cordless Electric Mower that we couldn’t get a replacement battery for.