Posts for year 2015

We’ve got a new pool

One of the features we looked for when we bought our house was an in-ground pool. As the kids have grown up and their confidence has increased, we've spent more and more time using the pool - last swimming season went from September 2014 to April 2015, with almost daily swims that stretched to being 2-3 hours long over the weekends in summer.

One aspect of our pool that we really did not like was the slate tiles around the side. While I'm sure they looked beautiful when installed, by the time we moved in (2007) they were looking tired and starting to shed. By the middle of this year we'd actually lost most of them, leaving some rather ugly concrete. The underwater surface was also starting to loosen, so the Barracuda would frequently pull bits up and leave them in the skimmer box.

We had to get it fixed.

It's been surprisingly difficult to get people to come and quote on a renovation job, and of those who did quote, some of them were more than the cost of a new pool. That didn't seem quite right. Eventually, J found Mark and his team at Sunseeker Pools, who not only quoted within our range, but helped us pick tiles and stone as well. He was also able to start within the next 3 weeks and estimated that it would take about 3 weeks to complete the whole job.

We had been umming and aahing about whether to get the pavers removed and replaced with tiles, thinking that would significantly add to the cost, however Mark's quote included that from the start so we were very happy. During construction we talked with him about our retaining wall, and he offered to build up the edge of the pool with a reinforced besser brick layer. This added about another 10% to the cost, but was very well worth it.

After a week's delay caused by the Brisbane Flu work started in earnest, with emptying the pool. That took about 24 hours, and showed just how bad a pool can get when you leave it unloved for a few months:

/images/2015/11/20150831_110501_IMG_2482.jpg

The next stage was to remove the pavers (we're planning on using them for some followup work in the rest of our outdoor area), jackhammer off the slate and the existing pool surface.

A day's worth of putting in formwork was quickly followed by the concrete truck (and pump), which delivered 3 cubic metres in about 45 minutes. That volume needed 3 days to cure, and then the tiles started going on. Mark and his team were very careful and precise in laying and cutting them all out, and then grouted them all at once. We went away to Ballandean (in the heart of Queensland's wine country) for a few days with friends, and when we came back the new pebblecrete had been laid, the waterline tiles and white river stone splashback had been installed and we just needed the acid wash.

The day after we got back, the acid wash subcontractor arrived and got to work - it took about 30 minutes to complete his task, and then we filled the pool. We've got about 40kL, and using town water from the hosepipe it took almost 24 hours to get up to the right level and it was time to put in chemicals. The chemical balance of Brisbane's town water is generally pretty close to what we need for a pool, so the only major concern that our pool shop had was that we add a lot of calcium. Apparently new pools can leach calcium, and it's nigh-on impossible to get back.

And with that, we have a new pool which we've been enjoying almost every day. It's nice to look at from the kitchen bench, too!

Thankyou very, very much to Mark and his team from Sunseeker Pools, and to Wayne and the gang at pool shop.

If you've got a suitable media-enabled browser, here's a video of the pool now - we really love the shimmery sparkly effect.




A recipe for running your pkg.depotd(1) server with SSL and Apache 2.4

As part of my contribution to the darktable community, I provide the Solaris packages needed to run the application via a locally-hosted pkg repo. You can

# pkg set-publisher -g https://www.jmcpdotcom.com/packages/packages JMCP

and then install the bits very easily.

What was a little non-obvious (to me at least) was how to get the pkg.depotd process to only listen on a secured port.

If you look at the SMF properties for svc:/application/pkg/server, you will observe these two likely-looking candidates:

pkg/ssl_key_file
pkg/ssl_cert_file

They are not, however, what you need. Running pkg/server outside of svc:/application/pkg/depot is actually restricted to plain http because the backing framework here is CherryPy – and the version which pkg.depotd uses apparently has some issues with https.

Hmmph.

So I asked a few colleagues who have worked on our packaging system for assistance. Liane pointed me at https://docs.oracle.com/cd/E23824_01/html/E21803/apache-config.html, which was an excellent place to start. I did, however, need some handholding from Tim and eventually wound up with the following configuration which works with Apache v2.4.

Firstly, /etc/apache2/2.4/httpd.conf:

  • Follow the Apache docs for enabling ssl

  • Once you’ve settled on the port to run your pkg.depotd on, add a ReWriteRule like this:

RewriteEngine On
RewriteRule ^/packages$ https://%{SERVER_NAME}:83 [R,L]

Secondly, svc:/application/pkg/depot:default:

# svccfg -s application/pkg/depot:default
svc:/application/pkg/depot:default> setprop config/port = 83
svc:/application/pkg/depot:default> setprop config/ssl_ca_cert_file = "/path/to/your/SSL CA cert bundle"
svc:/application/pkg/depot:default> setprop config/ssl_cert_file = "/path/to/your/SSL cert file"
svc:/application/pkg/depot:default> setprop config/ssl_key_file = "/path/to/your/SSL key file"
svc:/application/pkg/depot:default> refresh
svc:/application/pkg/depot:default> quit

Thirdly, svc:/application/pkg/server:

# svccfg -s application/pkg/server add packages
# svccfg -s application/pkg/server:packages addpg pkg application
# svccfg -s application/pkg/server:packages
svc:/application/pkg/server:packages>addprop pkg/proxy_base = astring: "https://your.ssl.url.here/packages"
svc:/application/pkg/server:packages>addprop pkg/inst_root = astring: "/path/to/your/REPO/on/disk"
svc:/application/pkg/server:packages>addprop pkg/readonly = boolean: true
svc:/application/pkg/server:packages>addprop pkg/log_access = astring: "/path/to/access/logfile"
svc:/application/pkg/server:packages>addprop pkg/log_errors = astring: "/path/to/error/logfile"
svc:/application/pkg/server:packages>addprop pkg/standalone = boolean: false
svc:/application/pkg/server:packages>refresh
svc:/application/pkg/server:packages>quit

Once you’ve got those steps completed, it’s time to enable the services and add the publisher:

# svcadm enable pkg/server:packages pkg/depot:default
# pkg set-publisher -g https://your.ssl.url.here/packages/packages yourpublishername

Pretty simple (now that you know how).




Darktable 1.6.8 for Solaris 11.3 (beta)

As promised, I've built the latest release of Darktable (1.6.8) for Solaris 11.3 Beta. You can get the release details at darktable 1.6.8 .

I've also (finally) set up my own pkg(5) server, so you can simply utter

$ sudo pkg set-publisher -g https://www.jmcpdotcom.com:10001 JMCP
$ sudo pkg install 'pkg://JMCP/JMCPdarktable*'

If you'd just like a gzipped p5a, please use this link instead .

Following my previous post I've ensured that the bits were built with -msse4.2.




Fixing up GPX ride data with lxml.etree

Last week I went for a ride on a rather grey day. The route was one of my usuals (40.2km in to the Goodwill Bridge), and I shaved a minute or two off the ride time. I was mightily disappointed to find that both ipBike and Strava reckoned I had ridden a bare 100m. This, despite the ipBike summary field claiming "40.270km with 347m climb in 1:43:41".

I had an incident like this happen to me earlier this year and was unable to fix it using SNAP, so I figured it was time to bite the bullet and fix the recorded file : particularly since the temperature, heart rate, cadence all appeared to be correctly recorded.

My first pass attempted to make use of Tomo Krajina's gpxpy, which was fine until I realised that that library cannot handle the TrackPointExtensions that Garmin defined.

I then tried to make headway using minidom, but got myself tied in knots trying to create new document nodes. I'm sure I missed something quite obvious there but I'm not really worried. Note in passing : Lode Nachtergaele's http://castfortwo.blogspot.com.au/2014/06/parsing-strava-gpx-file-with-python.html was really useful, and helped with my final attempt.

My final (and successful) attempt uses lxml.etree to pull out the info I need, skip a few points (since the rides had different elapsed times, but somewhat dubious) and then create a new GPX document with the munged data points.

While I've now ot a close-enough fixed up file, I'm down about 2km on the ride total, and up about 30m on the climbing total (according to Strava). I am quite happy with the results overall, though more than willing to accept that my code (below) is rather fugly. Good thing I'm not integrating this to a project gate!

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/usr/bin/python
#
# Copyright (c) 2015, James C. McPherson. All Rights Reserved
#

from datetime import datetime, time, date
from copy import deepcopy
from lxml import etree as ET

NSMAP = {
    'gpxx': 'http://www.garmin.com/xmlschemas/GpxExtensions/v3',
    None: 'http://www.topografix.com/GPX/1/1',
    'gpxtpx': 'http://www.garmin.com/xmlschemas/TrackPointExtension/v1',
    'xsi': 'http://www.w3.org/2001/XMLSchema-instance'
}

schemaLocation = "http://www.topografix.com/GPX/1/1"
schemaLocation += "http://www.topografix.com/GPX/1/1/gpx.xsd"
schemaLocation += "http://www.garmin.com/xmlschemas/GpxExtensions/v3"
schemaLocation += "http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd"
schemaLocation += "http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
schemaLocation += "http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd"
schemaLocation += "http://www.garmin.com/xmlschemas/GpxExtensions/v3"
schemaLocation += "http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd"
schemaLocation += "http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
schemaLocation += "http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd"

gpxns = "{http://www.topografix.com/GPX/1/1}"
extns = "{http://www.garmin.com/xmlschemas/TrackPointExtension/v1}"

reftracks = []
failtracks = []


def parseTrack(trk, stime, keep=None):
    tracks = {}
    for s in trk.findall("%strkseg" % gpxns):
        for p in s.findall("%strkpt" % gpxns):
            # latitude and longitude are attributes of the trkpt node
            # but elevation is a child node in its own right
            el = {}
            el['lat'] = p.get("lat")
            el['lon'] = p.get("lon")
            el['ele'] = p.find("%sele" % gpxns).text
            if keep:
                el['trkpt'] = deepcopy(p)
                rfc3339 = p.find("%stime" % gpxns).text
                try:
                    t = datetime.strptime(rfc3339, '%Y-%m-%dT%H:%M:%S.%fZ')
                except ValueError:
                    t = datetime.strptime(rfc3339, '%Y-%m-%dT%H:%M:%SZ')
                    sec_t = int(t.strftime("%s"))
                    el['time'] = rfc3339
                    tracks[sec_t - stime] = el
    return tracks

##
# Main routine starts here.
##

rf1 = open("goodfile")
ff1 = open("dodgyfile")
rf = ET.parse(rf1)
ff = ET.parse(ff1)

rstimestr = rf.getroot().find("%smetadata" % gpxns).find("%stime" % gpxns).text
rstime = int(datetime.strptime(rstimestr, "%Y-%m-%dT%H:%M:%SZ").strftime("%s"))
fstimestr = ff.getroot().find("%smetadata" % gpxns).find("%stime" % gpxns).text
fstime = int(datetime.strptime(fstimestr, "%Y-%m-%dT%H:%M:%SZ").strftime("%s"))

for track in rf.findall("%strk" % gpxns):
    reftracks.append(parseTrack(track, rstime, False))

for track in ff.findall("%strk" % gpxns):
    failtracks.append(parseTrack(track, fstime, True))

# Now we need to fix node attributes in failtracks
# We're being lazy, so assume only one key for now

rpts = len(reftracks[0].keys())
fpts = len(failtracks[0].keys())

if fpts > rpts:
    skipn = fpts % rpts
else:
    skipn = rpts % fpts

# create a "fixed" track

ntrack = ET.Element("trk")
ntrkname = ET.SubElement(ntrack, "name")

ntrkname.text = ff.find("%strk" % gpxns).find("%sname" % gpxns).text

ntseg = ET.SubElement(ntrack, "trkseg")

for (n, v) in enumerate(failtracks[0]):
    if n % skipn is 0:
        continue
    badn = failtracks[0][n]['trkpt']
    goodn = reftracks[0][n]
    badn.set('lat', goodn['lat'])
    badn.set('lon', goodn['lon'])
    badne = badn.find("%sele" % gpxns)
    badne.text = goodn['ele']
    extn = badn.find("%sextensions" % gpxns)
    badn.append(extn)
    ntseg.append(badn)

# write a new file....
newwf = open("fixedp.gpx", "w")
gpx = ET.Element("gpx", nsmap=NSMAP)
gpx.set("creator", "James C. McPherson")
gpx.set("version", "1.1")
gpx.set("{http://www.w3.org/2001/XMLSchema-instance}schemaLocation",
        schemaLocation)
gpx.append(ntrack)
et = ET.ElementTree(gpx)
et.write(newf)
newf.close()