How to get ByVac BV4627 relay controller working with bash (advanced)

This how-to has multiple pages. This page is about advanced configuration. Previous, intro page is available here

Important note 1: As this page is a bit more advanced configuration, it’s expected also a bit advanced knowledge about how to use Linux shell and tools like cp, chown, chmod etc. All steps/commands are not fully explained or written out.

Important note 2: If you are using some other USB-to-serial adapters or something else with device name ttyUSB* you may encounter strange behaviour – when removing device, all relay-services are restarted. To avoid it, remove second UDEV rule.

Important note 3: If your computer default home directory location is other than /home, I suggest be careful and check scripts below for conflicts. Same thing applies if you want to rename scripts or users – scripts may stop working. Script names must be unique as names are used in some places to check scripts running status instead of pid/lock files (what I personally don’t like).

If you managed to start controller and manipulate relays with root user then its time to:

  1. Create some different (non-root) user account for relay- related stuff because of security reasons.
  2. Start service script automatically.
  3. Include support for multiple boards.

1. User account

Lets create system account named relay. This user must be set as owner of all USB-serial devices later with UDEV rules. Command creates also homedir for user relay, it should be something like /home/relay what I will use and refer to later as relay user home directory.

sudo useradd -r -m relay

 

2. Automatic start possibilities

It would be too long story if I would write here why I did not use first two options, so I just skip it for now.

I selected mix of the last two. I will use monitoring script which is executed with cron job.  It checks from time to time is the service script(-s) running and if not, it starts the script(-s). UDEV starts the same monitoring script when device is plugged in. Monitoring script must be put into relay user homedir and named relay_monitor.sh . It must be executable and owned by relay user. Script is in the end of this page.

3. Support for multiple boards

If you did not notice it before, scripts in intro page did not support multiple boards(controllers). That limited number of usable relays with 8. Here I try to fix it as I definitely need to switch more things in the future.

Luckily, it seems that at least some FTDI USB-serial IC-s have serial numbers what could be used to differentiate between multiple boards. They seem to be too short to be really unique but it is good enough. To see the “serial number” of each board (or IC), plug them in (one-by-one) to your RPi or PC and run command below. Change device name in command if needed.

sudo udevadm info --name=/dev/ttyUSB0 --attribute-walk |grep  ATTRS{serial}

Output should be something like this:

ATTRS{serial}==”A1024WGU
ATTRS{serial}==”bcm2708_usb”

If you get multiple “serials”, like I did on my RPi, use the first one in the top. Others are parent device attributes, which in this point are not important. Make a note of all boards serials for later usage – these must be put into one of the scripts. It would be wise to put a sticker with serial on each board. If you omit grep in command above you may see also few other important attributes like ATTRS{idVendor}==”0403″ and ATTRS{idProduct}==”6001″ what I intend also to use to create UDEV rule.

So, now it’s time to create UDEV rule. What it generally does is that it creates symbolic links to each device which contain serial in its name. It also starts monitoring script. Can’t use “idVendor” or “idProduct” attributes on device removal rule as after device is removed, these attributes can’t be read anymore. So, removal rule has only device name in it, if some of devices are disconnected, ALL services are restarted to avoid issues with changing device names or hanging scripts. By this point its important to know user account name under which all relay-related stuff is running (relay) and monitoring script location (/home/relay/relay_monitor.sh). If you are using something different, change them in UDEV rules.

UDEV rules:

I created file /etc/udev/rules.d/99-relay.rules and put there following contents:

# rule for FT232. creates symlink serialrelay under dev which is used later and runs monitoring script
# Bus 001 Device 013: ID 0403:6001 Future Technology Devices International, Ltd FT232 USB-Serial (UART) IC
ACTION=="add", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", OWNER="relay", GROUP="relay", MODE="0660", SYMLINK+="serialrelay_%s{serial}", RUN+="/bin/su - relay -c '/home/relay/relay_monitor.sh &'"
ACTION=="remove", KERNEL=="ttyUSB*", RUN+="/bin/su - relay -c '/home/relay/relay_monitor.sh restart &'"

Now its time to reload UDEV rules with following command and plug in your BV4627 to USB port. If it was already connected, disconnect and reconnect.

sudo udevadm control --reload-rules

NOTE: If you have not created scripts yet what are in the end of this page, this reloading should just create different symbolic links for each BV4627 board. Check it with command “ls -la /dev/ |grep ttyUSB“. If reloading the rules have no effect, consider restart. You should see something like this:

lrwxrwxrwx  1 root  root           7 Jun 24 14:52 serialrelay_A1024WGU -> ttyUSB0
crw-rw—T  1 relay dialout 188,   0 Jun 24 14:52 ttyUSB0

As visible above, UDEV rule created symbolic link which has serial number in the name. Also owner/group and permissions are changed. During first tests I had only one controller in my hands that’s why there is listed only one device above. UDEV part is done with this.

Script:  /home/relay/relay_monitor.sh

#!/bin/bash

# This script is used to monitor relay-board services and start them if needed
# Argo Ellisson 2014, BashPi.org, usage licence: GNU General Public License (GPL) Version 3
# version 1.1

# First check is some other monitoring process already running and exit if it is
pidof -x -o $ relay_monitor.sh >& /dev/null && exit 0

# functions

# gets user homedir
get_homedir() {
       getent passwd ${1} |cut -d':' -f6
}

# relay user
RELAYUSER="relay"
RELAYHOME=`get_homedir ${RELAYUSER}`

# if restart parameter is added, restarts all services no matter what the status
# UDEV runs it when device is removed
if [ $# -eq 1 -a "${1}" = "restart" ]; then
        echo "restarting all processes"
        RESTART=1
        pkill -f relay_service.sh
        pkill -f commands.lst
        rm -f /dev/shm/*commands.lst >& /dev/null
        sleep 5
fi

# FOR CYCLE to check processes for each board found

for SERIALDEVICE in `ls /dev/serialrelay_* 2>/dev/null`; do 

        echo "Checking ${SERIALDEVICE} processes"

        DEVNAME=`basename ${SERIALDEVICE}`
        SERIAL=`echo ${DEVNAME}|cut -d'_' -f2`

        # check both processes 
        SERVICE=`ps -fu ${RELAYUSER} | grep "relay_service.sh ${DEVNAME}"  | grep -v grep |wc -l`
        TAIL=`ps -fu ${RELAYUSER} | grep "tail -f /dev/shm/${DEVNAME}_commands.lst" | grep -v grep |wc -l`

        if [ ${SERVICE} -eq 0 -o ${TAIL} -eq 0 ];then

                #killing all scripts what may be still running
                pkill -f ${DEVNAME}
                sleep 5

                ${RELAYHOME}/relay_service.sh ${DEVNAME} &

                # there is sleep in service script so this will compensate!
                sleep 10

                # now everything should either still run or was just started up
                # check again and echo results

                SERVICE=`ps -fu ${RELAYUSER} | grep "relay_service.sh ${DEVNAME}"  | grep -v grep |wc -l`
                TAIL=`ps -fu ${RELAYUSER} | grep "tail -f /dev/shm/${DEVNAME}_commands.lst" | grep -v grep |wc -l`

                if [ ${SERVICE} -eq 0 -o ${TAIL} -eq 0 ];then

                        echo "Board ${SERIAL} not working"

                else 

                        echo "Board ${SERIAL} ok"

                fi

        else                                                                                                                                                                                                                                                                         
                # already ok                                                                                                                                                                               
                echo "Board ${SERIAL} ok"
                                                                                                                                                                                                                                                                             
        fi

done

exit 0

 

Script:  /home/relay/relay_service.sh

#!/bin/bash

# this script generally starts connection with relayboard and forwards commands
# Argo Ellisson 2014, BashPi.org, usage licence: GNU General Public License (GPL) Version 3
# version 1.2

usage() {
    echo "This script starts service with correct relay-controller"
    echo "usage: relay_service.sh <device name>"
    echo "do not include path in device name"
    exit 1
}

if [ $# -eq 1 ]; then

# this device should have been created by udev rule - following symlink should exist and point to ttyUSB0 or something else
SERIALDEVICE="/dev/${1}"

# file for passing commands to relays - created in ramdisk for performance reasons
COMMANDFILE="/dev/shm/${1}_commands.lst"

# cycle to try keep things running - not essential
while [ true ]; do

        # what code below generally does is that it configures serialdevice using stty and then forwards there all commands what will be written to COMMANDFILE

        # check does SERIALDEVICE exist

        if [ -c ${SERIALDEVICE} ]; then

           # configure serialdevice
           # these parameters may change if different device is used. 
           # Speed 9600 is enough for relayboard

           stty -F ${SERIALDEVICE} 9600 -parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8 -opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0 -isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

           # open serialdevice for writing and assign file descriptor 3 to it
           exec 3>${SERIALDEVICE}
           # could be opened also for reading as below in case we want some feedback from relayboard. Not in current scope.
           # exec 3<>${SERIALDEVICE}

           # send few Carriage Returns / CR-s to serialdevice to initiate connection
           # this is optional step but we are not taking any chances
           echo -en "\xD" >&3
           sleep 2
           echo -en "\xD" >&3
           sleep 2
           echo -en "\xD" >&3
           sleep 2

           # set up commandfile - this is what we use to get commands from one or many other scripts
           echo -en "\xD" > ${COMMANDFILE}
           chmod 666 ${COMMANDFILE}
   
           # tail commandfile and redirect results to file descriptor 3 which is serialdevice
           tail -f ${COMMANDFILE} >&3
   
           # close file descriptor if tail exits
           exec 3<&-

        else

           echo "${SERIALDEVICE} does not exist or is not character device"

        fi

        sleep 60

done
else

        usage

fi

 

Script:  /home/relay/relay.sh

#!/bin/bash

# this script is a wrapper for BV4627f relay board
# It generally translates plain simple on-off commands to characters what relayboard expects
# also it provides on-off functionality with duration 
# Argo Ellisson 2014, BashPi.org, usage licence: GNU General Public License (GPL) Version 3
# version 1.3

usage() {
echo "USAGE: relay.sh <controller> <relay> <command> <delay>"
echo "WHERE:"
echo "Controller: BV4627 FTDI chip serial number or alias in this script"
echo "Relay: [a-h]|[A-H]"
echo "Command: on|off|onfor|offfor"
echo "Delay: number of seconds to after which to turn on or off relay [0-3600]"
echo "All options are mandatory, including DELAY. Use delay 0 for now."
exit 1
}


if [ $# -eq 4 ];then 

##### LOG #####
DATE=`date`
echo "${DATE} controller:${1} relay:${2}  op:${3} duration:${4} sec"
##### LOG END #####

# controller/board aliases
# add serial numbers below
case "${1}" in
        board1|heating)  BOARD="A1024WGU"
        ;;
        board2|lights)  BOARD="BOARD2SERIAL"
        ;;
        board3|access)  BOARD="BOARD3SERIAL"
        ;;
        *) BOARD=${1}
        ;;
esac

SERIALDEVICE="/dev/serialrelay_${BOARD}"
COMMANDFILE="/dev/shm/serialrelay_${BOARD}_commands.lst"

# check does device exist
if [ ! -c ${SERIALDEVICE} ]; then

        echo "Error: selected board is not connected or UDEV rules are not creating symlink ${SERIALDEVICE}. exiting!"
        exit 1

fi

# check does the commandfile exist and is writable - if not, nobody is listening also!
if [ ! -w ${COMMANDFILE} ]; then

        echo "Error: Commandfile does not exist or is not writable. Is service-sript started? exiting!"
        exit 1

fi


# relay selection
# this is a good place for relay aliases also - note "lights" below

case "${2}" in
        a|A|lights)  RELAY="\x41"
        ;;
        b|B)  RELAY="\x42"
        ;;
        c|C)  RELAY="\x43"
        ;;
        d|D)  RELAY="\x44"
        ;;
        e|E)  RELAY="\x45"
        ;;
        f|F)  RELAY="\x46" 
        ;;
        g|G)  RELAY="\x47" 
        ;;
        h|H)  RELAY="\x48"
        ;;
        *) echo "incorrect relay chosen"
        usage
        ;;
esac

# timer config. using it always as its simpler

if [ "${4}" != "0" ];then
        if echo "${4}" | grep -qE ^\-?[0-9]?\.?[0-9]+$; then
                # calculate time
                TIME=$(echo "${4}" |awk '{print int($1*13.6);}')
                if [ ${TIME} -gt 65000 ];then
                        echo "Delay is too big, max ~4700sec/79min/1.3hours"
                        usage
                fi
                if [ ${TIME} -lt 1 ];then
                        TIME=1
                fi
                # convert to hex
                DELAY=`echo -n "${TIME}" |od -t x1 |head -1|awk 'BEGIN{ORS=""}{ for (i=2;i<=NF;i++)print "\x"$i}'`

        else
                echo "Delay is not positive number!"
                usage
        fi

else
        DELAY="\x31"
fi

# Command selection - note that as duration is not supported by relayboard, we send actually 2 commands to relayboard in case of onfor/offfor commands 
# First command in case construct below and second command as USUAL
case "$3" in
        on|ON)  COMM="\x31"
        ;;
        off|OFF) COMM="\x30"
        ;;
        onfor|ONFOR) COMM="\x30"
        # onfor - usual command does timed switch off so first switch that relay on
        echo -en "\x1b\x5b\x31${RELAY}" >> ${COMMANDFILE}
        # note that USUAL command is actually "OFF" above
        ;;
        offfor|OFFFOR)  COMM="\x31"
        # offfor - usual command does timed switch on so first switch that relay off
        echo -en "\x1b\x5b\x30${RELAY}" >> ${COMMANDFILE}
        # note that USUAL command is actually "ON" above
        ;;
        *) echo "incorrect command"
        usage
        ;;
esac

# put USUAL command together / Comma \x2c / ESC \x1b / [ \x5b
COMMAND="\x1b\x5b${COMM}\x2c${DELAY}${RELAY}"

# DEBUG
#echo ${BOARD}
#echo ${RELAY}
#echo ${COMM}
#echo ${DELAY}
#echo ${COMMAND}

# echo command to outputfile
echo -en "${COMMAND}" >> ${COMMANDFILE}

exit 0

fi

usage

Dont forget to make scripts executable by user relay!

Cron job:

Add following to relay user crontab. It will run monitoring script with 10 minute intervals. Generally, this cron job should not be needed as UDEV rules do all the starting-restarting when devices are plugged or unplugged. It is just for the case if service scripts should die because of some strange reason.

# relay monitoring&startup script, duplicates UDEV rules
0-50/10 * * * * $HOME/relay_monitor.sh > /dev/null 2>&1

Thats it. It should be now work like a charm. Wait for cron job to start service script or disconnect board and connect again. Check processes with “ps -fu relay” and if services are running try out following command:

for r in a b c d e f g h; do /home/relay/relay.sh board1 ${r} onfor 1;done

 

Adding more boards – few notes after I managed to test scripts above with multiple boards.

  • Read this page from top to bottom, find place where are described how to get board serial numbers
  • Connect boards one by one, get serials and label boards. Optionally you can configure board aliases in relay.sh script under relay user. I created aliases as I found much more comfortable to remember board1-board4 than four random serial strings.
  • Now you can connect the lights in your house to relays and get world most expensive running lights with following command (4 boards connected).
while true; do for b in board1 board2 board3 board4; do for r in a b c d e f g h; do /home/relay/relay.sh ${b} ${r} onfor 0.1;done;done;done

Scripts in this page were tested on:

  • Raspberry Pi Model B 512MB RAM. OS: Raspbian Wheezy, released in 2014-01-07. All updates installed on 1. september 2014.
  • Almost random pc with x86-64 architecture, running Ubuntu 12.04 (precise)

If you found this useful, say thanks, click on some banners or donate, I can always use some beer money.

NEXT How to get ByVac BV4627 relay controller working with bash (automated)