The Smell of Molten Projects in the Morning

Ed Nisley's Blog: Shop notes, electronics, firmware, machinery, 3D printing, laser cuttery, and curiosities. Contents: 100% human thinking, 0% AI slop.

Author: Ed

  • Ubuntu 9.10 HAL FDI: Input Device Configuration – Kensington Expert Mouse FAIL

    The recent Ubuntu 9.10 release uses HAL & UDEV to permit hotplugging USB mice / trackballs / tablets without killing the X server. This is a vast improvement over the Bad Old Days, with two minor issues:

    1. You must now write an FDI file to configure your widget
    2. There is absolutely no documentation on how to do that

    Basically, you’re left to find a blog post somewhere that describes an fdi file for something similar to your widget, then puzzle out how to get from there to what you have. If you’re reading this (and you’re not one of the few dozen folks who read my posts for their pure amusement value), then you’ve probably stalled on Step 2 and arrived here via search engine.

    So, here’s how the rest of the story goes down. I assume you’ve read all the various posts scattered here & there and have a vague notion of what goes into an fdi file and why it’s needed. I am not an expert on this stuff, but I did manage to get a few things working with an afternoon of concerted heads-down effort.

    Start with the widget plugged in. Type:

    lshal | less
    

    Then rummage through that steaming pile until you find the stanzas that (seem to) have something to do with the widget. One stanza should mention a driver that sounds familiar: evdev, wacom, whatever you formerly found in /etc/X11/xorg.conf.

    For example, I want to flip the buttons on my Kensington Expert Mouse (it’s really a trackball) to make it left-handed. The userland GUI mouse configuration isn’t relevant, because I also have a tablet and another trackball that must remain right-handed.

    Here’s the appropriate stanza for the trackball, with the key lines highlighted:

    udi = '/org/freedesktop/Hal/devices/usb_device_47d_1020_noserial_if0_logicaldev_input'
     info.capabilities = {'input', 'input.mouse'} (string list)
     info.category = 'input'  (string)
     info.parent = '/org/freedesktop/Hal/devices/usb_device_47d_1020_noserial_if0'  (string)
     info.product = 'Kensington      Kensington Expert Mouse'  (string)
     info.subsystem = 'input'  (string)
     info.udi = '/org/freedesktop/Hal/devices/usb_device_47d_1020_noserial_if0_logicaldev_input'  (string)
     input.device = '/dev/input/event7'  (string)
     input.originating_device = '/org/freedesktop/Hal/devices/usb_device_47d_1020_noserial_if0'  (string)
     input.product = 'Kensington      Kensington Expert Mouse'  (string)
     input.x11_driver = 'evdev'  (string)
     linux.device_file = '/dev/input/event7'  (string)
     linux.hotplug_type = 2  (0x2)  (int)
     linux.subsystem = 'input'  (string)
     linux.sysfs_path = '/sys/devices/pci0000:00/0000:00:1d.3/usb5/5-1/5-1:1.0/input/input20/event7'  (string)
    

    I don’t know what the difference between info.product and input.product might be, but it looks like the same string for both.

    Most of the fdi files I’ve seen try to match the largest possible number of different devices. I take the other tack: I only have one of the things and if I get something similar, it’ll likely be configured entirely differently. So, my fdi files assume one widget of that type, match its name directly without thinking, and are pared to the bare minimum.

    I called the file 10-expertmouse.fdi and plunked it in /usr/share/hal/fdi/policy/20thirdparty. The proper directory seems to move around, the files get renamed, and so forth and so on. This was the correct file in the correct spot for the current Ubuntu 9.10 configuration…

    <?xml version="1.0" encoding="ISO-8859-1"?>
    <deviceinfo version="0.2">
     <device>
     <match key="input.product" string="Kensington      Kensington Expert Mouse">
     <merge key="input.x11_options.ButtonMapping" type="string">3 8 1 4 5 6 7 2</merge>
     </match>
     </device>
    </deviceinfo>
    

    You get the X button numbers using xev; write them on the trackball for future reference.

    The default Expert Mouse trackball buttons are:

    • upper-left = 2 — middle mouse button
    • upper-right = 8 — page back in browsers, mostly
    • lower-left = 1 — left mouse button
    • lower-right = 3 — right mouse button

    So the ButtonMapping line swaps (2 & 8) and (1 & 3). If you prefer not interchanging the 2 and 8 buttons, so as to keep the “page back” button on the upper-left corner, then 3 2 1 will suffice.

    The scroll ring emits buttons 4 and 5 as usual. If you don’t like the rotation-to-up/down mapping you can (presumably) swap those using ZAxisMapping as you did before.

    The syntax for, say, button mapping is whatever the driver expects and if you can find that doc, great. I used whatever I used in xorg.conf and that seems to work; it matches what the current evdev doc suggests. Leaving out all the config other than the button mapping line seems to work, but I’m sure that’s not a general rule. Maybe it only works with devices that are already automagically recognized as some sort of mouse or tablet.

    With that fdi file in place, you just unplug and replug the trackball: no need to reboot or restart X or whatever you’re thinking.

    Here’s the new stanza…

    udi = '/org/freedesktop/Hal/devices/usb_device_47d_1020_noserial_if0_logicaldev_input'
     info.capabilities = {'input', 'input.mouse'} (string list)
     info.category = 'input'  (string)
     info.parent = '/org/freedesktop/Hal/devices/usb_device_47d_1020_noserial_if0'  (string)
     info.product = 'Kensington      Kensington Expert Mouse'  (string)
     info.subsystem = 'input'  (string)
     info.udi = '/org/freedesktop/Hal/devices/usb_device_47d_1020_noserial_if0_logicaldev_input'  (string)
     input.device = '/dev/input/event7'  (string)
     input.originating_device = '/org/freedesktop/Hal/devices/usb_device_47d_1020_noserial_if0'  (string)
     input.product = 'Kensington      Kensington Expert Mouse'  (string)
     input.x11_driver = 'evdev'  (string)
     input.x11_options.ButtonMapping = '3 8 1 4 5 6 7 2'  (string)
     linux.device_file = '/dev/input/event7'  (string)
     linux.hotplug_type = 2  (0x2)  (int)
     linux.subsystem = 'input'  (string)
     linux.sysfs_path = '/sys/devices/pci0000:00/0000:00:1d.3/usb5/5-1/5-1:1.0/input/input21/event7'  (string)
    

    Shazam! Suddenly, the trackball is completely left-handed and that configuration survives hotplugging and all that happens without killing X.

    Well, at least that’s what you’d expect, based on all the doc you can find on the Web.

    As it turns out, something in the Ubuntu 9.10 udev mouse event hal X input button stack absolutely prohibits swapping buttons 1 and 3. You can verify this by looking at what the X button IDs are, using xinput:

    xinput list --short
    "Virtual core pointer"    id=0    [XPointer]
    "Virtual core keyboard"    id=1    [XKeyboard]
    "Logitech Logitech USB Headset"    id=2    [XExtensionKeyboard]
    "Microsoft Comfort Curve Keyboard 2000"    id=3    [XExtensionKeyboard]
    "stylus"    id=4    [XExtensionKeyboard]
    "stylus cursor"    id=5    [XExtensionKeyboard]
    "eraser"    id=6    [XExtensionKeyboard]
    "Microsoft Comfort Curve Keyboard 2000"    id=7    [XExtensionKeyboard]
    "Power Button"    id=8    [XExtensionKeyboard]
    "Power Button"    id=9    [XExtensionKeyboard]
    "Macintosh mouse button emulation"    id=10    [XExtensionPointer]
    "Logitech USB Receiver"    id=12    [XExtensionPointer]
    "Kensington      Kensington Expert Mouse"    id=11    [XExtensionPointer]
    
    xinput get-button-map "Kensington      Kensington Expert Mouse"
    1 8 3 4 5 6 7 2 9 10 11 12
    

    Notice that buttons 2 and 8 are swapped, so you know the fdi file is in full effect.

    You can force the button mapping using xinput like this (leaving 2 and 8 unswapped, for effect):

    xinput set-button-map "Kensington      Kensington Expert Mouse"  3 2 1 4 5 6 7 8
    xinput get-button-map "Kensington      Kensington Expert Mouse"
    3 2 1 4 5 6 7 8 9 10 11 12
    

    As you might expect by now, whenever the trackball disconnects itself, the xinput mapping Goes Away and the button handedness changes. That completely defeats the entire purpose of the whole obscene-gerund HAL fdi concept.

    You might then think you could whip up a nice udev rule that would fire off xinput when the trackball reappears, but udev scripts execute outside the entire user-terminal-X paradigm: xinput complains that it can’t talk to the X server. Of course, you can’t hear it scream, because it’s not connected to a terminal…

    Game over. Thanks for playing.

    Equally of course, there’s no documentation for the Officially Approved way to configure these devices, if, indeed, there is a way.

    Oh, and the real punch line? HAL is (about to be?) Officially Deprecated, so all this information is (or should be, shortly, we’re told) completely obsolete.

    I would love to be proved wrong. Let me know…

    Surprisingly, Xubuntu 9.10 not only enumerates all the mouse-like objects, but also allows you to set their handedness. That part works fine, but occasionally the Kensington trackball’s scroll ring stops working for a while: xev reports no “button” events happening. Then, unpredictably, it starts up again and works fine. I’d love to believe it’s a hardware problem, but I have two of the things and it happens with both of ’em.

    Tomorrow: fun with a Wacom tablet.

  • Kubuntu 9.10 Karmic: Static IP FAIL

    Once again, it seems to be impossible to set a static IP address in the Latest & Greatest version of Kubuntu… with KDE 4.whatever, the triumph of glitz over usability.

    This seems peculiar, as Unix-oid operating systems have networking built into their DNA since the beginning and every single Unix-oid system has a network connection of some sort. Evidently, all Ubuntu systems for the last couple of years have had only wireless NICs and nobody in their whole obscene-gerund testing universe has ever tried to set a static IP address.

    Maybe I’m exaggerating, but it does look that way.

    The fix is the same as in 8.10… as described there.

    This time, use KPackageKit (aka, the KDE package manager) to remove network-manager & plasma-widget-network-manager. Evidently, the Gnome version is pooched, too.

    Sheesh…

  • WWVB Receiver Shield Enclosure

    Kapton tape over traces
    Kapton tape over traces

    The little C-Max CMMR-6P-60 WWVB receiver board is somewhat sensitive to its surroundings: putting it too close to fast-switching digital signals is a Bad Idea. Of course, when there’s an antenna connected to the thing, it’s hard to separate the effects, but I’ve been testing reception with the antenna at the end of a two-foot twisted pair: far enough away to eliminate most problems.

    Just to see what happens, I built a little shield enclosure around the receiver board. The clock board has a pair of solid planes isolated from everything else, with a header matching the receiver’s pinout, for this very purpose. The receiver has a fairly solid ground plane on the bottom, but it’s quite sensitive being snuggled up against other objects; the header holds it about 5 mm above the circuit board.

    The dark amber square is Kapton tape across the traces. If I ever do this again, I’ll put the traces on the bottom so the board is entirely shielded and the tape isn’t needed.

    Shield soldered to base
    Shield soldered to base

    Some 1-inch copper tape with adhesive on one side serves as the shield enclosure, with a layer of Kapton tape covering all but about 2 mm of the adhesive near the bottom to insulate the copper from the receiver. Bent those 2 mm strips outward, with the adhesive on the bottom, stuck it to the shield plane, and soldered it in place at the corners.

    The antenna leads poke out through one side; it’s not very elegant, but I think it’s about as good as is needed for this sort of thing.

    I cut the tape at the corners and folded it down to make a little box, stuck a square of copper tape over the top flaps, soldered the corners, and it’s cute. Admittedly, it doesn’t have perfect conduction around the joints; the next time it’s on the bench I’ll add a few solder dots at the midpoints.

    Completed shield enclosure
    Completed shield enclosure

    The immediate effect was to raise the receiver’s Glitchiness score by a factor of about four. However, that’s not entirely a bad thing; it turns out that the reciever is much less Glitchy when it’s subject to high noise levels: the receiver AGC cranks the gain down so low that only heroic pulses get through and the number of glitches drops dramatically.

    As nearly as I can tell, when there’s no WWVB signal, as during the day, a low Glitchiness count means there’s extremely high noise. Thus, a higher count means less noise and better sensitivity.

    More data collection is in order, but the receiver’s LED showing data pulses now tracks the Alpha Geek Clock‘s display almost perfectly.

  • WWVB Antenna: Location, Location, Location

    Given that the wavelength of WWVB’s 60 kHz carrier is 5 kilometers, you’d think that the position of a receiver’s ferrite bar antenna isn’t all that critical. I’ve been running a receiver hitched to a laptop atop a file cabinet, with the bar antenna a few feet away atop an adjoining bookcase.

    Putting the antenna 12 cm over the top shelf surface (in a kludged cardboard holder to keep it off the wood), located on the left side of a mechanical mantel clock, produces these samples:

    
    Glitchiness:  276 Histogram: N!qdNMKE8B8A133111..12.....1.......................
    Glitchiness:  256 Histogram: O!iSMLLCC9A52215111121...1.........................
    Glitchiness:  243 Histogram: V!RfKOF88D977213212...2.3..2...1...................
    Glitchiness:  227 Histogram: NrfaQG6595AI66.222411.1..2.....11..................
    Glitchiness:  249 Histogram: S!eXNIFB76E5432.122.1.11111........................
    Glitchiness:  258 Histogram: SulWLKIIBB68433212113.1......1.....................
    Glitchiness:  119 Histogram: DWJG78753AEB52.711.....23.1.....1.....11.1.........
    Glitchiness:  159 Histogram: McULIE7835H72..1........332............23..........
    
    

    Moving it 40 cm to the other side of the clock, the next few consecutive samples look like this:

    
    Glitchiness:    4 Histogram: .21......3RD11..........332............223.........
    Glitchiness:    4 Histogram: 11..1....3SB3.........1.63.............51..........
    Glitchiness:    3 Histogram: .2.......CJC11..........251............34..........
    Glitchiness:    5 Histogram: 121......9KC3...........18.........1...15..........
    Glitchiness:    5 Histogram: .22......8KD3...........36.........1...33..........
    Glitchiness:   11 Histogram: 131....1.3W71...........451....1.......411........4
    Glitchiness:   21 Histogram: 351......6X231..1.....1.233....2....1..23.........6
    Glitchiness:    6 Histogram: .21..1...7OD1......1....23211..........33..........
    
    

    The first position is roughly equidistant from the apex of the corner, so the antenna is on the diagonal of a corner reflector made from the metalized aluminum foil of the exterior insulation.

    I’m not sure what to make of this, other than that location is everything.

    More on the histogram format there.

    Memo to self: maybe display the Glitchiness on the clock before the first sync to help find a good position?

  • Arduino Pro: Ceramic Resonator Frequency Compensation

    The Arduino Pro gets its 16-MHz CPU clock from a ceramic resonator, rather than a quartz crystal, which means the frequency accuracy is ±0.5% rather than pretty much spot on. I’m building one into a WWVB-based clock, so it knows the exact elapsed time between synch events.

    My clock uses a 20-ms timebase: 16 MHz prescaled by 8, then divided by (nominally) 40000 using Timer1.

    Knowing the exact time between WWVB updates, the firmware compares that with the local time interval to find the offset, finds the fractional error, and then tweaks the Timer1 period to make the answer come out right the next time.

    Here’s what three days in the life of that algorithm look like:

    Drift: TS   5268489 UTC 10006.040959 Elapsed 13920 Offset 0 Corr +0 ICR1 39840
    Drift: TS   5268805 UTC 10006.092559 Elapsed 18960 Offset 1 Corr +2 ICR1 39842
    Drift: TS   5269711 UTC 10007.003159 Elapsed 54360 Offset 0 Corr +0 ICR1 39842
    Drift: TS   5269966 UTC 10007.044659 Elapsed 15300 Offset 0 Corr +0 ICR1 39842
    Drift: TS   5270079 UTC 10007.063959 Elapsed  4920 Offset -1 Corr -8 ICR1 39834
    Drift: TS   5271157 UTC 10008.003759 Elapsed 61440 Offset 12 Corr +7 ICR1 39841
    Drift: TS   5271833 UTC 10008.115359 Elapsed 39780 Offset 1 Corr +1 ICR1 39842
    

    The UTC field is YYDDD.HHMMSS. The TS value is a simple monotonic timestamp: UTC brutally converted to minutes assuming a year is 365.25 days.

    I set ICR1 to 39840 when the program starts, having already determined the actual oscillator frequency for this particular Arduino Pro. That’s not necessary, because the firmware will adjust it automatically, but it does eliminate the first big step that would compensate the resonator’s -0.4% initial frequency error.

    As nearly as I can tell, the corrections are tracking room temperature changes, as it’s been really cold around here lately and the clock is atop a bookcase in an outside corner of the room.

    After the first +2 change, it ran for 19 hours with less than one second of error: 14 ppm. The -8 change was probably an overcorrection, as the synch interval was just over an hour, but so it goes. That caused 195 ppm error over the next 17 hours, then it’s back on track.

    There’s an obvious conflict between getting quick updates as conditions change and minimizing long-term free-run drift. The firmware currently insists on a minimum of 60 minutes between synchs, but (given an initial preset) I think I can dramatically increase that without losing anything.

    This code does the Timer1 setup:

    #define TIMER1COUNTS            39841l
    
    TCCR1B    = B00011000;            // Timer1: CTC mode = 12 high bits, TOP=ICR1, stopped with no clock source
    TCNT1 = 0;            // force count to start from scratch, CTC mode low bits
    TCCR1A = 0;            // no compare outputs to OC1A OC1B, WGM1 1:0 = 00
    TCCR1C = 0;            // no forced compares
    TIMSK1 = 1 << ICIE1;            // allow interrupt on capture event (TCNT == ICF)
    SetICR1(TIMER1COUNTS - 1);            // total counts - 1, start running
    

    The SetICR1 function makes sure the new ICR1 isn’t below the current TCNT1 value, which would cause a horrible timekeeping blip. As it is, there’s a microsecond (more or less) glitch during the update.

    
    void SetICR1(word NewICR1) {
    TCCR1B &= ~B00000111;     // turn off Timer1 by removing the clock source
    ICR1 = NewICR1;
     if (TCNT1 > NewICR1) {     // force counter below new TOP value
     TCNT1 = NewICR1 - 1;
     }
    TCCR1B |= B00000010;     // turn on clock with prescaler
    }
    

    When the firmware does a WWVB synch, it then checks to see if enough time has passed since the last synch and, if so, tweaks ICR1. The variables hold what you’d expect and are all long ints to hold the expected values…

    if ((UTCRightNow.SyncAge != SYNC_UNSYNC) && (UTCRightNow.SyncAge > SYNC_MINDRIFT)) {
     WWVB_Elapsed = 60l * (WWVBToMinutes(&WWVB_Time_Predicted) - WWVBToMinutes(&WWVB_Time_Sync));
     TimeOffset = (60l * (long int)(UTCRightNow.SyncAge - 1)) + (long int)UTCRightNow.Second - WWVB_Elapsed;
     DriftTicks = (int)((FetchICR1() * TimeOffset) / WWVB_Elapsed);
     if (DriftTicks) {
      SetICR1(FetchICR1() + DriftTicks);
     }
    }
    

    The FetchICR1 function reads ICR1 without disabling interrupts, doing it twice to be sure nothing’s whacked the magic hardware that allows atomic two-byte register reads.

    One failure mode: if something goes badly wrong, ICR1 can become so far off the correct value that the clock will never synch again. I must add a bit of defensive code to SetICR1 that ensures the new value is never more than, say, 1% off the nominal value.

    All in all, this works a whole lot better than I expected…

    The catch is that most Arduino applications don’t know the exact time interval and, without that, there’s no way to tweak the oscillator on an ongoing basis. However, for any particular Arduino Pro, I think you could very accurately compensate the initial frequency error by measuring the actual oscillator frequency and then hardcoding the adjustment value.

  • Printing from QCAD in Xubuntu

    Went to print up a bunch of cards & pocket stationery again. Alas, QCAD wasn’t showing any of the printers. The obligatory search unearthed a discussion that solved some of the problem. The hack:

    sudo ln -s /var/run/cups/printcap /etc/printcap

    That’s recommended for KDE printing in Gnome, which is close enough to the situation: a QT app in Xubuntu. Maybe that’s solved in 9.10; I’m still using 8.10.

    The print dialog now shows all three CUPS printers on the file server downstairs.

    Can’t select Borderless paper, which means the crop lines don’t all appear.

    The Copies field doesn’t work: only one copy prints. That’s not a killer flaw, but it’s annoying when you need half a dozen copies of the biz card sheet.

    But it’s close enough for something I do once in a blue moon.

    Why print biz cards? One year they changed our house number and street name, changed the street name back, changed the area code, and then tweaked the ZIP code. I swore a mighty oath on the bones of my ancestors to never ever buy a commercial card again.

    Memo to Self: Trim the stationery in this order…

    • vertical edges
    • horizontal edges
    • vertical cuts
    • horizontal cut
  • Hobo Data Logger Battery Life

    Another data point…

    I just replaced an Energizer lithium cell that I installed on 19 March 2008. The logger runs full-time, taking data points every few minutes.

    That’s nigh onto two years of life!

    I must conclude the battery life problems mentioned there (admittedly, in a different logger) were due to craptastic Renata cells, rather than the Hobo logger itself.

    Lesson learned…