blog

2019-11-03

Remote-Controlled Date Clock & Message Board for Elderly Relative

Contents

Introduction

An elderly relative has been having trouble with times, dates, and schedules. I looked into technology that might help.

There are commercial products that try to address the problem. Amazon lists a few products like this:

Date Clock

They cost about $45 or so. Labels are big, clear, and do not use abbreviations. Redundant information like "Morning" and "Afternoon" can help reduce confusion. The screen automatically dims at night.

None of them, however, automatically update for daylight savings time. That made them completely useless for my purposes, since I live on the other side of the country. There's no way someone with technology difficulties is going to do well with a UI like this (located on the back of the screen):

Date Clock User Interface

Note that you can't even operate the buttons and look at the screen at the same time. I don't expect the staff at the assisted-living facility there to have to read a manual twice a year in order to adjust the clock.

Since WiFi is available on site, I thought I could put together something better using a small monitor and a Raspberry Pi Zero.

Design Specifications

Include the features of the commercial date clocks:

In addition, add an area for custom messages to appear on a scheduled or one-time basis:

Also:

And allow full remote access and modification:

I assumed I would have to be on site in person to set it up initially, but after that I hoped it would keep going without any hands-on intervention on my part.

The Result

Here is the finished device in operation:


Clock front with switch

(The green tab in the upper right corner is a pull-tab for the plastic screen protector, which is removed after installation.)

The text "Calendar / Clock" in the box is just an example of a message that might appear there. For the first few minutes after booting, the timezone appears in small green text above and to left of the message box. If the Raspberry Pi has successfully acquired an IP address, the IP address appears after the timezone.

The on/off switch in front turns off the monitor but leaves the computer running. Although the software contains code to dim the screen at night, even with the screen completely black there still a noticeable glow from the backlighting. I added the front switch for the monitor power in case this became a problem when trying to go to sleep.

Xxxxxx

The back of the monitor has the Raspberry Pi attached on the left with Velcro® hook and loop fastening. Cables are attached to the monitor with zip ties and adhesive pads. The on/off switch cable on the back controls power to the computer. (The switch cable is the one plugged in at the upper right of the Raspberry Pi; it's a bit hard to see the switch button in this photo.)


Xxxxxx

Xxxxxx

Operation

At present, the user interface is quite minimal, namely remote editing of a text file on the Raspberry Pi. Whenever the message text changes, the software also writes to a remote server an image file of the new screen (screen.png); this serves as confirmation that the software is responding to the control file.

Here is a sample of the https://example.net/relay/screen.png file (where "example.net" stands in for the personal domain I use for the remote server):

Screen.png web file

Here is a sample of the control file, notes.txt:

DAY_OF_WEEK WED - 11:30 13:20 Call Peter at 1:15 pm
ONE_TIME 2019-10-25 2019-10-25 11:30 13:00 Lunch with Jane
DATE 11-03 11-03 - - Mark's birthday
#
#DATE 09-03 09-10 11:00 13:15 Message
#ONE_TIME 2019-09-03 2019-09-05 - - Message
#DAY_OF_WEEK WED - 10:00 13:00 Message
#DAILY - - 07:00 14:00 Message
#NOW - - - - Message
#USE_BRIGHT - - 07:00 20:00
#DIM_VALUE - - - - 0.3

Device Hardware

Prices are approximate as of September 2019. Total outlay for the parts below was about $130, not including sales tax.

UNIROI 7 Inch HD LCD Screen for Raspberry Pi 1024 X 600 HDMI Monitor from Amazon. $50

Xxxxxx

Raspberry Pi Zero W from Adafruit. $10

Xxxxxx

5V 2.5A Switching Power Supply with 20AWG MicroUSB Cable from Adafruit. $7.50

Xxxxxx

Pi Zero Case - Frost/Clear from Adafruit. $6

Xxxxxx

SanDisk 16GB Ultra microSDHC UHS-I Memory Card with Adapter Amazon. $6

Xxxxxx

Mini HDMI Male to HDMI Female Converter Adapter Cable - 6-Inch from Amazon. 2-pack $3

HDMI Cable - 1 Foot - Black from Amazon. $7

Xxxxxx

Male to Female USB Cable Adapter with On/Off Switch from Amazon. $7

The white cable was included with the monitor.

Xxxxxx

MicroUSB Push On Off Power Switch Cable for Raspberry Pi from Amazon. $4

Xxxxxx

UL Certified USB Wall Charger 5V1A Plug from Amazon. $7

The monitor came with a USB power supply that wasn't UL-certified. I decided I'd prefer to use one that was.

Xxxxxx

100 Pack Zip Tie Adhesive Mounts from Amazon. $11

Xxxxxx

USB 2.0 Extension Cable - A-Male to A-Female Adapter Cord - 6.5 Feet from Amazon. $5

Xxxxxx

Development Hardware

These items weren't needed for the device itself, but I found them useful for development and to have available on site while setting up.

MicroUSB to USB 4 Port OTG Hub from Amazon. $6

Xxxxxx

Plugable Foldable Keyboard from Amazon. $55

Xxxxxx

Folds up nicely for stowing in luggage! Can be used as both Bluetooth and wired USB keyboard!

Xxxxxx

Networking

Xxxxxx

Ideally, it would be possible for the client systems (e.g., desktop computer, mobile phone) to reach the Raspberry Pi via ssh directly, without having to go through an intermediate system. (At least it would be nice to avoid an intermediate system that I would have to set up and pay for.)

One suggested solution is pwnat. It sounds great, but it doesn't seem to work for everyone. Given the fact that I would have limited time for debugging on-site, I wanted something more bullet-proof.

There's also the added complication that neither endpoint has a static IP address. If they did, and if there was full access to the NAT routers, then port forwarding could work.

In the end, it seemed the most reliable configuration would be if I used my own intermediate server with a static public-facing IP address. I've previously used https://lowendbox.com to find extremely cheap hosting, but ever since my last server from there abruptly shut down and the hosting company went out of business, I've been a bit wary of extreme low-end vendors. I opted for a nice $5 / month plan from linode, a company I have reliably used in the past, for a virtual private server (VPS) with 1 GB Ram, 1 CPU Core, 25 GB SSD, 1 TB Transfer. I installed CentOS 7 on the VPS. (See below for set up information.) For convenience, I attached a spare domain name that I own to the server, although I could just as well have used the static IP address of the server to reach it.

The general technique is called "Reverse ssh tunneling." In this method, the Raspberry Pi establishes an ssh connection to the public server, and the client system also establishes an ssh connection to the public server. The public server then patches the two connections together. I referred to the following helpful posts about how to set it up:

The remainder of this post has far more detail than any casual reader is likely to be interested in. I have tried to make it complete enough for anyone "skilled in the art" to recreate everything I did.

Combining tips from all them, and bearing in mind that the Raspberry Pi is running Raspbian Stretch and the linode VPS is running CentOS 7.7, these are steps I followed.

Note: these steps should done after the "Introduction" and "Names" sections in the System Setup: VPS Server description below.

During these setup steps, the Raspberry Pi is assumed to be on a network local to the client desktop on which the following commands are typed. In the text below, example.net is used as a placeholder for the domain name of the linode server and raspi is used as a placeholder for the local hostname (or IP address) of the Raspberry Pi.

Create a non-admin account ("relay") on the linode VPS to handle tunnelling. Provide for ssh key login to the account.

ssh admin@example.net
# Create account:
sudo useradd relay
sudo passwd relay
exit

ssh relay@example.net
# Set up for ssh key login to new account:
mkdir -p ~/.ssh
chmod -R 700 ~/.ssh/
touch ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
exit

ssh pi@raspi
# Set up key login from Raspberry Pi
ssh-keygen -b 4096
scp ~/.ssh/id_rsa.pub relay@example.net:~/.ssh/authorized_keys
# Test login (should not ask for password)
ssh relay@example.net
exit
exit
 

Open ports on VPS to be used for tunnelling. Ports 9922 and 9927 are for the installed unit. Ports 8822 and 8827 are for development work.

ssh admin@example.net
sudo firewall-cmd --permanent --add-port=9922/tcp
sudo firewall-cmd --permanent --add-port=9927/tcp
sudo firewall-cmd --permanent --add-port=8822/tcp
sudo firewall-cmd --permanent --add-port=8827/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports
exit

Test tunnelling

ssh pi@raspi
# Open reverse tunnel
ssh -fN -R example.net:9922:localhost:22 relay@example.net

Leave above ssh connection running. Open new terminal window and connect to VPS server.

ssh relay@example.net
# Check port
sudo netstat -nap | grep 9922
# should see something like:
# tcp        0      0 127.0.0.1:9922          0.0.0.0:*               LISTEN      27599/sshd: relay

# Connect to Raspberry Pi via tunnel
ssh -p 9922 pi@localhost

Exit from everything.

Install autossh software on Raspberry Pi.

sudo apt-get install autossh

Create a script run-screen on Raspberry Pi to automatically establish / reestablish ssh tunnel.

ssh pi@raspi
sudo vi /usr/local/bin/run-screen

Insert text:

#!/bin/bash
autossh -M 9927 -fN -o "PubkeyAuthentication=yes" -o "StrictHostKeyChecking=false" -o "PasswordAuthentication=no" -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" -R 9922:localhost:22 relay@example.net

Create a service run-screen.service on Raspberry Pi to start run-screen at boot.

sudo vi /etc/systemd/system/run-screen.service

Insert text:

[Unit]
After=default.target

[Service]
Type=simple
User=pi
ExecStart=/bin/bash /usr/local/bin/run-screen
RemainAfterExit=yes

[Install]
WantedBy=default.target

Enable service and reboot Raspberry Pi.

sudo systemctl enable run-screen.service
sudo reboot

Wait for a minute, then log in to VPS server and test tunnel.

ssh relay@example.net
# Check port
sudo netstat -nap | grep 9922

# Connect to Raspberry Pi via tunnel
ssh -p 9922 pi@localhost

Now, even if the server reboots, the Raspberry Pi is powered off and back on, or the network connection is temporarily lost, the tunnel should be automatically reestablished.

System Setup

Raspberry Pi

The following assumes a macOS computer with an SD card reader attached, and a Raspberry Pi Zero W with monitor, keyboard, and mouse.

On first boot, the Raspberry Pi will show a series of screens, beginning with this one.

Raspberry Pi setup screen

Proceed through the screens, and restart at the end of the series.

After rebooting, click the upper left icon and choose Preferences > Raspberry Pi Configuration.

Raspberry Pi prefs

Change entries for each tab as follows:

From this point on, access the Raspberry Pi via SSH on the Mac. I find it convenient to use a static IP address for the Pi, represented by r.r.r.r below.

Make changes for the Uniroi monitor. They supply a batch file to make these changes; I prefer to make the changes directly by editing config.txt.

ssh pi@r.r.r.r
sudo vi /boot/config.txt

Append the following:

max_usb_current=1
hdmi_force_hotplug=1 
config_hdmi_boost=10
hdmi_group=2
hdmi_mode=87
hdmi_cvt 1024 600  60 6 0 0 0

Reboot.

sudo reboot

Update system.

ssh pi@r.r.r.r
sudo apt-get update
sudo apt-get upgrade
sudo reboot

Change the console font.

ssh pi@r.r.r.r
sudo dpkg-reconfigure console-setup

Change the console prompt by editing .bashrc.

vi .bashrc

Insert the following:

PS1='\e[1;32m\$ \e[m'

Reboot.

sudo reboot

Test framebuffer display.

ssh pi@r.r.r.r
sudo apt-get -y install fbi
cd ~/Pictures
wget http://adafruit-download.s3.amazonaws.com/adapiluv320x240.jpg
# display picture for 5 seconds
sudo fbi -T 2 -t 5 -1 -d /dev/fb0 --noverbose -a /home/pi/Pictures/adapiluv320x240.jpg

Set up bluetooth keyboard.

sudo bluetoothctl
power on
scan on

Put the bluetooth keyboard in discovery mode. For the Pluggable Keyboard mentioned above, this is done by typing Fn-C. When the scan finds the keyboard a line like this will be displayed, where XX:XX:XX:XX:XX:XX represents the bluetooth address of the keyboard:

[CHG] Device XX:XX:XX:XX:XX:XX Name: Plugable Folding Keyboard

Continue bluetooth setup:

pair XX:XX:XX:XX:XX:XX
trust XX:XX:XX:XX:XX:XX
connect XX:XX:XX:XX:XX:XX
quit

Edit the bluetooth configuration file, to provide for automatic connection and reconnection.

sudo vi /etc/bluetooth/main.conf

Append these lines.

ReconnectIntervals=1
ReconnectAttempts=30

Before installation at the final location, add information for the site's WiFi network by editing wpa_supplicant.conf.

sudo vi /etc/wpa_supplicant/wpa_supplicant.conf 

Insert an entry for the final location's SSID (on_site_ssid) and password (on_site_password).

ctrl_interface=/var/run/wpa_supplicant
update_config=1
country=US

network={
        ssid="on_site_ssid"
        psk="on_site_password"
}

network={
        ssid="my_ssid"
        psk="my_password"
        key_mgmt=WPA-PSK
}

Edit the dhcpcd.conf file.

sudo vi /etc/dhcpcd.conf 

Comment out any static IP information for the local development network. (This assumes the final location uses a WiFi access point with DHCP.)

interface wlan0
#SSID my_ssid
#static ip_address=r.r.r.r/24
noipv6
#static routers=r.r.r.10
#static domain_name_servers=d.d.d.d
 

Install tool to provide more information about network environment; may be useful for debugging later.

sudo apt-get install dnsutils

# show WAN IP address
dig @resolver1.opendns.com ANY myip.opendns.com +short

VPS Server

Introduction

The following instructions are adapted from:

In these instructions:

Names

At the domain registrar (e.g. Pair Domains):

It may up to 24 hours for the DNS record to become active. Use the host command (on macOS Terminal) to verify the domain name is working:

$ host example.net
example.net has address w.x.y.z
example.net has IPv6 address aaaa:bbbb:cccc:dddd:eeee:ffff:gggg:hhhh

Log in to root account and set host name information. (Replace w.x.y.z with the actual address of the VPS.)

ssh root@w.x.y.z
yum update
hostnamectl set-hostname example

Edit the hosts file

vi /etc/hosts

And append the following, replacing (w.x.y.z) and (aaaa:bbbb:cccc:dddd:eeee:ffff:gggg:hhhh with the actual addresses of the VPS.

w.x.y.z example.net example
aaaa:bbbb:cccc:dddd:eeee:ffff:gggg:hhhh example.net example

Create an administrative user.

useradd admin
passwd admin xxxxx
usermod -aG wheel admin
reboot

Log in via the new user. Edit sshd_config to disable root logins over SSH and listen on only one internet protocol.

ssh admin@w.x.y.z
sudo vi /etc/ssh/sshd_config

Change / uncomment these lines:

PermitRootLogin no
AddressFamily inet

Restart SSH:

sudo systemctl restart sshd

If the hosting company provides for reverse DNS, follow the appropriate instructions, such as:

Networking

At this point, the Networking section above can be completed.

Install Apache web server

ssh admin@example.net
sudo yum install httpd

# back up default config file
cp /etc/httpd/conf/httpd.conf ~/httpd.conf.backup

Open web ports in firewall.

sudo firewall-cmd --add-service=http --permanent
sudo firewall-cmd --add-service=https --permanent
sudo systemctl restart firewalld

Edit config file.

sudo vi /etc/httpd/conf/httpd.conf

Change / comment out / uncomment these lines:

ServerAdmin self@example.com
ServerName example.net:80

#    Options Indexes FollowSymLinks
     Options -Indexes +FollowSymLinks

#AllowOverride None
 AllowOverride All

Verify config file by using the /usr/sbin/httpd -t command. Output should look like this:

$ /usr/sbin/httpd -t
Syntax OK

Start web server:

sudo systemctl enable httpd.service
sudo systemctl start httpd

Verify server status by using the systemctl status httpd command. Output should look something like this:

$ systemctl status httpd
• httpd.service - The Apache HTTP Server
   Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
   Active: active (running) since Tue 2019-10-29 17:30:46 UTC; 12s ago

Typing http://example.net in a browser should produce the Apache welcome page.

Create an index page.

sudo vi /var/www/html/index.html

Enter the following text.

<!DOCTYPE html>
<html>
<head>
<title>example</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
  background-color: lightgray;
  color: navy;
  font-family: Courier;
}
</style>
</head>
<body>
<p>example.net</p>
</body>
</html>

Typing http://example.net in a browser should produce the above index page.

Secure web access

Download to desktop computer the certificate files from the certificate authority, e.g.:

Copy files to VPS server.

scp example_net.zip admin@example.net:/home/admin/
scp example.net.key admin@example.net:/home/admin/

Install files on VPS server.

ssh admin@example.net
sudo yum install mod_ssl
sudo yum install unzip
sudo yum install wget

unzip example_net.zip

sudo cp example_net.crt /etc/pki/tls/certs/
sudo cp example_net.ca-bundle /etc/pki/tls/certs/
sudo cp example.net.key /etc/pki/tls/private/

Edit ssl.conf:

sudo vi /etc/httpd/conf.d/ssl.conf

Include / edit the following lines:

SSLCertificateKeyFile /etc/pki/tls/private/example.net.key
SSLCertificateFile /etc/pki/tls/certs/example_net.crt
SSLCertificateChainFile /etc/pki/tls/certs/example_net.ca-bundle

Restart web server:

sudo systemctl restart httpd

Test https access by visiting https://example.net

Require https access by creating or editing file .htaccess:

sudo vi /var/www/html/.htaccess

Append these lines:

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

Restart web server:

sudo systemctl restart httpd

Clear browser history, then test by visiting http://example.net - browser should go to https://example.net

Set up password access for user "webuser".

sudo htpasswd -c /etc/httpd/.htpasswd webuser
sudo chown apache:apache /etc/httpd/.htpasswd
sudo chmod 0660 /etc/httpd/.htpasswd

Edit .htaccess:

sudo vi /var/www/html/.htaccess

To contain this:

ErrorDocument 404 https://example.net/index.html
ErrorDocument 403 https://example.net/index.html

RewriteEngine On
RewriteCond %{HTTPS} !on
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI}

SSLRequireSSL
AuthType Basic
AuthName "private"
AuthUserFile /etc/httpd/.htpasswd
Require valid-user

Restart web server:

sudo systemctl restart httpd

Clear browser history, then test by visiting example.net. Use "webuser" for the user name and the value supplied to the command htpasswd for the password.

Test sample screen file

Add a new directory writable by user 'relay'.

sudo mkdir /var/www/html/relay
sudo chown relay:apache /var/www/html/relay
sudo chmod 750 /var/www/html/relay
exit

Log in with user 'relay'.

ssh relay@example.net

Copy a sample screen file to relay's web directory.

cd /var/www/html/relay
wget https://7402.org/images/screend.png
exit

Clear browser history, then test by visiting https://example.net/relay/screend.png

iPhone and iPad

Install an SSH client app. I use Termius.

Software

Get sources from github and build. Note: this project is presently just a proof of concept. The code is in an unfinished state.

ssh pi@r.r.r.r
mkdir Projects
cd Projects
git clone https://github.com/7402/time-screen.git
cd time-screen

# install library
sudo apt-get install libcairo2-dev

# build development version [change example.net to server domain]
# g++ -DDEV -DSERVER=example.net -Wall -g time-screen.cpp note.cpp -I/usr/include/cairo -L/usr/lib/arm-linux-gnueabihf/ -lcairo -o time-screend

# build release / installed version [change example.net to server domain]
# g++ -DSERVER=example.net -Wall -g time-screen.cpp note.cpp -I/usr/include/cairo -L/usr/lib/arm-linux-gnueabihf/ -lcairo -o time-screen

Copy the notes.txt file to the Projects directory. Edit the ~/Projects/notes.txt file to set desired message display

cp ~/Projects/time-screen/notes.txt ~/Projects/notes.txt
vi ~/Projects/notes.txt

To test the development version, append to the command line the number of seconds for which it should run.

# run for 20 seconds
./time-screen 20

Edit the script run-screen to start time-screen.

sudo vi /usr/local/bin/run-screen

Replace the contents with this:


#!/bin/bash

# turn off blinking cursor
sudo su - -c "echo 0 > /sys/class/graphics/fbcon/cursor_blink"

# establish ssh tunnel
autossh -M 9927 -fN -o "PubkeyAuthentication=yes" -o "StrictHostKeyChecking=false" -o "PasswordAuthentication=no" -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" -R 9922:localhost:22 relay@xfloat.net

# run time-screen
/home/pi/Projects/time-screen/time-screen

Reboot. The time-screen program should start automatically. The image at https://example.net/relay/screen.png should update automatically whenever the messages change.

Disable the service.

ssh pi@r.r.r.r
sudo systemctl enable run-screen.service
sudo reboot

Create development versions of run-screen and run-screen.service that can run without interfering with the installed version.

Create the script run-screend to start time-screend.

ssh pi@r.r.r.r
sudo vi /usr/local/bin/run-screend

Insert this: (Note use of alternate port numbers 8822 and 8827!)


#!/bin/bash

# turn off blinking cursor
sudo su - -c "echo 0 > /sys/class/graphics/fbcon/cursor_blink"

# establish ssh tunnel
autossh -M 8827 -fN -o "PubkeyAuthentication=yes" -o "StrictHostKeyChecking=false" -o "PasswordAuthentication=no" -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" -R 8822:localhost:22 relay@xfloat.net

# run time-screend
/home/pi/Projects/time-screen/time-screend

Create a service run-screend.service to start run-screend at boot.


sudo vi /etc/systemd/system/run-screend.service
# and insert text:

[Unit]
After=default.target

[Service]
Type=simple
User=pi
ExecStart=/bin/bash /usr/local/bin/run-screend
RemainAfterExit=yes

[Install]
WantedBy=default.target

Enable the service and reboot.

sudo systemctl enable run-screend.service
sudo reboot

The time-screend program should start automatically. The image at https://example.net/relay/screend.png should update automatically whenever the messages change.

Disable the service.

ssh pi@r.r.r.r
sudo systemctl disable run-screend.service
sudo reboot

Shut down the Raspberry Pi.

ssh pi@r.r.r.r
sudo poweroff

Make a backup of the SD card. Remove the card from the Raspberry Pi and insert it in the reader attached to the Mac. launch the macOS Terminal program. Figure out the drive path by using the diskutil command.

diskutil list

Look for a disk that is only 16GB in size, e.g.:


/dev/disk4 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk4
   1:             Windows_FAT_32 boot                    45.0 MB    disk4s1
   2:                      Linux                         15.9 GB    disk4s2

Use the dd to make a copy on the Mac. Be careful with dd; type the wrong thing and you can wipe out your computer's disk.

# replace disk4 with the correct disk number!
# sudo dd if=/dev/disk4 of=~/raspi-time-screen-sd-card.dmg

It takes an hour or so. When it's done, use the macOS Disk Utility to compress it.

Possible Future Improvements

Conclusion

This is not a product. It's not even a great open-source project. I don't recommend anyone trying to make it into a product unless they have a tech support system that can deliver cost-effective networking support to unsophisticated users of niche devices.

But it is an example of what someone with DIY software skills can do to solve a problem. If it inspires someone else to try something similar, I'll be happy. (And if you want me to document the source code some more, send me email!)