Monday, January 1, 2024

Remote door opener using a Raspberry Pi

I recently postfitted a SOREX's fingerprint-based lock to our door. The comfort of not requiring any "thing" (key, remote, NFC token) whatsoever to get in makes live much more relaxed. However, that's a piece of technology that may fail. And indeed, recognizing fingerprints in some circumstances (e.g. when sweating, after working physically with the finger, when they're rather cold) sometimes fails. Registering multiple fingers mitigates most of these - but hey: There's a remote for it, and even one that is meant to be "installed". So when wiring this to a Raspberry Pi somehow, it should be possible to open the door with it.

The remote's description says: 

Wireless switch with potential-free contact for door opening

Reading up the term "potential-free" basically says: You need to shorten the two wires to make it open the door. I quickly discarded using the Raspberry Pi's GPIO pins for that: As far as I understand, they're not meant to do just that. However, the GPIOs can be used to control a relais - which is basically an electronically operated switch. I ended up with a 4-relais board by AZDelivery, and a set of jumper wires like these.

Now connecting the remote is rather trivial, given the schematics for how the switch works are drawn onto the connectors: One of the wires goes into the middle. Then I chose to connect the other one to the "open" side, i.e. the one that is by default (without powering the board) not closed/connected. This assures that the switch won't open the door unless explicitly controlled to do so.

Now the tricky part for someone who gets into GPIO stuff the first time is to figure out which of the pins to use and connect to which pins on the board. Gladly, the Raspberry Pi docs help - a bit: At least, they explain what types of pins are available. Additionally, the gpio command on Raspbian prints out which pins are available on what they do.

The easy part is finding appropriate pins for power supply: The relais board operates on 5V, so the board's "VCC" pin must be connected to a raspberry 5V GPIO pin - e.g. pin 2 on a Pi 1 Model B+. Next, the relais board's "GND" is to be wired to one of the GPIO's Ground pins - e.g. pin 6 on the same Pi model.

Now the relais are operated by setting their respective connector (IN1 to IN4, one per relais on the board) to a HIGH or LOW (0 V) signal level. The raspberry uses 3,3 V for HIGH, which gladly is enough for the board to consider it HIGH as well (even though it operates on 5V). HIGH tells the board to close the switch - or more specifically: Shorten the middle connector with the one that is not connected by default. It will stay in that position until setting the signal to LOW again.

Next, I needed to find an appropriate GPIO pin. There's a caveat that I discovered the hard way: pins 0 to 8 as well as pins 14 & 15 are briefly set to HIGH whenever the Pi boots. So these pins make bad choices for a door opener 😉.  I went with pin 17. Now, opening the door can be done with the following commands:

gpio -g mode 17 out
gpio -g write 17 1
gpio -g write 17 0

The last thing to accomplish was wrapping these commands into a simple (albeit secure) web interface. I decided to for a simple Python solution, since there's an easy to use library for it. The core routine switches the given GPIO port on for a second:

def open(gpioPin):
 GPIO.setmode(GPIO.BCM)
 GPIO.setup(gpioPin, GPIO.OUT)
 GPIO.output(gpioPin, False)
 sleep(1)
 GPIO.output(gpioPin, True)
 GPIO.cleanup()

 

And that's it: Door opens using the browser!

Saturday, February 13, 2021

Minecraft Home Server mit IPv6 / DS Lite (german)

Ein ungeplanter Samstagmorgen...

Unser Sohn bekam heute früh die Nachricht, er MÜSSE heute unbedingt mit einem Freund Minecraft spielen. Im Corona-Lockdown war klar, dass ich damit unseren Raspberry Pi basierten Minecraft Server trotz DS Lite Internet "irgendwie" für seinen Freund erreichbar machen musste.

Nach diversen Versuchen mit der FritzBox, MyFRITZ und Port Mapping Diensten (die am Ende scheiterten, weil sie nur TCP unterstützen) habe ich eine Lösung gefunden, die so einfach ist dass ich sie hier kurz teilen möchte.

Was man braucht

Die Lösung basiert auf dem "FIP-VPN" von feste-ip.net*. Das ist ein kostenpflichtiger Dienst (für aktuell umgerechnet ca. 15 EUR pro Jahr), den man zunächst (für umgerechnet 17 Tage) kostenfrei & risikolos testen kann.

Außerdem benötigt ihr einen Raspberry Pi mit Raspbian OS, auf dem euer Minecraft Server (Java Edition) läuft. Hier solltet ihr wissen / nachschlagen, auf welchem Port euer Server lauscht - im Standard ist das 25565.

Und so geht's

Zunächst braucht ihr einen Account auf feste-ip.net*. Dazu registriert ihr euch mit eurer E-Mail-Adresse und bestätigt per Link in der zugeschickten E-Mail und vergebt euch ein Passwort - soweit Standard. Dann meldet ihr euch am System an und geht auf "Mein Account" und dort den Unterpunkt "FIP-VPN". Klickt dort auf das "+", um ein neues FIP VPN anzulegen. Möglicherweise müsst ihr nun noch per SMS TAN bestätigen, dass es euch gibt.

Zum Anlegen des VPN wählt ihr einen Server vom Typ "Port-VPN" aus der Liste, vergebt euch ein Alias (können sich die Kids aussuchen 😄) und tragt in die Liste der Ports den Port eures Minecraft-Servers ein. Das Resultat sieht dann z.B. so aus:

Nach dem Klick auf "VPN erstellen" erhaltet ihr das neue VPN in der Übersicht, z.B. so:

Hier seht ihr bereits die URL, die die Kids im Minecraft Mehrspieler-Menü eintragen können. Funktionieren tut das jetzt noch nicht, da der Raspberry Pi ja noch nicht mit dem VPN verbunden ist.

Und das geht so: Per Klick auf den Download-Button (Pfeil nach unten auf der rechten Seite) ladet ihr die VPN-Konfigurationsdateien in einem ZIP herunter. Im ZIP ist alles enthalten, was man zur OpenVPN-Einrichtung benötigt... richtig: Das OpenVPN, das ihr als nächstes auf dem Raspberry Pi installiert - im Terminal via:

sudo apt-get install openvpn

Legt euch dann ein Verzeichnis an, in dem ihr die OpenVPN-Konfiguration entpackt. Wenn ihr im Home-Verzeichnis eures Benutzers seid, z.B. so:

mkdir vpn
cd vpn
mv ~/Downloads/fipVPN-117010.zip .
unzip fipVPN-117010.zip

Namen & Verzeichnis der ZIP-Datei müsst ihr entsprechend anpassen - ihr seht im Beispiel die 117010, basierend auf der Nummer, die mein Beispiel-VPN-Account im Screenshot oben erhalten hat.

Wenn eure Raspbian so eingerichtet ist wie unseres, müsst ihr noch eine kleine Änderung an der VPN-Konfiguration vornehmen, da es die Standard-Protokollversion nicht unterstützt. Das macht das VPN zwar unsicherer (da eine ältere Protokollversion verwendet wird) - aber für Minecraft-Sessions ist das m.E. nicht weiter dramatisch. Und zwar ersetzt ihr in der Datei fipVPN-117010.conf (ersetzt die 117010 wieder durch die Nummer eures Accounts) die Zeile

#tls-version-min 1.1 

durch

tls-version-min 1.0 

Jetzt sollte sich das VPN problemlos starten lassen mit:

sudo openvpn --config fipVPN-117010.conf

(Ersetzt auch hier wieder die 117010 durch eure VPN-Account-Nummer.)

Jetzt kann's losgehen:

Viel Spaß!


* Affiliate Link - wenn euch dieser Artikel bei der Lösung hilft, freue ich mich für's Nennen meiner Account ID 43302 als Referenz. Ich bekomme dafür 10 % der von euch aufgeladenen Credits ohne dass es euch etwas kostet.

Thursday, July 12, 2012

Compacting VirtualBox Linux partitions

After re-installing Debian in a VirtualBox VM guest, the dynamically-allocated VDI obviously still contained the old data. Google didn't find a quick recipe on how to fix this - so here it is:

  1. Mount the VDI using:
    vdfuse -f volume.vdi /mnt
  2. zerofree the ext partition (look at the partition table and the file sizes in /mnt if unsure):
    zerofree /mnt/Partition1
  3. Compact the VDI:
    VBoxManage modifyhd volume.vdi --compact
This approach works from a Linux host for ext2/ext3 partitions within the VDI. It should also be possible to run zerofree from within the guest system, after changing to runlevel 1 and re-mounting the system partition read-only.

Update: The first two steps also work with VMDKs. Before Step 3, the VMDK has to be converted to VDI though:
VBoxManage clonehd volume.vmdk volume.vdi --format VDI

Thursday, April 5, 2012

Hacking Network Manager to solve auto-mounting in Gnome/Ubuntu - for good

A while ago I submitted a patch to the Gnome Network Manager mailing list, which enhances Network Manager to auto-mount network drives when connected to known networks (I've written about this issue before). The source is on github, along with more info.

Although the patch is not upstream, here's a quick post mortem on how it evolved.

Setting the stage

As a newbie in Gnome-development, I used this info on the Gnome Wiki to get a head start. I preferred Eclipse over anjuta because I'm already familiar with it. I had to install the following Ubuntu packages for being able to compile Network Manager:
autotools libtool libgtk-3-dev libclutter-1.0-dev libgda-4.0-dev libgconf2-dev intltool gtk-doc-tools libdbus-glib-1-dev libgudev-1.0-dev libnl-dev uuid-dev libnss3-dev ppp-dev libgnome-keyring-dev libnotify-dev
Getting & building the Network Manager and Network Manager applet (nm-applet) from source is described on their wiki. For installing to a workspace directory, I used this configure line with Network Manager:
./autogen.sh --prefix=/home/dom/workspace/NetworkManager --sysconfdir=/etc --localstatedir=/var
Telling nm-applet to link against the custom Network Manager required these build steps:
export PKG_CONFIG_PATH=/home/dom/git/NetworkManager:/home/dom/workspace/NetworkManager/lib/pkgconfig

./autogen.sh --prefix=/home/dom/workspace/nm-applet --sysconfdir=/etc --localstatedir=/var
For running the modified versions, the Ubuntu-packaged ones have to be killed before.

Finding the hooks

After successfully running my custom-build versions I could start hacking. The mounting was to be done as the current session user (to be able to use the stored credentials). As the Network Manager daemon runs as root, I looked at the nm-applet source. At first I was looking for a single point of code where the connection events (connect/disconnect) occur. As far as I can tell, there is none (in the applet). So I dived into the connection-type-specific sources, which is applet-device-wifi.c for WiFi.

I isolated the wireless_device_state_changed function to be the most reliable place for getting the WiFi connect/disconnect events. For testing, I triggered shell scripts that would already mount/unmount network drives.

Mounting & unmounting the drives

While the shell scripts were an easy solution, they're not quite easy to maintain with a GUI, and not easy to package. So in the next step, I replaced the scripts with using the gvfs API directly.

The scripts would work with a URI, such as smb://server/share (passed to the gvfs-mount command). The hardest part was finding a way to mount such a URI using the API: gvfs contains quite some layers of abstraction, and only GFile provides a way of creating an object using such a URI. It figured out that g_file_mount_enclosing_volume was the function I was looking for. For unmounting, finding g_file_find_enclosing_mount and g_mount_unmount_with_operation were rather easy now.

So I could now mount fixed network drives upon WiFi connect.

GUIifaction...

Mounting fixed drives was nice for my own use, but for real live the next TODO was enhancing the connection editor with appropriate settings. Using glade, the interface was quickly built. Integrating it into the applet was a matter of copy & paste (I used the IPv4 page as copy-base, as it also provides a table).

The only thing that took a while was the dropdown for the supported protocols within the table.

... and the quest for persistent settings

I now had a table view that could edit the network drives to be auto-mounted - now I had to persist the settings. To keep things simple, I decided to not store individual columns, but assemble them into the URIs that are required for mounting. So each row is basically just one string - and the table itself is an array of strings. nm-applet does not store the connection configurations itself, but passes them to the Network Manager daemon via dbus to have them stored there (which makes a lot of sense).

So I had to pass the array of URI strings via dbus to Network Manager, where I had to take them and persist them with the other configuration settings. This is where I really missed collection data types and built-in serialization that Java or C#/.NET provide... it cost me hours finding the right dbus data type and getting all the tiny code fragments right.

Now I could edit network drives in the GUI, they would be mounted on connect and unmounted on disconnect.

Making it human

So it worked now, but it requires an advanced user to actually use the connection editor. Thinking a while on a more intuitive solution, I came up with capturing manual mount events and then asking if they should be persisted. Implementing this was fun: Everything worked as expected quite quickly.

But wait: Why do I have to enter the WPA key every time I added/removed a network drive this way?

Solving this took really long: In the connection editor, the network drives are persisted with the other settings - I could just "backpack" them. Modifying them outside the connection editor meant I had to write code for persisting them on my own - which was really straightforward in the first place.

The network credentials, though, are stored in some special way - they have to fetched separately via dbus. If the network settings are saved without fetching and re-adding the credentials, they're overwritten. The code for getting this right required a callback chain that is not trivial. And as soon as it worked, I started receiving segfaults. It was only with the support of gicmo, an experienced Gnome coder, that I was able to use gdb ("thread apply all bt full") and valgrind to identify the memory leak.

This is also the reason the patch only works with WiFi-connections at the moment: For every other connection type, it has to be determined if there are secret settings stored this way. If so, fetching and re-adding those has to be implemented.

The result

Here's some screenshots of how the patch looks like:




Wednesday, March 21, 2012

Auto-mounting network drives in Linux/Ubuntu when server is available

After switching the network connection of my Ubuntu desktop box from LAN to Wifi/WLAN, my pam_mount configuration ceased to work. The plain reason: The Wifi connection is established by the Gnome Network Manager only after I login. pam_mount fails to mount them right before the connection is established. While I'm sure that there's some way to work that out, I chose a quick but reliable solution inspired by this thread.

In detail, here's my script that is run after login:

#!/bin/bash

SERVER=
while [ -z "$SERVER" ] ; do
SERVER=`host server | grep address`
done

gvfs-mount smb://server/share

It surely isn't perfect, but solves the need. If you want to use it, replace the two occurences of server with the name of your Windows/Samba server and share with the name of the network share you want to mount. You can of course place more gvfs-mount lines at the bottom.

It might also be a good starting point for some if-up / ip-up scripts, or even in laptop situations where the drives are only mounted when connected to the 'right' network (identified by the host lookup).

Any comments?

Monday, February 6, 2012

Findings on Bluetooth connectivity between iPhone & Android

I tried to come up with a way to discover iPhones and Android phones via Bluetooth in both directions in the last couple of days. As a tech-savvy, that sounded like a piece of cake: The Bluetooth menus on both devices feature a "discover" and "discoverable" feature, right?

On Android, everything went smooth. Except, you can't set a device to be discoverable for an unlimited timespan.

Then came the iPhone: In brief, it doesn't work. In detail, this is what I tried:
  • Looked for a Bluetooth API. There is none except iOS 5's Bluetooth 4 Low Energy (LE) one, which requires a BT 4 capable hardware - which is only the iPhone 4S currently. On my iPad 2 it doesn't discover anything. Same on my neighbor's iPhone 4S: Obviously, this API can only detect low range BT 4 LE devices. The "dual-mode" (enabling the chipset to run in the otherwise incompatible 2/3 and 4 versions, according to Wikipedia) seems unsupported.
  • Looked for 3rd party frameworks. There are some, but they require a rooted iPhone and/or take any chance of getting the App into the App store.
  • The high-level API called GameKit is only able to connect to other iOS-devices, and there are quite some people who tried to connect somehow - without success.
  • Tried to use the Bonjour low-level API (dns_sd.h) and found two problems with that:
    1. Discovering services via Bluetooth unsurprisingly only seems to work on active Bluetooth connections: I paired and connected my iPad to my Mac (had to initiate the connection from the iPad to the Mac, the other way does not seem to find a usable combination of profiles). After disabling WiFi on the iPad, I ran the BonjourWeb sample on it and
      dns-sd -A
      on the Mac to get provide a Bonjour service to be discovered. Success! The iPad discovered the sample service (Proof: disappeared when stopping the service on the Mac). The bad news: The iPad didn't discover anything as soon as the Bluetooth connection was dropped (without even touching the pairing). Side note: Without an active connection, the Mac doesn't provide a Bluetooth interface that Bonjour can bind to, so even if iOS didn't require an active connection, the other side probably would anyway. Learned assumption: iOS probably accepts Bluetooth GameKit connections (i.e. Bluetooth PAN(U) connections) temporarily without asking the user to provide a link-layer to Bonjour...
    2. I tried to setup Bonjour (zeroconf) via Bluetooth on Android. I used the existing JmDNS for that, which is capable of binding to any device that has an IP. I quickly found that the Android Bluetooth API doesn't support the PAN(U) profile (starting with Android 3.0, the API seems extended though), and even on my rooted Android phone I would have needed to add some kernel modules... Plus, JmDNS also requires an IP address that is only assigned after a connection has been established (that's the behavior on my Linux box at least).
So for now, iPhone apps stay blind when it comes to discovering other "smart" Bluetooth devices: Be it Android-phones, Linux boxes or even Macs...

(post has been updated on Feb 8)

Friday, March 13, 2009

Aventail VPN client on Ubuntu 08.10 64bit

The Aventail VPN client is a 32bit application linking against libssl and libcrypto 0.9.7. Ubuntu 08.10 comes with version 0.9.8 of these libraries, yielding this error:

$ startct
/usr/local/Aventail/AvConnect: error while loading shared libraries: libssl.so.0.9.7: cannot open shared object file: No such file or directory

Or more specifically:

$ ldd AvConnect
linux-gate.so.1 => (0xf7f10000)
libpthread.so.0 => /lib32/libpthread.so.0 (0xf7edc000)
libssl.so.0.9.7 => not found
libcrypto.so.0.9.7 => not found
libm.so.6 => /lib32/libm.so.6 (0xf7eb5000)
libc.so.6 => /lib32/libc.so.6 (0xf7d57000)
/lib/ld-linux.so.2 (0xf7f11000)

The fix is easy and hands-on. Run these commands:

cd /lib32
sudo ln -s /usr/lib32/libssl.so.0.9.8
sudo ln -s libssl.so.0.9.8 libssl.so.0.9.7
sudo ln -s /usr/lib32/libcrypto.so.0.9.8
sudo ln -s libcrypto.so.0.9.8 libcrypto.so.0.9.7

If you don't have the /lib32-directory, install the linux32 package first.