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
Actual doc
PCs
Tractor simulator (cabin tractor interface with Farming Simulator 17): Part 3 - the software, 3/ PCs