US coin values by weight

February 6, 2017

One thing that it’s often wise to have for emergencies is a handful of change. If you’re stuck somewhere where there’s nothing to eat but vending machine goodness, it might mean the difference between having a meal or not.

But if you bring change, what sort of change should you bring? Going strictly by weight, the $1 coin is the best value for weight right now. But there’s one drawback to that: some vending machines don’t take $1 coins. So what should you carry if you want to get maximum snacks?

The US Mint has a list of US coin weights as part of their specifications. That tells you what you need to know. Here’s a table based on the weight of mint condition coins (coins lose mass as they get used):

Coin Value ($) Coin Weight (g) Weight (g) for $1
0.01 2.5 250.0
0.05 5.0 100.0
0.10 2.268 22.68
0.25 5.670 22.68
1.00 11.34 11.34

That reveals some interesting facts.

  • Nickles weigh exactly 5 g each
  • Two pennies weigh the same as one nickle
  • $10 in nickels or $4 in pennies weigh 1 kg
  • Quarters and dimes have the same value by weight
  • Dollar coins are twice as valuable as quarters and dimes by weight

So if you’re on a road trip and plan to dine at the automat, grab your dollar coins first, then your dimes and quarters. Skip the nickles (unless you think that you’ll have to weigh something – it might be handy to have a few 5g weights around). And there’s a reason that only zinc producers like pennies.


Dockerizing TicketsCAD

January 4, 2017

Over the recent holiday, I decided to teach myself about Docker by using it to build a running TicketsCAD system. It turned out to be pretty slick. Here’s what I did.

TicketsCAD is a PHP application that requires an SQL backend. It does computer dispatching for public safety answering points (PSAPs). Yes, I have weird hobbies.

When I first started, I grabbed the official Docker PHP image, then tried to customize it by installing a MariaDB server. But that turned out to be a bad idea. (It’s certainly possible, but not the right thing to do.) After a little while, I did a search for “Docker philosophy”, which brought me to this page:
techblog.constantcontact.com/devops/a-tale-of-three-docker-anti-patterns
which set me straight. The right way to do what I wanted to do was to have two separate (but linked) Docker container instances – one with the web server and PHP (customized the way I wanted) and the other with the SQL database. So here’s what I came up with.

Grab TicketsCAD

I grabbed the files from the TicketsCAD download area: tickets_3.12A_082516.

Next I created a directory for everything. In that, I put a directory for the ticketscad server (I cleverly called it ticketscad) and another directory for the ticketscad SQL server (even more cleverly called ticketscadmariadb). Then I created var-www-html in ticketscad, and untarred the TicketsCAD files into it.

Dockerfile for TicketsCAD

Here’s the first Docker file (named Dockerfile in the ticketscad directory).

FROM php:7.0-apache
# Set the timezone
RUN echo Canada/Eastern > /etc/timezone && dpkg-reconfigure -f \
        noninteractive tzdata
# get PHP GD libraries
RUN apt-get update && apt-get install -y \
        libfreetype6-dev \
        libjpeg62-turbo-dev \
        libmcrypt-dev \
        libpng12-dev \
    && docker-php-ext-install -j$(nproc) iconv mcrypt \
    && docker-php-ext-configure gd \
       --with-freetype-dir=/usr/include/ \
       --with-jpeg-dir=/usr/include/ \
    && docker-php-ext-install -j$(nproc) gd

# Install Mariadb client
RUN apt-get install mariadb-client -y
RUN docker-php-ext-install mysqli pdo pdo_mysql

# Fix permissions for ticketscad
RUN chown -R www-data.www-data /var/www/html
RUN chmod -R u+w /var/www/html

From top to bottom, here’s the explanation.

  • I used the official PHP Docker build with Apache from hub.docker.com. There were some issues with TicketsCAD and PHP 7.1, so I went down to PHP 7.0. (Docker made this very easy to do.)
  • I set the timezone. This is a bit of Docker weirdness – there’s no standard way in Linux to set timezones, so you need to know how to do it in the distribution you’re using. This is how to do it in the PHP distribution. More details about timezones are at www.ivankrizsan.se/2015/10/31/time-in-docker-containers
  • I installed the PHP GD libraries. This is stolen from the PHP Core Extensions section of PHP on hubs.docker.com, and it installs a few other things I probably don’t need (mcrypt, etc.) I just copied and pasted.
  • I installed the MariaDB client and PHP SQL extensions.
  • TicketsCAD wants to write to /var/www/html and its subdirectories, so I made them rw. (I’m not sure how that makes me feel about security – I’d kind of prefer it not to do that and to store configuration somewhere not visible to the world. But that’s how it works.)

Now that I had that running, I needed a SQL backend. Luckily, there’s an official MariaDB Docker build as well. The Dockerfile for this is so simple that I almost don’t need it, but hey, I had a directory created already, so here it is:

Dockerfile for MariaDB

FROM mariadb:10.1
RUN echo Canada/Eastern > /etc/timezone && dpkg-reconfigure -f \
      noninteractive tzdata
RUN chown -R mysql.mysql /var/lib/mysql

Pretty self-explanatory. I don’t know for sure that I need that chown, but it doesn’t hurt.

docker-compose.yml

I built a docker-compose.yml in my parent directory. This is a way to start both instances at the same time.

version: '2'
services:
  ticketscadmariadb:
    build: ./ticketscadmariadb
    image: ticketscadmariadb:1.0
    volumes:
     - ./ticketscadmariadb/var-lib-mysql:/var/lib/mysql
    ports:
     - "3306:3306"
    environment:
     - MYSQL_ROOT_PASSWORD=secret
     - MYSQL_DATABASE=ticketscaddb
     - MYSQL_USER=ticketscaduser
     - MYSQL_PASSWORD=ticketscadpassword
  ticketscad:
    build: ./ticketscad
    image: ticketscad:1.0
    volumes:
     - ./ticketscad/var-www-html:/var/www/html
    ports:
     - "8080:80"
    links:
     - ticketscadmariadb

There’s a lot in there. I’ll break it down.

  • I’m using version 2 of the docker-compose.yml file format. Gotta say so.
  • First I specified the way to build ticketscadmariadb. I pointed to the subdirectory that holds the Dockerfile and gave it an image name.
  • I exposed the directory on my local machine that will hold the MariaDB database. (It’s /var/lib/mysql on the Docker instance; I called it var-lib-mysql in the ticketscadmariadb directory on my local machine.) Why did I do this? So the SQL database would persist across Docker rebuilds. Needless to say, I created an empty var-lib-mysql under ticketscadmariadb on my local system.
  • I exposed the MariaDB SQL port (3306) and mapped it to 3306 on my local machine. If you have SQL running on your local machine already, you’ll need a different local port.
  • I specified a bunch of environment variables for MariaDB. The MariaDB Docker image is smart enough to know that if it sees those variables, it creates a database with the specified name / user / password / root user. Thanks, MariaDB Docker image! (You’ll probably want different passwords than I used.)
  • Now it’s time for the TicketsCAD image (PHP/Apache server). I pointed to the ticketscad subdirectory (which holds the Dockerfile) and gave it an image name.
  • I mapped the /var/www/html directory on my Docker image to ./ticketscad/var-www-html on my local machine. Why not just COPY? Because TicketsCAD writes to /var/www/html and /var/www/html/incs, and I want that to persist across builds.
  • I exposed port 80 on the Docker image as port 8080 on my local machine.
  • Finally, I told Docker that I want to be able to have the ticketscad image talk to the ticketscadmariadb image using the links: command. (If I didn’t do this, the two images wouldn’t be able to communicate.)

Building and running

So now everything’s set up. At this point, I can go to the parent directory and type:

docker-compose up

That reads docker-compose.yml by default, downloads the files that are needed and creates my images. And voilà, a working TicketsCAD install is ready to go.

Well, almost ready to go. Because Docker assigns IP addresses for each build, I had to interrogate the running images in order to figure out what they were. First I used docker ps to find out that the container ID of the ticketscadmariadb instance was 2e13d01384ac, then I used docker inspect to discover its IP address. I needed to know the IP address for the TicketsCAD configuration screen.

docker ps
docker inspect 2e13d01384ac | grep IPAddress

For me it was 172.17.0.2.

Because I have the TicketsCAD port 80 exposed on my local machine as 8080, I can now go to localhost:8080 to run TicketsCAD.

What next?

This is enough to get TicketsCAD up and running in a “play” environment. In a real environment, you’d want to harden things. In particular, remove the install.php script and save the Docker image after installation, and make sure that random yahoos on the Internet can’t write to /var/www/html. Give the machines real hostnames. Set up real certificates and put them on the network. Run something to prevent DDOS attacks. All them devops things.

Hey TicketsCAD guys!

You’ve got some cool software. If possible, it would be nice for TicketsCAD to have a check box to delete install.php for you after running it – then it would be more compatible with Docker.

A few more more useful things

1. If you want to look into a running Docker instance, this is nice to know:

docker exec -it containerID bash

It brings you to a root shell running bash on the instance.

2. I ran into problems after I removed some images with docker -rmi. stackoverflow.com/questions/37454548/docker-compose-no-such-image solved my problem (in short: docker-compose down).

3. Sometimes you’ll need to rebuild.

docker-compose up --build

is a handy way to do that.

My directory structure

docker-ticketscad/
docker-compose.yml
  ticketscad/
    Dockerfile
    var-www-html/
      (all the TicketsCAD files go here)
  ticketscadmariadb/
    Dockerfile
    var-lib-mysql/

Tracking and blocking BRW70188B

January 2, 2017

I’ve been monitoring wifi traffic on my network. I’ve seen a large amount sent up by one device, which was reported as starting with BR70188B (mac address 70:18:8b) with manufacturer HonHaiPr.

HonHaiPr is Hon Hai Precision Industry, which makes network devices. The one in question (with the name BRW70188Bxxyyzz) was from a Brother MFC-650DW that is on the network.

Now that I’ve identified the printer, what to do about it? It was spewing lots of uploaded data – perhaps just to the clients that printed from it, but I’m perhaps a little paranoid. (It seems strange that it’s uploading almost as much as gets downloaded to the printer, though.) So I decided to knock it off the Internet to see what happened.

First, I gave it a static IP address in my dhcpd.conf:

host mfc650dw {
    hardware ethernet 70:18:8B:xx:yy:zz;
    fixed-address 192.168.1.253;
    option host-name "mfc650dw";
}

Next, I updated it in DNS (db and db.rev files) just ’cause now that it’s static it’s handy to have a name to deal with.

Finally, I added a rule to my pf.conf:

block out log quick from 192.168.1.253/32 to ! 192.168.1/24

Now if the printer’s trying to send data up to the Internet, it’s not going to make it through the firewall.

After I did all this, the printer wouldn’t work – Brother apparently stores the IP address but doesn’t refresh if it can’t find it. So I needed to download the Brother Network Connection Repair Tool to tell the Windows printer driver to look for the printer again. Sheesh.


Translation of Evangeline Acadian Queen

December 3, 2016

Angèle Arsenault wrote this back in 1977, and I haven’t been able to find a translation that I really liked. So I had to do it myself. It’s from her album Libre (SPPS Disques, PS-19903) and was my first introduction to Acadia.

I’m going to talk to you of someone that you know
Yes but don’t deceive yourself, she did not come from the States
Even if a certain fellow who was called Longfellow
Popularized her two hundred years ago
She was called Évangéline, she was very very fine
She loved Gabriel on earth as if in heaven
They lived in Acadia, they were damned rich1
But one day the English were no longer satisfied
So they deported them, Gabriel disappeared
Discouraged2 Évangéline searched for him as long as she could
She searched for him in Acadia in Quebec in Ontario
Then in the United States in Florida in Idaho
Arriving in Louisiana with her cousin Diane
She said I have lost my time3
She was 75 years old4
Working a the hospital, she cared for the sick
Then she saw her Gabriel who was leaving for heaven
She jumped on his neck
And said thank you very much
At the hour that you’re interred I will be able to return
I’m going to invest in the companies of the future
So that the name of Évangéline will be bloody well known5

Évangéline Fried Clams
Évangéline Salon Bar
Évangéline Sexy Ladies Wear
Évangéline Comfortable Running Shoes
Évangéline Automobile Springs
Évangéline Regional High School
Évangéline Savings Mortgage and Loans
Évangéline The only French Newspaper in New Brunswick
Évangéline Acadian Queen

  1. This is “riches en maudit” in the original.
  2. This is “déconfortée”in the original.
  3. This proves that Angèle didn’t come from Clare, since she wrote “soixante et quinze” instead of “septante-cinq.”
  4. This is “A dit là j’perdrai pu mon temps.” It seems to indicate both past and future.
  5. This was the hardest to translate, “soit connu en câline.” Literally, “will be known in cuddly” but câline is a milder form of the sacre (expletive) câlisse or chalice.

Ubuntu 16.04.1 – cron mail not working

September 19, 2016

I recently ran into a strange issue. I wasn’t getting mail from cron – even though I could mail myself locally without incident. My cron daemon was running fine, and I had MAILTO=user specified in the crontab.

The first piece of advice everyone says when you search about this is “make sure you can send mail to yourself.” And I could – using mail or mailx and sending to andrew. And if you try searching for help after that, you get lost in the weeds of people trying to send mail to Gmail, and setting up postfix, and going insane.

After a little poking around, I noticed this in my /var/log/mail.log:

Sep 12 04:28:01 myserver postfix/qmgr[2902]: A292710059B: 
   from=<root@myserver.mydomain.com>, size=800, nrcpt=1 (queue active)
Sep 12 04:28:01 myserver postfix/error[20839]: A292710059B:
   to=<andrew@myserver.mydomain.com>, orig_to=<andrew>, relay=none, delay=1.4,
   delays=1/0.12/0/0.25, dsn=5.0.0, status=bounced (myserver.mydomain.com)

I’ve been faking my domain name and it looks like when I upgraded to Ubuntu 16.04.1 things stopped working. (I have a sneaking suspicion that the upgrade process yanked the domain address out of /etc/hosts. But maybe cron changed and started using my FQDN instead of my local mail address.)

But even after changing my hosts file from:

127.0.1.1 myserver

to:

127.0.1.1 myserver myserver.mydomain.com

things weren’t mailing again. I finally changed my crontab to MAILTO=andrew@localhost instead. But that seems kind of bogus. If you’ve got better ideas (/etc/mailname maybe?) let me know.


Making use of GIMP plugins

September 1, 2016

(or how to draw an arrow with an outline)

As part of a project that I’m working on, I found myself drawing lots of red arrows with yellow outlines. To do this I was using the GIMP image editor.

This was tedious. I would draw a yellow arrow for the outline, then draw a red arrow slightly smaller, then merge down so I had one layer. I started wondering about scripting it.

First I started by just calling the FU_arrow.scm script with my values. It wasn’t hard to write a script that did that. In my case, I did:

    (FU-arrow image drawable
            80.0
            25
            TRUE
            75
            500 ; brush thickness
            FALSE ; use forst point as head
            FALSE ; delete path after arrow was drawn
            TRUE ; use new layer for arrow
            FALSE ; draw double headed arrow
            FALSE ; useless
            )

In other words, my plugin just called the FU-arrow plugin. Next I added a little bit of code around that:

    (gimp-image-undo-group-start image)
    (gimp-context-push)
    (gimp-palette-set-foreground '(255 255 0)) ; yellow
    (FU_arrow image drawable 80.0 ...) ; draw outer (bottom) layer
    (gimp-palette-set-foreground '(255 0 0)) ; red
    (FU_arrow image drawable 80.0 ...) ; draw inner (top) layer
    (gimp-context-pop)
    (gimp-image-undo-group-end image)

This saved the state and set the foreground colours appropriately so I didn’t have to, and also made it easy to undo in a single action.

You can see that I called FU_arrow twice. Next I needed to merge them down. For that, I used the facility in the arrow plugin that lets you create the arrow as a new layer. New layers are added at the top of the layer stack, so it’s fairly easy to grab that and work with it. The interesting code is:

    (set! current-layers (cadr (gimp-image-get-layers image)))
    (set! arrow-foreground-layer
      (vector-ref current-layers 0))

Once I have a handle on the foreground layer, I can use gimp-image-merge-down with CLIP-TO-BOTTOM-LAYER to merge the two layers:

(gimp-image-merge-down image arrow-foreground-layer CLIP-TO-BOTTOM-LAYER)

Because I know nobody else created a layer between the two layers I created, it’s easy to get a handle on the new layers the FU-arrow plugin made.

My total plugin is:

(define
  (script-fu-quick-arrow image drawable)
  (let *
       (
       (arrow-background-layer -1)
       (arrow-foreground-layer -1)
       (current-layers -1)
       )
    (gimp-image-undo-group-start image)
    (gimp-context-push)
    (gimp-palette-set-foreground '(255 255 0))
    (FU-arrow image drawable
			80.0
			25
			TRUE
			75
			500 ; brush thickness
			FALSE ; use forst point as head
			FALSE ; delete path after arrow was drawn
			TRUE ; use new layer for arrow
			FALSE ; draw double headed arrow
			FALSE ; useless
			)
    (set! current-layers (cadr (gimp-image-get-layers image)))
    (set! arrow-background-layer
	  (vector-ref current-layers 0))
    (gimp-palette-set-foreground '(255 0 0))
    (FU-arrow image drawable
			80.0
			25
			TRUE
			75
			1 ; brush thickness
			FALSE ; use first path as head
			TRUE ; delete path after arrow was drawn
			TRUE ; use new layer for arrow
			FALSE ; draw double headed arrow
			FALSE ; useless
			) ;script-fu-draw-arrow function call
    (set! current-layers (cadr (gimp-image-get-layers image)))
    (set! arrow-foreground-layer
	  (vector-ref current-layers 0))

    (if (= -1 arrow-foreground-layer) (gimp-message "Foreground is -1"))
    (if (= -1 arrow-background-layer) (gimp-message "Background is -1"))
    (gimp-image-merge-down image arrow-foreground-layer
         CLIP-TO-BOTTOM-LAYER )
    (gimp-context-pop)
    (gimp-image-undo-group-end image)
    ) ; let
  ) ;define

; Register with GIMP:

(script-fu-register "script-fu-quick-arrow"
  _"Quick Arrow"
  _"Draw a nearly arbitrary arrow in your image in red with a yellow outline. Arrow will be created in a separate layer. Needs FU_arrow.scm"
  "Andrew"
  "2016, Andrew"
  "2016-09-01"
  "*"
  SF-IMAGE       "The image"   0
  SF-DRAWABLE    "The drawable"   0
)

(script-fu-menu-register "script-fu-quick-arrow" "/Script-Fu/")

Quick edit: to install the script, copy it to the scripts directory. You can find that with Edit -> Preferences -> Folders -> Scripts (I used the user folder rather than the system folder). Then Filters -> Script-Fu -> Refresh Scripts. Et voilà!


Building Signalink Cables

June 13, 2016

Many of us have sound card interfaces for our radios that use the standard RJ-45 plug on one end and a custom connector for the radio on the other. If you’ve got more than one radio, it’s sometimes possible to buy additional interface cables. That can get pricey, though – and depending on the connector on your radio, an interface cable might no longer be available.

For many rigs it’s possible to buy a connector that ends in bare wire fairly cheaply. I hit eBay and found a cheapie Kenwood connector for $2.49 (“4 Wire Speaker Mic Cable for Baofeng UV5R Kenwood TK-240”).

Kenwood style connector with bare ends

While holding one of these in my hand, I noticed that the individual wires in the radio cable were roughly the same diameter as the wires in cat-5 network cable.

Before doing anything else I wrote down which wires connected to which pins on the radio. All of the wires in my cable had different colors, which made identification a lot easier. Next, I determined which pin in the RJ-45 plug should be connected to which wire. This varies depending on the radio connector and sound card interface you use. In my case, green went to the 2.5mm plug tip aka speaker, red went to the 3.5mm ring aka mic, black went to the 3.5mm sleeve aka PTT, and white went to the 2.5mm sleeve aka ground. I found this Tigertronics page useful.

Close-up of connector and wire

After that, I cut the interface cable straight across with diagonal cutters. My cable came with an integrated strain relief, and I cut that off as well. Then I carefully removed a little more than half an inch (about 13mm) of the cable jacket, being careful not to nick the wires inside.

Cable together but before crimping

I arranged the wires in the correct order they’d need to be into the RJ-45 plug. The wires were solid core, so I was able to spread them more or less into position. Next I inserted the cable into the RJ-45 plug, being careful to slip each wire into the appropriate channel. One or two recalcitrant wires needed persuasion with a pin to find the right home.

Once all the wires were in their channels, I pushed hard on the cable to ensure all the wires were as far forward in the plug as they would go. At this point I crimped the RJ-45 plug. There are two nice things about an RJ-45 crimp: there’s no need to strip the wires (the plug bites down on them to make the connection), and the crimp forces part of the plug’s shell against the cable, which keeps it in place.

Then came the moment of truth: I tested continuity of each pin on the connector. Success!

Completed cable

The radio’s connectors were in the right place, and I had a professional-looking interface cable for a radio that needed it.