Andrew Cooke | Contents | RSS | Previous


Welcome to my blog, which was once a mailing list of the same name and is still generated by mail. Please reply via the "comment" links.

Always interested in offers/projects/new ideas. Eclectic experience in fields like: numerical computing; Python web; Java enterprise; functional languages; GPGPU; SQL databases; etc. Based in Santiago, Chile; telecommute worldwide. CV; email.

© 2006-2015 Andrew Cooke (site) / post authors (content).

[Computing, Music] Raspberry Pi Media (Audio) Streamer

From: andrew cooke <andrew@...>

Date: Sat, 7 May 2022 17:36:36 -0400

I have a Raspberry Pi that I want to take music from a HDD and send it
to a USB DAC.  I want to be able to control this remotely from my
phone.  It should look good and "just work".

I have tried a pile of things in the last week.  The best (by far) has
been volumio.  After flashing the image and booting you may need to
enable ssh (via http://address.of.volumio/dev) and then you can modify
fstab and mount the music at /mnt/INTERNAL

You can also modify
/volumio/app/plugins/music_service/mpd/mpd.conf.tmpl to enable
replaygain (and then save settigns from the gui to regenerate

Various other things learnt:

 - mopidy is an MPD rewrite in Python (ish).  Unfortunately it's not
   super-well supported and the clients are buggy (even when you
   finally understand what it is and that you need to install
   mopidy-local).  The MPD plugin doesn't make much sense.

 - MPD clients for android are not great.  MpDroid seems to be no
   longer maintained and MALP is flakey.

 - I tried moode a while back and that was OK (but volumio seems more
   responsive and easier to use).


Permalink | Comment on this post

Previous Entries

For comments, see relevant pages (permalinks).

[Computing] Amazing Hack To Embed DSL In Python

From: andrew cooke <andrew@...>

Date: Mon, 31 Jan 2022 17:35:03 -0300

This library - - extends Python's
syntax (and semantics) by calling a preprocessor triggered by the line

#coding: pythonql

at the top of the file.

The meaning of "#coding" is defined here - - and was an early way of
defining source encodings (eg UTF8) for Python code.



[Bike] Ruta Del Condor (El Alfalfal)

From: andrew cooke <andrew@...>

Date: Sun, 23 Jan 2022 13:12:33 -0300

Was up in El Alfalfal today and noticed two riders disappear into the
village.  The guards at the hydroelectric plant said that there's a
path up to the trail above.  I didn't find it on a quick look, but
when I got home checked online maps.  Google is useless, but
OpenStreetMap clearly shows the path!  It's along the side of the
football pitch in the village (the NE side).

Maybe this will help someone searching for how to access the route.

And if not, the road ride up + back is pretty nice anyway!



[Bike] Estimating Power On Climbs

From: andrew cooke <andrew@...>

Date: Thu, 20 Jan 2022 19:31:49 -0300

I was slowly pedaling up a hill today, staring at my cycle computer
to avoid looking at the endless climb in front of me, when I realized
that you can easily estimate your power from the grade and speed

Me + my bike weight about 72kg.  Actually a bit more (I need to lose a
kilo or two), but 72 turns out to be a nice number in what follows.

72kg ascending vertically at 1m/s requires a power of 720W (because
g=10, more or less).

A speed of 1km/h is 1000m/3600s or 1/3.6 m/s.

Small angle approx means that if you are riding at a speed S(km/h) on
a gradient of G then your vertical speed is SG/100 km/h or SG/360 m/s.

So the power required (at 72kg) is SGx720/360 W or 2SG W.

For example, at 10km/h on a 5% grade your power is 2x10x5 = 100W.

Now this is incorrect if you're moving fast enough, or riding up a
shallow enough slope, that air resistance is important.  But for
climbing it's fine.

Obviously if you+bike weighs more or less than 72kg you need to
adjust accordingly.



[Computing] Applying Azure B2C Authentication To Function Apps

From: andrew cooke <andrew@...>

Date: Mon, 17 Jan 2022 20:09:08 -0300

Solving this has taken a long time (months) going back + forth with
Microsoft support.  Since I was stuck because I couldn't find this
info on the web I thought I should put something out there.

This is not a general explanation of how to enable B2C authentication
between a SPA (Single Page Application - JavaScript in a web page) and
a Function App.  Instead, what I will focus on is converting the
project at to
work with a Function App that you have deployed.  If you can get that
working, you can carry everything across to your application.

So this is what you need to do:

1 - Clone
and get it working as is.  Understand it.

2 - Deploy your application that uses a Function App (currently
without authentication), make sure it works, and note what the result
is if you call your Function App directly from a browser (in my case,
for example, I had a Function App that provided an access token to
Azure Maps - if I called it by hand, I got a token).

3 - Configure B2C.  There are three parts that need to be connected:

  3a - Define a User Flow.  Go to B2C in the Portal and select "User
  Flows" (left menu) then "New user flow" (top bar), "Sign up and sign
  in" (option box), "Recommended" (popup) and "Create" (button).
  Provide a name (eg "demoflow"), select "Email Signup" (checkbox) and
  "Create" (button).  Leave everything else as defaults.

  3b - Register the Function App.  Go to B2C in the Portal and select
  "App registrations" (left menu), "New registration" (top bar).
  Provide a name (eg "myfnapp"), set "Select a platform" as "Web" then
  select "Register".  Leave everything else as defaults.

  3c - Register the SPA.  Go to B2C in the Portal and select "App
  registrations" (left menu), "New registration" (top bar).  Provide a
  name (eg "msdemo"), set "Select a platform" as "SPA" with a redirect
  URI of "http://localhost:6420" then select "Register".  Leave
  everything else as defaults.

  3d - Connect things.  Go to B2C in the Portal and select "App
  registrations" (left menu), select "myfnapp", select "Expose an API"
  (left menu), select "Add a scope", save the default URI (popup on
  right), provide a name for the scope (eg "access") and provide some
  text for display (anything will do), then select "Add Scope"

  Go to B2C in the Portal and select "App registrations" (left menu),
  select "msdemo", select "Add a permission", then "My APIs" (top
  menu), then "myfnapp", select "access" and then "Add permissions".
  In the "Configured permissions" page (displayed after the above),
  select "Grant admin consent" and "Yes".

4 - Enable authentication for the Function App.  Find the Function App
in the Portal, select "Authentication" (left menu), select "Add
identity provider", select "Microsoft", select "Pick an existing app
registration in this directory" and then select "myfnapp".  Choose
HTTP 401 for "Unauthenticated requests" and select "Add".

CORS also needs to be enabled for the Function App.  Select "CORS"
(left menu) and add "http://localhost:6420" then select "Save".

If you call your Function App from a browser now it will give an

5 - Modify the MS demo.  All the above is standard, really; it's this
section that is critical.  The files that need altering are all in the
App directory and are all pure data structures (no code - the app is
nicely structured).

  5a - authConfig.js

  Change "clientID" to the "Application (client) ID" for the "msdemo"
  registration you defined above (it's displayed in the Portal in the
  App registration for that name).

  5b - policies.js

  Remove the "editProfile" entries because we're not using them.

  Change the "authorityDomain" to be "" (more exactly,
  use the domain in the "Issuer URL" if you go to "Authentication" for
  the Function App and then select "Edit" for the provider).

  Change "fabrikamb2c" in "authentication" to match whatever you call
  your B2C.

  Change "B2C_1_susi_reset_v2" in the "authority" to be the user flow
  defined above ("B2C_1_demoflow")

  5c - apiConfig.js

  Change "b2cScopes" to be the default URI created for the "access"
  scope above (go to B2C, "App registrations", "myfnapp", "Expose an
  API" and copy the URI)

  Change "webApi" to be the URL of your Function App.

6 - Run the demo.

  > npm install
  > npm start

Open http://localhost:6420 and enable the dev console so you can see
network requests.  Sign-in and call the API.  In a perfect world, it
should work (should make successful calls; the web app itself won't do
much because the function being called is not what is expected).  You
should be able to see the expected return value from the API in the
network panel.

If you get an HTTP 401 error when calling the API then you may be able
to view the actual error from the MS auth system by doing the
following (this is deep magic, but it works):

Select the Function App in the Portal.  Select "New Support Request"
(left menu) (don't worry, we are not going to actually submit a
request).  Enter "authentication error" as "Summary", select
"Authentication and Authorization" as "Problem Type", select "Azure
App Service built-in authentication" as "Problem subtype".  Select
"Next:Solutions".  When something is displayed, scroll down and see if
there are any errors.

If you don't see any errors, wait a little and try again - it seems to
take 30min or so for the errors to propagate.

The critical step in all the above was learning how to see the errors.
From those I managed to infer setting the authorityDomain in 5b above.

Have fun,


[Bike] Gearing On The Back Of An Envelope

From: andrew cooke <andrew@...>

Date: Mon, 4 Oct 2021 21:28:20 -0300


If you + bike weigh 100kg and you can put out a steady 100W then you can
ascend vertically at 0.1m/s (work against gravity if g=10m/s^2).

On a 10% incline (steep, but possible where I ride in the Andes foothills)
that means you can move forwards at 1m/s.

The circumference of a 700C wheel is about 2m and the slowest comfortable
cadence while seated is around 60rpm.  So with 1:1 gearing that's 2m/s.

So you need 1:2 gearing for the climb.

That kind of gearing is pushing the low end of available MTB gears.  In other
words, pretty much no-one has gearing that low.  Pretty much all entry-level
riders would have real problems climbing a 10% grade for any distance.


Air resistance increases as v^3.  That has a sharp "bend" - either it's not
important or it's a brick wall.  So basically everyone (whatever their power
output) is limited to a similar top speed.  From experience it's roughly

Faster / steeper than that you need to get seriously aero and pedalling
efficiently isn't really an option.

You can probably manage 100rpm if you have to, in your top gear, all out.
With a 2m circumference wheel that's 200m/min.

60km/h is 1km/min.  So you need 5:1 gearing for the descent (to go from
200m/min to 1,000 m/min).

This is consistent with the top end of traditional "race" gearing (maybe 53
teeth at the front and 11 at the rear).

Gear Range

The above implies a gear range from 1:2 to 5:1.  That's a factor of 10, or

In practice, pro riders can put out around 4x the power / weight used above.
Compared to inexperienced riders they are absolute monsters.  That shifts the
lower gears needed up by a factor of 4.  So instead of a 1,000% range they
need a 250% range, which is well within typical "race bike" gearing.

Non-pro riders (and people with loaded touring bikes) that have the technical
understanding compromise at both ends.  At the top, 4:1 is generally enough,
and at the bottom they can live with maybe 1:1.5.  That gives a 600% range
which is possile with a Rohloff IG hub, or a frankenstien mix of road and MTB
components (as on my own road bike).

Currently fashionable 1x road / gravel can reach maybe 500% range.  Exactly
where that is "centred" depends on the front ring - there will likely be
significant compromise at both the high and low ends.



[Computing] Okular and Postscript in OpenSuse

From: andrew cooke <andrew@...>

Date: Thu, 9 Sep 2021 10:13:20 -0300

Wow.  PS is so old-fashioned now that support in okular is in a separate
package (okular-spectre) that is not installed by default.



[Computing] Fail2Ban on OpenSuse Leap 15.3 (NFTables)

From: andrew cooke <andrew@...>

Date: Fri, 30 Jul 2021 09:58:50 -0400

Couldn't find this documented anywhere, but it works just fine.

  localhost:/etc/fail2ban # cat jail.local

  bantime = 1h
  maxretry = 3

  enabled = true
  backend = systemd
  banaction = nftables

  localhost:/etc/fail2ban # cat fail2ban.local

  loglevel = INFO

Typical log output:

  localhost:/var/log # tail fail2ban.log
  2021-07-30 08:19:02,184 fail2ban.filter         [16653]: INFO    [sshd] Found - 2021-07-30 08:19:01
  2021-07-30 08:19:02,249 fail2ban.actions        [16653]: NOTICE  [sshd] Ban
  2021-07-30 08:28:00,927 fail2ban.actions        [16653]: NOTICE  [sshd] Unban
  2021-07-30 09:05:49,815 fail2ban.actions        [16653]: NOTICE  [sshd] Unban
  2021-07-30 09:19:02,048 fail2ban.actions        [16653]: NOTICE  [sshd] Unban
  2021-07-30 09:23:52,935 fail2ban.filter         [16653]: INFO    [sshd] Found - 2021-07-30 09:23:52
  2021-07-30 09:23:54,934 fail2ban.filter         [16653]: INFO    [sshd] Found - 2021-07-30 09:23:54
  2021-07-30 09:24:52,933 fail2ban.filter         [16653]: INFO    [sshd] Found - 2021-07-30 09:24:52
  2021-07-30 09:24:53,125 fail2ban.actions        [16653]: NOTICE  [sshd] Ban
  2021-07-30 09:24:54,434 fail2ban.filter         [16653]: INFO    [sshd] Found - 2021-07-30 09:24:54

All under systemd control.



[Cycling, Computing] Power Calculation and Brakes

From: andrew cooke <andrew@...>

Date: Sat, 24 Apr 2021 16:20:34 -0400

It's common (eg Strava) to estimate a cyclist's power output from their GPS
and elevation (barometric) data.

The basic idea is fairly simple: we can calculate the power required for the
bicycle to behave as recorded at each point in time using mass / gravity /
height, CdA / speed, and rolling resistance / speed.

This ignores wind, which makes the effective speed for wind resistance (CdA)
different from the measured speed, and so introduces errors.

Also ignored, but not commonly discussed, is braking.

If the cyclist does not use the brakes then all we have discussed so far is

If the cyclist brakes *and* pedals then we will underestimate their power
output because they are expending additional energy we do not "see" in the
data (assuming that there is no power meter data).

If the cyclist brakes when not pedalling then we *should* (if we have
sufficient resolution, perfect parameters, etc) see a negative input power.

In practice, when you estimate power, you do see negative spikes.  I had
assumed that these were noise, but the above suggests that it is also
reasonable to interpret them as braking.

In conclusion, then, braking may be visible in the output from modelling and
is not inherently a source of uncertainty *unless* the cyclist brakes when
also pedalling.  That exception is more likely than it sounds since "when
also" does not, in practice, mean (only) concurrently, but also at any time
within the "time step" used in the calculation (eg 10s).



[Hardware, Computing] Amazing Pockit Computer

From: andrew cooke <andrew@...>

Date: Thu, 18 Mar 2021 21:02:15 -0300

I am not sure I really believe this is real.

Watch the top video in the timeline.




From: andrew cooke <andrew@...>

Date: Thu, 18 Mar 2021 04:59:59 -0300

I was buliied recently.  I am a 53 year old, white, male, successful
professional.  It was a bit weird.

I don't want to give details, because they would identify the people involved,
which would be complicated socially.  But I was harassed by email over a
couple of weeks by two people, who also contacted my partner, Paulina.

At first it just seemed like odd behaviour, but when I asked them to stop, and
they did not, I recognised the similarities with episodes in childhood.  Like
then I was confused but resistant (so you might call it an attempt at
bullying, rather than the real thing, because I never felt intimidated).  Like
then, the people involved were - I think (I can find no other explanation) -
resentful that I have a better life than they do.

There's really no word other than weird.  It was damn weird.

They apologised later and we haven't communicated since.



How I Am - 3 Years Post Accident, 8+ Years With MS

From: andrew cooke <andrew@...>

Date: Fri, 26 Feb 2021 21:28:47 -0300


My back was fixed by yoga and a harder mattress.  The legs (no idea what was
wrong with the right) have now healed enough that I am generally able to sleep
on my side (although not always after exercise - sometimes it is more
comfortable to switch to the softer sofa).  There can be some discomfort (not
pain) on the sides of the legs when walking and the tendon at the back of my
left leg (which interfered with the rod, I think) can get a little sore.

Recovering fitness post-accident has taken longer than expected.  Covid didn't
help - there was a time when we were allowed out for just a few hours twice a
week (time spent shopping, mainly).  I am now, almost three years on, capable
of climbing to Yerba Loca, but not to Farellones.  My power and endurance are
still not as they were - I suspect it will be another 6 months work.  On the
other hand, Yoga on days off the bike seems to have improved my core.

MS has been pretty steady for years.  In fact, a few months ago I was feeling
like I had 6 months almost symptom-free.  Unfortunately that ended with
intermittent "white noise" in my right leg, the muscle above the knee.  This
has been coming and going a month or more now - it is disturbing, but seems to
have no effect on strength or balance.

In a few weeks I may be due for the Covid vaccine.  This will likely be
Sinovac and my doctor is a little worried (something to do with the underlying
carrier and my borked immune system, not the Covid part).  We will see what

We've spent, what, maybe a year pretty much isolated.  Paulina is working from
home.  We go out only to buy supplies or exercise.

Hello to future self.  Good luck.