Category Archives: Tech Stuff

Linux: add a USB network interface to a bridge

By   January 11, 2018

I have some Raspberry Pi0Ws that I’m connecting to a LAN via the USB gadget network driver on a Raspberry Pi3.  The problem I encountered was that the usb[0-3] interfaces weren’t showing up when the Pi3 booted because the Pi0w’s booted later.  Only ‘eth0’ appeared on ‘br0’.

You need to make sure to add the usb[0-3] interfaces to the Bridge when the Pi0w’s boot.  This also works if Pi0W’s are plugged in after the Pi3 is already booted.  A simple change to /etc/network/interfaces is all it took to make this happen:

auto br0
iface br0 inet dhcp
bridge_ports eth0 usb0 usb1 usb2 usb3
bridge_stp off
bridge_fd 0
bridge_waitport 0

allow-hotplug usb0
allow-hotplug usb1
allow-hotplug usb2
allow-hotplug usb3

auto usb0
iface usb0 inet static
address 10.0.0.1
netmask 255.255.255.0
up ifconfig usb0 up
up brctl addif br0 usb0

auto usb1
iface usb1 inet static
address 10.0.0.2
netmask 255.255.255.0
up ifconfig usb1 up
up brctl addif br0 usb1

auto usb2
iface usb2 inet static
address 10.0.0.3
netmask 255.255.255.0
up ifconfig usb2 up
up brctl addif br0 usb2

auto usb3
iface usb3 inet static
address 10.0.0.4
netmask 255.255.255.0
up ifconfig usb3 up
up brctl addif br0 usb3

Note the IP addresses on the individual usb[0-3] interfaces are irrelevant. Those are present only so I can bring the interface ‘up’.

Venstar ColorTouch T7900

By   December 11, 2017

The thermostat arrived and I was excited.  I plugged it in on my bench at home and the first thing I found was the temperature sensor was off by about 5F.  I knew I could set the calibration in the menus but wasn’t sure if the amount it was off was linear or not.  I contacted tech support and received a response back almost right away. After a couple of back/forths with tech support, they offered to send me a wired temperature sensor to try instead.  That was on November 12th.   Since then, I’ve updated the firmware on the thermostat and now it has, twice, locked up completely.  I have to cycle the power on it to get it to come back.  I fired off another question to tech support and received no response.

It is now December 11th and I’ve not heard anything from tech support since the first instance on November 12th.

In the meantime, I’ve mounted the thermostat on the wall and hooked it up to my furnace and it seems to work fine enough.  I’ve also modified a few scripts so I can continue to monitor it remotely. I’ve even written a bit of an API for it in Python.

It hasn’t hung on me since I mounted it to the wall so I’ll continue to keep an eye on that.

It’s still a great thermostat as long as you don’t want tech support.

Edit: Tech support is actually responsive.  The problem is that my emails don’t reach them.  They claim to have never received any emails from me even though I’ve confirmed in my mail logs that their MX has accepted my message.  If I communicate with them via their web-form then tech-support is very responsive.  Clearly they have some aggressive anti-spam filters on their email that are generating false-positives.

Use Environment Canada Weather forecast atom feed to decide whether to irrigate

By   August 20, 2017

I have more or less switched to using Home Assistant to automate things at the cabin.  One of the things I’ve had to do is integrate the Etherrain/8 from QuickSmart into HomeAssistant by creating a new component and switch module.  This is currently (as of this writing) on a branch waiting for release integration.

Now the next thing was to automate the irrigation.  HomeAssistant’s automation scripts take a bit of getting used to.. It’s certainly not very intuitive but it is what it is.

First the automation, to water the front beds at 7 AM on Mon/Wed/Fri if the chance of rain is <60%:

    - id: water_front_beds_on
      alias: Start Watering Front Beds mon/wed/fri at 7AM
      initial_state: on
      trigger:
        platform: time
        hours: 7
        minutes: 0
        seconds: 0
      condition:
        condition: and
        conditions:
          - condition: state
            entity_id: binary_sensor.rain_unlikely
            state: 'on'
          - condition: time
            weekday:
              - mon
              - wed
              - fri
      action:
        service: switch.turn_on
        entity_id: switch.front_beds

So the next question is likely “where does rain_unlikely come from”?  It’s here:

    binary_sensor:
      - platform: threshold
        name: rain_unlikely
        threshold: 59
        type: lower
        entity_id: sensor.environment_canada_pop
    
      - platform: mqtt
        state_topic: environment_canada/pop
        name: environment_canada_pop

Essentially, an MQTT message with a percentage that is the “probability of precipitation”.  But who generates this MQTT message?  In fact, nobody really.  This is just a placeholder sensor that holds the POP.  But something must populate this. Well, here’s an Appdaemon script written in Python:

    import appdaemon.appapi as appapi
    import feedparser
    import sys
    import time
    import datetime

    class EnvCanada(appapi.AppDaemon):

      def initialize(self):
        if "locator" in self.args:
          loc = self.args["locator"]
        else:
          loc = "ab-52"  # Default to Calgary
        if "hr" in self.args:
          hr = int(self.args["hr"])
        else:
          hr = 4
        if "ahead" in self.args:
          add=int(self.args["ahead"])
        else:
          add = 0

        myargs={}
        myargs["loc"] = loc
        myargs["add"] = add
        myargs["module"] = self.args["module"]
        # First run immediately
        h = self.run_in(self.get_pop, 1, **myargs)
        # Then schedule a runtime for the specified hour.
        runtime = datetime.time(hr, 0, 0)
        h = self.run_once(self.get_pop, runtime, **myargs)

      def get_pop(self, args):
        loc = args["loc"]
        add = args["add"]
        d=feedparser.parse('http://weather.gc.ca/rss/city/{0}_e.xml'.format(loc))
        weekdays=["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"]
        today = (weekdays[time.localtime().tm_wday+add])
        pop = 0
        for entry in iter(d.entries):
          if today in entry.title:
            if "howers" in entry.title:
              pop = 100
            if "POP" in entry.title:
              next=0
              for word in iter(entry.title.split(" ")):
                if next:
                  pop = int(word.rstrip("%"))
                if word == "POP":
                  next=1
        print("{0}: Got POP {1}".format(args["module"], pop))
        self.set_state("sensor.environment_canada_pop", state = pop)

      def terminate(self):
        self.log("Terminating!", "INFO")

This essentially grabs the Atom feed from Environment Canada, looks for the word “Showers” or “showers” or “POP” and generates that percentage.  It then reaches under the skirt of Home Assistant and populates the above mentioned ‘MQTT sensor’.. I really wish I could think of a better way to do that.

Just for the sake of completion; here is the AppDaemon configuration to get it all started:

  EnvCanada:
  module: environment_canada
  class: EnvCanada
  hr: 5
  ahead: 0
  locator: ab-53

The ‘ahead’ attribute is “how many days ahead to determine the POP.  Ie: at 0, it returns todays probability of precipitation.  At 1, it will return tomorrow’s.   the ‘hr’ attribute is when AppDaemon should run this script.  In this case, at 05:00AM.  The ‘locator’ is the portion of the URL that specifies which Environment Canada weather location to use.  ‘ab-53’ is Sundre Alberta.

 

Hope that helps someone.

 

NFS client mount within a Proxmox LXC container.

By   December 23, 2016

Another “memo to self” …

 

[ Edit: minor change for Proxmox 5.x at bottom]

Having trouble doing an NFS mount from within a Proxmox LXC container?  A google search took me here and it pretty much answers the question but doesn’t work with Proxmox 4.4-1.  The error I was seeing after following the advice in the above was:

apparmor="STATUS" operation="profile_replace" profile="unconfined" name="lxc-container-default-cgns" pid=11339 comm="apparmor_parser"

So you also need to edit /etc/apparmod.d/lxc/lxc-container-default-cgns and make it look like so:

# Do not load this file. Rather, load /etc/apparmor.d/lxc-containers, which
# will source all profiles under /etc/apparmor.d/lxc

profile lxc-container-default-cgns flags=(attach_disconnected,mediate_deleted) {
 #include <abstractions/lxc/container-base>

# the container may never be allowed to mount devpts. If it does, it
 # will remount the host's devpts. We could allow it to do it with
 # the newinstance option (but, right now, we don't).
 deny mount fstype=devpts,
 mount fstype=nfs,
 mount fstype=cgroup -> /sys/fs/cgroup/**,
}

and then subsequently do:

service apparmor reload

Edit: On proxmox 5.2-1 the file is /etc/apparmor.d/lxc/lxc-default-cgns.  The rest of the above is still correct.

socat on OS X – TCDRAIN returns Invalid Argument.

By   June 26, 2016

When using socat, as installed by ‘brew install socat’ on OS X, you will likely get this error when trying to proxy a serial device to another host via TCP:

TCSADRAIN, 0x7fffffffe148):Invalid argument

This is because OS X uses the FreeBSD termios interface and the bug is explained here:

https://lists.freebsd.org/pipermail/freebsd-ports-bugs/2015-March/304366.html

This is the patch you want to apply to ‘socat’:

https://bz-attachments.freebsd.org/attachment.cgi?id=154044

 

Unfortunately, ‘brew install socat’ just gives you someone else’s precompiled binary and you want to retrieve the source so you can apply the above patch.

 

Do it like so:

 

cd `brew --cache`
brew unpack socat
cd socat-1.7.3.1
curl https://bz-attachments.freebsd.org/attachment.cgi?id=154044 > patch
patch < patch
./configure
make
make install

 

pfSense openvpn client to generic openvpn server in bridge mode

By   May 27, 2016

This should really go into the ‘memo to self’ category but I don’t have one.  Regardless…

I have an Ubuntu VM running OpenVPN in Bridge mode (tap).  I wanted to bridge my cottage network to my home network using pfSense out at the cottage. In the process of making this work, a fair amount of googling was involved so I decided to aggregate all of the information in one place in case I ever needed to reproduce it.  Friend Kurt was running up against some of the same issues.

 

First, make sure your OpenVPN server is working and that you have the following client specific files available (filenames will likely vary):

  • site.ovpn
  • ca.crt
  • ta.key
  • client.crt
  • client.key

On the server, I had to make some minor changes to make everything work:

If you can ping client->server but the connection hangs when you try to edit a file or view a web-page

mssfix 142
fragment 1200

If on the client, you see “OpenVPN Bad LZO decompression header byte:”? I had to comment out “comp-lzo” on the server… This seems bogus but it made it work. Need to investigate this later.

The client says “Authenticate/Decrypt packet error: cipher final failed”, the issue is the cipher being used. The default on the server was “BF-CBC” but the pfSense default was “AES-128-CBC”. Change the pfSense to “BF-CBC” and you’re good to go.

The general procedure for making this work in pfSense is the following:

    • Go to System->Cert. Manager and add your server’s “ca.crt” to Certificate Authorities. Give it a descriptive name.
    • Then go to System->Cert. Manager->certificates and add your client.crt and client.key.  Give it a descriptive name as well.   Ensure you do this after you’ve added ca.crt so that when you add this certificate, it will reference the above ca.crt.
    • Go to VPN->OpenVPN->Client and click ‘Add’
        • Select Peer-to-Peer under ‘Server mode’
        • Select ‘tap’ under ‘Device Mode’
        • Select ‘WAN’ under ‘Interface’
        • Set your server host/address to your VPN server address.
        • Set the port accordingly.
        • Set description to something you’ll recognize.
        • Under TLS Authetication, set ‘Enable authentication of TLS packets’.  It will drop down a text box into which you can paste the contents of ‘ta.key’.
        • Set ‘Peer Certificate Authority’ to the one you added above.
        • Set ‘Client Certificate’ to the one you added above.
        • Set encryption algorithm to whatever your VPN server is using (BF-CBC in my case)
        • Under ‘Custom Options’, I had:

      mssfix 142
      fragment 1200

The final note I’d like to add is one about IP addresses. When you set your ‘server-bridge’ parameter on the server’s VPN config, you assign a pool of IP addresses that are not in your dhcp server’s range. By default, the IP addresses assigned are specific to the client certificate. So if you find your clients are all getting the same IP address, it is because they each need a unique client certificate. You can override this behavior using the ‘duplicate-cn’ directive in your server’s config file. It’s generally not a good idea though so you should just create unique client certificates.

iterm2 arrow keys not working in cursor application mode

By   February 3, 2016

(TL;DR at the bottom)

This is one of those things that irritated me for ages.  I generally don’t use arrow/home/end keys for anything except when I run (rarely) certain applications like ‘make menuconfig’ where I’m forced to navigate using arrow keys.

For the longest time, the arrow keys didn’t work on iterm2 in certain applications.  After digging in, I discovered the problem.

Ages ago, I started using OS-X, but terminal.app sucked so I installed iterm.  Then iterm2 came out and I upgraded.  Sometime thereafter I discovered the arrow keys didn’t work.  This morning, I decided enough was enough and I got to the bottom of it.  One of the answers on this question posted a handy little script to test whether the keys work in cursor application mode:

 

sh -c "$(cat <<\EOF
noecho_appmode() {
  stty -echo
  printf '\033[?1h'
}
modes="$(stty -g)"
restore_echo_and_appmode() {
  stty "$modes"
  printf '\033[?1l'
}
printf '\nType <Up> <Down> <Right> <Left> <Control-D> <Control-D>\n'
printf '(no output until after the first <Control-D>, please type "blindly")\n\t'
noecho_appmode             ; trap 'restore_echo_and_appmode' 0
cat -v
restore_echo_and_appmode   ; trap ''                         0
printf '\nExpected:\n\t'
printf 'kcu%c1\n' u d f b | /usr/bin/tput -S | cat -v
printf '\n\n'
EOF
)"

This told me that iterm2 wasn’t working correctly. But it obviously works for many other people.

TL;DR:

 

When I upgraded from iterm to iterm2, my settings survived and Preferences->Profiles->Keys (NOT Preferences->Keys) contained overrides for the arrow keys and home/end.  Once I loaded a Preset for “xterm default”, exited iterm2 and restarted it, arrow keys worked fine.

 

 

Some christmas leds

By   December 1, 2015

Working on a sort of secret-santa gift for members of my truck club, I decided to do something with the WS2812B RGB LED’s. There’s a metric buttload of blog articles about these so I thought I would try to add something.

From what I can see, people are spending a lot of time dealing with timing. Nodemcu can do it but not if WIFI is enabled; or at least reliably anyway.. Since I’d had some success with the hardware SPI on the ESP8266 talking to a thermocouple amplifier, I thought I would try to get the SPI hardware to do my bitbanging for me. Turns out it’s actually quite trivial and ‘just worked’. Thanks to Joost Damad for the pattern which saved me the effort of figuring it out myself. Isn’t the internet amazing? Whenever you get a bright idea, turns out someone’s already done it.

First initialize the hardware SPI interface per David Ogilvy’s blog:

bool
ICACHE_FLASH_ATTR
ws2812b_init(void)
{
        if (initialized || sysCfg.board_id != BOARD_ID_PHROB_WS2812B)
                return true;
        spi_init_gpio(SPI_DEV, SPI_CLK_USE_DIV);
        spi_clock(SPI_DEV, SPI_CLK_PREDIV, SPI_CLK_CNTDIV);
        spi_tx_byte_order(SPI_DEV, SPI_BYTE_ORDER_HIGH_TO_LOW);
        spi_rx_byte_order(SPI_DEV, SPI_BYTE_ORDER_HIGH_TO_LOW);
        SET_PERI_REG_MASK(SPI_USER(SPI_DEV), SPI_CS_SETUP|SPI_CS_HOLD);
        CLEAR_PERI_REG_MASK(SPI_USER(SPI_DEV), SPI_FLASH_MODE);
        initialized = 1;
        pcfg.stringlen = 16;
        pcfg.ms_delay = 500;
        os_timer_setfn(&PatternTimer, PatternTimerHandler, NULL);
        return true;
}

Then simply load the bit in question into the SPI data register:

ws2812b_send_zero(void)
{

        int xtemp;
        xtemp = spi_transaction(1, 8, 0x80, 0, 0, 0, 0, 0, 0);
}

ws2812b_send_one(void)
{
        int xtemp;
        xtemp = spi_transaction(1, 8, 0xe0, 0, 0, 0, 0, 0, 0);
}

and that’s it. To send an RGB sequence for a single LED:

static inline void
ws2812b_send_color(uint8_t c)
{
        uint8_t bit=0x80;
        while(bit) {
                if (c&bit)
                        ws2812b_send_one();
                else
                        ws2812b_send_zero();
                bit>>=1;
        }
}

void
ws2812b_send_rgb(uint8_t r, uint8_t g, uint8_t b)
{
        ws2812b_send_color(g);
        ws2812b_send_color(r);
        ws2812b_send_color(b);
}

I haven’t really put any effort into optimizing this. I suppose it might be possible to pre-generate a pattern and blow the whole thing out the SPI but I wasn’t in the mood. What I’ve got works fairly well.

Here it is in action

Code is available here

How to reboot a DLink router from a script

By   November 7, 2015

I have a Dlink DIR-615 that periodically drops its connection to the outside world. It appears to coincide with my wireless provider going down but the DLink never recovers. I don’t know why; but whatever.

I was going to use a relay Phrob to just power cycle it but figured I’d explore doing a soft reboot since that appears to bring the connection back up. Because the HTTP foo is not strong within me, I searched and found this article which gave me the basic steps required to login to a DLink and reboot it. It didn’t work and I didn’t need to append a “A” to the password; but after some futzing and looking at the POST headers in Google Chrome, I eventually reached this script that I put in cron:

#!/bin/sh
# Check whether we can see google's DNS, if not, login to the router and reboot it.
ADDR=192.168.34.3
ADMIN_PASS="Zm9vCg=="

ping() {
        echo Pinging;
        ping -q -c 1 -n 8.8.8.8 >/dev/null && exit 0
}

login() {
        curl -o - -X POST -d "html_response_page=login.asp&login_name=YWRtaW4A&login_pass=$ADMIN_PASS&graph_id=5190c&&log_pass=$ADMIN_PASS&graph_code=&login=Login" http://$ADDR/login.cgi | grep index.asp
}

reboot() {
        echo "Rebooting ... " ;
        curl -X POST -d  "html_response_page=reboot.asp" http://$ADDR/reboot.cgi
}

ping || (login && reboot)

In order to encode your $ADMIN_PASS, you need to:

$ echo -n MYPASSWORD | base64

The ADMIN_PASS=”Zm9vCg==” above is what you’d get if your admin password was “foo”.

This works on my DIR-615, Hardware Version E3 and Firmware Version 5.10. Hope it helps someone.

tftpd and xinetd on Ubuntu

By   May 10, 2015

I’ve been doing embedded for a long time but I’ve been doing unix admin stuff for way longer. I’ve probably been using inetd and tftp for 25 years. I’m always shocked at the ability for tftp to take up a couple hours of time the first time you want to get it running on a host.

When doing embedded, I typically like to point tftpboot at my compiler output directory which saves a copy step. Sometimes you forget to copy and you can’t figure out why your printf()s aren’t showing up (because you’re still running an old version).

Anyway, on Ubuntu, you typically install xinetd and tftpd…

sudo apt-get install xinetd tftpd

Then you want to create /etc/xinetd.d/tftpd :

service tftp
{
        disable         = no
        socket_type     = dgram
        protocol        = udp
        wait            = yes
        user            = hpeyerl
        server          = /usr/sbin/in.tftpd
        server_args     = -s /home/hpeyerl/trunk/firmware/esp8266
}

Then poke xinetd:

$ sudo pkill -1 xinetd

I always like to debug by using tcpdump:

06:11:42.796755 IP (tos 0x0, ttl 255, id 4, offset 0, flags [none], proto UDP (17), length 56)
    192.168.37.249.69 > 192.168.37.30.69:  28 RRQ "/images/antares.rom" octet
    0x0000:  4500 0038 0004 0000 ff11 ef48 c0a8 25f9  E..8.......H..%.
    0x0010:  c0a8 251e 0045 0045 0024 c8b6 0001 2f69  ..%..E.E.$..../i
    0x0020:  6d61 6765 732f 616e 7461 7265 732e 726f  mages/antares.ro
    0x0030:  6d00 6f63 7465 7400                      m.octet.
06:11:42.799663 IP (tos 0x0, ttl 64, id 8086, offset 0, flags [DF], proto UDP (17), length 49)
    192.168.37.30.42622 > 192.168.37.249.69:  21 ERROR EACCESS "Access violation"
    0x0000:  4500 0031 1f96 4000 4011 4ebe c0a8 251e  E..1..@.@.N...%.
    0x0010:  c0a8 25f9 a67e 0045 001d cc96 0005 0002  ..%..~.E........
    0x0020:  4163 6365 7373 2076 696f 6c61 7469 6f6e  Access.violation
    0x0030:  00                                       .

Sometimes syslog is helpful:

May 10 06:03:57 pm001 in.tftpd[4564]: connect from 192.168.37.30 (192.168.37.30)
May 10 06:03:57 pm001 tftpd[4565]: tftpd: trying to get file: antares.rom
May 10 06:03:57 pm001 tftpd[4565]: tftpd: serving file from /srv/tftp

The key there is “/srv/tftp”… Since there’s no mention of /srv/tftp in your xinetd.d config file, something else must be happening…

So, here are some gotchas that will consume some debug time:

  • If you’re testing this by using tftp on the same host as tftpd, you won’t see anything in tcpdump because the packets are short-cutting through your localhost interface.
  • On Ubuntu, there is also an /etc/inetd.conf which is your culprit:
#:BOOT: TFTP service is provided primarily for booting.  Most sites
#       run this only on machines acting as "boot servers."
tftp           dgram   udp     wait    nobody  /usr/sbin/tcpd  /usr/sbin/in.tftpd /srv/tftp
  • tftpd is very picky about permissions. You might be tempted to make every element in your path 777, I don’t need to tell you this is a bad idea.