My final project is going to be a self-reliant solar powered offline access-point that can be used as a way to communicate and share data. Some initial thoughts can be found at Exercise 01 and Exercise 13. The video and slide for the project can be found at Exercise 19. Some more information about the project development and a bill of materials can be found at Exercise 20.
The device is supposed to run a Secure Scuttlebutt social networking application. Secure Scuttlebutt is a serverless off-the-grid network protocol.
To save energy only a small secondary system will be running all the time, which is responsible for controlling the loading of the batterys and starting the primary system. A person can start the actual access-point using a button and then connect to it to see posts by other people who have synced with the device. Other possible applications could be file-sharing or running a version control system.
The case for the device is supposed to look rather boring to reduce the risk of theft or vandalization. It has to be weather resistant and quite sturdy. I decided to make the front cover using vacuum forming.
As a first try I made a positive from laser cut MDF and rounded it using the belt sander. I first tried forming it using the large Combi-Therm machine, but that turned out to be unfeasible. The material was too small and lost a lot of heat when transferring it from the oven onto the vacuum plate. Instead I used the smaller Formech machine. First the positive is placed inside the machine at the middle of the plate. The forming material is clamped into the frame firmly enough to allow it to close up airtight. It's not a problem if it's not airtight right away, it will improve when the material is getting soft. At the same time the heaters can be preheated by turning up the standby temperature to around 75%. The largest heating zone should be at around 15-20% less power than the smaller zones to get even heating. When the heaters are hot enough we can pull them on top of the material. The timer will start to count up automatically. For me it took ~2 minutes to soften up enough, but this can strongly vary. You can move back the heaters and carefully touch the material to see if it is ready (wearing gloves of course). When you decide it is soft enough quickly move back the heater all the way, pull the lever towards you and turn on the vacuum pump. Afterwards let the material cool down before lowering the lever and pressing the vacuum release.
The settings for the laser cutting where the same as in the Computer-Controlled Cutting exercise.
The DFX file for the positive can be found here. It is held together by the four long pieces that are hammered in from the backside.
After letting it cool I removed the excess material using a bandsaw. As you can see on the last picture this technique is quite wasteful, laser cutting a press fit box is a lot more efficient in that regard.
After this first experiment I made a slightly larger case to be used in the final project.
To have a backplate and a structure that holds all components in place I laser cut some MDF at rougly the same size as the opening of the vacuum formed cover. I glued the pieces together and could slide the cover over it.
To make it fit perfectly I clamped the cover together, reheated it using a heat gun and let it cool down again. This way it adapted the exact dimensions of the backplate. Last I drilled two holes in the front for the LED and the button and one hole at the top for the cable of the solar panel.
The batteries have to be held in a case that avoids short circuits and allows easy connection. I decided to 3D print this divided into two parts. The larger one is also the more simple one. It's simply two tubes holding the batteries in place. The more complicated parts where the caps preventing them from falling out and creating the connection. On the first try I simply laser-cut two covers and screwed them on using watchmaker screws, clamping in a wire for the connection. But the tiny screws did not hold in the rather soft PLA, which was a big problem because a lot of pressure would have been needed to create a reliable connection.
The next approach was to print caps for both sides that slide over the main part. Even then the connection was not reliable enough. Even if it worked at first after some time it lost friction and the power was cut.
This could be resolved by covering the inside of the caps with copper tape and also adding small magnets to the ends of the batteries. The magnets helped by making the batteries a bit longer, compensating for a small design mistake in the main element. On one cap I glued on the commercial battery controller I bought and soldered on all the wires. On the input and output wires I also soldered on some pin headers, so the connections can quickly be changed. This way I don't have to rely on the batteries and/or the sun while working on the Raspberry Pi.
The settings where the same as in the 3D Scanning and Printing exercise.
I designed a PCB to control when the Raspberry Pi is powered on. For this I used the ATMega 328PB AU microcontroller by Atmel.
First I tried designing the circuit in Kicad which worked out quite well, but I had a lot of problems getting nice footprints for the pin headers. Kicad allows to define own parts and footprints, but to save time for now I switched to Eagle.
First I copied the microcontroller from one of the Satshakit schematics and then started to add all necessary parts around it. The largest difference to a standard board is the 2SC458 transistor which will be used to turn the pi on and off. Another difference are the VCC and GND lanes which I added to make it easy to extend the board, e.g. with a temperature sensor or a display.
When routing the board I tried to keep everything compact and have the different pin headers be easily distinguishable. In this image there is a missing connection for the GND network segment, this is fixed in the final version.
I milled the board with the same settings described in the documentation for Electronics Production exercise.
final-eagle.sch final-eagle.brd
It turned out that powering the Pi from the same circuit as the board introduced too much resistance, so I milled an additional board with a MOSFET instead of a normal resistor, connecting the ground of the Pi to the emitter and the ground of the battery to the collector. I glued that additional board onto the backplate and connected everything.
For the first version the embedded software is rather simple. If the button is pressed the Pi should be powered on. Then the devices waits for 15 minures and turns it off again.
The millis()-start < fivem
construct is used so there are no errors when the millis() counter is rolling over after 50 days.
const long fivem = 900000L; // 15 Minutes
const long twentys = 30000L; // 30 Seconds
const long fives = 5000L; // 5 Seconds
unsigned long start;
unsigned long warning;
void resetStart(){
start = millis();
};
void setup() {
pinMode(PD5, OUTPUT); // Pi
pinMode(PD3, INPUT_PULLUP); // Button
pinMode(PD7, OUTPUT); // LED
start = millis()-fivem;
attachInterrupt(digitalPinToInterrupt(3), resetStart, LOW); // Attach ISR
}
void loop() {
if(millis()-start < fivem){ // Check if we should do anything
digitalWrite(PD5, HIGH); // Power on Pi
digitalWrite(PD7, HIGH); // Power on LED
while(millis()-start < fivem){} // Loop for five minutes
warning = millis(); // Show first warning sequence for 20 seconds
while(millis()-warning < twentys && millis()-start >= fivem){
digitalWrite(PD7, LOW);
delay(1000);
digitalWrite(PD7, HIGH);
delay(1000);
}
warning = millis(); // Show second warning sequence for 5 seconds
while(millis() - warning < fives && millis()-start >= fivem){
digitalWrite(PD7, LOW);
delay(400);
digitalWrite(PD7, HIGH);
delay(400);
}
if(millis()-start >= fivem){ // If the timer has not been reset power everything off
digitalWrite(PD5, LOW);
digitalWrite(PD7, LOW);
}
}
}
Before programming the board I had to add support for the 328PB to the Arduino IDE by following the instructions at https://github.com/watterott/ATmega328PB-Testing to add the board in the Arduino board manager.
For some reason I could not configure the microcontroller to use the external crystal using the IDE, so i manually ran the command
avrdude -v -patmega328pb -cstk500v1 -P/dev/ttyACM0 -b19200 -e -Ulock:w:0xFF:m -Uefuse:w:0xFD:m -Uhfuse:w:0xDE:m -Ulfuse:w:0xFF:m
to set the correct fuses.
I downloaded the Raspbian Stretch Lite image from raspberrypi.org/downloads/raspbian and wrote it to the MicroSD card using dd: sudo dd bs=4M if=2019-04-08-raspbian-stretch-lite.img of=/dev/mmcblk0 conv=fsync status=progress
.
Because I did not have adaptors for the HDMI connector of the RaspberryPi I decided to set up the WiFi access point "offline" while having the Raspbian system mounted on my normal computer. To do this I first tried using proot and qemu-user to chroot into the Raspbian system. I mounted the SD card, changed to the rootfs directory and executed sudo proot -q qemu-arm -S .
. This resulted in a segmentation fault. Next I tried skipping the SD card and editing the image directly before copying it to the card. For this I followed a guide from hblock.net. First I checked the correct offset for the second partition using sudo fdisk -lu 2019-04-08-raspbian-stretch-lite.img
. In my case this was 98304. I inserted this into the mount command sudo mount -o loop,offset=$(( 512 * 98304 )) 2019-04-08-raspbian-stretch-lite.img /mnt/rpi
and used it to mount the image to /mnt/rpi. Next I copied the qemu-arm-static binary from my system to /mnt/rpi/usr/bin/ and mounted /dev, /proc and /sys. The next command looks a little complicated, but all it does is registering the ARM executable format to qemu, which allows us to execute ARM binaries in the chroot environment.
echo ':arm:M::\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00:\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff:/usr/bin/qemu-arm-static:' > /proc/sys/fs/binfmt_misc/register
Now I could change into the chroot environment using chroot /mnt/rpi
.
For the Pi to act as an access point some additional software is needed. I installed dnsmasq and hostapd using apt-get. Next I configured a static IP in /etc/dhcpcd.conf.
interface wlan0
static ip_address=192.168.4.1/24
nohook wpa_supplicant
I created /etc/dnsmasq.conf with the following settings:
interface=wlan0
dhcp-range=192.168.4.2,192.168.4.200,255.255.255.0,2h
The next file is /etc/hostapd/hostapd.conf
interface=wlan0
driver=nl80211
ssid=ScuttlebuttIsland
hw_mode=g
channel=7
wmm_enabled=0
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
wpa=2
wpa_passphrase=12345678
wpa_key_mgmt=WPA-PSK
wpa_pairwise=TKIP
rsn_pairwise=CCMP
In /etc/default/hostapd I set DAEMON_CONF to /etc/hostapd/hostapd.conf. In /etc/wpa_supplicant/wpa_supplicant.conf I added the line country=DE to make the pi use the legal WiFi frequencies for Germany. The iptables configuration did not work in the chroot environment, but as far as I understood it's not necessary for the connection and can be configured afterwards.
After this I left the chroot environment and used dd again to copy the modified image to the SD card.
To enable SSH access I simply created an empty file named "ssh" at the root of the boot partition.
Sadly I could not start the hostapd and dnsmasq services from the chroot environment, so I had to connect via USB OTG once to bootstrap the WiFi connection. For this I configured a static IP address for the usb0 interface in /etc/network/interfaces
allow-hotplug usb0
iface usb0 inet static
address 192.168.10.254
netmask 255.255.255.0
and added the line dtoverlay=dwc2
to the config.txt file on the boot partition, as well as the command modules-load=dwc2,g_ether
to the cmdline.txt file.
Next I connected the Pi via the USB port (not the PWR port), waited for it to be detected as a network device, configured a static IP for my own host using NetworkManager and connected via SSH.
ssh pi@192.168.10.254
. Now I could configure iptables and start all services using systemctl.
Now I removed the USB OTG cable and tried connecting to the "ScuttlebuttIsland" network. I entered the password I configured before (12345678) but the connection never finished. It turned out that the dhcp daemon would not run while there are static IPs set in /etc/network/interfaces, so I removed those again, restarted the services and rebooted the system. Now I could ping the system and connect via ssh pi@ssh pi@192.168.4.1
.
Now that I knew that all the hardware was working I fixed it on the backplate and put on the cover.
For the solar panel I soldered two wires with pin headers for quick connection on to the contacts of the panel, covered the contacts in hot glue to avoid corrosion and mounted everything on top of the case using glue and two little spacers to give it a slight angle. The wires where guided trough the hole in the cover to the inside of the device.
To keep the cover from falling off I drilled holes into the side and screwed it on using two M3 screws.
To install Scuttlebutt while beeing offline I fist installed node-gyp on a connected machine using npm and cloned the scuttlebutt repository from GitHub. Next I copied the node-gyp files to the global npm directory of the Pi.
I locally installed Scuttlebutt in a directory on the connected machine using npm i --target_arch=armv6l --target_platform=linux
and copied everything to the Pi. Now I could start the scuttlebutt server using ./bin.js server
, but this exited after a while with the error message "Illegal Instruction". Probably something went wrong when cross-compiling to ARM. This was fixed by executing npm rebuild
on the Pi, rebuilding everything without any cross-compilation but without needing to download any packages.
To test if everything is working I connected to the AP with my phone and started the Manyverse app. When running ./bin.js status
we can see the Scuttlebutt ID of the phone in the list of local clients. But we can not see any posts, yet.
If we add another client to the network we can see that the two clients are connecting over the local network. Now they can befriend each other and sync messages as long as they are both connected to the ScuttlebuttIsland access point.
To make the ssb-server replicate messages it needs to act as a pub. Users can redeem an invite code to connect to the pub. Sadly ssb-server does not allow IP adresses from private adress space, it only accepts IPs from public adress space or Domains. For this reason I reconfigured dnsmasq to resolve "scuttlebutt.island" to the address of the pi. The dot is needed to fool the ssb-invite modul into thinking this is a real URL. This is a little hacky but it works. Now I could configure ssb and create a new invite that can be redeemed 10000 times using ./bin.js invite.create 10000
. The invite code is written down on the case and can be copy-pasted from a small website on the island network. For this website I used lighttpd, it can be reached by typing scuttlebutt.island
in a browser.
The last thing I did was setting up a cron job to run ssb-server at startup and changing the pubs name and profile picture using the following commands:
./bin.js publish \
--type about \
--about "@SNvJv8nZ/M2UqnzZE+tNbbhXHd09ZOqr5bHU7KgAbQA=.ed25519" \
--name "Scuttlebutt Island"
cat profile.png | ./bin.js blobs.add
./bin.js publish \
--type about \
--about "@SNvJv8nZ/M2UqnzZE+tNbbhXHd09ZOqr5bHU7KgAbQA=.ed25519" \
--image.link "&aGIejCViyLk5eq5m0s9MoaJoHzO+v5O9RLSsEgTcHXw=.sha256"
All design files and code can be downloaded here: scuttlebuttisland.zip