Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.
Category: Software
General-purpose computers doing something specific
This may not be a LibreOffice problem, but that’s where it shows up: the font selection dialog won’t display fonts with nonstandard Style names. There is, of course, no documentation anywhere (that I can find, anyway) on what Style names are permitted, so you discover this only when a font style that’s properly installed and accessible by other programs (like, say, Inkscape or Scribus) doesn’t render properly and doesn’t appear in the list.
In Xubuntu 12.10, LibreOffice 3.6.2.2 can’t handle the American Typewriter font style called Medium, which is what I’ve been using for the return address field on my (very few, these days) mail envelopes. Over the years, various versions of OpenOffice and LibreOffice have alternately accepted and rejected the Medium style, so this isn’t exactly a regression. It is, however, Yet Another Annoyance.
The solution, hinted at in that thread, involves using FontForge to rename the offending Style to, say, Regular, then saving the font. It’s actually the Weight property, hidden in Element → Font Info → PS Names tab. In this case, I changed the word “Medium” in the Fontname, Name for Humans, and Weight fields to “Regular”, which also updates the values in the TTF Names tab.
I save the modified font files in ~/.local/share/fonts using TrueType format, just to be sure I don’t confuse them with the original Postscript version in /usr/share/fonts/custom, delete the original, and then run fc-cache -v -f to update the caches. This surely isn’t the cleanest way to make it happen and almost certainly isn’t allowed by the Adobe EULA I agreed to, back when I actually bought the fonts, but so it goes.
It turns out that if you put convenient symlinks in your directories, then use them to build a LibreOffice document, LO will cheerfully put those paths into the graphic file links inside its XML files. That will produce horrible breakage on a new system without those links. We’ve come to the conclusion that the only way to keep LO happy is to create a Pictures directory in whatever directory holds the document file, then put all of the document’s image files into that directory, and make sure LO stores relative paths. Of course, this leaves us with the prospect of updating a whole bunch of existing (and, alas, horribly broken) documents by hand, which is unappealing. My previous solution worked for a single file, but now it’s time for some scripting…
This would probably be easier in Python, but Bash works fine after you get the quoting straightened out. This script builds several other scripts that actually do the heavy lifting, because that way you can inspect the scripts before running them to verify that you’re not about to make a bad situation much, much worse. I recommend copying the presentations into another directory, running this script, check the output scripts, run them by hand, and then copy the fixed files and the Pictures directory back where they belong.
You must tweak the actual paths to the pictures to match your situation; for these documents, one simple change sufficed for all the image files. Those paths are not variables, because I can barely keep the quoting straight without adding another layer of indirection. Make sure all the paths match up, verify the scripts before you run them, and don’t trust anything you see.
CAUTION: It’s highly likely that the multiple levels of character escaping required to make these listings appear correctly on the screen will produce incorrect results when copied-and-pasted. You can download the script file as FixGraphics.sh.odt, which is a bare-ASCII TXT file (which you must rename to eliminate the ODT extension, then make executable as a shell script), to see how it compares.
The main FixGraphics.sh script, with some key lines highlighted:
#!/bin/bash
echo "Extract list of images from all ODP files"
rm images.txt
for f in *odp
do
unzip -p "$f" content.xml | sed 's/></>\n</g' | grep Cameras | cut -d \" -f 2 | sort -u >> images.txt
done
echo "Make source file name list"
# strip off leading relative pathing, set actual absolute path, un-quote blanks and special characters, add quotes
sed 's/..\/..\/..\/../\/mnt/' images.txt | sed 's/%20/ /g' | sed 's/&/\&/g' | sed 's/^.*/\"&\"/' > source.lst
echo "Make target file name list"
# set relative to current directory
sed 's/\/mnt\/bulkdata\/Cameras\/MCWN/\.\/Pictures/' source.lst > target.lst
echo "Make target directory list"
# must add trailing quote stripped by dirname
rm dirs.lst
cat target.lst | while read tline ; do
tdir=`dirname "$tline"`
echo ${tdir}\"
done > dirs.lst
echo "Create target directory structure script"
rm mkdirs.sh
sort -u dirs.lst | while read dline ; do
echo mkdir --parents ${dline}
done > mkdirs.sh
chmod u+x mkdirs.sh
echo "Create image file copy script"
rm cpjpgs.sh
cat dirs.lst | while read dline ; do
echo cp -n -t ${dline}
done > cptemp.txt
paste cptemp.txt source.lst > cpjpgs.sh
chmod u+x cpjpgs.sh
echo "Create ODP fixup script"
echo "for f in *odp ; do" > fixodp.sh
echo "unzip -p \"\$f\" content.xml > raw.xml" >> fixodp.sh
echo "sed 's/..\/..\/..\/..\/bulkdata\/Cameras\/MCWN/\.\.\/Pictures/g' raw.xml > content.xml" >> fixodp.sh
echo "zip \"\$f\" content.xml" >> fixodp.sh
echo "done" >> fixodp.sh
echo "rm raw.xml content.xml" >> fixodp.sh
chmod u+x fixodp.sh
Run mkdirs.sh, cpjpgs.sh, and fixodp.sh: then it Just Works.
Some of the tricky parts:
The content.xml file may be stored in unformatted mode, with everything mushed together into one huge line. To make it readable and parse-able, insert a newline between each pair of adjoining angle brackets:
sed 's/></>\n</g'
This burst of line noise un-escapes the file name from the way LO stores it internally. Note that the middle sed command really does have the literal escape sequence ampersand-amp-semicolon in it and the ampersand in the last one is the sed-ism for “the whole matching string”:
sed 's/%20/ /g' | sed 's/&/\&/g' | sed 's/^.*/\"&\"/'
The difference between these two sed strings indicates the actual relative path to the Pictures subdirectory in the filesystem and the faked relative path from the LO pseudo-subdirectory where the document stores its internal state. The string of periods in the second command shows what LO stored for the original files in our documents; your mileage will certainly differ:
sed 's/\/mnt\/bulkdata\/Cameras\/MCWN/\.\/Pictures/' source.lst > target.lst
sed 's/..\/..\/..\/..\/bulkdata\/Cameras\/MCWN/\.\.\/Pictures/' raw.xml > content.xml
I don’t know how they could make the file linkages work better, but it’d be really nice if there were a less horrible way to fix the breakage.
The hose going into the handle of the never–sufficently-to-be-damned Samsung VAC-9048R suck dog has been collapsing for quite some time, but I couldn’t figure out how to take the handle apart. Recently, the lock ring that I would have sworn was glued in place came loose, revealing the secret:
Samsung vacuum cleaner – handle lock ring
You slide four lugs on the lock ring into the open slots, then turn the ring clockwise to force the lugs over barriers into recesses that capture them and hold the lock ring against the handle. The handle under the lock ring isn’t quite circular, nor is the lock ring, and I think (based on later events) that they expect the ring to deform as it turns in order to let the lugs spring over the barriers.
Anyhow, with the lock ring loose, removing four screws released the two halves of the handle:
Samsung vacuum cleaner – handle interior
The handle includes a switch for the powered floor brush, which we rarely use, and a suction control lever that’s basically a binary leak: on or off. With the handle opened in front of you, remove the innards, unwrap the decorative duct tape, unwind enough of the two power conductor / spring wire ribs to allow for rebuilding the electrical connections, and cut off the damaged part of the hose.
Now, obviously, what that hose needs is a little bit of strain relief, along the lines of the hideous snout I’d affixed to its other end a while ago. The general idea is to replace the lock ring with a little attachment that will hold the heatshrink tubing in place. Something like this:
Bushing Solid Model – top
The bottom view, looking up through the layer of 1 mm cubes defining the Z=0 plane, shows the lugs:
Bushing Solid Model – bottom
I thought the slit would provide enough springiness to let the lugs bump over the ridges, but it wasn’t quite enough: the relatively stiff ABS isn’t nearly as springy as the original black plastic for about the same thickness. For the next version, I’ll try four slits, all of which must end at different levels to avoid concentrating the stress on a single layer.
In any event, it came out about like you’d expect:
Handle Bushing – on platform
As with many projects, though, I had to make a pair of simpler prototypes to get the measurements correct. The lugs, for example, are not 90° apart, spaced neatly around the handle’s midline seam, as I assumed for Prototype 1 on the right:
Handle bushings – prototypes 2 and 1
Prototype 2, on the left, has a support structure holding up a horizontal step that butted against the handle, which turned out to be unnecessary. The OpenSCAD version substitutes a pair of conical transitions that worked much better; they’re at different levels with a thicker wall section between them.
With the ring and somewhat preshrunk heatshrink tubing slipped along the hose, rewiring proceeds in reverse order. Next time, I’ll add a QD fitting in the hose-to-socket wire so I can take the whole thing apart again without cutting that wire:
Samsung Vacuum Handle – wiring detail
Assemble the handle, snap the glaring white strain relief fitting in place, shrink the tubing, add a cable tie mostly for show:
Samsung Vacuum Handle – heatshrink over bushing
I cut a few slits in the tubing’s end to improve its bendiness, but it’s already Much Better than it was.
A few things I’d do differently:
Add a recess for the cable tie, with a flat spot for its latch
Four slits, not just one
Ribs on the snout to help anchor the tubing
Longer snout?
The OpenSCAD source code for the final version, with a module for the support ring that you won’t need:
// Samsung Vacuum cleaner hose bushing
// Ed Nisley KE4ZNU January 2013
// Layout options
Layout = "Build";
// Overall layout: Show Build
// Parts: Ring Sleeve
//- Extrusion parameters must match reality!
// Print with +1 shells and 3 solid layers
ThreadThick = 0.25;
ThreadWidth = 2.0 * ThreadThick;
HoleWindage = 0.75;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
Protrusion = 0.1; // make holes end cleanly
//----------------------
// Dimensions
HoseOD = 47.0; // spiral tube diameter
TubeWall = 1.4; // heatshrink tubing wall thickness
HandleRingLong = 8.5; // length of ring stub on handle
RingID = 51.0; // lock ring over handle end
RingOD = 58.0;
RingLong = 12.0;
Locks = 4; // bumps inside lock ring
LockLength = 4.0;
LockWide = 4.0;
LockThick = 0.75;
LockAngleOffset = 52.0; // offset of lock bump from handle top dead center
LockAngleIncluded = 102.4; // between first and second lock bump (also 3 & 4)
LockAngles = [-LockAngleOffset,
-(LockAngleOffset+LockAngleIncluded),
-(LockAngleOffset+180),
-(LockAngleOffset+LockAngleIncluded+180)];
BushID = HoseOD + 1.0; // over spiral hose
BushOD = RingOD - 2*TubeWall; // allow flush heatshrink fit
BushLength = 15.0;
SlitWidth = 2*ThreadWidth; // allow expansion of lock ring, sorta kinda
SlitHeight = 20.0;
SlitAngle = 0;
SlitLength = max(RingOD,BushOD);
RingSides = 4*8;
RingAlign = 360/(2*RingSides);
$fn = RingSides;
//----------------------
// Useful routines
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(r=(FixDia + HoleWindage)/2,
h=Height,
$fn=Sides);
}
module ShowPegGrid(Space = 10.0,Size = 1.0) {
Range = floor(50 / Space);
for (x=[-Range:Range])
for (y=[-Range:Range])
translate([x*Space,y*Space,Size/2])
%cube(Size,center=true);
}
//-------------------
// Component parts
module Ring() {
union() {
difference() {
union() {
cylinder(r=RingOD/2,h=(RingLong + Protrusion));
translate([0,0,RingLong])
cylinder(r1=(RingOD/2),r2=(BushOD - Protrusion)/2,h=(RingOD - BushOD));
}
translate([0,0,-Protrusion]) {
PolyCyl(RingID,(HandleRingLong + Protrusion),RingSides);
cylinder(r=BushID/2,h=(2*RingLong));
}
translate([0,0,(HandleRingLong - Protrusion)])
cylinder(r1=((RingID/2) / cos(180/RingSides) + HoleWindage),
r2=BushID/2,
h=(RingID - BushID)/2);
}
for (i=[0:Locks-1])
rotate(LockAngles[i] + RingAlign)
translate([(RingID/2),0,LockWide/2])
cube([2*LockThick,LockLength,LockWide],center=true);
}
}
module Sleeve() {
difference() {
cylinder(r=BushOD/2,h=(BushLength + Protrusion));
translate([0,0,-Protrusion])
cylinder(r=BushID/2,h=BushLength + 3*Protrusion);
}
}
module Bushing() {
difference() {
union() {
Ring();
translate([0,0,RingLong])
Sleeve();
}
rotate(SlitAngle)
translate([SlitLength/2,0,(SlitHeight - Protrusion)/2])
cube([SlitLength,SlitWidth,(SlitHeight + Protrusion)],center=true);
}
}
// This turned out to be unnecessary after tapering the transitions
module Support() {
SuppHeight = RingLong - ThreadThick;
color("Yellow")
union() {
difference() {
cylinder(r=(RingID/2 - LockThick - ThreadWidth/2),h=SuppHeight);
translate([0,0,-Protrusion])
cylinder(r=(BushID/2 - ThreadWidth),h=2*RingLong);
for (i=[0:RingSides-1])
rotate(i*2*RingAlign)
translate([RingID/4,0,SuppHeight - ThreadThick/2 + Protrusion/2])
cube([RingID/2,(LockLength - 3*ThreadWidth),(ThreadThick + Protrusion)],center=true);
}
}
}
//----------------------
// Build it!
ShowPegGrid();
if (Layout == "Build")
union() {
Bushing();
// Support();
}
if (Layout == "Show")
Bushing();
if (Layout == "Ring")
Ring();
if (Layout == "Sleeve")
Sleeve();
if (Layout == "Support")
Support();
Starting in late 2006, I’ve have several Hobo dataloggers recording the temperature / humidity / light at various locations, under the principle that if you observe something long enough, it turns into science. Regrettably, logging the data is one thing, actually processing it into usable information is entirely another; there’s never a good time for the latter. Perhaps if I break it down into monthly chunks, I can actually make some progress on getting it done.
The first problem is that the Hobo dataloggers lack a convenient user interface: the only way to extract data is through the Hoboware graphical program. Unfortunately, Hoboware stores the extracted data in their proprietary format, locked away from any other program. I eventually discovered the configuration setting that automatically saves the data in CSV format, but I didn’t find that until rather late in the game, didn’t always set it with new versions, and it seems their CSV format has changed slightly over the years. Thus, one of my to-do items is to manually process the remaining Hoboware files to produce the corresponding CSV files, then convert those into a standard format that’s useful with, say, Gnuplot.
The intent is that I can simply concatenate all the CSV data files for a given sensor, run them through a Bash script to sanitize the data, plot what emerges, and then maybe slice-and-dice the data a few different ways. The less manual processing this requires, the more it will get done…
But the first step is to show that something emerges from the data, so here’s the last year of data (recorded in 2012, which includes a bit of 2011 and not quite up to the end of 2012) from the logger that’s been monitoring the air temperature of the Basement Laboratory and the temperature at the house water inlet. I assume the minimum water temperature on the pipe at the basement wall tracks the ground temperature four or five feet down from the surface; more on the hardware behind the data in a while.
Town_Water_Inlet
The fuzz on the purple trace shows the relatively rapid temperature variation as we draw water from the supply: it falls as water moves into the house and rises as still water warms. The inlet always remains cooler than the air temperature, because it’s cemented to the wall, but a closer look (again, in a while) shows a nice exponential curve. The thin straight-line sections show gaps in the data record: sometimes I forget to do my monthly science for a few days or weeks.
An extract from the CSV files, including some data not plotted above:
"Plot Title: Town Water Inlet "
"#","Time, GMT-04:00","Temp, °F","RH, %","Temp, °F","Host Connected","Stopped","End Of File"
1,09/25/2012 09:20:00,66.344,58.707,64.632,,,
2,09/25/2012 09:25:00,66.173,57.579,64.459,,,
-- snippage --
12962,11/09/2012 09:25:00,60.174,54.301,56.685,,,
12963,11/09/2012 09:28:48,,,,Logged,,
12964,11/09/2012 09:28:55,,,,,Logged,Logged
-- snippage --
"Plot Title: Town Water Inlet "
"#","Time, GMT-05:00","Temp, °F()","RH, %()","Temp, °F()","End Of File()"
1,11/09/12 08:35:00 ,64.247,52.282,56.728,
2,11/09/12 08:40:00 ,63.304,51.465,56.728,
-- snippage --
14473,12/29/12 14:35:00 ,56.599,51.454,48.895,
14474,12/29/12 14:40:00 ,56.599,51.485,49.116,Logged
A touch of sed can handle the reformatting I’ve seen so far:
Convert headers to comments: sed 's/^\"/#&/'
Convert non-data events to comments: sed 's/^.*Logged/#&/'
Remove spurious trailing blanks in data fields: sed 's/ ,/,/'
Here’s the Bash and Gnuplot source code that produced the graph, complete with cruft that may come in handy later:
#!/bin/sh
#-- overhead
export GDFONTPATH="/usr/share/fonts/truetype/"
base="${1%.*}"
echo Base name: ${base}
tfile1=$(tempfile)
tfile2=$(tempfile)
ofile=${base}.png
echo Input file: $1
echo Temporary files: ${tfile1} ${tfile2}
echo Output file: ${ofile}
#-- prepare csv Hobo logger file
sed 's/^\"/#&/' $1 > ${tfile1}
sed 's/^.*Logged/#&/' ${tfile1} > ${tfile2}
#-- do it
gnuplot << EOF
#set term x11
set term png font "arialbd.ttf" 18 size 950,600
set output "${ofile}"
set title "${base}"
set key noautotitles
unset mouse
set bmargin 4
set grid xtics ytics
set timefmt "%m/%d/%Y %H:%M:%S"
set xdata time
set xlabel "Week of Year"
set format x "%W"
#set xrange [1.8:2.2]
#set xtics 0,5
#set mxtics 2
#set logscale y
#set ytics nomirror autofreq
set ylabel "Temperature - F"
#set format y "%4.0f"
set yrange [30:90]
#set mytics 2
#set y2label "right side variable"
#set y2tics nomirror autofreq 2
#set format y2 "%3.0f"
#set y2range [0:200]
#set y2tics 32
#set rmargin 9
set datafile separator ","
#set label 1 "label text" at 2.100,110 right font "arialbd,18"
#set arrow from 2.100,110 to 2.105,103 lt 1 lw 2 lc 0
plot \
"${tfile2}" using 2:3 with lines lt 3 title "Air", \
"${tfile2}" using 2:5 with lines lt 4 title "Water"
EOF
The tiny Lenovo Q150 has become the dedicated Windows box for running TurboTax this season. In earlier years, I used the Token Windows Laptop through a remote desktop session that appears on a Xubuntu desktop, but the Q150 runs rings around the old laptop.
This time, no matter what I tried, I couldn’t connect to the Windows 7 desktop on the Q150 from my Xubuntu desktop. The usual search results suggested Windows configuration settings that didn’t quite match what the Q150 provided; a bit more searching revealed that Windows 7 Home flavors of the OS (this one is Home Premium) lack the Remote Desktop Protocol server required to export the desktop. The Q150 could act as a client that controlled another machine’s desktop server, but not the other way around.
The suggested solutions required applying patches, in the form of EXE files downloaded from sketchy websites, or dropping in replacement DLLs obtained from similar sites. All that seems like Bad Practice, particularly for a Windows box used to prepare our taxes, and I was unwilling to proceed along those lines.
Instead, I fetched UltraVNC, installed it on the Q150, and it works perfectly. Remmina occasionally requires a resize-window-to-match-server at startup and then it’s all good.
From what I hear, Windows 7 doesn’t display the classic Blue Screen of Death nearly so often as before, although I did manage to lock it up during the course of this adventure. That’s OK, I can still use my favorite Windows wallpaper image:
Our Larval Engineer received a logic probe / pulser set for Christmas:
RSR Logic Probe Pulser Set – with formed covers
They’re the low-cost RSR-611 and -620 from the usual eBay vendor, not my ancient HP10525/10526 set, but they should suffice. Perhaps nobody uses logic probes these days, what with most of the parts being too small for even a needle tip, but …
Anyhow, they didn’t have caps over the sharp probe tips, so I rummaged around until I found the stash of cigar tubes (some of which went into that air flow straightener) that were about the right size. I thought about 3D printing an adapter between tubes and probes:
RSR Probe Cap Adapter – solid model
It’s actually a subtractive kind of thing, with a model of the probe tip subtracted from a suitable cylindrical object:
RSR Logic Probe – solid model
But then I realized the tubes were thermoplastic, held each one over a stove burner until the open end went transparent and droopy, rammed it down over the probe tip, and trimmed off the ragged edge. Worked fine, fits securely, and even looks pretty good:
RSR Covers – detail
I’ll never print the adapters, but maybe one of us will tweak the model to do something else…
The OpenSCAD source code:
// RSR Logic Probe / Pulser Cap
// Ed Nisley KE4ZNU December 2012
// Adapts cigar tube to probe body
// Layout options
Layout = "Build";
// Overall layout: Show Build
// Parts: Probe
//- Extrusion parameters must match reality!
// Print with +1 shells and 3 solid layers
ThreadThick = 0.25;
ThreadWidth = 2.0 * ThreadThick;
HoleWindage = 0.2;
function IntegerMultiple(Size,Unit) = Unit * ceil(Size / Unit);
Protrusion = 0.1; // make holes end cleanly
//----------------------
// Dimensions
ProbeDia = 18.0; // dia of main body
ProbeTipDia = 6.8; // dia at end of plastic cone
ProbeTipLen = 30.0; // length of metal ferrule + tip
ProbeConeLen = 17.5; // cone taper length
TubeOD = 17.25;
TubeWall = 0.50;
TubeID = TubeOD - 2*TubeWall;
TubeLen = 15; // slip fit over tube body
BodyLen = 20; // slip fit over probe body
WallThick = 3.5*ThreadWidth; // basic adapter wall thickness
AdapterLen = TubeLen + BodyLen;
AdapterOD = ProbeDia + 2*WallThick;
AdapterSides = 4*4;
//----------------------
// Useful routines
module PolyCyl(Dia,Height,ForceSides=0) { // based on nophead's polyholes
Sides = (ForceSides != 0) ? ForceSides : (ceil(Dia) + 2);
FixDia = Dia / cos(180/Sides);
cylinder(r=(FixDia + HoleWindage)/2,
h=Height,
$fn=Sides);
}
module ShowPegGrid(Space = 10.0,Size = 1.0) {
Range = floor(50 / Space);
for (x=[-Range:Range])
for (y=[-Range:Range])
translate([x*Space,y*Space,Size/2])
%cube(Size,center=true);
}
module Probe() {
union() {
cylinder(r=((ProbeDia + HoleWindage)/2),
h=(BodyLen + 1.2*Protrusion),$fn=2*AdapterSides);
translate([0,0,(BodyLen + Protrusion)])
cylinder(r1=(ProbeDia + HoleWindage)/2,
r2=ProbeTipDia/2,
h=ProbeConeLen,$fn=2*AdapterSides);
cylinder(r=ProbeTipDia/2,h=(BodyLen + ProbeConeLen + ProbeTipLen),$fn=2*AdapterSides);
}
}
module ProbeSleeve() {
difference() {
cylinder(r=AdapterOD/2,h=AdapterLen);
translate([0,0,-Protrusion])
Probe();
PolyCyl((TubeOD + HoleWindage),(AdapterLen + Protrusion),2*AdapterSides);
}
}
//----------------------
// Build it!
ShowPegGrid();
if (Layout == "Show")
ProbeSleeve();
if (Layout == "Build")
translate([0,0,AdapterLen])
rotate([180,0,0])
ProbeSleeve();
if (Layout == "Probe")
Probe();