All Posts

Keeping notes with Orgzly and Nextcloud

in Tools by evan power
Tags: , , , , ,

I tend to keep notes for non-work stuff on my phone using the Orgzly Android app. It stores notes in a plain-text format called org-mode. I have it set up to sync my notes to a Nextcloud server via WebDAV. This works nicely, but Nextcloud by default doesn't allow me to view or edit my notes through its web interface. With a little tinkering, though I was able to change that. This post is mostly to record these steps for myself, but you never know, someone else might find them useful.

Setting up WebDAV in Orgzly

First, from the Nextcloud web interface create a folder in your Nextcloud instance called "notes". Then on your phone open Orgzly's settings and select "Sync" and then "Repositories". Press the "+" button at the top right to add a new sync location. You will be prompted to select either "WebDAV" or "Directory". Select "WebDav". You will then see fields for a URL, a username, and a password. In the URL field enter "[Your Nextcloud Instance's URL]/remote.php/dav/files/[your Nextcloud user name]/notes". In the username and password fields enter your Nextcloud username and password. Go to one of your notebooks in Orgzly and select "sync" from the menu. Your Orgzly notes should synchronize to the "notes" folder on your Nextcloud instance.

Configure Nextcloud to Edit .org Files

Having your notes in your Nextcloud is handy for backup purposes, but by default Nextcloud's web interface can't view or edit .org files. Changing this requires shell access to the machine that your Nextcloud instance is running on and permissions to modify the Nextcloud configuration.

First install the Plain Text Editor app through the Nextcloud web interface. This will add a "Edit in plain text editor" entry to the right click menu for certain file types in the Nextcloud web interface's file browser. Unfortunately .org is not one of the types of file that the plain text editor app supports out of the box. Fortunately, the Nexcloud developers implemented a nice feature called [Mimetype Mapping] that allows us to tell Nextcloud to treat .org files as plain text files. SSH into the server that hosts your Nextcloud instance and navigate to the directory that Nextcloud is installed in. (On Debian-like operating systems this could be something like /var/www/nextcloud/.) In the "config" sub-folder make a file called "mimetypemapping.json" that contains the following:

{
  "org": ["text/plain"]
}

Now we will have to tell Nextcloud to update its database of MIME types and files. From the Nextcloud installation directory run the following commands. These commands assume that the username that Nexcloud runs under is "www-data" if this is not the case on your system replace "www-data" with the username that runs Nextcloud.

sudo -u www-data php occ maintenance:mimetype:update-js

sudo -u www-data php occ files:scan --all

You should now be able to right click on .org files in Nextcloud's web iterface and edit them as plain text files. There will not be any fancy highlighting or formatting because Nexcloud has no understanding of the org-mode syntax, but it's good enough for quick edits.

These instructions were written for 18.0.6 and Orgzly 1.8.3. It looks like in version 19 of Nextcloud the "mimetypemapping.json" file should be named "mimetypealiases.json" instead.

Drawing a Snow Blade in OpenSCAD

in Craftwerks by evan power
Tags: , ,

Over the holidays my friend Greg was talking about making some snow blades but he said he didn't know how to draw a 3D model of one. I figured I'd make some instructions for drawing a simple snow blade core in OpenSCAD. These instructions don't include any camber or rocker. I don't really know anything about ski design, so this example probably won't lead to a usable model, but it's a start.

Step 1: The Body of the Ski

We'll draw the tips and tails of the ski separately from the rest of the ski, which I'm going to call the body. We'll draw the body first. The body will start as a simple rectangle.

edgeLength = 100;
maxWidth = 15;
maxThickness = 2;

cube([edgeLength, maxWidth, maxThickness]);

Step 2: Centring the Ski

We'll centre the ski in the X and Y axes. This will make things a little easier later on.

edgeLength = 100;
maxWidth = 15;
maxThickness = 2;

translate([-edgeLength/2, -maxWidth/2, 0])
    cube([edgeLength, maxWidth, maxThickness]);

Step 3: Make the Body Into a Module

To keep thing neat we'll make a module that contains the ski body and call that module.

edgeLength = 100;
maxWidth = 15;
maxThickness = 2;

module body(length, maxWidth, maxThickness)
{
    translate([-length/2, -maxWidth/2, 0])
        cube([length, maxWidth, maxThickness]);
}

body(edgeLength, maxWidth, maxThickness);

Step 4: Make the Tip

Make the tip. Keep it simple and just make it a semicircle. We'll draw a full circle because it's simpler.

edgeLength = 100;
maxWidth = 15;
maxThickness = 2;

module body(length, maxWidth, maxThickness)
{
    translate([-length/2, -maxWidth/2, 0])
        cube([length, maxWidth, maxThickness]);
}

module tip(edgeLength, maxWidth, maxThickness)
{
    translate([edgeLength/2, 0, 0])
        cylinder(h=maxThickness, r=maxWidth/2);
}

body(edgeLength, maxWidth, maxThickness);
tip(edgeLength, maxWidth, maxThickness);

Step 5: Make the Tail

The tail is just like the tip.

edgeLength = 100;
maxWidth = 15;
maxThickness = 2;

module body(length, maxWidth, maxThickness)
{
    translate([-length/2, -maxWidth/2, 0])
    cube([length, maxWidth, maxThickness]);
}

module tip(edgeLength, maxWidth, maxThickness)
{
    translate([edgeLength/2, 0, 0])
    cylinder(h=maxThickness, r=maxWidth/2);
}

module tail(edgeLength, maxWidth, maxThickness)
{
    translate([-edgeLength/2, 0, 0])
    cylinder(h=maxThickness, r=maxWidth/2);
}

body(edgeLength, maxWidth, maxThickness);
tip(edgeLength, maxWidth, maxThickness);
tail(edgeLength, maxWidth, maxThickness);

Step 6: Make the Sidecut

This is where things get fun. We'll make the sidecuts circular for simplicity. They could be parabolic with a little change to this function. To make them we'll draw two cylinders and subtract them from the ski's shape. We need to find the part of the circle that is the width of the edge length. That will take a little trigonometry. When defining the cylinders we need to set the $fa parameter to 1 to make each segment of the cylinder one degree or less.

edgeLength = 100;
maxWidth = 15;
maxThickness = 2;
sidecutRadius = 600;

module body(length, maxWidth, maxThickness)
{
    translate([-length/2, -maxWidth/2, 0])
        cube([length, maxWidth, maxThickness]);
}

module tip(edgeLength, maxWidth, maxThickness)
{
    translate([edgeLength/2, 0, 0])
        cylinder(h=maxThickness, r=maxWidth/2);
}

module tail(edgeLength, maxWidth, maxThickness)
{
    translate([-edgeLength/2, 0, 0])
        cylinder(h=maxThickness, r=maxWidth/2);
}

module sidecut(radius, edgeLength, thickness, maxWidth)
{
    hypotenuse = radius;
    opposite = edgeLength/2;
    offset = sqrt(hypotenuse*hypotenuse - opposite*opposite) + maxWidth/2;
    translate([0, offset, 0])
        cylinder(h = thickness, r = radius, $fa = 1);
    translate([0, -offset, 0])
        cylinder(h = thickness, r = radius, $fa = 1);
}

difference()
{
    union()
    {
        body(edgeLength, maxWidth, maxThickness);
        tip(edgeLength, maxWidth, maxThickness);
        tail(edgeLength, maxWidth, maxThickness);
    }
    sidecut(sidecutRadius, edgeLength, maxThickness, maxWidth);
}

Step 7: Make the Thickness Profile

To make the thickness profile we'll make a series of cubes that vary in height. Polyhedrons would probably work better here, but let's keep things simple. I chose a cosine function for the width. I don't know if that makes sense, but we can use any function here. We'll use the intersection function to keep only the spaces where the profile intersects with the ski.

edgeLength = 100;
maxWidth = 15;
minThickness = 1;
maxThickness = 2;
sidecutRadius = 600;

tipLength = maxWidth/2;
tailLength = maxWidth/2;
totalLength = edgeLength + tipLength + tailLength;

module body(length, maxWidth, maxThickness)
{
    translate([-length/2, -maxWidth/2, 0])
        cube([length, maxWidth, maxThickness]);
}

module tip(edgeLength, maxWidth, maxThickness)
{
    translate([edgeLength/2, 0, 0])
        cylinder(h=maxThickness, r=maxWidth/2);
}

module tail(edgeLength, maxWidth, maxThickness)
{
    translate([-edgeLength/2, 0, 0])
        cylinder(h=maxThickness, r=maxWidth/2);
}

module sidecut(radius, edgeLength, thickness, maxWidth)
{
    hypotenuse = radius;
    opposite = edgeLength/2;
    offset = sqrt(hypotenuse*hypotenuse - opposite*opposite) + maxWidth/2;
    translate([0, offset, 0])
        cylinder(h = thickness, r = radius, $fa = 1);
    translate([0, -offset, 0])
        cylinder(h = thickness, r = radius, $fa = 1);
}

module zProfile(length, minThickness, maxThickness, width)
{
    segments = 100;
    increment = length/segments;

    for(x = [0 : segments])
    {
        thickness = minThickness + (maxThickness - minThickness)*cos(90*(x / segments));

        echo(90*x/segments, thickness);
        translate([x*((length/2)/segments), -(width/2), 0])
            cube([increment, width, thickness]);

        translate([-x*((length/2)/segments), -(width/2), 0])
            cube([increment, width, thickness]);
    }
}

intersection()
{
    difference()
    {
        union()
        {
            body(edgeLength, maxWidth, maxThickness);
            tip(edgeLength, maxWidth, maxThickness);
            tail(edgeLength, maxWidth, maxThickness);
        }
        sidecut(sidecutRadius, edgeLength, maxThickness, maxWidth);
    }
    zProfile(totalLength, minThickness, maxThickness, maxWidth);
}

Drawing a GPX Track on a Map in Python

in Programming by evan power
Tags: , , ,

Programming in python is fun because you can do complex things relatively easy. Incredibly powerful libraries are just a 'pip install' away. Today I'll show you a quick example using a few such libraries to create a simple map with a GPS track drawn on it.

We will be using the GPXPY package to parse our gps track, and the Cartopy and Matplotlib packages do draw the underlying map and draw the GPS track on it.

We will be using Stamen Terrain map tiles.

First open a terminal emulator and type "pip install gpxpy matplotlib cartopy" You may need to add 'sudo' to the beginning of the 'pip install' command.

Run the code below. Set the trackFile variable so that it contains a path that points to the gpx file that you want to show on the map.

import gpxpy
import array
import matplotlib.pyplot as pyplot
import cartopy.crs as crs
from cartopy.io.img_tiles import StamenTerrain

# Open and parse your GPX file.
trackFile = './pemberton.gpx'
track = gpxpy.parse(open(trackFile))
# Make an iterator over the points in the GPS track.
trackPoints = track.walk()
# Make empty arrays to put the latitudes an longitudes in.
lats = array.array('f')
lons = array.array('f')
# Iterate over all points an populate the latitude and longitude arrays.
for p in trackPoints:
    lats.append(float(p[0].latitude))
    lons.append(float(p[0].longitude))
# Get the minimum and maximum latitudes and longitudes from the GPS track.
bounds = track.get_bounds()
# Use the Stamen Terrain tile server
tiler = StamenTerrain()
fig = pyplot.figure(figsize=(5,5))
axes = pyplot.axes(projection=tiler.crs)
axes.set_extent([bounds.min_longitude, bounds.max_longitude,
                bounds.min_latitude, bounds.max_latitude])
zoom = 14
axes.add_image(tiler, zoom)

pyplot.plot(lons, lats, 'm-', transform=crs.Geodetic(), linewidth=2)
pyplot.savefig('map.png')

The output looks like this:

map

Easy peasy.

Thanks to th developers of Python, GPXPY, Cartopy, Matplotlib for their work, and thanks to the folks at Stamen for making their map tiles pretty an easy to use.

Mountain Bike Map T-Shirt

in Craftwerks by evan power
Tags: , , , ,

shirt graphic
I decided to make this shirt after playing with an intriguing piece of software called OSMnx. OSMnx is a Python module that retrieves road and trail networks from Open Street Map, and can make high quality images of those networks.

I'm no graphic designer but I love t-shirts and I sometimes find myself thinking "The Ramones are great, and I like wearing their logo on my shirt, but what else am I enthusiastic about that can be easily put on a t-shirt?". I absolutely love the trails in Squamish, particularly Entrails and the trails that branch off of it. Many of Squamish's mountain bike trails are mapped in Open Street Map so with OSMnx it was easy and fun to put bike trails in a form that can be screen-printed onto a t-shirt.

To generate an image of the Squamish road and trial network I entered the following lines into a Python interactive interpreter:

import osmnx as ox  
g = ox.graph_from_place('Squamish, Canada', network_type='bike', simplify=False)  
gproj = ox.project_graph(g)  
gsimp = ox.simplify_graph(gproj)  
ox.plot_graph(gsimp, fig_height=30, node_size=0, edge_linewidth=0.5, save=True, file_format='svg', filename='squamishbike')

In my case this created a file called 'squamishbike.svg' in the 'images' folder in my home folder. The location of the output will vary depending on your operating system.

I used Inkscape (an excellent free and open source vector graphics editor) to crop the image and highlight a few permutations of my favorite Entrails loop; and I sent the resulting image off to a t-shirt printing company.

Thanks to all of the trail builders and maintainers, and code builders and maintainers, and Open Street Map contributors who made this project possible!

Dorkyboard Beginnings

in Dorkyboard by evan power
Tags: , , ,

I don't know much about mechanical keyboards, but I decided to build one. I was inspired by open hardware projects like the HacKeyboard and the Keyboardio 01, but I wanted something less flashy with a layout more similar to what I am used to using. I like the ISO style enter key and backslash key, but I prefer the ANSI style left shift key. I also wanted a full number pad and 12 function keys. At that point I thought I might as well include the 'Home', 'End', etc. keys as well in the interest of keeping things familiar. I saw no need for backlights or any LEDs other than the usual capslock, numlock, and scroll-lock LEDs. (Even those seemed fairly unnecessary, but I opted to stay close to conventionality in hopes that the project may be of more use to others that way.)

I chose the Atmel Atmega3U4 microcontroller so that I could leverage the wealth of existing free and open source keyboard firmware available for that device.

The PCB is designed, but untested so far. The hardware is open source and licensed under the CERN Open Hardware License. Schematics and PCB files in KiCad format can be downloaded via GitHub.