1/ Raspberry's configuration

 

I have downloaded the "Raspbian Stretch with Desktop" version from the Rasperry site and copy the image with Win32DiskImager on a 16Go micro SDHC card (class 10, up to 100 MB/s, "A1").

I have let the raspberry boot on it (expand the filesystem), then I have adapted  the local configs (keyboard, TimeZone, no splash ..) and allowed SSH login .

 

After that, I have added some extra info in the /boot/cmdline.txt file :

- logo.nologo         to hide the 4 raspberry logos on the boot screen

- consoleblank=0    to turn screen blanking off completely

- net.ifnames=0    to restore original ethernet names

 

I have then removed all the apps I don't use (from here).

sudo apt-get --purge remove wolfram-engine bluej greenfoot nodered nuscratch scratch sonic-pi libreoffice* claws-mail claws-mail-i18n minecraft-pi python-pygame

sudo apt-get autoremove --purge

sudo apt-get update && sudo apt-get upgrade -y

 

I have disabled the X screen blanking/sleep (screensaver) in changing one line in /etc/lightdm/lightdm.conf, below the [Seat:*] section

#xserver-command=X     to    xserver-command=X -s 0 -dpms

(ie. blanking timeout to 0 and turn off the display power management signaling)

 

I have installed nuitka to compile my python scripts:

sudo pip3 install nuitka

 

I have created a devDH dir (my dev dir) under /home/pi.

 

I have written compile file under /home/pi/devDH to compile my python scripts (and chmod u+x on it; usage: ./compile myPythonScript Option)

 

compile________________________________________________________________

#!/bin/bash

nuitka --recurse-all --python-version=3.5 --remove-output  $2 $1.py

if [ -d $1.build ]; then

    rm -r $1.build

fi

if [ -d $1.dist ]; then

    cp -u -r ./$1.dist/* .

    rm -r ./$1.dist

fi

if [ -f $1.exe ]; then

    chmod u+x ./$1.exe

fi

______________________________________________________________________________________

 

 

I have added a setConfig.ini under /boot dir whose utility will be described later:

 

setConfig.ini _________________________________________________________________

[SETTINGS]

done = 0

stoplaunch = 0

debug = 0

verbose = b11

 

[NET]

base = 192.168.0.

node = oneNameInTheAddressSectionBelow

links = Name(s)InTheAddressSectionBelowSeparatedByCommaOrEmpty

port_base = 17700

wifi = 0

ssid =

psk =

static = 1

gateway = 1

dns = 3

 

[ADDRESS]

fs_hat = 60

rpi_pres = 61

rpi_out = 62

rpi_disp = 63

master = 64

show = 65

______________________________________________________________________________________

 

The advantage of this file (as in /boot)  is it can be modified directly on the microSD card (under windows for example) without the need to be under Raspbian. As guessed, it helps for the net configuration of the raspberrys.

 

 

I have added a file launch.desktop under /home/pi/.config/autostart to launch my app automatically after boot:

 

launch.desktop__________________________________________________________

[Desktop Entry]

Type=Application

Name=launch

NoDisplay=true

Exec=/home/pi/devDH/launch.sh

______________________________________________________________________________________

 

For the Raspberry "Outputs", I add at the end of .bashrc the line /home/pi/devDH/launch.sh as it doesn't need the graphic (startx) environment (just CLI - "Command Line Interface" - mode).

 

with launch.sh (under /home/pi/devDH and chmod u+x on it):

launch.sh____________________________________________________________________

#!/bin/bash

cd /home/pi/devDH

# ==========================

#  LAUNCH PROGRAM IF NOT SSH

# ==========================

if [ "$(echo $(who am i) | cut -c 4-6)" != "pts" ]; then

    # ======

    # CONFIG

    # ======

    clear

    echo -e -n "\nSet config ..."

    sudo ./setConfig.exe

    let exval=$?

    echo -e " Ok\n"

    # ---------

    if [ $exval -eq 1 ]; then

        progName="NameOfTheExeProgramToLaunch"

        # ===================

        # Wait for connection

        # ===================

        echo -e -n "\nConnection ..."

        IP=$(hostname -I) || true

        until [ "$IP" ]; do

            IP=$(hostname -I) || true

        done

        echo -e "\b\b\b => IP address = $IP\n"

        # ==============

        # LAUNCH PROGRAM

        # ==============

        # ----- PROGRAM -----

        while [ $exval -eq 1 ]; do

            echo -e "\nLaunching program\n"

            debug=`cat /boot/setConfig.ini | grep debug | cut -d'=' -f2`

            if [ $((debug)) -eq 1 ]; then

                # ----- through lxterminal if in debug mode

                lxterminal -e ./$progName.exe

                let exval=0

            else

                ./$progName.exe

                let exval=$?

            fi

        done

        if [ $exval -eq 3 ]; then

            sudo halt

        elif [ $exval -eq 2 ]; then

            sudo reboot

        fi

    elif [ $exval -eq 2 ]; then

        echo -e -n "\nReboot ..."

        sudo reboot

    fi

fi

______________________________________________________________________________________

 

Principle: I use the exit codes of the programs (exval) to choose either to stop (0) or reboot the program (1), reboot (2) or shutdown (3) the raspberrys.

 

Also, depending on the attached screen, some lines to change or add in the /boot/config.txt

hdmi_force_hotplug=1                         force the HDMI output even if not detected

hdmi_group=2                                 see here

hdmi_mode=87                                 see here

hdmi_cvt={W} {H} {Hz} {ratio} 0 0 0          see here

max_usb_current=1                            for max current in the USB plug, to power a screen for example

display_rotate=2                             if needed to rotate the screen of 180° (here)

 

 

If needed to add some extra fonts (for Zen@Terra App), copy the fonts (.TTF, .OTF, .TTC) to /usr/local/share/fonts

 

Finally, for the tactile screen (Zen@Terra App):

sudo apt-get install xserver-xorg-input-evdev xinput-calibrator -y

sudo mv /usr/share/X11/xorg.conf.d/10-evdev.conf /usr/share/X11/xorg.conf.d/45-evdev.conf

(ie. renaming 10-evdev.conf to 45-evdev.conf in order to get it to load after 40-libinput.conf so Xinput_calibrator could calibrates and save the calibration data into /usr/share/X11/xorg.conf.d/99-calibration.conf)

 

 

 

2/ Common python function

 

2.1/ Set Configuration

 

As seen above, I wrote a setConfig exe file to configure automatically after boot the rapsberry, depending on the done field in the /boot/setConfig.ini file

 

setConfig.py_________________________________________________________________

#!/usr/bin/python3.5

# -*- coding: utf-8 -*-

 

""" setConfig

    =========

    Set raspberry configuration

""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.1'

__date__ = 'Nov 2017'

 

# ======================================================================

#                                MAIN

# ======================================================================

if __name__ == "__main__":

   

    from configparser import ConfigParser

    from  os import name as osname

 

    dhMark = '@ddevDH'

 

    iniFile = '/boot/setConfig.ini'

    dhcpcdFile = '/etc/dhcpcd.conf'

    wifiFile = '/etc/wpa_supplicant/wpa_supplicant.conf'

    hostfile1 = '/etc/hostname'

    hostfile2 = '/etc/hosts'

 

    if osname == 'nt':

        # ----- changes for windows env to test

        ptxt = '.txt'

        iniFile = iniFile.replace('/', '_')

        dhcpcdFile = dhcpcdFile.replace('/', '_')

        wifiFile = wifiFile.replace('/', '_')

        hostfile1 = hostfile1.replace('/', '_') + ptxt

        hostfile2 = hostfile2.replace('/', '_') + ptxt

 

    config = ConfigParser()

    config.filename = iniFile

    config.read(config.filename, encoding='utf-8')

 

    SETTINGS = config['SETTINGS']

    NET = config['NET']

   

    if bool(int(SETTINGS['done'])):

        exitVal = 1 # ----- continue, nothing to configure

    else:

        # ===== set the hostname

        hostname = NET['node'].strip().title()

        IdRecv = int(config['ADDRESS'][hostname])

        netBase = NET['base'].strip()

           

        wifiFlag = bool(int(NET['wifi']))

 

        # ***** ==== read DHCPCD.CONF

        # ----- read the  file

        with open(dhcpcdFile, 'rt') as dhcpcd: txt = dhcpcd.readlines()

           

        # ----- copy until "@ddevDH"

        txtr = []

        dhMarkFlag = False

        for t in txt:

            txtr.append(t)

            if dhMark in t:

                dhMarkFlag = True

                break

 

        if not dhMarkFlag: txtr.append('# {}\n'.format(dhMark))

 

        if bool(int(NET['static'])):

            # ----- ajout addresse

            if wifiFlag:

                txtr.append('interface wlan0\n')

            else:

                txtr.append('interface eth0\n')

            txtr.append('static ip_address={}{}/24\n'.format(netBase, IdRecv))

            txtr.append('static routers={}{}\n'.format(netBase, NET['gateway'].strip()))

            dns = NET['dns'].strip()

            if len(dns) > 3:

                txtr.append('static domain_name_servers={} 8.8.8.8\n'.format(dns))

            else:

                txtr.append('static domain_name_servers={}{} 8.8.8.8\n'.format(netBase, dns))

           

        # ***** ===== rewrite ----- DHCPCD.CONF

        with open(dhcpcdFile, 'wt') as dhcpcd: dhcpcd.writelines(txtr)

 

        # §§§§§ ===== read WPA_SUPPLICANT.CONF

        # ----- read the  file

        with open(wifiFile, 'rt') as wifi: txt = wifi.readlines()

           

        # ----- copy until "@ddevDH"

        txtr = []

        dhMarkFlag = False

        for t in txt:

            txtr.append(t)

            if dhMark in t:

                dhMarkFlag = True

                break

 

        if not dhMarkFlag: txtr.append('# {}\n'.format(dhMark))

 

        if wifiFlag:

            txtr.append('network={\n')

            txtr.append('ssid="{}"\n'.format(NET['ssid']))

            txtr.append('psk="{}"\n'.format(NET['psk']))

            txtr.append('}\n')

 

        # §§§§§ ===== rewrite WPA_SUPPLICANT.CONF

        with open(wifiFile, 'wt') as wifi: wifi.writelines(txtr)

 

        # ##### ===== rewrite "hostname"

        with open(hostfile1, 'wt') as hst1: hst1.write(hostname + '\n')

 

        # $$$$$ =====  read "hosts"

        with open(hostfile2, 'rt') as hst2: txt = hst2.readlines()

 

        lclIp = '127.0.1.1'

        txtr = []

        for t in txt:

            if lclIp in t:

                txtr.append('{}       {}\n'.format(lclIp, hostname))

            else:

                txtr.append(t)

 

        # $$$$$ =====  rewrite "hosts"

        with open(hostfile2, 'wt') as hst2: hst2.writelines(txtr)

 

        # ---- set the modification flag in setConfig.ini

        config['SETTINGS']['done'] = '1'

        with open(config.filename, 'wt', encoding='utf-8') as cfgfile:

            config.write(cfgfile)

 

        exitVal = 2 # ----- to reboot (see launch.sh)

       

    if bool(int(SETTINGS['stoplaunch'])):

        exitVal = 0       

   

    exit(exitVal)

______________________________________________________________________________________

 

It modifies the files /boot/setConfig.ini, /etc/dhcpcd.conf, /etc/wpa_supplicant/wpa_supplicant.conf, /etc/hostname and /etc/hosts with the right parameters read from /boot/setConfig.ini.

The setConfig.exe file was built with ./compile setConfig in /home/pi/devDH.

 

 

2.2/ TCP/IP manager

 

As seen on the general presentation (Part 1), all the PCs/Raspberrys "talk" to each other thanks to an ethernet link. I choose a TCP/IP protocol which is very reliable.

 

defTCPIP.py_________________________________________________________________

#!/usr/bin/python3.5

# -*- coding: utf-8 -*-

 

""" TCPIP link manager

    ==================

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.2'

__date__ = 'Nov 2017'

 

import socket, select

from multiprocessing import Process, Queue

from queue import Empty

 

BUF_SIZE = 512          # large enough fro my app

 

# -----------------------------------------------------------------------------

#                              TCP Send

class TCPLink():

    """

    TCPLink(IPtoNnode, IPtoNlinks, portBase, feedback=None)

        portBase = port base to define receive and send port from address

        IPtoN = {'192.168.1.xx':'Name1', '192.168.1.xy':'Name2', ... }

        feedback = None if managed by the process TCPLink

            otherwise, it's a queue IN for the calling process (q.put_nowait)

                       (q.get in the calling process)

            from sender:

             ['TCP:snt1', msgs,     toHost] when msgs successfully sent

             ['TCP:snt0', msgs,     toHost] when msgs failed to send   

             ['TCP:out', 'TCPSend', toHost] when 'TCPSend' exited

            from reicever:

             ['TCP:rcv1', msgs, fromHost] when msgs received from FromHost

             ['TCP:rcv0', msgs, addr] when msgs received from addr

             ['TCP:rcv?', msgs, ''] when received msgs not in good format 

             ['TCP:out', 'TCPRecv', '<:>'] when 'TCPRecv' exited

    """

    def __init__(self, IPtoNnode, IPtoNlinks, portBase, feedback=None):

       

        # ----- queue to send to

        if feedback is None:

            self.qFeedback = Queue()

            feedback = self.qFeedback.put_nowait

        else:

            self.qFeedback = None

        self.feedback = feedback

       

        # ----- get IP Addr from hosts

        self.IPtoN = IPtoNlinks

        self.portBase = portBase

 

        # ----- set local address

        self.myAddr, self.hostName = list(IPtoNnode.items())[0]

        self.portRecv = portBase + int(self.myAddr.split('.')[-1])

        print('\tConnecting "{}" ({}, {}) to'\

              .format(self.hostName, self.myAddr, self.portRecv))

 

        # ----- receiver process

        self.pRecv = Process(target=TCPRecv, \

                             args=(self.portRecv, IPtoNlinks, feedback))

        self.pRecv.start()

       

        # ----- sender processes

        self.N_AdportQueProcStat = {}

        for addr, host in IPtoNlinks.items():

            qSend = Queue()

            port = portBase + int(addr.split('.')[-1])

            if port == portBase:

                # ----- local host

                addr = socket.gethostbyname('localhost')

                port += int(addr.split('.')[-1])

            print('\t\t"{}" : ( {}, {} )'.format(host, addr, port))

            pSend = Process(target=TCPSend, \

                            args=(addr, port, host, qSend.get, \

                                        feedback, self.myAddr))

            pSend.start()

            self.N_AdportQueProcStat[host] = [ [addr, port], \

                                                qSend, pSend, True ]

 

    def sendTos(self, msg, hosts):

        # ----- send msg to hosts

        for host in hosts:

            self.N_AdportQueProcStat[host][1].put_nowait(msg)

 

    def sendTo(self, msg, host):

        # ----- send msg to one host

        self.N_AdportQueProcStat[host][1].put_nowait(msg)

 

    def send(self, msg):

        # ----- send msg to all

        for host in self.N_PorAdQueProcStat.keys():

            self.N_AdportQueProcStat[host][1].put_nowait(msg)

 

    def relaunch(self, host):

        # ----- relaunch a process, host = toHost or '<:>'

        #       (['TCP:out', 'TCPSend', toHost] or

        #        ['TCP:out', 'TCPRecv', '<:>'] or

        if host == '<:>':

            # ----- Receiver

            print('\t[ ] relaunch TCP link to receive from emitter(s)')

            # ----- relaunch

            self.pRecv = Process(target=TCPRecv, \

                                 args=(self.portRecv, self.IPtoN, \

                                       self.feedback))

            self.pRecv.start()

        else:

            # ----- Sender

            print('\t[ ] relaunch TCP link to send to ' + host)

            # ----- relaunch

            addrPort, qSend, pSend, status = self.N_AdportQueProcStat[host]

            pSend = Process(target=TCPSend, \

                                args=(addrPort[0], addrPort[1], host, \

                                      qSend.get, self.feedback, self.myAddr))

            pSend.start()

            self.N_AdportQueProcStat[host][2] = pSend

           

    def recv(self, wait4data=True, returnAll=True):

        # ----- receive data

        if self.qFeedback is not None:

            while True:

                try:

                    typ, msgs, host = self.qFeedback.get(wait4data)

                except Empty:

                    typ = msgs = host = ''

 

                if returnAll or (not returnAll and typ == 'TCP:recv'):

                    break

                else:

                    if typ == 'TCP:out':

                        self.relaunch(host)

                    elif typ == 'TCP:snt1':

                        print('\t-> {} to {}'.format(msgs, host))

                    elif typ == 'TCP:snt0':

                        print('\t/!\ Fail sending {} to {}'.format(msgs, host))

 

            if returnAll:

                return typ, msgs, host

            else:

                return msgs, host

 

    def stop(self, host):

        addrPort, qSend, pSend, status = self.N_AdportQueProcStat[host]

        if pSend.is_alive:

            pSend.terminate()

            pSend.join()

        self.N_AdportQueProcStat[host][3] = False

        return [val[3] for val in self.N_AdportQueProcStat.values()]

 

    def close(self):

        # ----- close process

        if self.pRecv.is_alive(): self.pRecv.terminate()

        for addrPort, qSend, pSend, status \

                in self.N_AdportQueProcStat.values():

            if pSend.is_alive:

                pSend.terminate()

                pSend.join()

        print(' << TCPSend proc: Out')

        self.pRecv.join()

        print(' << TCPRecv proc: Out')

#

# -----------------------------------------------------------------------------

       

# -----------------------------------------------------------------------------

#                              TCP Send

def TCPSend(toAddr, toPort, toHost, get2send, feedback, myAddr):

    """

    toAddr = 192.168.... to send to

    toPort = port to send to

    toHost = hostname of the corresponding toAddr

    getdata2send  => message to send

            protocol: cmd=val@myAddr | ack:cmd=val@myAddr

            @myAddr to manage the receivers list (see TCPRecv)

    feedback : to send data back

        ['TCP:snt1', msgs,    toHost] when msgs in qIn successfully sent

        ['TCP:snt0', msgs,    toHost] when msgs in qIn failed to send   

        ['TCP:out', procName, toHost] when procName = 'TCPSend' exited

    """

 

    procName = 'TCPSend'

 

    print(' >> {} proc for {}: On'.format(procName, toHost))

   

    class fakesock():

        def send(self, msg): raise

        def close(self): pass

 

    s = fakesock() # to begin, time for other process to be connected

 

    while s is not None:

       

        msgs = get2send() + '@' + myAddr

 

        retry = True

       

        while retry:

            try:

                s.send(msgs.encode())

                feedback(['TCP:snt1', msgs, toHost])

                retry = False

 

            except:

                s.close()

                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

                s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

 

                try:

                    s.connect((toAddr, toPort))

 

                except:

                    s = None

                    retry = False

                    feedback(['TCP:snt0', msgs, toHost])

                   

    feedback(['TCP:out', procName, toHost])

       

    if s is not None: s.close()

    print('\t(-) Error: TCP sending to ' + toHost)

   

    print(' << {} proc for {}: Out'.format(procName, toHost))

#

# -----------------------------------------------------------------------------

 

# -----------------------------------------------------------------------------

#                            TCP Receive

def TCPRecv(portRecv, IPtoN, feedback):

    """

    portRecv = port to listen to

    IPtoN : IP[addr] = alias hostName

    feedback : send info to calling process (queue.put_nowait)

        ['TCP:rcv1', msgs, fromHost] when msgs received from identified alias host

        ['TCP:rcv0', msgs, addr] when msgs received from addr

        ['TCP:rcv?', msgs, ''] when received msgs not in good format 

        ['TCP:out', procName, '<:>'] when procName = 'TCPRecv' exited

    """

 

    procName = 'TCPRecv'

 

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    try:

        s.bind(('', portRecv))

        s.listen(5)

        s.settimeout(None)

        R_LIST = [s]

        print(' >> {} proc: On'.format(procName))

        TCPOn = True

 

    except:

        print(' << {} proc: Off'.format(procName))

        feedback(['TCP:out', procName, '<:>'])

        TCPOn = False

 

    R_Socks = []

 

    # ================================================= Main Loop

    while TCPOn:

       

        # ----- listen to procs

        R_Socks.clear()

        # ----- use select

        try:

            R_Socks = select.select(R_LIST, [], [], None)[0]

        except: pass

 

        # ----- read readable socks

        for sock in R_Socks:

            if sock == s:

                # new connection detected

                new_sock = s.accept()[0]

                R_Socks.append(new_sock)

                R_LIST.append(new_sock)

            else:

                # Data received from client

                try:

                    data = sock.recv(BUF_SIZE)

                    smsgs = data.decode().strip()

                    if smsgs:

                        if '@' in smsgs:

                            msg, addr = smsgs.split('@')

                            if addr in IPtoN:

                                feedback(['TCP:rcv1', msg, IPtoN[addr]])

                            else:

                                feedback(['TCP:rcv0', msg, addr])

                        else:

                            feedback(['TCP:rcv?', smsgs, ''])

                except:

                    sock.close()

                    if sock in R_LIST: R_LIST.remove(sock)

    s.close()

       

    print('\t(-) Error: TCP listening to Emitter')

    print(' << TCPRecv proc: Out')

#

# -----------------------------------------------------------------------------

______________________________________________________________________________________

 

This class creates a process to receive from and another to send to the TCP/IP link. The main class TCPLink manages these send/receive functions through queues

 

This is set with:

eth = TCPLink(IPtoNnode, IPtoNlinks, portBase, qIn.put_nowait)

IPtoNnode is a lookup table that associate an address to the current host name

IPtoNnode = {baseAddress+endAddressHostName: hostName} (for example: {'192.168.0.' + '63': "Rpi_Disp"}, see /boot/setConfig.ini)

 

IPtoNlinks is  a lookup table that associate the addresses of the other hosts to couple with their names

IPtoNlinks  {baseAddress+endAddressHostToLink: nameOfHost for nameOfHost in listOfHostsToLink } (for example {'192.168.0.64': "Master", '192.168.0.60': "Fs_Hat"})

 

So, in the program it will be a convenient and easy way to send orders to a host:

eth.sendTo(order, nameOfTheHostToSendTo) (example: eth.sendTo('exit=1', 'Master'))

or a same command to multiple processes

eth.sendTos(order, listOfHostsToSendTo) (example: eth.sendTos('exit=1', ['Master', 'Fs_Hat']))

 

portBase is a base of port number (here, set to 17700, see in /boot/setConfig.ini) from which a port will be computed from an host address (from the end of this address) and attached to this address

Actually I will use 2 channels to send and receive allowing hard-replug; as long as a process don't have to send to another process, the link is not initialized.

For example (according to  /boot/setConfig.ini) "Rpi_Disp" will be on 192.168.0.63:17763 and "Master" on 192.68.0.64:17764

     if "Rpi_Disp" wants to send an info to "Master", it will address it to 192.68.0.64:17764

     if "Master" wants to send an info to "Rpi_Disp", it will address it to 192.68.0.63:17763

 

To set the IPtoNnode, IPtoNlinks, portBase from the /boot/setConfig.ini from ConfigParser:

config = ConfigParser()

config.read('/boot/setConfig.ini', encoding='utf-8')

netCfg = config['NET']

addrCfg = config['ADDRESS']

procName = netCfg['node'].strip().title()

baseAddr = netCfg['base'].strip()

IPtoNnode = {baseAddr+addrCfg[procName]:procName}

if netCfg['links']:

    IPtoNlinks = {baseAddr + addrCfg[s.strip()].strip():\

                    s.strip().title() \

                        for s in netCfg['links'].split(',')}

 

qIn is a Queue from which the TCPLink class will transmit informations (errors, sending, receiving infos) to the main process which gathers all the info coming from the parrallel processes.

The info transmitted by TCPLink to qIn follows this format: [TCPInfo, message, fromOrToHost] (example: ['TCP:rcv1', 'exit=1', 'Master'])

The TCPInfo could be:

     from sender:

             ['TCP:snt1', msgs,     toHost]      when msgs successfully sent

             ['TCP:snt0', msgs,     toHost]      when msgs failed to send   

             ['TCP:out', 'TCPSend', toHost]      when 'TCPSend' in charge of sending to toHost exited

     from reicever:

             ['TCP:rcv1', msgs, fromHost]           when msgs received from FromHost in the list set from /boot/setConfig.ini

             ['TCP:rcv0', msgs, addr]                   when msgs received from addr not in the list set from /boot/setConfig.ini

             ['TCP:rcv?', msgs, '']                       when received msgs not in a good format 

             ['TCP:out', 'TCPRecv', '<:>']        when 'TCPRecv' in charge of receiving the info from the TCP link exited

 

In case of receiving 'TCP:out', just use eth.relaunch(fromOrToHost) to relaunch the terminated process.

 

2.3/ Printing an error message

 

Very easy, just display a message surrounded by the '*' char ... That helps when a raspberry reboot due to an error, to have a quick view of what happened.

 

defErrPrint.py________________________________________________________________

# -----------------------------------------------------------------------------

#

def errPrint(Msg):

    """

    print an error message

    """

    sep = '*'*60

    print('{}\n{}\n{}\n'.format(sep, Msg.center(60), sep))

#                       

# -----------------------------------------------------------------------------

______________________________________________________________________________________

 

 

 

3.a/ Outputs

 

This raspberry is in charge of switching on/off, blinking the tractor lights, from orders coming from TCP/IP.

To use the GPIO, I've chosen the gpiozero library which offers an easy and simple way to manage the inputs/outputs. It has first to be installed:

sudo apt-get install python3-gpiozero

sudo pip3 install gpiozero

 

Rpi_Outputs.py_______________________________________________________________

#!/usr/bin/python3.5

# -*- coding: utf-8 -*-

 

""" PROCESS Rpi_Outputs

    ===================

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.0'

__date__ = 'nov 2017'         

 

# =============================================================================

#                                MAIN

if __name__ == "__main__":

 

    from configparser import ConfigParser

    from defTCPIP import TCPLink

    from multiprocessing import Queue

    from queue import Empty

    from os import path

    from time import strftime, sleep, time

    from gpiozero import LED

    from defErrPrint import errPrint

 

    # ------------------------------------------------------------- read config

    config = ConfigParser()

    config.filename = r'/boot/setConfig.ini'

   

    from  os import name as osname

    if osname == 'nt':

        # windows to test program

        config.filename = config.filename.replace('/','_')

 

    # ----- verify config ini ok

    try:

        config.read(config.filename, encoding='utf-8')

        netCfg = config['NET']

        addrCfg = config['ADDRESS']

        procName = netCfg['node'].strip().title()

        baseAddr = netCfg['base'].strip()

        IPtoNnode = {baseAddr+addrCfg[procName]:procName}

        if netCfg['links']:

            IPtoNlinks = {baseAddr + addrCfg[s.strip()].strip():\

                            s.strip().title() \

                                for s in netCfg['links'].split(',')}

        else: IPtoNlinks = {}

        portBase = int(netCfg['port_base'])

        Ok = True

    except:

        Ok = False

 

    if not Ok:

        errPrint(r'/!\ Error reading .ini file')

        sleep(10)

        exit(1)

       

    # ----- Proc Name from ini = Rpi_Outputs

    print('> {} [{}] >'.format(procName, strftime('%d/%m/%y %H:%M:%S')))

 

    qIn = Queue()

    # --------------------------------------------------- list of PC to connect

    eth = TCPLink(IPtoNnode, IPtoNlinks, portBase, qIn.put_nowait)

    print('\t{}: {} '.format(procName, eth.myAddr))

 

    # ---------------------------------------------------- Digital LookUp Table

    outLUT = { 'ReLeftBlinker'   : 14,  \

               'ReRightBlinker'  : 10 , \

               'FrLeftBlinker'   : 18,  \

               'FrRightBlinker'  : 23 , \

               'FrRightLghtDown' :  8 , \

               'FrLeftLghtDown'  : 24 , \

               'ReLeftLghtDown'  :  7 , \

               'ReRightLghtDown' : 25 , \

               'FrLeftLghtUpR'   : 16 , \

               'FrLeftLghtUpL'   : 20 , \

               'FrRightLghtUpL'  : 21 , \

               'FrRightLghtUpR'  :  6 , \

               'ReLeftLghtUpL'   : 13 , \

               'ReLeftLghtUpR'   : 19 , \

               'ReRightLghtUpR'  : 26 , \

               'ReRightLghtUpL'  : 12 , \

               'LeftBrake'       :  4 , \

               'Beacon'          : 27 , \

               'RightBrake'      : 15 , \

               'ReLeftRedLght'   : 11 , \

               'ReRightRedLght'  : 22

            }

 

    # --- .on(), .off(), .blink(on_time=1, off_time=1, n=None, background=True)

    leftBlinkers  = [ LED(outLUT['FrLeftBlinker'],   active_high=False) , \

                      LED(outLUT['ReLeftBlinker'],   active_high=False) ]               

    rightBlinkers = [ LED(outLUT['FrRightBlinker'],  active_high=False) , \

                      LED(outLUT['ReRightBlinker'],  active_high=False) ]

    lghtFrDown    = [ LED(outLUT['FrLeftLghtDown'],  active_high=False) , \

                      LED(outLUT['FrRightLghtDown'], active_high=False) ]

    lghtFrUp      = [ LED(outLUT['FrRightLghtUpR'],  active_high=False) , \

                      LED(outLUT['FrRightLghtUpL'],  active_high=False) , \

                      LED(outLUT['FrLeftLghtUpR'],   active_high=False) , \

                      LED(outLUT['FrLeftLghtUpL'],   active_high=False) ]

    lghtReDown    = [ LED(outLUT['ReLeftLghtDown'],  active_high=False) , \

                      LED(outLUT['ReRightLghtDown'], active_high=False) ]

    lghtReUp      = [ LED(outLUT['ReLeftLghtUpL'],   active_high=False) , \

                      LED(outLUT['ReLeftLghtUpR'],   active_high=False) , \

                      LED(outLUT['ReRightLghtUpL'],  active_high=False) , \

                      LED(outLUT['ReRightLghtUpR'],  active_high=False) ]

    brakes        = [ LED(outLUT['LeftBrake'],       active_high=False) , \

                      LED(outLUT['RightBrake'],      active_high=False) ]

    beacon        =   LED(outLUT['Beacon'],          active_high=False)

    ReRedLght     = [ LED(outLUT['ReLeftRedLght'],   active_high=False) , \

                      LED(outLUT['ReRightRedLght'],  active_high=False) ]

 

    lights = 0x000000

 

    iFrLeftBlinker   =  0x000001

    iReLeftBlinker   =  0x000002        

    iFrRightBlinker  =  0x000004

    iReRightBlinker  =  0x000008

    iFrLeftLghtDown  =  0x000010

    iFrRightLghtDown =  0x000020

    iFrLeftLghtUpL   =  0x000040

    iFrLeftLghtUpR   =  0x000080

    iFrRightLghtUpL  =  0x000100

    iFrRightLghtUpR  =  0x000200

    iReLeftLghtDown  =  0x000400

    iReRightLghtDown =  0x000800

    iReRightLghtUpR  =  0x001000

    iReRightLghtUpL  =  0x002000          

    iReLeftLghtUpR   =  0x004000         

    iReLeftLghtUpL   =  0x008000

    iLeftBrake       =  0x010000

    iBeacon          =  0x020000

    iRightBrake      =  0x040000

    iReLeftRedLght   =  0x080000

    iReRightRedLght  =  0x100000

 

    iLeftBlinker  = iFrLeftBlinker  | iReLeftBlinker     

    iRightBlinker = iFrRightBlinker | iReRightBlinker

    iLghtFrDown   = iFrLeftLghtDown | iFrRightLghtDown

    iLghtFrUp     = iFrLeftLghtUpL  | iFrLeftLghtUpR | \

                    iFrRightLghtUpL | iFrRightLghtUpR

    iLghtReDown   = iReLeftLghtDown | iReRightLghtDown

    iLghtReUp     = iReRightLghtUpR | iReRightLghtUpL | \

                    iReLeftLghtUpR  | iReLeftLghtUpL

    iWarning      = iLeftBlinker    | iRightBlinker

    iBrakes       = iLeftBrake      | iRightBrake

    iReRedLght    = iReLeftRedLght  | iReRightRedLght

 

    warning = leftBlinkers + rightBlinkers

 

    allLights = warning + lghtFrDown + lghtFrUp + \

                lghtReDown + lghtReUp + brakes + ReRedLght

 

    chaserFr = [ lghtFrDown[1], rightBlinkers[0] ] + lghtFrUp + \

               [ leftBlinkers[0], lghtFrDown[0] ]           

    chaserRe = [ lghtReDown[0], leftBlinkers[1]] + lghtReUp + \

               [ rightBlinkers[1], lghtReDown[1] ]

 

    def animLights():

        beacon.on()

        for lght in allLights: lght.on()

        sleep(.75)

        for lght in allLights: lght.off()

        sleep(.2)

        for i in range(4):

            for lghtFr, lghtRe in zip(chaserFr, chaserRe):

                lghtFr.on()

                lghtRe.on()

                sleep(.225)

                lghtFr.off()

                lghtRe.off()

            for lght in brakes: lght.on()

            sleep(.225)

            for lght in brakes: lght.off()

        sleep(.2)

        for lght in allLights: lght.on()

        sleep(.75)

        for lght in allLights: lght.off()

        beacon.off()

           

    exitVal = 0

    MainOn = True

    exitFlag = False

    getdata = qIn.get

    wait4data = True

    T0 = time()

    Tend = 5

    ackn = False

 

    while MainOn:

 

        try:

            ret = getdata(wait4data)

        except Empty:

            ret = []

        except KeyboardInterrupt:

            ret = []

            exitFlag = True

            MainOn = False

 

        if ret: typ, msgs, proc = ret

        else: typ = ''

 

        if 'TCP' in typ:

            # ----------------------------------------------------- TCP

            typ2 = typ.split(':')[-1]

           

            if 'out' in typ2:

                eth.relaunch(proc)

               

            elif 'snt1' in typ2:

                print('\t-> {} to {}'.format(msgs, proc))

               

            elif 'snt0' in typ2:

                print('\t/!\ Fail sending {} to {}'.format(msgs, proc))

               

            elif typ2 in ['rcv1', 'rcv0']:

                # ----- aknowledge

                print('\t<- {} from {}'.format(msgs, proc))

                if exitFlag:

                    print('\t {} stopped'.format(proc))

                    if typ2 == 'rcv1':

                        MainOn = not all(eth.stop(proc))

 

                cmd, val = msgs.split('=')

 

                ackn = True

 

                if cmd == 'Anim':

                    if int(val) == 1:

                        animLights()

                        # ----- put back to what was in memory

                        j = 1

                        n = 0

                        for i in range(21):

                            if lights & j :

                                if n < 4: allLights[n].blink(.66, .33)

                                else:     allLights[n].on()

                            j <<= 1

                            n += 1

                       

                elif cmd == 'LeftBlinker':

                    if int(val) == 1:

                        for lght in leftBlinkers: lght.blink(.66, .33)

                        lights |= iLeftBlinker         

                    else:

                        for lght in leftBlinkers: lght.off()

                        lights &= ~iLeftBlinker        

 

                elif cmd == 'RightBlinker':

                    if int(val) == 1:

                        for lght in rightBlinkers: lght.blink(.66, .33)

                        lights |= iRightBlinker         

                    else:

                        for lght in rightBlinkers: lght.off()

                        lights &= ~iRightBlinker  

 

                elif cmd == 'Beacon':

                    if int(val) == 1:

                        beacon.on()

                        lights |= iBeacon

                    else:

                        beacon.off()

                        lights &= ~iBeacon

 

                elif cmd == 'Brake':

                    if int(val) == 1:

                        for lght in brakes: lght.on()

                        lights |= iBrakes

                    else:

                        for lght in brakes: lght.off()

                        lights &= ~iBrakes

 

                elif cmd == 'LghtDown':

                    if int(val) == 1:

                        for lght in lghtFrDown + lghtReDown: lght.on()

                        lights |= (iLghtFrDown | iLghtReDown)

                    else:

                        for lght in lghtFrDown + lghtReDown: lght.off()

                        lights &= ~(iLghtFrDown | iLghtReDown)

 

                elif cmd == 'LghtFrDown':

                    if int(val) == 1:

                        for lght in lghtFrDown: lght.on()

                        lights |= iLghtFrDown

                    else:

                        for lght in lghtFrDown: lght.off()

                        lights &= ~iLghtFrDown

 

                elif cmd == 'LghtFrUp':

                    if int(val) == 1:

                        for lght in lghtFrUp: lght.on()

                        lights |= iLghtFrUp

                    else:

                        for lght in lghtFrUp: lght.off()

                        lights &= ~iLghtFrUp

 

                elif cmd == 'LghtReDown':

                    if int(val) == 1:

                        for lght in lghtReDown: lght.on()

                        lights |= iLghtReDown

                    else:

                        for lght in lghtReDown: lght.off()

                        lights &= ~iLghtReDown

 

                elif cmd == 'LghtReUp':

                    if int(val) == 1:

                        for lght in lghtReUp: lght.on()

                        lights |= iLghtReUp

                    else:

                        for lght in lghtReUp: lght.off()

                        lights &= ~iLghtReUp

 

                elif cmd == 'ReRedLght':

                    if int(val) == 1:

                        for lght in ReRedLght: lght.on()

                        lights |= iReRedLght

                    else:

                        for lght in ReRedLght: lght.off()

                        lights &= ~iReRedLght

 

                elif cmd == 'Warning':

                    if int(val) == 1:

                        for lght in warning: lght.blink(.66, .33)

                        lights |= iWarning

                    else:

                        for lght in warning: lght.off()

                        lights &= ~iWarning

 

                elif cmd == 'Exit':

                    # stop Rpi_INputs

                    exitVal = int(val)

                    exitFlag = True

                    wait4data = False

                   

            elif 'rcv?' in typ2:

                print('\t ????? {} received from "{}"'.format(msgs, proc))

                ackn = False

               

        elif typ:

            print('\t/!\ unknown type : ', typ)

 

        if exitFlag:  MainOn = not (time() - T0 > Tend)

 

    qIn.cancel_join_thread()   

    # ----------------------------------------------------------- close TCPLink

    eth.close()

 

    # -------------------------------------------------------------------- exit

    print('< {} [{}] <\n\n'.format(procName, strftime('%d/%m/%y %H:%M:%S')))

       

    exit(exitVal)

#

# =============================================================================

______________________________________________________________________________________

 

 

 

3.b/ Zen@Terra

 

This raspberry, at the driver's right side, reproduces the Michelin App "Zen@Terra®" that consists of tuning the tires pressures depending on the coupled tooling and the rolling surface (road of field). The driver selects with a tactile screen the corresponding conditions of tooling/loads and rolling surface to (virtually here) adjust the pressure in the tires.

It's named RPi_Pressure as it manages the tire pressures ...

It uses the fonts: Caracteres L1.ttf, Michelin-Black_0.otf, Michelin-Bold_0.otf, Michelin-Light.otf, Michelin-Regular.otf, Michelin-SemiBold.otf.

tkinter manages the graphics and it consists to overlay (tk.Canvas.create_image), showing (state=tk.NORMAL) or hiding (state=tk.HIDDEN) images and/or texts (tk.Canvas.create_text) or other shapes (tk.Canvas.create_oval, tk.Canvas.create_arc).

 

Images in the img directory:

The main part (__main__) manages the TCP/IP link. It communicates with the graphic part (board) thanks to a queue (named setBoard).

The board scans every 40ms if some data are in this queue (in def wait4action).

The board can send info to the __main__ through another queue (qIn) which gather also the messages coming from TCP/IP.

The incoming data in qIn follows this format [type, msgs, process]

if type == 'TCP:...', the msgs comes from the TCP/IP link

if type == 'SUB', the msgs comes from board and is send to TCP/IP link (eth.sendTo), notably to send the new max speed reference.

 

Rpi_Pressure.py_______________________________________________________________________

# -*- coding: utf-8 -*-

 

""" RPi_Pressure

    ============

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.0'

__date__ = '27 Nov 2017'

 

import tkinter as tk

from configparser import ConfigParser

from multiprocessing import Process, Queue

from queue import Empty

from defErrPrint import errPrint

 

from time import time, sleep

from os import path

 

appRed = '#CC0000'

appBlue = '#006699'

appGreen = '#41B649'

appOrange = '#CC6600'

 

def nullFun(): return

 

# =============================================================================

#

class board():

    """

    Main animation

    """

 

    # ========================================================== initializing

    def __init__(self, debug, qIn, qOut, langs):

           

        # ======================= open graphic window, top screen, full screen

        fig = tk.Tk()

        if not debug: fig.overrideredirect(True)

        fig.lift()

        WxH = [1280, 800]

        fig.wm_attributes('-topmost', True)

        fig.geometry('{}x{}+0+0'.format(*WxH))

        fig.title('zen@Terra')

 

        fig.bind('q', self.Quit)

        fig.bind('Q', self.Quit)

        fig.bind('H', self.Hide)

        fig.bind('h', self.Hide)

        fig.bind('S', self.Show)

        fig.bind('s', self.Show)

 

        fig.bind('<Control-KeyPress-F>', self._toFr)

        fig.bind('<Control-KeyPress-E>', self._toEn)

        fig.bind('<Control-KeyPress-G>', self._toGe)

        fig.bind('<Control-KeyPress-R>', self._toRu)

      

        self.getData = qIn.get_nowait

        self.enterEmpty = qIn.empty

        self.putData = qOut.put_nowait

       

        # ----- attach a canvas to the window

        cnv = tk.Canvas(fig, bd=0, bg='white', highlightthickness=0, \

                       width=WxH[0], height=WxH[1])

        cnv.pack()

        # ----- to simplify the notations

        setText = cnv.create_text

        setImage = cnv.create_image

        setCircle = cnv.create_oval

        setArc = cnv.create_arc

        self.setConfig = cnv.itemconfig

        self.getConfig = cnv.itemcget

        self.setCoords = cnv.coords

        self.getCoords = cnv.coords

        self.wbind = cnv.tag_bind

        self.getTag = cnv.find_withtag

        self.move = cnv.move

 

        curLang = langs['LANG']['curlang']

        self.curLang = curLang

        self.labels = {}

        self.langs = langs

 

        # ----- fonts

        fontButton = ('Michelin', 12, 'bold')

        fontLabel = ('Michelin', 17, 'bold')

        fontPres = ('Michelin', 22, 'bold')

        fontTimer = ('Michelin', 22)

        fontLabelSmall = ('Michelin', 9, 'bold')

        fontSpeed = ('CARACTERES L1', 40)

 

        # ----- Directory containing the pictures

        imgDir = '../img'

        if not path.exists(imgDir): imgDir = imgDir.replace('..','.')

 

        def imgLoadTk(fn): return tk.PhotoImage(file=path.join(imgDir,fn))

 

        # =================================== load and display background image

        self.bckImgTk = imgLoadTk('background.png')

        self.BckImg = setImage(640, 400, image=self.bckImgTk)

 

        # ===================================================== top view button

        self.topviewTk = imgLoadTk('top_view.png')

        # setImage(1280-50-5, 67/2, anchor=tk.E, image=self.topviewTk)

        tk.Button(cnv, bd=8, bg='#6699CC', image= self.topviewTk, \

                  anchor=tk.CENTER, \

                  command=self.toggleTopView, height=42, width=42)\

                       .place(x=1280-5, y=67/2, anchor=tk.E)

 

        self.langButTxt = tk.StringVar()

        self.langButTxt.set(curLang[:2])

        # ======================================================= lang button

        self.langBut=tk.Button(cnv, textvariable=self.langButTxt,  \

                 bd=1, relief=tk.FLAT, command=self.nextLang, \

                  bg='#6699CC', font=fontLabelSmall)

        self.langBut.place(x=1280-75, y=67/2-6, anchor=tk.E)

                

         # ===== load tractor alone: tractor =========================== TRACTOR

        self.tractorAloneTk = imgLoadTk('tractor.png')

        self.tractorAlone = setImage(495, 428, image=self.tractorAloneTk)

 

        # ----- load the tractor + trailer (3rdWhl)

        self.tractorTrailerk = imgLoadTk('tractor_trailer.png')

        self.tractorTrailer = setImage(513, 428, image=self.tractorTrailerk,\

                                           state=tk.HIDDEN)

 

        self.wheelTk = {}

        self.wheel = {}

        self.nbAxles = 2

        self.curAxle = 0

        self.curWheelClr = ['Green']*3

        self.selAxle = {na:[] for na in range(3)}

 

        self.wshift = 152

       

        # -------------------- pressure probe, check sign, sandglass positions

        midfx = 374

        midfy = 546

        midrx = 655

        midry = 533

        midtx = 768 + self.wshift

        midty = midfy

        # ----- wheels, check, sandglass positions

        xys = [[midfx, midfy],[midrx, midry],[midtx, midty]]

 

        # -------------------------------------------------------------- WHEELS

        for clr in ['Blue', 'Orange', 'Green', 'Red']:

            tag = ('whl' + clr, 'toShift')

            clrl = clr.lower()

            self.wheelTk[clr] = []

            self.wheel[clr] = []

            for i, fn in enumerate(['', 'rear_', '']):

                self.wheelTk[clr].append(imgLoadTk(fn+'wheel_{}.png'.format(clrl)))

                tagi = ('whl{}{}'.format(i, clr), *tag)

                self.wheel[clr].append(\

                    setImage(*xys[i], state=tk.HIDDEN, tag=tagi, \

                                 image=self.wheelTk[clr][-1]))

                self.selAxle[i].append(self.wheel[clr][-1])

 

        self.wShow(self.wheel['Green'][:self.nbAxles])

 

        # ----- pressure probe positions

        xyp = [[x, y+z] for [x,y],z in zip(xys,[50, 59, 50])]

 

        # ------------------------------- displays

        ptfx = 404

        ptfy = 656

        ptrx = 687

        ptry = ptfy

        pttx = 796 + self.wshift

        ptty = ptfy

        # ----- 'real' pressures

        xyd = [[ptfx, ptfy],[ptrx, ptry],[pttx, ptty]]

        # ----- target pressures

        xyt = [[x+5, y] for x,y in xyd]

        # ----- manometer

        xym = [[x-20, y+15] for x,y in xyd]

        tagi = tag

        wTargetPres = []

        self.targetPres = [1.8]*3

        self.curPres = self.targetPres.copy()

        self.prevPres = self.targetPres.copy()

        self.oriTargetPres = self.targetPres.copy()

        self.xflTimer = [None]*3

        self.curPresClr = [appGreen]*3

 

        # ------------------------------- PRESSURE PROBE, CHECK SIGN, SANDGLASS

        tag = 'toShift'

        self.mesPresTk = []

        self.checkTk = imgLoadTk('check.png')

        self.check = []

        self.sandglassTk = imgLoadTk('sandglass.png')

        self.sandglass = []

        self.manoTk = imgLoadTk('mano.png')

        self.mano = []

        wCurPres = []

        tagi = tag

        state = tk.NORMAL

        for i, fn in enumerate(['Fr', 'Re', 'Fr' ]):

            self.mesPresTk.append(imgLoadTk('mesPres_{}.png'.format(fn)))

            if i == 2:

                tagi = ('3rdWhl', tag)

                state = tk.HIDDEN

            # ----- pressure probes

            setImage(*xyp[i], image=self.mesPresTk[-1], anchor=tk.NW, \

                     state=state, tag=tagi)

            # ----- check icones

            self.check.append(setImage(*xys[i], anchor=tk.CENTER, state=state, \

                                    image=self.checkTk, tag=tagi))

            # ----- target pressures

            wTargetPres.append(setText(*xyt[i], text='1.8', \

                                            fill=appGreen, state=state, \

                                    anchor=tk.SW, font=fontPres, tag=tagi))

            if i == 2: tagi = ('3rdWhl+', tag)

            # ----- sandglass

            self.sandglass.append(setImage(*xys[i], image=self.sandglassTk, \

                                           state=tk.HIDDEN, \

                                           anchor=tk.CENTER, tag=tagi))

            # ----- 'real' pressure

            wCurPres.append(setText(*xyd[i], text='1.8', fill=appOrange, \

                                         state=tk.HIDDEN, tag=tagi, \

                                         anchor=tk.NW, font=fontPres))

            # ----- manometers

            self.mano.append(setImage(*xym[i], image=self.manoTk, tag=tagi, \

                             anchor=tk.E, state=tk.HIDDEN))

  

        # -------------------------------------------------- TOOLS (PRESSURES)

        self.variableLoadList = ['DIRECTDRILL', 'TRAILER', 'SPREADER' , \

                                 'DRILL']

        tool2img = { 'TRACTOR': 'nothing',  'CULTIVATOR': 'cultivator', \

                     'DIRECTDRILL': 'direct_drill',\

                     'TRAILER': 'trailer', 'PLOUGH': 'plough',\

                     'DRILL': 'drill', 'SPREADER':'spreader' }

 

        self.toolPressures = {}

        self.toolSpeed = {}

        for i, key in enumerate(tool2img.keys()):

            if key in self.variableLoadList:

                self.toolPressures[key] = {}

                self.toolSpeed[key] = {}

                for mode in ['road', 'field', 'boost']:

                    self.toolPressures[key][mode] = []

                    self.toolSpeed[key][mode] = []

                    for load in [0.0, 0.5, 1.0]:

                        pressures, speed = (langs[key]['_{}@{:.1f}'.format(mode, load)]).split('@')

                        self.toolPressures[key][mode].append(\

                                        eval(pressures.replace('-', '1.2')))

                        self.toolSpeed[key][mode].append(int(speed))

            else:

                self.toolPressures[key] = {}

                self.toolSpeed[key] = {}

                for mode in ['road', 'field', 'boost']:

                    pressures, speed = (langs[key]['_{}@-'.format(mode)]).split('@')

                    self.toolPressures[key][mode] = [\

                                               eval(pressures.replace('-', '1.2'))]

                    self.toolSpeed[key][mode] = [int(speed)]

       

        self.xflatingTool = {na:[self.sandglass[na], wCurPres[na], \

                                self.mano[na]] for na in range(3)}

 

        # --------------------------------------------------------- TOOLS (draw)

        self.toolsTk = {}

        for key, img in tool2img.items(): \

            self.toolsTk[key] = imgLoadTk(img + '.png')

        self.setImage = setImage

        self.setText = setText

        self.fontButton = fontButton

        self.toolInd = 0

        self.toolList = {i:key for i, key in enumerate(tool2img.keys())}

        self.indTool = {key:i for i, key in self.toolList.items()}

        self.curTool = self.toolList[self.toolInd]

        self.frontMassTool = ['DIRECTDRILL', 'PLOUGH']

        self.load = 0

        self.mode = 'road'

        

        # ===== load large button 1 ================================ [BUTTON 1]

        tag = 'Tooling'

        # ----- label 1

        self.labels['TOOLING'] = setText(194+5, 82, font=fontLabel, \

                                            text=langs['TOOLING'][curLang],

                                              anchor=tk.NW)

        # ----- shadow

        self.but1sTk = imgLoadTk('big_shadow.png')

        but1s = setImage(194+4, 114+5, image=self.but1sTk, anchor=tk.NW)

 

        def wbind(ws, tag, wc, but, action):

            def handler(event, self=self, ws=ws, tag=tag, wc=wc, action=action):

                return self.pressAction(event, ws, tag, wc, but, action)

            self.wbind(wc, '<Button-1>', handler)

       

        # ----- button

        self.but1Tk = imgLoadTk('big_but.png')

        but1 = setImage(194, 114, image=self.but1Tk, tag=tag, anchor=tk.NW)

        wbind(but1s, tag, but1, None, self.selTooling)

 

        # ----- image in button

        self.tractorButTk = imgLoadTk('tractor_but.png')

        tractorBut = setImage(194+222/2, 114+97-29, image=self.tractorButTk, \

                                        tag=tag, anchor=tk.SE)

        wbind(but1s, tag, tractorBut, None, self.selTooling)

 

        # ----- front mass

        self.frontMassTk = imgLoadTk('front_mass.png')

        self.frontMass = self.setImage(194+222/2-79+3, 114+97/2-3, \

                                 image=self.frontMassTk, state=tk.HIDDEN, \

                                        tag=tag, anchor=tk.E)

        wbind(but1s, tag, self.frontMass, None, self.selTooling)

       

        # ----- tool attached to tractor

        self.toolBut = self.setImage(194+222/2, 114+97-29, \

                                 image=self.toolsTk[self.curTool], \

                                        tag=tag, anchor=tk.SW)

        wbind(but1s, tag, self.toolBut, None, self.selTooling)

       

        # ----- text in button

        self.txtBut1 = self.setText(194+222/2, 114+92, anchor=tk.S, \

                     tag=tag, text=self.langs[self.curTool][self.curLang], \

                               font=self.fontButton )       

        wbind(but1s, tag, self.txtBut1, None, self.selTooling)

 

        # -------------------------------------------------------- sub button 1

        self.subbut1Tks = imgLoadTk('small_shadow.png')

        self.subbut1Tkg = imgLoadTk('small_but_grey.png')

        self.subbut1Tky = imgLoadTk('small_but_yellow.png')

        self.subbut1Tki = []

        self.subbut1y = []

        tag = 'subequip'

        for i, fn in enumerate(['empty', 'half', 'full' ]):

           

            tag2 = 'but1#{}'.format(i)

            taga = (tag, tag2)

            j = 84*i           

            # ----- shadow

            subbut1s = setImage(194+4+j, 225+5, anchor=tk.NW, \

                                tag=tag, state=tk.HIDDEN, image=self.subbut1Tks)

           

            # ----- grey background

            subbut1b = setImage(194+j, 225, anchor=tk.NW, tag=taga, \

                                state=tk.HIDDEN, image=self.subbut1Tkg )

            wbind(subbut1s, tag2, subbut1b, i, self.selToolingSub)

           

            # ----- yellow background

            self.subbut1y.append(\

                setImage(194+j, 225, anchor=tk.NW, tag=taga, \

                             state=tk.HIDDEN, image=self.subbut1Tky ))

            wbind(subbut1s, tag2, self.subbut1y[-1], i, self.selToolingSub)

           

            # ----- image

            self.subbut1Tki.append(imgLoadTk(fn+'.png'))

            subbut1i = setImage(194+j+53/2, 225+52/2, anchor=tk.CENTER, tag=tag, \

                             state=tk.HIDDEN, image=self.subbut1Tki[-1] )

            wbind(subbut1s, tag2, subbut1i, i, self.selToolingSub)

           

        # ===== load large button 2 ================================ [BUTTON 2]

        tag = 'Surf'

        self.field = False

        # ----- label 2

        self.labels['USAGE'] = setText(610+5, 82, font=fontLabel, \

                                         text=langs['USAGE'][curLang], \

                                           anchor=tk.NW)

        # ----- shadow

        but2s = setImage(610+4, 114+5, image=self.but1sTk, anchor=tk.NW)

       

        # ----- button

        but2 = setImage(610, 114, image=self.but1Tk, \

                                      tag=tag, anchor=tk.NW)

        wbind(but2s, tag, but2, None, self.toggleSurf)

       

        # ----- image in button: Field

        self.fieldButTk = imgLoadTk('field.png')

        fieldBut = setImage(610+222/2, 114+10, image=self.fieldButTk, \

                                  tag=(tag, 'field'), state=tk.HIDDEN, \

                                     anchor=tk.N)

        wbind(but2s, tag, fieldBut, None, self.toggleSurf)

 

        # ----- image in button: Road

        self.roadButTk = imgLoadTk('road.png')

        roadBut = setImage(610+222/2, 114+10, image=self.roadButTk, \

                                     tag=(tag, 'road'), anchor=tk.N)

        wbind(but2s, tag, roadBut, None, self.toggleSurf)

 

        # ----- text in button: field

        txtBut2f = setText(610+222/2, 114+92, anchor=tk.S, \

                               state=tk.HIDDEN, tag=(tag, 'field'), \

                                   text=langs['FIELD'][curLang], \

                                       font=fontButton )

        self.labels['FIELD'] = txtBut2f

        wbind(but2s, tag, txtBut2f, None, self.toggleSurf)

 

        # ----- text in button: road

        txtBut2r = setText(610+222/2, 114+92, anchor=tk.S, \

                               tag=(tag, 'road'), \

                                   text=langs['ROAD'][curLang], \

                                       font=fontButton )

        self.labels['ROAD'] = txtBut2r

        wbind(but2s, tag, txtBut2r, None, self.toggleSurf)

       

        # -------------------------------------------------------- sub button 2

        self.subbut2Tki = []

        self.subbut2y = []

        tag = 'field'

        for i, fn in enumerate(['bank', 'flat']):

                       

            j = 91*i

            tag2 = 'but2#{}'.format(i)

            taga = (tag, tag2)

 

            # ----- shadow

            subbut2s = setImage(650+4+j, 225+5, anchor=tk.NW, state=tk.HIDDEN, \

                             tag=tag, image=self.subbut1Tks )

           

            # ----- grey background

            subbut2b = setImage(650+j, 225, anchor=tk.NW, state=tk.HIDDEN, \

                             tag=taga, image=self.subbut1Tkg )

            wbind(subbut2s, tag2, subbut2b, i, self.surfSub)

           

            # ----- yellow background

            self.subbut2y.append(\

                setImage(650+j, 225, anchor=tk.NW, state=tk.HIDDEN, \

                             tag=taga, image=self.subbut1Tky ))

            wbind(subbut2s, tag2, self.subbut2y[-1], i, self.surfSub)

           

            # ----- image

            self.subbut2Tki.append(imgLoadTk(fn+'.png'))

            subbut2i = setImage(650+j+53/2, 225+52/2, anchor=tk.CENTER, state=tk.HIDDEN, \

                             tag=taga, image=self.subbut2Tki[-1] )

            wbind(subbut2s, tag2, subbut2i, i, self.surfSub)

 

        self.bankFlag = False

 

        # ===== load button MANUAL ============================ [BUTTON MANUAL]

        tag = 'Manual'

        self.manualOn = False

        # ----- shadow

        self.rightButtTks = imgLoadTk('medium_but_shadow.png')

        manualButs = setImage(1098+4, 88+5, image=self.rightButtTks, \

                                     anchor=tk.NW)

        # ----- normal button

        self.manualButtTk = imgLoadTk('manual_but_off.png')

        self.manualBut = setImage(1098, 88, image=self.manualButtTk, \

                                     tag=tag, anchor=tk.NW)

        wbind(manualButs, tag, self.manualBut, None, self.toggleManualMode)

 

        # ----- blue button

        self.manualButtTkb = imgLoadTk('manual_but_on.png')

        self.manualButBlue = setImage(1098, 88, image=self.manualButtTkb, \

                                         tag=tag, state=tk.HIDDEN, anchor=tk.NW)

        wbind(manualButs, tag, self.manualButBlue, None, self.toggleManualMode)

 

        # ----- text in button

        txtButManual = setText(1098+108/2, 88+78-5, anchor=tk.S, \

                                        text=langs['MANUAL'][curLang], \

                                    font=fontLabelSmall, tag=tag )

        self.labels['MANUAL'] = txtButManual

        wbind(manualButs, tag, txtButManual, None, self.toggleManualMode)

 

        # ===== load button - ====================================== [BUTTON -]

        tag = ('submanual', '-')

        # ----- shadow

        self.plusminusButtTks = imgLoadTk('plus_minus_shadow.png')

        minusButs = setImage(235+4, 742+5, image=self.plusminusButtTks, \

                                state=tk.HIDDEN, tag=tag, anchor=tk.CENTER)

        # ----- normal button

        self.minusButtTk = imgLoadTk('minus_but.png')

        minusBut = setImage(235, 742, image=self.minusButtTk, \

                                state=tk.HIDDEN, tag=tag, anchor=tk.CENTER)

        wbind(minusButs, '-', minusBut, None, self.minusButFun)

 

        # ===== load button Axle ================================ [BUTTON AXLE]

        tag = ('submanual', 'axle')

        # ----- shadow

        self.axleButtTks = imgLoadTk('axle_shadow.png')

        axleButs = setImage(513+4, 742+5, image=self.axleButtTks, \

                                     state=tk.HIDDEN, anchor=tk.CENTER, tag=tag)

        # ----- normal button

        self.axleButtTk = imgLoadTk('axle_but.png')

        axleBut = setImage(513, 742, image=self.axleButtTk, \

                                    state=tk.HIDDEN, anchor=tk.CENTER, tag=tag)

        wbind(axleButs, 'axle', axleBut, None, self.nextAxle)

       

        # ----- text in button

        txtButAxle = setText(513, 742+61/2-5, anchor=tk.S, \

                                     state=tk.HIDDEN,  font=fontLabelSmall, \

                                         tag=tag, text=langs['AXLE'][curLang])

        self.labels['AXLE'] = txtButAxle

        wbind(axleButs, 'axle', txtButAxle, None, self.nextAxle)

 

        self.lastAxle = 1

 

        # ===== load button + ====================================== [BUTTON +]

        tag = ('submanual', '+')

        # ----- shadow

        plusButs = setImage(790+4, 742+5, image=self.plusminusButtTks, \

                                     state=tk.HIDDEN, anchor=tk.CENTER, tag=tag)

        # ----- normal button

        self.plusButtTk = imgLoadTk('plus_but.png')

        plusBut = setImage(790, 742, image=self.plusButtTk, \

                                    state=tk.HIDDEN, anchor=tk.CENTER, tag=tag)

        wbind(plusButs, '+', plusBut, None, self.plusButFun)

 

        # ===== load button BOOST ============================== [BUTTON BOOST]

        tag = 'Boost'

        self.boostOn = False

        boostButs = setImage(1098+4, 189+5, image=self.rightButtTks, \

                                  state=tk.HIDDEN, tag=tag, anchor=tk.NW)

        # ----- normal button

        self.boostButtTk = imgLoadTk('boost_but_off.png')

        self.boostBut = setImage(1098, 189, image=self.boostButtTk, \

                                  state=tk.HIDDEN, tag=tag, anchor=tk.NW)

        wbind(boostButs, tag, self.boostBut, None, self.toggleBoostMode)

 

        # ----- red button

        self.boostButtTkr = imgLoadTk('boost_but_on.png')

        self.boostButRed = setImage(1098, 189, image=self.boostButtTkr, \

                                        state=tk.HIDDEN, tag=tag, anchor=tk.NW)

        wbind(boostButs, tag, self.boostButRed, None, self.toggleBoostMode)

 

        # ----- text in button

        txtButBoost = setText(1098+108/2, 189+78-7, anchor=tk.S, \

                                       state=tk.HIDDEN, font=fontLabelSmall, \

                                          tag=tag, text=langs['BOOST'][curLang])

        self.labels['BOOST'] = txtButBoost

        wbind(boostButs, tag, txtButBoost, None, self.toggleBoostMode)

 

        # --------------------------------------------------------- triple warn

        tag = 'subBoost'

        self.triplwarnw = []

        ux, uy, d = 1073, 331, 44

        dx, dy, r = ux+d, uy+d, round(d/2)

        for i in range(3):

            j = i*58

            self.triplwarnw.append(\

                setCircle(ux+j, uy, dx+j, dy, outline='black', width=3, \

                              tag=tag, state=tk.HIDDEN))

        self.wFill([self.triplwarnw[0]], appRed)

 

        # --------------------------------------------------------- Boost Timer

        ux, uy, d = 1073, 416, 160

        dx, dy, r = ux+d, uy+d, round(d/2)

        self.boostTimer = [\

            setArc(ux, uy, dx, dy, width=0, fill=appRed, tag=tag, \

                   start=90, style=tk.PIESLICE, state=tk.HIDDEN, extent=0)]

        dr = 15

        self.boostTimer.append(\

                setCircle(ux+dr, uy+dr, dx-dr, dy-dr, outline='white', width=5, \

                              fill='black', tag=tag, state=tk.HIDDEN))

        self.boostTimer.append(\

            setText(ux+r, uy+r, text='03:00', fill='white', font=fontTimer, \

                    tag=tag, state=tk.HIDDEN))

        self.boostTimerTimes = 0

        self.boostTime = 180

 

        self.refreshTimerDisplay()

       

        # ----- speed indicator ---------------------------------------- SPEED

        ux, uy, d = 1083, 348, 141

        dx, dy, r = ux+d, uy+d, round(d/2)

        tag = 'Speed'

        setCircle(ux, uy, dx, dy, tag=tag, outline='black', width=4)

        dr = 3

        setCircle(ux+dr, uy+dr, dx-dr, dy-dr, tag=tag, outline='white', \

                          fill=appRed, width=2)

        dr = 27

        setCircle(ux+dr, uy+dr, dx-dr, dy-dr, tag=tag,  outline=None, \

                          fill='white', width=0)

        self.speedvalw = setText(ux+r, uy+r, text='35', font=fontSpeed, \

                                 tag=tag)

 

        # ========================================================== TOP VIEW 1

        self.topViewFlag = -1

        self.topview1Tk = imgLoadTk('topview_tractor.png')

        self.topview = [setImage(0, 294, anchor=tk.NW, state=tk.HIDDEN, \

                             image=self.topview1Tk )]

       

        # ========================================================== TOP VIEW 2

        self.topview2Tk = imgLoadTk('topview_tractor_trailer.png')

        self.topview.append(setImage(0, 294, anchor=tk.NW, state=tk.HIDDEN, \

                             image=self.topview2Tk ))

 

        tag = 'topview'

        ptx = [190, 473, 739]

        pty = [y+294 for y in [48, 457]]

       

        wTargetPresTV = {}

        wCurPresTV = {}

        self.manoTV = {}

        tagi = tag

        for i, x in enumerate(ptx):

            wTargetPresTV[i] = []

            wCurPresTV[i] = []

            self.manoTV[i] = []

            for j, y in enumerate(pty):

                if i == 2:

                    tagi = ('3rdWhlTV', tag)

                    state = tk.HIDDEN

                # ----- target pressures

                wTargetPresTV[i].append(setText(x+5*j, y+2, text='1.8', \

                                                fill=appGreen, state=state, \

                                           anchor= j and tk.SW or tk.NW, \

                                                  font=fontPres, tag=tagi))

                if i == 2: tagi = ('3rdWhlTV+', tag)

                # ----- 'real' pressure

                wCurPresTV[i].append(setText(x+5*(j==0), y+2, text='1.8', fill=appOrange, \

                                             state=tk.HIDDEN, tag=tagi, \

                                             anchor= j and tk.NW or tk.SW, \

                                               font=fontPres))

                # ----- manometers

                self.manoTV[i].append(setImage(x-20, y-7+21*j, image=self.manoTk, \

                                            tag=tagi, \

                                            anchor=tk.E, state=tk.HIDDEN))

        self.topViewOn = False

        self.topViewMoved = False

        self.topViewPrevAxle = 3

        self.xflatingToolTV = {na:[*wCurPresTV[na], \

                                   *self.manoTV[na]] for na in range(3)}

 

        self.wTargetPresAx = []

        self.wTargetPresAll = []

        self.wCurPresAx = []

        for i in range(3):

            self.wTargetPresAx.append([wTargetPres[i], \

                                            *wTargetPresTV[i]])

            self.wTargetPresAll += self.wTargetPresAx[-1]

            self.wCurPresAx.append([wCurPres[i], *wCurPresTV[i]])

           

        # ------------------------------------- some variable to save or to add

        self.xflateOn = [False]*3

        self.setTimer = fig.after

        self.stopTimer = fig.after_cancel

        self.fig = fig

        self.setTimer(10, self.wait4action)

        self.setPresSpeed()

        print('  >> mainBoard: On')

        self.fig.mainloop()

 

    # ======================================================= widgets management

    # -------------------------------------------------------- general functions               

    def pressAction(self, event, ws, tag, wc, but, action):

        """ press button action:    remove the shadow (ws)

                                    move the image down (tag)

                                    extra widget to hanlde (but)

                                    set the action after release (action)

        """

        self.wHide([ws])

        self.move(tag, 4, 4)

        def handler(event, self=self, \

                        ws=ws, tag=tag, wc=wc, but=but, action=action):

            return self.releaseAction(event, ws, tag, wc, but, action)

        self.wbind(wc, '<ButtonRelease-1>', handler)

 

 

    def releaseAction(self, event, ws, tag, wc, but, action):

        """ release button action:    display the shadow (ws)

                                      move the image up (tag)

                                      extra widget to hanlde (but)

                                      set the action after release (action)

        """

        def handler(event, self=self, \

                        ws=ws, tag=tag, wc=wc, but=but, action=action):

               return self.pressAction(event, ws, tag, wc, but, action)

        self.wbind(wc, '<Button-1>', handler)

        self.move(tag, -4, -4)

        self.wShow([ws])

        if but is None: action()

        else:           action(ws, but)

 

    def setSpeed(self, v):

        """ Display the speed in the roadsign

        """

        self.curSpeed = v

        self.setConfig(self.speedvalw, text='{:d}'.format(v))

 

    def wHide(self, lst):

        """ hide the widgets in the passed list

        """

        for tag in lst: self.setConfig(tag, state=tk.HIDDEN)

       

    def wShow(self, lst):

        """ show the widgets in the passed list

        """

        for tag in lst: self.setConfig(tag, state=tk.NORMAL)

 

    def wFill(self, lst, clr):

        """ Fill the shape of the widgets in the passed list

        """

        for tag in lst: self.setConfig(tag, fill=clr)

 

    def wGetColor(self, w):

        return self.getConfig(w, 'fill')

 

    def refreshTimerDisplay(self):

        """ Refresh the timer display

        """

        self.setConfig(self.boostTimer[0], extent=-2*self.boostTime)

        self.setConfig(self.boostTimer[2], text='{:02d}:{:02d}'\

                       .format(*divmod(self.boostTime, 60)))

 

    def setTargetPressure(self, na):

        """ Display the target pressure of the axle #na

        """

        txt = '{:.1f}'.format(self.targetPres[na])

        for w in self.wTargetPresAx[na]: self.setConfig(w, text=txt)

           

    def setCurPressure(self, na):

        """ Display the current pressure of the axle #na

        """

        txt = '{:.1f}'.format(self.curPres[na])

        for w in self.wCurPresAx[na]: self.setConfig(w, text=txt)

 

    def _toFr(self, event): self.translate('fr')

    def _toEn(self, event): self.translate('en')

    def _toGe(self, event): self.translate('ge')

    def _toRu(self, event): self.translate('ru')

 

    def nextLang(self):

        Langs = ['En', 'Fr', 'Ge', 'Ru']

        indx = Langs.index(self.curLang.title()) + 1

        if indx >= len(Langs) : indx=0

        self.translate(Langs[indx])                         

           

    def translate(self, ln):

        """ translate the labels

        """

        for key, w in self.labels.items():

            self.setConfig(w, text=self.langs[key][ln])

        self.setConfig(self.txtBut1, text=self.langs[self.curTool][ln])

        self.curLang = ln

        self.langButTxt.set(ln[:2])

       

    # ------------------------------------------------------------------ But 1               

    def selTooling(self):

        self.toolInd += 1

        if self.toolInd >= len(self.toolList): self.toolInd = 0

        self.curTool = self.toolList[self.toolInd]

        self.setTool()

 

    def selToolingSub(self, ws, but):

        self.wHide(self.subbut1y)

        self.wShow([ws, self.subbut1y[but]])

        self.load = but

        self.setPresSpeed()

       

    def setTool(self):

        """ set the tool attached to but1

        """

        # ----- change image of tool

        self.setConfig(self.toolBut, image=self.toolsTk[self.curTool])

 

        self.load = 0

       

        # ----- front mass or not

        if self.curTool in self.frontMassTool:

            self.wShow([self.frontMass])

        else:

            self.wHide([self.frontMass])

       

        # ----- text in button

        self.setConfig(self.txtBut1, \

                       text=self.langs[self.curTool][self.curLang])       

 

        # ----- display or not the load

        if self.curTool in self.variableLoadList:

            self.wShow(['subequip'])

            self.wHide(self.subbut1y)      

        else:

            self.wHide(['subequip'])

 

        # ----- if 3rdWhl : image

        if 'TRAILER' in self.curTool:

            self.wHide([self.tractorAlone, *self.selAxle[2]])

            self.wShow([self.tractorTrailer, '3rdWhl', 'whl2Green'])

            if self.nbAxles == 2: self.move('toShift', -self.wshift, 0)

            self.nbAxles = 3

            self.curPresClr[2] = appGreen

        else:

            self.wShow([self.tractorAlone])

            self.wHide([self.tractorTrailer, '3rdWhl', '3rdWhl+', \

                            *self.selAxle[2]])

            if self.nbAxles == 3: self.move('toShift', self.wshift, 0)

            self.nbAxles = 2

 

        self.setPresSpeed(not self.topViewOn)

       

        if self.topViewOn and self.topViewPrevAxle != self.nbAxles:

            self.toggleTopView(True)

 

    def setPresSpeed(self, refreshPres=True, saved=None):

        self.oriTargetPres = self.toolPressures[self.curTool][self.mode][self.load]

        Vref = self.toolSpeed[self.curTool][self.mode][self.load]

        self.setSpeed(Vref)

        if saved is None:

            self.targetPres = self.oriTargetPres.copy()

        else:

            self.targetPres = saved

        if refreshPres: self.manPress()

        self.putData(['SUB', 'Vref={}'.format(Vref), 'Rpi_Disp'])

       

    # ------------------------------------------------------------------ But 2               

    def toggleSurf(self):

        # ----- no action if boost on

        if not self.boostOn:

            if self.field:

                # ----- field set -> set ROAD

                self.wHide(['field', 'Boost'])

                self.wShow(['road'])

                self.mode = 'road'

            else:

                # ----- road set -> set FIELD

                self.wShow(['field', 'Boost'])

                self.wHide(['road', *self.subbut2y, self.boostButRed])

                self.mode = 'field'

            # ----- toggle value

            self.field = not self.field

            self.setPresSpeed()

 

    def surfSub(self, ws, but):

        """ Sub Button 2 [but] released (angle / flat)

        """

        self.wHide(self.subbut2y)

        self.wShow([ws, self.subbut2y[but]])

        if but:

            if self.bankFlag:

                self.oriTargetPres = [round(v-0.4, 1) \

                                          for v in self.oriTargetPres]

                self.bankFlag = False

        else:

            if not self.bankFlag:

                self.oriTargetPres = [round(v+0.4, 1) \

                                          for v in self.oriTargetPres]

                self.bankFlag = True

        if self.targetPres != self.oriTargetPres:

            self.targetPres = self.oriTargetPres.copy()

            self.manPress()

 

    # ----------------------------------------------------------------- MANUAL               

    def toggleManualMode(self):

        # ----- boost on not on

        if not self.boostOn:

            if self.manualOn:

                # ----- Manual Off

                self.manualOn = False

                self.wShow([self.manualBut, \

                            *self.wheel['Green'][:self.nbAxles]])

                self.wHide([self.manualButBlue, 'submanual', 'whlBlue'])

                self.wFill(self.wTargetPresAll, appGreen)

                self.curWheelClr = ['Green']*3

                self.curPresClr = [appGreen]*3

                self.targetPres = self.prevPresM.copy()

                self.manPress()

            else:

                # ----- Manual On

                self.manualOn = True

                self.wHide([self.manualBut])

                self.wShow([self.manualButBlue, 'submanual'])

                self.prevPresM = self.oriTargetPres.copy()

                self.curAxle = self.nbAxles

                self.nextAxle()

 

    # --------------------------------------------------------------------- [-]            

    def minusButFun(self):

        if not self.boostOn:

            self.targetPres[self.curAxle] = \

                max(round(self.targetPres[self.curAxle] - 0.1, 1), 0.4)

            self.manPress()

       

    # ------------------------------------------------------------------ [AXLE]            

    def nextAxle(self):

        if not self.boostOn:

            self.lastAxle = self.curAxle

            self.curAxle += 1

            self.xflate(self.lastAxle)

            if self.curAxle >= self.nbAxles: self.curAxle = 0

            self.wFill(self.wTargetPresAx[self.curAxle], appBlue)

            self.wHide([*self.selAxle[self.curAxle]])

            self.wShow([self.wheel['Blue'][self.curAxle]])

            self.curPresClr[self.curAxle] = appBlue

            self.curWheelClr[self.curAxle] = 'Blue'

       

    # --------------------------------------------------------------------- [+]            

    def plusButFun(self):

        if not self.boostOn:

            self.targetPres[self.curAxle] = \

                min(round(self.targetPres[self.curAxle] + 0.1, 1), 3.0)

            self.manPress()

       

    # ------------------------------------------------------------------- BOOST               

    def toggleBoostMode(self):

        if self.boostOn:

            # ----- Boost Off

            self.boostOn = False

            self.wHide([self.boostButRed, 'subBoost', 'whlRed'])

            self.wShow([self.boostBut])

            if self.manualOn: self.wShow(['submanual'])

            self.curWheelClr = self.prevWheelClr.copy()

            for na in range(self.nbAxles):

                self.wShow([self.wheel[self.curWheelClr[na]][na]])

            self.curPresClr = self.prevPresClr.copy()

            for na, clr in enumerate(self.curPresClr):

                self.wFill(self.wTargetPresAx[na], clr)

            self.move('Speed', 0, -267)

            self.mode = 'field'

            mem = self.prevPresB.copy()

            self.boostTime = 180

            self.boostTimerTimes = 0

        else:

            if self.manualOn: self.wHide(['submanual'])

            # ----- Boost On

            self.boostOn = True

            self.wHide([self.boostBut, 'whlGreen', 'whlBlue'])

            self.wShow([self.boostButRed, 'subBoost', \

                        *self.wheel['Red'][:self.nbAxles]])

            self.wFill(self.wTargetPresAll, appRed)

            self.prevWheelClr = self.curWheelClr.copy()

            self.curWheelClr = ['Red']*3

            self.prevPresClr = self.curPresClr.copy()

            self.curPresClr = [appRed]*3

            self.move('Speed', 0, 267)

            self.mode = 'boost'

            self.prevPresB = self.targetPres.copy()

            mem = None

            self.setTimer(1000, self.boostDelay)

        self.setPresSpeed(saved=mem)

 

    # ============================================================== ANIMATIONS

    # --------------------------------------------------------------- Pressures

    def manPress(self, redraw=False):

        for na in range(self.nbAxles):

            self.setCurPressure(na)

            self.setTargetPressure(na)

            if self.curPres[na] != self.targetPres[na] or redraw:

                if appGreen in self.curPresClr[na]:

                    self.wFill(self.wTargetPresAx[na], 'black')

                if not self.xflateOn[na]:

                    if self.topViewOn: self.wShow(self.xflatingToolTV[na])

                    self.wShow(self.xflatingTool[na])

                    if not self.boostOn:

                        self.wHide([self.wheel[self.curWheelClr[na]][na]])

                        self.wShow([self.wheel['Orange'][na]])

                    self.xflateOn[na] = True

                if self.xflTimer[na] is not None:

                    self.stopTimer(self.xflTimer[na])

                self.xflTimer[na] = self.setTimer(1000, self.xflate, na)

               

    def xflate(self, na):

        if na < self.nbAxles:

            if self.curPres[na] == self.targetPres[na]:

                if self.topViewOn: self.wHide(self.xflatingToolTV[na])

                self.wHide([*self.xflatingTool[na], self.wheel['Orange'][na]])

                if 'Blue' in self.curWheelClr[na]:

                    if na != self.curAxle:

                        self.curWheelClr[na] = 'Green'

                        if self.curPres[na] == self.oriTargetPres[na]:

                            self.curPresClr[na] = appGreen

                self.wShow([self.wheel[self.curWheelClr[na]][na]])

                self.wFill(self.wTargetPresAx[na], self.curPresClr[na])

                self.xflateOn[na] = False

            else:

                if self.curPres[na] > self.targetPres[na]:

                    self.curPres[na] = round(self.curPres[na] - 0.1, 1)

                else:

                    self.curPres[na] = round(self.curPres[na] + 0.1, 1)

                self.setCurPressure(na)

                self.xflTimer[na] = self.setTimer(1000, self.xflate, na)

 

    # -------------------------------------------------------------------- Boost

    def boostDelay(self):

        self.boostTime -= 1

        if not self.boostTime:

            self.boostTimerTimes += 1

            if self.boostTimerTimes > 2: self.boostTimerTimes = 0

            self.wFill([self.triplwarnw[self.boostTimerTimes]], appRed)

            self.wFill(self.triplwarnw[1:], '')

            self.boostTime = 180

            self.toggleBoostMode()

        elif self.boostOn: self.setTimer(1000, self.boostDelay)

        self.refreshTimerDisplay()

 

    # ------------------------------------------------------------------ top view

    def toggleTopView(self, redraw=False):

        if self.topViewFlag < 0 or redraw:

            self.topViewFlag = self.nbAxles - 2

            self.wShow([self.topview[self.topViewFlag], 'topview'])

            if self.nbAxles == 2 :

                self.wHide([self.topview[1], '3rdWhlTV', '3rdWhlTV+'])

                if not self.topViewMoved: self.move('topview', 161, 0)

                self.topViewMoved = True

            else:

                self.wShow([self.topview[0], '3rdWhlTV', '3rdWhlTV+'])

                if self.topViewMoved: self.move('topview', -161, 0)

                self.topViewMoved = False

            self.topViewPrevAxle = self.nbAxles

            self.topViewOn = True

        else:

            self.wHide([*self.topview, 'topview'])

            self.topViewFlag = -1

            self.topViewOn = False

        self.manPress(True)

 

    # ================================================================== ACTIONS              

    def wait4action(self):

       

        loop = True

 

        while not self.enterEmpty():

            ret = self.getData()

 

            cmd, val = ret

 

            if ('Tool' in cmd) and (val in self.indTool):

                # ----- 'Tool=TRAILER'

                self.toolInd = self.indTool[val]

                self.curTool = val

                self.setTool()

 

            elif 'Lang' in cmd:

                # ----- 'Lang=En'

                self.translate(val[:2].title())

 

            elif ('Surf' in cmd) and (('field' in val) != self.field):

                # ----- 'Surf=road'

                self.toggleSurf()

                   

            elif ('Manual' in cmd) and (bool(int(val)) != self.manualOn):

                # ----- 'Manual=1'

                self.toggleManualMode()

               

            elif ('Boost' in cmd) and (bool(int(val)) != self.boostOn):

                # ----- 'Boost=1'

                self.toggleBoostMode()

 

            elif 'Pressures' in cmd:

                # ----- 'Pressures=[.8, .8, .8]'

                self.targetPres = eval(val)

                self.manPres()

               

            elif 'Exit' in cmd:

                loop = False

                break

                   

            self.fig.update()

                   

        if loop: self.setTimer(40, self.wait4action)

        else:    self.Quit()

       

    def Hide(self, event):

        self.fig.overrideredirect(False)

        self.fig.iconify()

 

    def Show(self, event):

        self.fig.overrideredirect(True)

 

    def Quit(self, *param):

        self.putData(['SUB', 'Exit', '()'])

        print('  << mainBoard: Off')

        self.fig.destroy()          # quit the program

#

# =============================================================================

 

# =============================================================================

#

if __name__ == '__main__':

 

    from defTCPIP import TCPLink

    from time import strftime

 

    # ----- read params from ini file

    config = ConfigParser()

    config.filename = r'/boot/setConfig.ini'

   

    from  os import name as osname

    if osname == 'nt':

        # windows pour test programme

        config.filename = config.filename.replace('/','_')

 

    # ----- verify config ini ok

    try:

        config.read(config.filename, encoding='utf-8')

        settings = config['SETTINGS']

        vrbstr = settings['verbose']

        if   'b' in vrbstr: verbose = int(vrbstr.split('b')[1], base=2)

        elif 'x' in vrbstr: verbose = int(vrbstr.split('x')[1], base=16)

        else:               verbose = int(vrbstr)

        debug = int(settings['debug'])

        netCfg = config['NET']

        addrCfg = config['ADDRESS']

        procName = netCfg['node'].strip().title()

        baseAddr = netCfg['base'].strip()

        IPtoNnode = {baseAddr+addrCfg[procName]:procName}

        if netCfg['links']:

            IPtoNlinks = {baseAddr + addrCfg[s.strip()].strip():\

                            s.strip().title() \

                                for s in netCfg['links'].split(',')}

        else: IPtoNlinks = {}

        portBase = int(netCfg['port_base'])

        Ok = True

    except:

        Ok = False

 

    if not Ok:

        errPrint(r'/!\ Error reading .ini file')

        sleep(10)

        exit(1)

       

    # ----- Proc Name from ini = Rpi_Outputs

    print('> {} [{}] >'.format(procName, strftime('%d/%m/%y %H:%M:%S')))

 

    #

    # verbose : 1 = main, 2 = ethernet

    #

    vrbm = bool(verbose & 1)

    vrbe = bool(verbose & 2)

 

    langs = ConfigParser()

    langs.read('Rpi_Pressure.ini', encoding='utf-16')

   

    qIn = Queue()

    # --------------------------------------------------- list of PC to connect

    eth = TCPLink(IPtoNnode, IPtoNlinks, portBase, qIn.put_nowait)

    print('\t{}: {}\n'.format(procName, eth.myAddr))

 

    # ------------------------------------------------------- Board management

    qCmd = Queue()

    setBoard = qCmd.put_nowait

 

    # ----- launch main program and loop

    pB = Process(target=board, args=(debug, qCmd, qIn, langs))

    pB.start()

 

    exitVal = 0

    MainOn = True

    exitFlag = False

    getdata = qIn.get

    wait4data = True

    T0 = time()

    Tend = 5

    ackn = False

    exitFromBoard = False

 

    while MainOn:

 

        try:

            ret = getdata(wait4data)

        except Empty:

            ret = []

        except KeyboardInterrupt:

            setBoard(['Exit',''])

            ret = []

 

        if ret: typ, msgs, proc = ret

        else: typ = ''

 

        if 'TCP' in typ:

            # ----------------------------------------------------- TCP

            typ2 = typ.split(':')[-1]

           

            if 'out' in typ2:

                eth.relaunch(proc)

               

            elif ('snt1' in typ2) and vrbe:

                print('\t-> {} to {}'.format(msgs, proc))

               

            elif 'snt0' in typ2:

                print('\t/!\ Fail sending {} to {}'.format(msgs, proc))

               

            elif typ2 in ['rcv1', 'rcv0']:

                # ----- aknowledge

                if vrbe: print('\t<- {} from {}'.format(msgs, proc))

                if exitFlag:

                    print('\t {} stopped'.format(proc))

                    if typ2 == 'rcv1':

                        MainOn = not all(eth.stop(proc))

 

                for cmdval in msgs.split('&'):

 

                    cmd, val = cmdval.split('=')

 

                    ackn = True

 

                    if 'Exit' in cmd:

                        # stop Rpi_Pressure

                        exitVal = int(val)

                        T0 = time()

                       

                    setBoard([cmd, val])

                   

            elif 'rcv?' in typ2:

                print('\t ????? {} received from {}'.format(msgs, proc))

                ackn = False

               

        elif 'SUB' in typ:

            if msgs == 'Exit':

                MainOn = False

                exitFromBoard = True

                exitFlag = True

                wait4data = False

 

            else:

                eth.sendTo(msgs, proc)

               

        elif typ:

            print('\t/!\ unknown type : ', typ)

 

        if exitFlag: MainOn = not (time() - T0 > Tend)

   

    if not exitFromBoard: setBoard(['Exit','', ''])

    qIn.cancel_join_thread()   

    # ----------------------------------------------------------- close TCPLink

    eth.close()

    pB.join()

   

    # -------------------------------------------------------------------- exit

    print('< {} [{}] <\n\n'.format(procName, strftime('%d/%m/%y %H:%M:%S')))

   

    exit(exitVal)

#

# =============================================================================

______________________________________________________________________________________

 

Some screenshots:

 

3.c/ Dashboard

 

This raspberry (behind the steeringwheel) simulates a dashboard. As for previously, it ovelays, shows or hides images.

 

Images in img directory:

 

I use the Ackerman  steering angles (in def turn) for the turning  (see here) to be closer to reality.

It's simpler and faster to display images of rotated wheels than rotating an image...

 

Rpi_Display.py_________________________________________________________________________

# -*- coding: utf-8 -*-

 

""" RPi_Display

    ===========

    Tractor board

"""

 

__author__ = 'Damien HALLEZ'

__version__ = '1.0'

__date__ = 'Nov 2017'

 

import tkinter as tk

from configparser import ConfigParser

from multiprocessing import Process, Queue

from queue import Empty

from defErrPrint import errPrint

 

from math import pi, sin, cos, hypot, atan2, tan, atan, copysign

from time import sleep

from os import path

 

TrWb = 3.3        # wheelbase

TrTrs2 = 2.1/2    # Track

 

# -----------------------------------------------------------------------------

#

def rotate(rhoTheta, offset, angle):

    """

    Rotate a figure (with polar coordinates), return cartesian coords

    """

    rada = angle*pi/180

    rx, ry = offset

    return [n for elem in \

                [[r*cos(p+rada)+rx, r*sin(p+rada)+ry] for r, p in rhoTheta] \

                    for n in elem]

#

# -----------------------------------------------------------------------------

 

BEACONON = 0x01

BLINKON  = 0x02

 

# =============================================================================

#

class board():

    """

    Main animation

    """

 

    # ========================================================== initializing

    def __init__(self, debug, qIn, qOut):

           

        # ======================= open graphic window, top screen, full screen

        fig = tk.Tk()

        if not debug: fig.overrideredirect(True)

        fig.lift()

        WxH = [1024, 600]

        fig.wm_attributes('-topmost', True)

        fig.geometry('{}x{}+0+0'.format(*WxH))

        fig.title('RPi_Display')

       

        fig.bind('q', self.Quit)

        fig.bind('Q', self.Quit)

        fig.bind('H', self.hide)

        fig.bind('h', self.hide)

        fig.bind('S', self.show)

        fig.bind('s', self.show)

       

        self.getData = qIn.get_nowait

        self.enterEmpty = qIn.empty

        self.putData = qOut.put_nowait

       

        # ----- attach a canvas to the window

        cnv = tk.Canvas(fig, bd=0, bg='white', highlightthickness=0, \

                       width=WxH[0], height=WxH[1])

        cnv.pack()

        # ----- to simplify the notations

        setText = cnv.create_text

        setImage = cnv.create_image

        setPolygon = cnv.create_polygon

        self.setConfig = cnv.itemconfig

        self.setCoords = cnv.coords

 

        imgDir = '../img'

        if not path.exists(imgDir): imgDir = imgDir.replace('..','.')

 

        # ----- load and display background image

        self.bckImgTk = tk.PhotoImage(file=path.join(imgDir,'BoardBckgrnd.png'))

        self.BckImg = setImage(512, 300, image=self.bckImgTk)

 

        # ================================================ loading images files and texts...

        # loading tires (all angles)

        self.tireImg = {}

        self.RtiVwImg = {}

        self.LtiVwImg = {}

        for i in range(-35,36):

            if i >= 0:

                self.tireImg[i] = tk.PhotoImage(\

                        file=path.join(imgDir, 'FrTire+{:02d}.png'.format(i)))

            else:

                self.tireImg[i] = tk.PhotoImage(\

                    file=path.join(imgDir, 'FrTire-{:02d}.png'.format(abs(i))))

            self.RtiVwImg[i] = setImage(519, 204, image=self.tireImg[i], anchor=tk.W, state=tk.HIDDEN)

            self.LtiVwImg[i] = setImage(508, 204, image=self.tireImg[i], anchor=tk.E, state=tk.HIDDEN)

 

        self.setConfig(self.RtiVwImg[0], state=tk.NORMAL)

        self.setConfig(self.LtiVwImg[0], state=tk.NORMAL)

        self.prevRtiVwImg = 0

        self.prevLtiVwImg = 0

 

        # ===== TopView

        self.tpVwImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'TopView.png'))

        self.tpVwImg = setImage(512, 300, image=self.tpVwImgTk)

 

        self.hLights = {}

        # ===== Blinkers: FRONT / RIGHT

        self.FRBlImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'light_front_blinker.png'))

        self.hLights['FRBl'] = [setImage(631, 268, image=self.FRBlImgTk, \

                                        anchor=tk.S, state=tk.HIDDEN)]

        # ----- Blinkers: FRONT / LEFT

        self.FLBlImgTk = self.FRBlImgTk.copy()

        self.hLights['FLBl'] = [setImage(395, 268, image=self.FLBlImgTk, \

                                        anchor=tk.S, state=tk.HIDDEN)]

        # ----- Blinkers: REAR / RIGHT

        self.RRBlImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'light_rear_blinker.png'))

        self.hLights['RRBl'] = [setImage(620, 527, image=self.RRBlImgTk, \

                                        anchor=tk.N, state=tk.HIDDEN)]

        # ----- Blinkers: REAR / LEFT

        self.RLBlImgTk = self.RRBlImgTk.copy()

        self.hLights['RLBl'] = [setImage(406, 527, image=self.RLBlImgTk, \

                                        anchor=tk.N, state=tk.HIDDEN)]

        # ----- both (left/right for blinkers)

        self.hLights['BLBl'] = self.hLights['FLBl'] + self.hLights['RLBl']

        self.hLights['BRBl'] = self.hLights['FRBl'] + self.hLights['RRBl']

 

        # ===== lights: FRONT / RIGHT

        self.FRLdImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'light_front_right.png'))

        self.hLights['FRLd'] = [setImage(550, 71, image=self.FRLdImgTk, \

                                        anchor=tk.S, state=tk.HIDDEN)]

       

        # ----- lights: FRONT / LEFT

        self.FLLdImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'light_front_left.png'))

        self.hLights['FLLd'] = [setImage(475, 71, image=self.FLLdImgTk, \

                                        anchor=tk.S, state=tk.HIDDEN)]

        # ----- both front

        self.hLights['FBLd'] = self.hLights['FRLd'] + self.hLights['FLLd']

 

 

        # ===== lights: FRONT / RIGHT UP

        self.FRLuImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'light_front_up_right.png'))

        self.hLights['FRLu'] = [setImage(589, 321, image=self.FRLuImgTk, \

                                        anchor=tk.S, state=tk.HIDDEN)]

        # ----- lights: FRONT / LEFT

        self.FLLuImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'light_front_up_left.png'))

        self.hLights['FLLu'] = [setImage(435, 321, image=self.FLLuImgTk, \

                               anchor=tk.S, state=tk.HIDDEN)]

        # ----- both front

        self.hLights['FBLu'] = self.hLights['FRLu'] + self.hLights['FLLu']

 

 

        # ===== Brakes: REAR / RIGHT

        self.RRBrImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'light_brakes.png'))

        self.hLights['RRBr'] = [setImage(604, 531, image=self.RRBrImgTk, \

                                        anchor=tk.N, state=tk.HIDDEN)]

        # ----- Brakes: REAR / LEFT

        self.RLBrImgTk = self.RRBrImgTk.copy()

        self.hLights['RLBr'] = [setImage(428, 531, image=self.RLBrImgTk, \

                                        anchor=tk.N, state=tk.HIDDEN)]

        # ----- both Rear

        self.hLights['RBBr'] = self.hLights['RRBr'] + self.hLights['RLBr']

 

        # ===== lights: REAR / RIGHT UP

        self.RRLuImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'light_rear_up_right.png'))

        self.hLights['RRLu'] = [setImage(583, 461, image=self.RRLuImgTk, \

                                        anchor=tk.N, state=tk.HIDDEN)]

        # ----- lights: REAR / LEFT

        self.RLLuImgTk = tk.PhotoImage(\

                            file=path.join(imgDir, 'light_rear_up_left.png'))

        self.hLights['RLLu'] = [setImage(448, 461, image=self.RLLuImgTk, \

                                        anchor=tk.N, state=tk.HIDDEN)]

        # ----- both Rear

        self.hLights['RBLu'] = self.hLights['RRLu'] + self.hLights['RLLu']

 

        self.beaconAng = 0

        rhoTheta = [[hypot(xi, yi), atan2(yi, xi)] for xi, yi in \

                            zip([4, 163, 169, 174, 177, 179, 180],\

                                [2, 76, 61.5, 46.5, 31, 15.5, 0])]

        rhoTheta += [[r,-th] for r,th in list(reversed(rhoTheta[:-1]))]

        self.beaconPol = rhoTheta

        self.LbeaconOffs = [405, 444]

        self.LBeacon = setPolygon(*rotate(rhoTheta, self.LbeaconOffs, \

                                         self.beaconAng + 180), \

                                  fill='orange', outline='', stipple='gray75',\

                                  state=tk.HIDDEN)

        self.RbeaconOffs = [629, 442]

        self.RBeacon = setPolygon(*rotate(rhoTheta, self.RbeaconOffs, \

                                         self.beaconAng), \

                                  fill='orange', outline='', stipple='gray75',\

                                  state=tk.HIDDEN)

 

        # ----- set speed

        setText(180, 65, text='km/h', font=('DS-Digital', 30),\

                fill='darkblue')

        setText(180, 145, text='888', font=('DS-Digital', 100),\

                fill='#B2B2B2')

        self.speed = []

        for i in range(3):

            self.speed.append(setText(151+64.5*i,145, anchor=tk.E, text='0', \

                             font=('DS-Digital',100), fill='darkblue'))

 

        # ----- load other element

        self.symbMiscTk = tk.PhotoImage(file=path.join(imgDir, 'LeftElems.png'))

        setImage(180, 390, image=self.symbMiscTk)

 

        # ----- load right symbols

        xoff = 55

        self.rightSymbsTk = tk.PhotoImage(file=path.join(imgDir, 'RightElems.png'))

        setImage(890-xoff, 300, image=self.rightSymbsTk)

 

        # ----- load right arrow

        self.rightArrowTk = tk.PhotoImage(file=path.join(imgDir, 'RightArrow.png'))

        self.rightArrow = setImage(930, 319, image=self.rightArrowTk, \

                                             state=tk.HIDDEN)

        self.hLights['BRBl'] += [self.rightArrow]

              

        # ----- load left arrow

        self.leftArrowTk = tk.PhotoImage(file=path.join(imgDir, 'LeftArrow.png'))

        self.leftArrow = setImage(738, 319, image=self.leftArrowTk, \

                                            state=tk.HIDDEN)

        self.hLights['BLBl'] += [self.leftArrow]

 

        # ----- load warning symbol

        self.warnTk = tk.PhotoImage(file=path.join(imgDir, 'Warning.png'))

        self.warning = setImage(516, 380, image=self.warnTk, state=tk.HIDDEN)

 

 

        self.hLights['WARN'] = self.hLights['BLBl'] + self.hLights['BRBl'] \

                                                   + [self.warning]

 

        # ----- load warning disk

        self.warnDskTk = tk.PhotoImage(file=path.join(imgDir, 'RedDisk.png'))

        self.hLights['ERR'] = [setImage(833, 319, image=self.warnDskTk, \

                                                 state=tk.HIDDEN)]

       

        # ----- some variable to save or to add

        self.setTimer = fig.after

        self.cancelTimer = fig.after_cancel

        self.beaconTimer = None

        self.blinkTimer = None

        self.fig = fig

        self.setTimer(10, self.wait4action)

        print('  >> mainBoard: On')

        self.actionOn = 0

        self.fig.mainloop()

 

    def beaconRotate(self):

        """ rotate beacon ray

        """

        self.setCoords(self.LBeacon, \

                       *rotate(self.beaconPol, self.LbeaconOffs, \

                               self.beaconAng + 180))

        self.setCoords(self.RBeacon, \

                       *rotate(self.beaconPol, self.RbeaconOffs, \

                               self.beaconAng))

        self.beaconAng += 30

        if self.beaconAng >= 360: self.beaconAng = 0

        self.beaconTimer = self.setTimer(40, self.beaconRotate)

       

    def beaconOnOff(self, OnOff):

        """ stop the beacon

        """

        if self.beaconTimer is not None:

            self.cancelTimer(self.beaconTimer)

            self.beaconTimer = None

        if OnOff :

            for h in [self.LBeacon, self.RBeacon]:

                self.setConfig(h, state=tk.NORMAL)

            self.actionOn |= BEACONON

            self.beaconRotate()

        else:

            for h in [self.LBeacon, self.RBeacon]:

                self.setConfig(h, state=tk.HIDDEN)

            self.actionOn &= ~BEACONON

 

    def lightsOnOff(self, lights, OnOff):

        """ lights on/off

        """

        if OnOff :

            for h in self.hLights[lights]:

                self.setConfig(h, state=tk.NORMAL)

        else:

            for h in self.hLights[lights]:

                self.setConfig(h, state=tk.HIDDEN)

 

    def blink(self):

        """ blink (a) light(s)

        """

        self.blinkFlag = (self.blinkFlag == False)

        if self.blinkFlag:

            for h in self.blinkCurh:

                self.setConfig(h, state=tk.NORMAL)

        else:

            for h in self.blinkCurh:

                self.setConfig(h, state=tk.HIDDEN)

        self.blinkTimer = self.setTimer(400, self.blink)

 

    def blinkOnOff(self, lights, OnOff):

        """ stop blinking

        """

        if self.blinkTimer is not None:

            self.cancelTimer(self.blinkTimer)

            self.blinkTimer = None

        if OnOff :

            self.blinkCurh = self.hLights[lights]

            self.blinkFlag = False

            self.actionOn |= BLINKON

            self.blink()

        else:

            for h in self.hLights[lights]:

                self.setConfig(h, state=tk.HIDDEN)

            self.actionOn &= ~BLINKON

               

    def turn(self, alpha):

        """ rotate the front wheels folling angle alpha

        """

        if abs(alpha) > 30: alpha = copysign(30, alpha)

        if alpha:

            # ----- compute Ackerman angles

            Li = TrWb/tan(alpha*pi/180)

            alpha2 = copysign(round(abs(atan(TrWb/(Li + TrTrs2))*180/pi)), \

                              alpha)

            alpha1 = copysign(round(abs(atan(TrWb/(Li - TrTrs2))*180/pi)), \

                              alpha)

        else:

            alpha1 = alpha2 = 0

        # ----- limitations

        if abs(alpha1) > 35: alpha1 = copysign(35, alpha1)

        if abs(alpha2) > 35: alpha2 = copysign(35, alpha2)

        # ----- hide the previous ones

        self.setConfig(self.RtiVwImg[self.prevRtiVwImg], state=tk.HIDDEN)

        self.setConfig(self.LtiVwImg[self.prevLtiVwImg], state=tk.HIDDEN)

        # ----- show the new ones

        self.setConfig(self.RtiVwImg[alpha2], state=tk.NORMAL)

        self.setConfig(self.LtiVwImg[alpha1], state=tk.NORMAL)

        # ----- save the status

        self.prevRtiVwImg = alpha2

        self.prevLtiVwImg = alpha1

 

    def setSpeed(self, val):

        """ Display the speed

        """

        digits = [d for d in '{:03d}'.format(val)]

        first = True

        for i, di in enumerate(digits):

            if di == '0' and first and i != 2:

                # ----- don't display the headed 0

                self.setConfig(self.speed[i], state=tk.HIDDEN)

            else:

                first = False

                self.setConfig(self.speed[i], text=di, fill='darkblue', \

                               state=tk.NORMAL)

 

    def wait4action(self):

        """ wait for the orders

        """

       

        loop = True

 

        while not self.enterEmpty():

            ret = self.getData()

           

            cmd, which, val = ret

           

            if cmd == 'Beacon':   self.beaconOnOff(val)

 

            elif cmd == 'Light':

                 if which in self.hLights: self.lightsOnOff(which, val)

 

            elif cmd == 'Blink':

                 if which in self.hLights: self.blinkOnOff(which, val)

 

            elif cmd == 'Turn': self.turn(-val)

 

            elif cmd == 'Speed': self.setSpeed(val)

 

            elif cmd == 'Exit':

                loop = False

                break

 

            self.fig.update()

           

        if loop:

            # ----- info at 25Hz

            self.setTimer(40, self.wait4action)

        else:

            self.Quit()

       

    def hide(self, event):

        """ hide the window

        """

        self.fig.overrideredirect(False)

        self.fig.iconify()

 

    def show(self, event):

        """ show the window

        """

        self.fig.overrideredirect(True)

 

    def Quit(self, *param):

        """ Quit the window

        """

        print('  << mainBoard: Off')

        self.putData(['SUB', 'Exit', '()'])

        self.fig.destroy()          # quit the program

#

# =============================================================================

 

# =============================================================================

#                                MAIN

if __name__ == '__main__':

 

    from defTCPIP import TCPLink

    from time import time, strftime, sleep

 

    # ----- read params from ini file

    config = ConfigParser()

    config.filename = r'/boot/setConfig.ini'

 

    from  os import name as osname

    if osname == 'nt':

        # windows pour test programme

        config.filename = config.filename.replace('/','_')

 

    # ----- verify config ini ok

    try:

        config.read(config.filename, encoding='utf-8')

        settings = config['SETTINGS']

        vrbstr = settings['verbose']

        if   'b' in vrbstr: verbose = int(vrbstr.split('b')[1], base=2)

        elif 'x' in vrbstr: verbose = int(vrbstr.split('x')[1], base=16)

        else:               verbose = int(vrbstr)

        debug = int(settings['debug'])

        netCfg = config['NET']

        addrCfg = config['ADDRESS']

        procName = netCfg['node'].strip().title()

        baseAddr = netCfg['base'].strip()

        IPtoNnode = {baseAddr+addrCfg[procName]:procName}

        if netCfg['links']:

            IPtoNlinks = {baseAddr + addrCfg[s.strip()].strip():\

                            s.strip().title() \

                                for s in netCfg['links'].split(',')}

        else: IPtoNlinks = {}

        portBase = int(netCfg['port_base'])

        Ok = True

    except:

        Ok = False

 

    if not Ok:

        errPrint(r'/!\ Error reading .ini file')

        sleep(10)

        exit(1)

       

    # ----- Proc Name from ini = Rpi_In

    print('> {} [{}] >'.format(procName, strftime('%d/%m/%y %H:%M:%S')))

 

    #

    # verbose : 1 = main, 2 = ethernet, 4 = serial

    #

    vrbm = bool(verbose & 1)

    vrbe = bool(verbose & 2)

   

    qIn = Queue()

    # --------------------------------------------------- list of PC to connect

    eth = TCPLink(IPtoNnode, IPtoNlinks, portBase, qIn.put_nowait)

    print('\t{}: {}\n'.format(procName, eth.myAddr))

 

    # ------------------------------------------------------- Board management

    qCmd = Queue()

    setBoard = qCmd.put_nowait

   

    # ----- launch main program and loop

    pB = Process(target=board, args=(debug, qCmd, qIn))

    pB.start()

 

    setBoard(['Speed', '', 0])

 

    exitVal = 0

    MainOn = True

    exitFlag = False

    getdata = qIn.get

    wait4data = True

    T0 = time()

    Tend = 5

    ackn = False

    Vref = 16

    exitFromBoard = False

 

    while MainOn:

 

        try:

            ret = getdata(wait4data)

        except Empty:

            ret = []

        except KeyboardInterrupt:

            ret = ['TCP:rcv0', 'Exit=0','Ctrl+C']

 

        if ret: typ, msgs, proc = ret

        else: typ = ''

 

        if 'TCP' in typ:

            # ----------------------------------------------------- TCP

            typ2 = typ.split(':')[-1]

           

            if 'out' in typ2:

                eth.relaunch(proc)

               

            elif 'snt1' in typ2:

                if vrbe: print('\t-> {} to {}'.format(msgs, proc))

               

            elif 'snt0' in typ2:

                print('\t/!\ Fail sending {} to {}'.format(msgs, proc))

               

            elif typ2 in ['rcv1', 'rcv0']:

                # ----- aknowledge

                if vrbe: print('\t<- {} from {}'.format(msgs, proc))

                if exitFlag:

                    print('\t {} stopped'.format(proc))

                    if typ2 == 'rcv1':

                        MainOn = not all(eth.stop(proc))

 

                cmd, val = msgs.split('=')

 

                ackn = True

 

                if cmd == 'LeftBlinker':

                    setBoard(['Blink', 'BLBl', bool(int(val))])

 

                elif cmd == 'RightBlinker':

                    setBoard(['Blink', 'BRBl', bool(int(val))])

 

                elif cmd == 'Beacon':

                    setBoard(['Beacon','', bool(int(val))])

 

                elif cmd in ['Brake', 'ReRedLght']:

                    setBoard(['Light', 'RBBr', bool(int(val))])

 

                elif cmd == 'LghtDown':

                    bval = bool(int(val))

                    setBoard(['Light', 'FBLd', bval])

                    setBoard(['Light', 'RBLu', bval])

 

                elif cmd == 'LghtFrDown':

                    setBoard(['Light', 'FBLd', bool(int(val))])

 

                elif cmd == 'LghtFrUp':

                    setBoard(['Light','FBLu', bool(int(val))])

 

                elif cmd == 'LghtReDown':

                    setBoard(['Light', 'RBLu', bool(int(val))])

 

                elif cmd == 'LghtReUp':

                    setBoard(['Light','RBLu', bool(int(val))])

 

                elif cmd == 'Warning':

                    setBoard(['Blink', 'WARN', bool(int(val))])

 

                elif cmd == 'STW':

                    setBoard(['Turn', '', int(val)])

 

                elif cmd == 'Vref':

                    Vref = float(val)

                       

                elif cmd == 'Acc':

                    setBoard(['Speed', '', min(int(Vref*float(val)),Vref)])

 

                elif cmd == 'Exit':

                    # stop Rpi_Inputs

                    exitVal = int(val)

                    T0 = time()

                    setBoard(['Exit','', ''])

                   

            elif 'rcv?' in typ2:

                print('\t ????? {} received from {}'.format(msgs, proc))

                ackn = False

               

        elif 'SUB' in typ:

            if msgs == 'Exit':

                MainOn = False

                exitFromBoard = True

                exitFlag = True

                wait4data = False

 

        elif typ:

            print('\t/!\ unknown type : ', typ)

 

        if exitFlag:  MainOn = not (time() - T0 > Tend)

 

    # ----- exit board is not done

    if not exitFromBoard: setBoard(['Exit','', ''])

 

    # ----- free the queue

    qIn.cancel_join_thread()   

    # ----------------------------------------------------------- close TCPLink

    eth.close()

    pB.join()

   

    # -------------------------------------------------------------------- exit

    print('< {} [{}] <\n\n'.format(procName, strftime('%d/%m/%y %H:%M:%S')))

   

    exit(exitVal)

#

# =============================================================================

______________________________________________________________________________________

 

Some screenshots:

     rotating beacon

     front and rear lights                                                                                                                                       warning lights

     left blinker                                                                                                                                                    right blinker

     @65kph                                                                                                                                                        Error system

     turn full right (see Ackerman angles)                                                                                                           turn full left

 

Back to intro

Tractor Simulator (interface with Farming Simulator 17)

 

Global presentation

Tractor simulator (cabin tractor interface with Farming Simulator 17): Part 1 - global presentation

 

The hardware

Tractor simulator (cabin tractor interface with Farming Simulator 17): Part 2 - the hardwareTractor simulator (cabin tractor interface with Farming Simulator 17): Part 2 - the hardware

 

Arduinos

Tractor simulator (cabin tractor interface with Farming Simulator 17): Part 3 - the software, 1/ Arduinos

 

Actual doc

 

PCs

Tractor simulator (cabin tractor interface with Farming Simulator 17): Part 3 - the software, 3/ PCs