MyRaspberryAndMe

Tinkering with Raspberry (and other things)

Real Time Clock (reloaded)

4 Comments

I could not stop thinking about the RTC and the Raspberry. In my personal setup, the RTC is not always connected to the Raspberry, so it would not make sense to throw away the fake hardware clock and rely on the RTC only. I needed a solution that was most flexible and with the smallest possible overhead.

The solution I came up with is to have a script check for the RTC on startup and create the device only when the RTC is there. This means tinkering around with “autostart” functionality and I am not going to edit rc.local, but do it the more correct and formal way. So if you want to learn how to run scripts on boot/reboot read on!

What was the problem again?

The RTC is not always connected to my Raspberry. So it would not make sense to always try and set the system time from the RTC. Second, the NTP server may be more accurate than the RTC and third, what, if there is no NTP?

So it boils down to the following scenario:

  1. On boot/reboot check if the network is present.
  2. Then check, if the NTP services are running.
  3. Then look for the Real Time Clock and if it’s there, create the necessary devices.
  4. If there is NTP and an RTC, then set the RTC-time to the NTP time
  5. If there is no NTP but an RTC, set the system time FROM the RTC-time
  6. If there is no NTP and no RTC, use the fake hardware clock.

 

And again, it’s much more complicated as it seems. One case is not covered here: Imagine what happens if there is no NTP and the RTC that is connected does have the wrong time (because it is installed for the first time, the battery got changed, etc.). Then the system time will be set to the wrong time.
So, if you install the RTC the first time, set the correct time manually. Same if you needed to change the battery.

Script logic

I wrote a shell script that covers the functionality from above. It relies on “nmap“, a free network scanner. This package is not included in the standard distribution so it needs to be installed. For reasons I can’t see, installing nmap would install numerous other packages I do not need (or I do not want at all). To inhibit this, we are going to use apt-get with a parameter that tells apt to only install the file specified and not everything that is “recommended”. As always, it is a good idea to do an apt-get update and apt-get upgrade beforehand.

From the prompt type

sudo apt-get --no-install-recommends install nmap

The following bash script does everything that is needed to cover points 1.-6. If you are going to use it, please leave me a comment and drop me a note. Thank you! I have added extensive comments so it should be pretty self explaining.

#!/bin/bash

# some global variables
NTP_RUNNING=1     # false as default
RTC_THERE=1       # false

fnCheckNTP()
{
    # ping default gateway as quick check to see if network is up and running
    # what's going on here (from the inside out):
    #    ip r               gets the default route
    #    grep default       for which we search in the output from "ip r"
    #    cut -d ' ' -f 3    and then cut the third entry in this output line (-f 3) and surround it with spaces (-d ' ')
    # this leaves only the ip address which is then used in the ping command:
    #    ping -q -w 1 -c 1 ip_address_extracted
    #                       -q      ping is running quiet
    #                       -w 1    timeout is 1 second
    #                       -c 1    no more than one reply from ip_address is needed
    ping -q -w 1 -c 1 $(ip r | grep default | cut -d ' ' -f 3) > /dev/null 2>&1
    # the next line assigns the output from the last command (that is ping...) to variable gw
    ping_up=$?

    if [ $ping_up -eq 0 ]; then
        # network is running. in shell 0=true, 1=false!
echo "[**RTC**] Network is running"
        # check all defined NTP servers
        # grep server /etc/ntp.conf     get all occurencies of "server" in file /etc/ntp.conf
        # -v "#"                        without comments
        # -v "127.127.1."               without local clock
        # awk...                        get 2nd entry per line which is the ntp server name
        for ntp_server in $(grep server /etc/ntp.conf | grep -v "#"| grep -v "127.127.1." | awk {'print $2'})
        do
            # with every entry from above command do the following:
            # -sU           scan UDP only
            # -p123         on port 123
            # -oG           return a "grepable" format
            # grep Ports    find string "Ports" in nmap's output
            # grep open     and only ports that are "open"
            # awk..         and get the 5th entry in every line returned
            ntp=$(nmap -sU -p123 -oG - $ntp_server | grep Ports | grep open | awk {'print $5'})
            ntp_up=$?
            # check if ntp is there
            if [ $ntp_up -eq 0 ] && [ -n "$ntp" ]; then
                NTP_RUNNING=0     # ntp is here
                echo "[**RTC**]  NTP is running"
            else
                NTP_RUNNING=1
            fi
        done
    else
        NTP_RUNNING=1
    fi

    return $NTP_RUNNING
}

fnCheckCreateRTC()
{
    # if device already exists we can return OK state
    if [ -e /dev/rtc0 ]; then
        RTC_THERE=0
        echo "[**RTC**] Clock already exists."
        return $RTC_THERE
    fi

    # load modules i2c and rtc if not already there
    modprobe i2c-dev
    modprobe rtc-ds1307

    # to make this script compatible with all Raspberry revisions we iterate over both i2c buses
    # between revisions the bus ID changed
    for bus in $(ls -d /sys/bus/i2c/devices/i2c-*);
    do
        # rtc with 1307 chip does have ID 0x68 so this is fix
        # try creating new device on bus
        echo ds1307 0x68 >> $bus/new_device;
        # probe for new device
        if [ -e /dev/rtc0 ];then
            RTC_THERE=0
            echo "[**RTC**] RTC created"
            break
        else
            # device has not been created so we remove 0x68 from bus
            echo 0x68 >> $bus/delete_device
            RTC_THERE=1
        fi
    done

    return $RTC_THERE
}

if fnCheckCreateRTC; then
    if fnCheckNTP; then
        echo "[**RTC**] Setting system time (NTP) to RTC"
        hwclock -w
    else
        echo "[**RTC**] Setting RTC time as system time"
        hwclock -s
    fi
else
    echo "[**RTC**] No RTC found."
fi

To run this script you need to make it executable. This is done with the chmod command. Use “chmod a+x filename” to make it executable by everyone, for example.

How about “autostart”

When you google about starting things on boot, many (in fact, most) people tell you to call your scripts from a file called “rc.local”. This is a possibility and it is working. However, I do not like editing “system files”. And there is a better alternative that lets us do different things on different runlevels. I find this to be the cleaner solution, as the necessary additions and symbolic links are created by a specialized command. And thus can be removed with the same command.

Imagine a runlevel to be a specific state of the operating system. The following runlevels are usually defined:

0    System Halt, safe to power down
1    Single User (Maintenance mode)
2    Multiuser without NFS (Network File System) <-- Standard runlevel on Raspberry Pi
3    Multiple Users (Standard runlevel on most Unix systems. NOT Raspberry!)
4    not defined/user definable
5    Multiuser with graphical interface
6    Reboot

All these runlevels require different actions to be called. You can check the runlevel your Raspberry is currently in by typing the following command:


runlevel

What to do in which runlevel is controlled by the files in the directories “/etc/rcX.d” (where X is the number of the runlevel). If you look into these directories you will see that there are no actual files in there, but links to files within the directory “/etc/init.d“. A more thorough guide to the things happening on startup can be found here and here.

So all that needs to be done is add a script to the /etc/init.d-directory that calls the above RTC/NTP script and then add symbolic links into all directories /etc/rcX.d. Sounds like a lot of work. But, as I said earlier, there is a program that can help us a great way. But first things first. We need to move the RTC/NTP script to a place where it can be found by the system. Leaving it in our personal user-directory is not a good idea. I recommend moving or copying the file to “/usr/local/bin”. Second, we need a script in init.d that calls RTC/NTP. These init.d-scripts do have a certain syntax.

I called my script “startRTC.sh” and it looks like this:

#!/bin/bash

### BEGIN INIT INFO
# Provides: RTC-clock
# Required-Start: networking ntp
# Required-Stop:
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Checks for NTP and RTC and creates devices
# Description:
### END INIT INFO

# Check runlevel
case "$1" in
start)
    /usr/local/bin/checkRTC.sh
    echo "Check and Start RTC"
    ;;
stop)
    # do nothing
    ;;
restart)
    /usr/local/bin/checkRTC.sh
    echo "Check and Start RTC"
    ;;
*)
    echo "Usage: /etc/init.d/startRTC {start|restart}"
    exit 1
    ;;
esac
exit 0

Make the file executable and copy or move it to /etc/init.d. Now we need to create the symbolink links in then rcX.d directories. The command update-rc.d makes this very easy. The complete syntax can be seen at the man page. With the command


sudo update-rc.d startRTC.sh defaults

all the links in the different runlevel directories are created. Check it out with “ls -al /etc/rc2.d”. There should be one entry like this:

lrwxrwxrwx   1 root root   21 Jul  9 17:11 S01startRTC.sh -> ../init.d/startRTC.sh

And that’s it. Safely shutdown the Raspberry, remove power, connect RTC and restart. You should see some messages from the above scripts. If the RTC is not connected, everything will be running as before. But in case an RTC gets detected we now have a clock backup.

On my machine the boot messages look like this:

rtc-ntp

 

Advertisements

4 thoughts on “Real Time Clock (reloaded)

  1. Isn’t there a redundancy here ? Since required line

    # Required-Start: networking ntp

    indicates that ntp needs to be up, why recheck that in the script ?

    Is this meant to account for times when ntp crashes ?

    Also, I can’t find the source, but I roughly remember reading somewhere that when resyncing, the time doesn’t jump quickly from old and wrong value to correct value, but rather moves progressively, to avoid some file access times issues. If it is so, then could there be a period for which ntp is up but the system time is not perfectly synced yet ?

    • That’s a good point. And frankly I do not know for sure.

      I do think that “#Require-Start: networking…” tells the init-system to run the script named “networking” in /etc/init.d beforehand. But this does not mean that whatever the script does was successful.
      So it is required that the init-system did run “ntp”, but that gives you no guarantee that ntp really is.

      But again, I may be wrong here. So in doubt I’d go with the redundancy.

      I never heard about the system time moving progressively, so, sorry, no idea if there were any syncing issues. But if you find the source again, please let me know!

  2. thank you by tutorial.
    i want to ask,
    where are you write text????
    you donot say file save.
    Thank you.

    • I do not understand the question, sorry. The two code segments need to be saved as files and named correctly. Then make sure they are made executable. The files themselves do write something to the console (check for the echo-command in the sources).